TL;DR
getterの命名とか、悩みを吐き出して冷静になります
「これgetterだからgetHoge()
の方がわかりやすくないですか?」と言われ、明確にNoと言えなかったので言語化しておきたいのです
※ setterにはあまり触れません
結論
hoge
プロパティを参照する際は
-
__get()
を実装し -
@property-read hoge
を定義し - getterとしての
getHoge()
は撲滅
発端
ゲームとかで、IDと経験値を持った以下のPlayerクラスを考えます
class Player
{
private $id;
private $exp;
public function __construct(int $id, int $exp)
{
$this->id = $id;
$this->exp = $exp;
}
}
何も考えずにgetterをつけてやると、おそらく以下です
class Player
{
private $id;
private $exp;
public function __construct(int $id, int $exp)
{
$this->id = $id;
$this->exp = $exp;
}
public function getId(): int
{
return $this->id;
}
public function getExp(): int
{
return $this->exp;
}
}
要件
このPlayerクラスに「経験値を取得する」メソッドを定義したいですが、どういう命名が適切でしょうか?
いわゆるsetterとしてsetExp()
を定義すべきでしょうか?
class Player
{
private $id;
private $exp;
public function __construct(int $id, int $exp)
{
$this->id = $id;
$this->exp = $exp;
}
public function getId(): int
{
return $this->id;
}
public function getExp(): int
{
return $this->exp;
}
public function setExp(int $exp): self
{
$this->exp = $exp;
return $this;
}
}
あるいはプロパティのexp
に対して加算するので、addExp()
を定義すべきでしょうか?
class Player
{
private $id;
private $exp;
public function __construct(int $id, int $exp)
{
$this->id = $id;
$this->exp = $exp;
}
public function getId(): int
{
return $this->id;
}
public function getExp(): int
{
return $this->exp;
}
public function addExp(int $exp): self
{
$this->exp += $exp;
return $this;
}
}
違和感
addExp()
にしたとして、利用する側を考えると以下のようになりそうです
$player = new Player($id, $exp);
// 現在の経験値
echo $player->getExp() . PHP_EOL;
$addExp = someFunction(); // 獲得したい経験値
$player->addExp($addExp);
// 加算後の経験値
echo $player->getExp() . PHP_EOL;
でも「経験値を取得する」って、本来はPlayerから見た振る舞いではないでしょうか?
setExp()
やaddExp()
は誰かがPlayerに「取得させてる」って感じの振る舞いです
「経験値を取得する」をPlayerから見たら、それこそgetExp()
なのでは?と思うわけです
class User
{
private $id;
private $exp;
public function __construct(int $id, int $exp)
{
$this->id = $id;
$this->exp = $exp;
}
public function getId(): int
{
return $this->id;
}
public function getExp(): int
{
return $this->exp;
}
public function getExp(int $exp): self
{
$this->exp += $exp;
return $this;
}
}
でもこれは当然、本来のgetterとしてのgetExp()
と重複してるのでエラーになります
そもそも
Playerの現在の経験値を参照するメソッドがgetExp()
っていうのも、やっぱり「誰かから見た」振る舞いです
オブジェクト指向って、もっとオブジェクトが自身の振る舞いを定義するべきでは?と思うわけです
そう考えると、Playerの現在の経験値を参照するメソッドは、なんならtakeExp()
とかshowExp()
とかの方がよっぽどオブジェクト指向的だと思うわけです
でも文脈によってtake
やshow
は適切でない場合が多々あると思うので、プロパティそのものが参照できれば一番無難そうです
解決
PHPではマジックメソッド__get()を実装することで、存在しないプロパティへアクセスした際の挙動を制御できます
「経験値を取得する」をgetExp()
として、合わせて定義すると以下です
/**
* @property-read int $exp
*/
class User
{
private $id;
private $exp;
public function __construct(int $id, int $exp)
{
$this->id = $id;
$this->exp = $exp;
}
public function __get(string $property)
{
if (!property_exists($this, $property)) {
throw new \Exception("Invalid access: {$property}");
}
return $this->$property;
}
public function getExp(int $exp): self
{
$this->exp += $exp;
return $this;
}
}
利用する側は以下のようになります
$player = new Player($id, $exp);
// 現在の経験値
echo $player->exp . PHP_EOL;
$addExp = someFunction(); // 獲得したい経験値
$player->getExp($addExp);
// 加算後の経験値
echo $player->exp . PHP_EOL;
@property-read
アノテーションを付けておくことで、PhpStorm等のIDEでも認識してくれるようになります
しかしこの場合、@property-read
アノテーションを付けていないid
プロパティも同様にアクセスできてしまいます
@property-read
したものしか見せたくないという場合は、__get()
でバリデーションするしかなさそうです
/**
* @property-read int $exp
*/
class User
{
private $id;
private $exp;
private const VISIBLE_PROPERTIES = [
'exp',
];
public function __construct(int $id, int $exp)
{
$this->id = $id;
$this->exp = $exp;
}
public function __get(string $property)
{
if (!property_exists($this, $property)) {
throw new \Exception("Invalid access: {$property}");
}
if (!in_array($property, self::VISIBLE_PROPERTIES)) {
throw new \Exception("Cannot access: {$property}");
}
return $this->$property;
}
public function getExp(int $exp): self
{
$this->exp += $exp;
return $this;
}
}
まとめ
さすがにgetExp()
は紛らわしいですよ…という意見はありそうですが、それはそれとして
脳死でgetter定義せずに、もっともっとオブジェクティブな振る舞いを定義していきたいなと思う次第です