getterの命名について（PHPの場合）

# TL;DR

getterの命名とか、悩みを吐き出して冷静になります

「これgetterだから`getHoge()`の方がわかりやすくないですか？」と言われ、明確にNoと言えなかったので言語化しておきたいのです

※ setterにはあまり触れません

# 結論

`hoge`プロパティを参照する際は
* `__get()`を実装し
* `@property-read hoge`を定義し
* **getterとしての`getHoge()`は撲滅**

# 発端

ゲームとかで、IDと経験値を持った以下のPlayerクラスを考えます

```php
class Player
{
    private $id;

    private $exp;

    public function __construct(int $id, int $exp)
    {
        $this->id = $id;
        $this->exp = $exp;
    }
}
```

何も考えずにgetterをつけてやると、おそらく以下です

```php
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()`を定義すべきでしょうか？

```php
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()`を定義すべきでしょうか？

```php
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()`にしたとして、利用する側を考えると以下のようになりそうです

```php
$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()`なのでは？と思うわけです

```php
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()](https://www.php.net/manual/ja/language.oop5.overloading.php#object.get)を実装することで、存在しないプロパティへアクセスした際の挙動を制御できます

「経験値を取得する」を`getExp()`として、合わせて定義すると以下です

```php
/**
 * @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;
    }
}
```

利用する側は以下のようになります

```php
$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()`でバリデーションするしかなさそうです

```php
/**
 * @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定義せずに、もっともっとオブジェクティブな振る舞いを定義していきたいなと思う次第です