private/protectedなプロパティを外部から読み込み可能にする


シンプルなPHPのクラスがあったとするよね。

```User.php
<?php
namespace YourSystem\Model;

class User
{
    /** @var int */
    public $id;
    /** @var string */
    public $name;
    
    public function __construct($id, $name)
    {
        $this->id = $id;
        $this->name = $name;
    }
}
```

```test.php
<?php
use YourSystem\Model\User;

$user = new User(39, "Miku");
echo $user->id;
// => 39
echo $user->name;
// => "Miku"
```

このクラスの問題点は、プロパティを外部から読み出したいがために、可視性を`public`にしてしまってること。

しかし、予期しないデータの書き込みは防ぎたい…

## :sparkles: 解決策

### ゲッターメソッドを定義する

プロパティの可視性を`private`または`protected`に変更し、プロパティ名と共通する規則でメソッドを定義する。

```User.php
<?php
namespace YourSystem\Model;

class User
{
    /** @var int */
    private $id;
    /** @var string */
    private $name;
    
    public function __construct($id, $name)
    {
        $this->id = $id;
        $this->name = $name;
    }

    /**
     * @return int|null
     */
    public function getId()
    {
        return $this->id;
    }
    
    /**
     * @return string|null
     */
    public function getName()
    {
        return $this->name;
    }
}
```

```test.php
<?php
use YourSystem\Model\User;

$user = new User(39, "Miku");
echo $user->getId();
// => 39
echo $user->getName();
// => "Miku"
```

* 良いところ
  * 明示的
* 悪いところ
  * クラス×プロパティごとにメソッド明示しなきゃいけないのだるい

### `__get()`を定義する

原理としては、参照不可能なプロパティ(`private`/`protected`などの可視性を含む)を参照しようとすると`__get()`が呼ばれ、`__get()`は(当然)オブジェクトに属するメソッドなので`private`/`protected`なプロパティを参照して返り値として返すことができる、といった具合です。

```User.php
<?php
namespace YourSystem\Model;

/**
 * @property-read int    $id
 * @property-read string $name
 */
class User
{
    /** @var int */
    private $id;
    /** @var string */
    private $name;
    
    public function __construct($id, $name)
    {
        $this->id = $id;
        $this->name = $name;
    }
    
    public function __get($name)
    {
        return $this->$name;
    }
    
    public function __isset($name)
    {
        return isset($this->$name);
    }
}
```

* 良いところ
  * クラスの外部にプロパティとして公開できる
* 悪いところ
  * クラスごとに同じ`__get()`と`__isset()`を定義するのだるい
  * PhpStormなどのIDEで外部から参照させるためには`@property-read`を記述する必要がある

## 提案手法


[BaguettePHP/objectsystem](https://github.com/BaguettePHP/objectsystem)ってライブラリに[`trait PrivateGetter`](https://github.com/BaguettePHP/objectsystem/blob/master/src/Object/PrivateGetter.php)を用意いたしました。

実装としては、上記の`__get()`と`__isset()`が定義されてるだけです。おてがる。

```User.php
<?php
namespace YourSystem\Model;

/**
 * @property-read int    $id
 * @property-read string $name
 */
class User
{
    use \Teto\Object\PrivateGetter;

    /** @var int */
    private $id;
    /** @var string */
    private $name;
    
    public function __construct($id, $name)
    {
        $this->id = $id;
        $this->name = $name;
    }
}
```

トレイトでGetterが生やしてボイラープレートが一気に減ったぞ！

* 良いところ
  * クラスの外部にプロパティとして公開できる
  * クラスに一行書くだけでゲッターが生えてくる
* 悪いところ
  * トレイトは継承と組合せると、ちょっと想像しにくい動作をする
  * PhpStormなどのIDEで外部から参照させるためには`@property-read`を記述する必要がある

PHPでの継承は基本的にバッドプラクティスだと思ってるので推奨はしませんが、動作が気になる型は[PrivateGetterTest.php](https://github.com/BaguettePHP/objectsystem/blob/master/tests/Object/PrivateGetterTest.php)をお読みください。

## まとめ

いはゆる<abbr title="Plain Old PHP Object">POPO</abbr>([Plain Old Java Object](https://ja.wikipedia.org/wiki/Plain_Old_Java_Object))で利用すると良いのでは。

(この記事のサンプルコードは説明を簡単にするためにプロパティ定義とコンストラクタしかありませんが、単なる構造体オブジェクトをPOJOと呼ぶ[^1]、といった意図は毛頭ございません)

[^1]: [日本と欧米ではPOJOの意義に対して微妙な解釈の違いがあるような気がする - 達人プログラマーを目指して](http://d.hatena.ne.jp/ryoasai/20101031/1288493027)