PHP で callable なプロパティを呼び出す方法

PHP

PHP の小ネタです。 PHP において「 callable なオブジェクト a が別のオブジェクト b のプロパティとして格納されているときに、 a をどのように呼び出せばよいのか」についてです。

// ??
$b->a();

サンプルとして、インスタンスが callable な Registry クラスがある場合を考えます。

class Registry
{

  /**
   * マジックメソッド __invoke()
   */
  public function __invoke($name)
  {
    switch ($name) {
      case 'current_user':
        return new User();

      case 'timer':
        return new Timer();

      // ...
    }
  }

}

このクラスのオブジェクトは単体では次のように呼び出して利用できます。

$registry = new Registry();

$current_user = $registry('current_user');
$timer = $registry('timer');

このクラスのオブジェクトが他のクラスでプロパティにセットされて使用される場合を考えてみましょう。 次の MyController クラスは Registry クラスのオブジェクトを registry プロパティに格納して利用します。

class MyController {

  private $registry;

  /**
   * コンストラクタ
   */
  public function __construct(Registry $registry)
  {
    $this->registry = $registry;
  }

  /**
   * GET リクエストを処理する
   */
  public function run()
  {
    // ...
    $timer = $this->registry('timer');
    // ...
  }

}

使用側のイメージは次のとおりです。

$controller = new MyController(new Registry());
$controller->run();

しかし、これをそのまま実行すると次のようなエラーが出てしまいます。

// Uncaught Error: Call to undefined method MyController::registry()

これは、 $this->registry() が「プロパティ registry__invoke() の呼び出し」ではなく「 MyController クラスの registry() というメソッドの呼び出し」とみなされてしまうためです。 MyControllerregistry() というメソッドは存在しないためエラーが起こります。

では、 Registry__invoke() を呼び出したければどのようにすればよいかなのですが、 PHP 7 であればプロパティアクセスのところまでを () で囲めば OK です。

$timer = ($this->registry)('timer');

このように書くと、まず $this->registry というプロパティにアクセスしてその __invoke() を呼んでね、という指定になります。

ちなみに、この書き方は PHP 5 ではシンタックスエラーになってしまうため使えません。 PHP 5 ではよい方法が無いので、同じことをやりたい場合は「いったん変数に代入してから呼び出す」といった方法を採ることになります。

// $timer = $this->registry('timer');
// これを次のように書き換えると動く
$registry = $this->registry;
$timer = $registry('timer');

ただ・・・これではせっかく __invoke() を使っているのが台無しな感じがします。

PHP 5 で使えるもうひとつのアプローチとしては、 call_user_func() または call_user_func_array() を使った方法があります。

// $timer = $this->registry('timer');
// 次のように書き換えても OK
call_user_func($this->registry, 'timer');

ただこれも __invoke() を有効活用できているとは言いがたい感じですね。 このやり方で呼び出すぐらいなら(マジックメソッドではない)通常のメソッドを定義して使った方がましな気がします。

また、 PHP 5 で使えるもうひとつ奥の手として、プロパティを持つクラスの方で __call() を実装して呼び出せるようにするというアプローチがあります。

class MyController {

  private $registry;

  /**
   * コンストラクタ
   */
  public function __construct(Registry $registry)
  {
    $this->registry = $registry;
  }

  /**
   * GET リクエストを処理する
   */
  public function run()
  {

    // ...
    $timer = $this->registry('timer');
    // ...
  }

  /**
   * マジックメソッド __call()
   *
   * callable なプロパティを直接呼び出せるようにする
   */
  public function __call($name, $arguments)
  {
    if (isset($this->{"$name"}) && is_callable($this->{"$name"})) {
      return call_user_func_array($this->{"$name"}, $arguments);
    }

    throw new Exception("メソッド $name の不正な呼び出しがありました。");
  }

}

// $this->registry('timer') が問題なく動作する
$controller = new MyController(new Registry());
$controller->run();

ただし、ここまでやるのは少々やり過ぎな感じがします。 これをやるぐらいなら、 Registry に普通の public なメソッドを定義してそれを呼び出すようにした方が良い気がします。

というわけで、まとめると次のような感じでした。

PHP で callable なプロパティを呼び出す方法:

  • PHP 7
    • プロパティアクセスまでを () で囲えば OK
    • ($obj->prop)(...$args); のように書く
  • PHP 5
    • 原則不可能
    • 迂回作としては「いったんプロパティを変数に代入する」「 call_user_func() で呼び出す」「 __call() を実装する」等がある

というわけで、 PHP のマジックメソッドに関する小ネタでした。

参考


アバター
後藤隼人 ( ごとうはやと )

Python や PHP を使ってソフトウェア開発やウェブ制作をしています。詳しくはこちら