5
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

PHPのオブジェクトから、自身のアクセス不能メソッドを静的コンテキストで呼び出せない件(追記あり)

Last updated at Posted at 2018-06-26

まとめ

  • あるオブジェクト内で自身のクラスの定義されていないメソッドを呼び出す場合、静的コンテキストで動く__callStaticを呼び出せない。
  • どんな呼び出し方をしても、オブジェクトコンテキストで動く__callが呼び出される。
  • 別のクラスであれば、同じ書き方で意図通り静的コンテキストの__callStaticを呼び出せる。
  • オブジェクト内で自分が属するクラスの __callStatic を呼び出したい場合は、直接 self::__callStatic() と書けば良い。

※ 追記あり

@rana_kualu さんが自分自身から__callStaticを呼び出したいにて、この様な動作になった経緯について纏めて下さいました。ありがとうございます。

検証コード

  • PHPバージョン7.0.30
  • オブジェクトメソッドから、様々な方法で到達不能なメソッドの静的呼び出しを試みる。
class A
{
    public function object_method()
    {
        print("A のオブジェクトメソッドから、自身のクラス(A)のアクセス不能メソッドを呼び出す".PHP_EOL);
        self::check('self::check()');
        static::check('static::check()');
        A::check('A::check()');
        call_user_func([__CLASS__, 'check'], 'call_user_func');
        forward_static_call([__CLASS__, 'check'], 'forward_static_call');

        print(PHP_EOL);

        print("A のオブジェクトメソッドから、別のクラス(B)のアクセス不能メソッドを呼び出す".PHP_EOL);
        $b = get_class(new B());
        B::check('B::check()');
        call_user_func([$b, 'check'], 'call_user_func');
        forward_static_call([$b, 'check'], 'forward_static_call');
    }

    // アクセス不能メソッドをオブジェクトのコンテキストで実行したときに起動
    public function __call($name, $args) {
        print("__call (Object context) {$name} {$args[0]}". PHP_EOL);
    } 

    // アクセス不能メソッドを静的コンテキストで実行したときに起動
    public static function __callStatic($name, $args) {
        print("__callStatic (Static context) {$name} {$args[0]}". PHP_EOL);
    } 
}

class B extends A
{
}

$a = new A();
$a->object_method();

実行結果

  • Aのオブジェクトメソッドからのクラス A のアクセス不能メソッドの呼び出しは、全てオブジェクトコンテキストの __callとなった。
  • 別のクラス B は、同じ書き方で静的コンテキストの __callStaticが呼ばれた。
A のオブジェクトメソッドから、自身のクラス(A)のアクセス不能メソッドを呼び出す
__call (Object context) check self::check()
__call (Object context) check static::check()
__call (Object context) check A::check()
__call (Object context) check call_user_func
__call (Object context) check forward_static_call

A のオブジェクトメソッドから、別のクラス(B)のアクセス不能メソッドを呼び出す
__callStatic (Static context) check B::check()
__callStatic (Static context) check call_user_func
__callStatic (Static context) check forward_static_call

追記 : 仕様を読んでの理解

まず、指摘を受けたスコープ定義演算子 (::) は静的コンテキストで起動するという機能はありませんでした。ご指摘ありがとうございます。
また、 static::(静的遅延束縛)、および内部で静的遅延束縛を使う forward_static_call について。遅延静的束縛は「静的コンテキストで『参照できる』」だけで、『参照する』とも『参照を優先する』とも書いていません。

アクセス不能メソッドの実行時に呼ばれる __call__callStatic の挙動を見ると、
オブジェクト内で自身のクラスのアクセス不能メソッドを参照する場合、書き方の如何に関わらず、
__call が呼ばれます。オブジェクト化していない別のクラスのアクセス不能メソッドであれば __callStatic が呼ばれます。

静的メソッドと静的でないメソッドは共存できません。よって、通常は何の問題もありません。ただし、マジックメソッド(__call__callStatic)を呼び出すメソッドの場合は、実行場所によってどちらが動くか変わってしまうという事になります。これはシンプルに「あるオブジェクト内で自分のクラスのメソッドを呼び出すなら、オブジェクトコンテキストになる。書き方は関係ない」と考えれば良いと思いました。

尚、オブジェクト内で自分が属するクラスの __callStaticを呼び出したい場合は、直接 self::__callStatic() と書けば良いです。

class A
{
    public function object_method()
    {
        // `__callStatic` したいなら直接呼べばOK
        self::__callStatic('check', ['__callStatic']);
    }

    // アクセス不能メソッドを静的コンテキストで実行したときに起動
    public static function __callStatic($name, $args) {
        print("__callStatic (Static context) {$name} {$args[0]}". PHP_EOL);
    }
}

以上。

5
1
7

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
5
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?