まとめ
- あるオブジェクト内で自身のクラスの定義されていないメソッドを呼び出す場合、静的コンテキストで動く
__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);
}
}
以上。