Help us understand the problem. What is going on with this article?

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

More than 3 years have passed since last update.

シンプルな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、といった意図は毛頭ございません)

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away