【PHP8.2】call_user_func()と$callable()の矛盾を解消する

PHP8.1もまだリリースされていないというのに、早々とPHP8.2に導入される機能がひとつ決まりました。
導入というか削減ですが。

そんなわけで以下はPHP8.2のRFC、[Deprecate partially supported callables](https://wiki.php.net/rfc/deprecate_partially_supported_callables)の日本語訳です。


# Deprecate partially supported callables

## Introduction

このRFCは、`call_user_func($callable)`でサポートされているが、`$callable()`ではサポートされていない構文を非推奨とします。

## Proposal

以下の構文は、[is_callable](https://www.php.net/manual/ja/function.is-callable)や[call_user_func](https://www.php.net/manual/ja/function.call-user-func)では有効な構文ですが、[$callable()](https://www.php.net/manual/ja/language.types.callable.php)には非対応です。

```php
"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`になります。

```php
"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未満との互換性が必要なければ、[ファーストクラスのコールバック構文](https://qiita.com/rana_kualu/items/a6601b49e0591eb42200#first-class-callable-syntax)`self::method(...)`を使うことも可能です。

`[new Foo, "Bar::method"]`は単純に置き換えができませんが、ほとんどの人はこんな使い方自体を見たことがないでしょう。

```php
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では、オーバーライドされたメソッドをスコープの外側から呼び出すための、わかりやすい仕組みはありません。
どうしても必要であればリフレクションやクロージャを使って実現することはできます。

```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の全会一致で可決されました。

# 感想

```php
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を全然使ったことがないのでよくわからないのですが、このような書式が有用な事例ってあったりするんですかね。
むしろ実際に使っている事例ってあるんですかね。