PHP8.1もまだリリースされていないというのに、早々とPHP8.2に導入される機能がひとつ決まりました。
導入というか削減ですが。
そんなわけで以下はPHP8.2のRFC、Deprecate partially supported callablesの日本語訳です。
Deprecate partially supported callables
Introduction
このRFCは、call_user_func($callable)
でサポートされているが、$callable()
ではサポートされていない構文を非推奨とします。
Proposal
以下の構文は、is_callableやcall_user_funcでは有効な構文ですが、$callable()には非対応です。
"self::method"
"parent::method"
"static::method"
["self", "method"]
["parent", "method"]
["static", "method"]
["Foo", "Bar::method"]
[new Foo, "Bar::method"]
このRFCでは、これらの構文をPHP8.2で非推奨とし、PHP9で非対応とします。
この構文はcall_user_func()
やarray_map()
等では非推奨の警告を発生します。
is_callable()
やcallable
は、サポートされなくなるまで非推奨の警告は発生しません。
通常のコールバック構文、function
・Foo::method
・["Foo", "method"]
・[new Foo, "method"]
はこのRFCによる影響はありません。
Discussion
このRFCは、コールバックにおける二つの問題を解決しようとしています。
ひとつめは、callable
の定義が矛盾している現状を解決することです。
最近はコールバックには$callable()
を使うことが推奨されていますが、実際にはcallable
であっても$callable()
できないものが存在しました。
この矛盾の解決方法はふたつあります。
callable
のサポートを廃止するか、$callable()
の呼び出しをサポートするか、です。
しかし、ここでふたつめの問題が発生します。
例文では、最後の二つを除いて呼び出しはコンテキストに依存します。
self::method
が参照するメソッドは、何処から呼び出されたかによって変わってしまいます。
このRFCふたつめの目的は、コールバックのコンテキスト依存性を減らすことです。
このRFCが通過すると、あと残っているのはメソッドの可視性だけになります。
すなわち、Foo::bar
はあるスコープでは可視だけど別のスコープでは不可視になるということです。
将来的にコールバックがpublicメソッドに限定されたりすればこの依存性も解消しますが、このRFCでは可視性については取り扱いません。
Backward Incompatible Changes
ここで非推奨とされるコールバックは、大抵は単純に置き換えることで解決します。
たとえば"self"
はself::class
になります。
"self::method" -> self::class . "::method"
"parent::method" -> parent::class . "::method"
"static::method" -> static::class . "::method"
["self", "method"] -> [self::class, "method"]
["parent", "method"] -> [parent::class, "method"]
["static", "method"] -> [static::class, "method"]
後者の形式は、コンテキストに依存しません。
コールバックが作成された場所ではなく、呼び出す側のself/parent/staticスコープを参照するようになります。
PHP8.1未満との互換性が必要なければ、ファーストクラスのコールバック構文self::method(...)
を使うことも可能です。
[new Foo, "Bar::method"]
は単純に置き換えができませんが、ほとんどの人はこんな使い方自体を見たことがないでしょう。
class Bar {
public function method() {}
}
class Foo extends Bar {
public function method() {}
}
このコールバック構文の考え方としては、[new Foo, "method"]
はBar::method()
をオーバーライドしたFoo::method()
を呼び出します。
[new Foo, "Bar::method"]
や[new Foo, "parent::method"]
は、オーバーライドされる前のBar::method()
を呼び出すということです。
Fooスコープ内では["Bar", "method"]
、あるいは[parent::class, "method"]
と呼び出せばいいです。
これは静的メソッドではなくスコープされたメソッド呼び出しであり、要はparent::method()
と同じです。
PHPでは、オーバーライドされたメソッドをスコープの外側から呼び出すための、わかりやすい仕組みはありません。
どうしても必要であればリフレクションやクロージャを使って実現することはできます。
// リフレクション
(new ReflectionMethod("Bar", "method"))->invoke(new Foo);
// クロージャ
Closure::fromCallable([new Bar, "method"])->bindTo(new Foo)();
Vote
投票期間は2021/10/08から2021/10/22で、有権者の2/3の賛成で受理されます。
本RFCは賛成32反対0の全会一致で可決されました。
感想
class Foo{
public function bar()
{
$callable = 'self::hoge';
is_callable($callable); // true
call_user_func($callable); // "Foo::hoge"
$callable(); // Fatal error: Class 'self' not found
}
public function hoge()
{
var_dump(__METHOD__);
}
}
(new Foo())->bar();
is_callable
はtrueだしcall_user_func
では実際に呼ぶこともできるのに、$callable()
ではエラーになってしまいます。
そんなわけでcall_user_func
のこのような使い方はPHP8.2でE_DEPRECATEDにし、PHP9ではエラーにします。
is_callable
はPHP8.2で呼び出しは可能なのでtrueのままですが、PHP9ではfalseになります。
今後はこのような使い方をしないようにしましょう。
$callable()
が動くようにしなかったのは、本文中にもあるとおり、$callable()
を書く位置によって動いたり動かなかったりするからということのようです。
静的なcallableを全然使ったことがないのでよくわからないのですが、このような書式が有用な事例ってあったりするんですかね。
むしろ実際に使っている事例ってあるんですかね。