[PHP] Traitと他のオブジェクトからのアクセス権
こんにちは、ナカエです。今日はPHPのTraitとアクセス権(visibility)についての検証のメモです。
他のオブジェクトからのアクセス権
PHPでは、private・protectedを指定していても、同じ型のオブジェクトからであればそのメンバーにアクセスすることができます。
同じ型のオブジェクト間では、たとえ同一のインスタンスでなくても お互いの private メンバーや protected メンバーにアクセスすることができます。 これは、そのオブジェクトの内部ではオブジェクトの実装の詳細が既知であるからです。
他のオブジェクトからのアクセスの例
<?php
class User
{
/** @var int */
private $id;
/** @var string */
private $firstName;
/** @var string */
private $lastName;
/** @var DateTimeImmutable */
protected $birthDay;
public function __construct(int $id, string $firstName, string $lastName, DateTimeImmutable $birthDay)
{
$this->id = $id;
$this->firstName = $firstName;
$this->lastName = $lastName;
$this->birthDay = $birthDay;
}
public function equals(User $user): bool
{
if ($this->id !== $user->id) {
return false;
}
if ($this->firstName !== $user->firstName) {
return false;
}
if ($this->lastName !== $user->lastName) {
return false;
}
if ($this->birthDay->getTimestamp() !== $user->birthDay->getTimestamp()) {
return false;
}
return true;
}
}
$taro = new User(1, 'Taro', 'Yamada', new DateTimeImmutable('1990-07-03 09:00:00'));
$jiro = new User(2, 'Jiro', 'Suzuki', new DateTimeImmutable('1995-12-03 09:00:00'));
$renamedTaro = new User(1, 'Taro', 'Sato', new DateTimeImmutable('1990-07-03 09:00:00'));
var_dump($taro->equals($taro));
var_dump($taro->equals($jiro));
var_dump($taro->equals($renamedTaro));
// bool(true)
// bool(false)
// bool(false)
例えば、上述のコードのように同一性判定を行う場合に余計なGetterなどを生やさなくていいので便利なことがあります。
Traitとの併用
継承元のクラスを変更せずにTraitで機能を後付けした場合に、この他のオブジェクトへのアクセス権を気にする機会がありました。先日のEloquent用バルクインサートTraitの件です。
継承したサブクラスにおいてTraitを使う場合、Trait内からも同じ型のオブジェクトのprotectedなメンバーにアクセスすることができます。
/**
* @mixin User
*/
trait AgeCalculation
{
public function diffAge(User $user): int
{
if (!$this instanceof User) {
throw new LogicException('AgeCalculationトレイトを使うクラスはUserクラスまたはUserのサブクラスにしてください');
}
$interval = $this->birthDay->diff($user->birthDay);
return $interval->y;
}
}
class SuperUser extends User
{
use AgeCalculation;
}
$taro = new SuperUser(1, 'Taro', 'Yamada', new DateTimeImmutable('1990-07-03 09:00:00'));
$jiro = new SuperUser(2, 'Jiro', 'Suzuki', new DateTimeImmutable('1995-12-03 09:00:00'));
var_dump($taro->diffAge($jiro));
// int(5)
$birthDayのアクセス修飾子がprivateだった場合はサブクラスからのプロパティへのアクセス権がないので上記コードはうまく動作しませんが、UserクラスでTraitを使う場合はprivateなメンバにもアクセスできます。Trait利用先のアクセス権に準ずるということでしょう。
以上、使い所は非常に限定されますが、アクセス権を厳密に理解しておくことで、オブジェクトの中身を公開するGetterを減らしたりReflectionを使わずに済ませるといったことができることもある、というお話でした。