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

  • 11
    いいね
  • 3
    コメント
この記事は最終更新日から1年以上が経過しています。

シンプルな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 namespace 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 namespace 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ってライブラリにtrait PrivateGetterを用意いたしました。

実装としては、上記の__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をお読みください。

まとめ

いはゆるPOPO(Plain Old Java Object)で利用すると良いのでは。

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