【Declined】PHP8に入ることのできなかった機能たち

PHP8では大量の[RFCが](https://qiita.com/rana_kualu/items/a6601b49e0591eb42200)[受理され](https://qiita.com/rana_kualu/items/fe7998fbe773544d5d25)、多くの文法の拡充が行われました。
光あるなら陰もまたあり、すなわち華々しく活躍するRFCの傍らには、却下され消えゆくRFCもまた存在するということです。

ということでPHP8に入り込むことを目指したものの[却下されたRFC](https://wiki.php.net/rfc#declined)を見てみることにしましょう。
採用されたRFCと却下されたRFCを見比べることで、PHPの今後の方向性とかがわかるかもしれませんよ。


# Declined RFC


## [Dump results of expressions in `php -a`](https://wiki.php.net/rfc/readline_interactive_shell_result_function)

賛成11、反対6で却下。

`php -a`のインタラクティブシェルは寡黙です。

```php
$ php -a
Interactive shell

php > 1+1;
php > $a = 1;
php > $a;
php > var_dump($a);
int(1)
```

代入などの操作を行っても何も言いません。

これがたとえばJavaScriptコンソールであれば毎回最後の演算結果を出力します。

```js
> 1+1;
<< 2
> a = 1;
<< 1
> console.log(a);
1
```

そんなわけでPHPコンソールも饒舌に、デフォルトで最後の演算結果を出力しよう、という提案。

また出力を制御する関数`readline_interactive_shell_result_function()`というちょっとどうかしてる名前の関数も導入しようという提案でしたが、却下されました。
おそらく演算結果の出力だけだったら通ってた気がしないでもない。


## [StackFrame class](https://wiki.php.net/rfc/stack-frame-class)

賛成14、反対14で却下。

[debug_backtrace](https://www.php.net/manual/ja/function.debug-backtrace.php)はトレースの返り値が配列です。
そこでStackFrameクラスのインスタンスにしようという提案。
これによってメモリ省力化につながります。
RFCでは、巨大なテストスクリプトでメモリ消費量が390MBから192MBへ削減できたということでした。

しかし投票時点でもプルリクに[メモリリークのバグがあり](https://externals.io/message/111091#111139)、テストスクリプトが公開されていなかったので検証もできないということで却下になりました。
そこらへんきちんと準備していたらおそらく通っていただろうに、惜しいことをしたものです。

## [Make constructors and destructors return void](https://wiki.php.net/rfc/make_ctor_ret_void)

賛成34、反対22で却下。

コンストラクタ、デストラクタの返り値に`void`を強制する提案。

[マニュアル](https://www.php.net/manual/ja/language.oop5.decon.php)ではコンストラクタ、デストラクタの返り値の型は`void`となっています。
しかし、これを無視して値を返しても何も言われないし、むしろ`void`を付けると逆に怒られます。

```php
class HOGE{
    public function __construct(){
        return 1; // OK
    }
    public function __destruct():void{ // Fatal error: Method HOGE::__destruct() cannot declare a return type
    }
}
```

そこでマニュアルと挙動を合わせ、コンストラクタ、デストラクタは値を返さないようにし、`void`を付けられるようにしようというものです。

正しくするといえばそうなのですが、これまで`void`以外を返していたメソッドが動かなくなるという非互換があったため反対が多くなったようです。


## [Rename T_PAAMAYIM_NEKUDOTAYIM to T_DOUBLE_COLON](https://wiki.php.net/rfc/rename-double-colon-token)

賛成44、反対30で却下。

`::`はT_PAAMAYIM_NEKUDOTAYIMというよくわからない名前で知られています。
意味がわからないので[T_DOUBLE_COLONにしよう](https://gist.github.com/KalleZ/e5be75fa515db625200df955d5d814b8)という提案。

これ[10年前](https://externals.io/message/49978)からずっと言われているのですが、T_PAAMAYIM_NEKUDOTAYIMが導入されたのは20年以上前なので歴史の差では勝てません。
実はT_DOUBLE_COLONも[大昔から定義されている](https://github.com/php/php-src/blob/8566193449479a5641418ca1e9451c74f0acdb4e/ext/tokenizer/tokenizer_data.c#L150)のですが、これがどうなっているのかはよくわかりません。


## [Opcache optimization without any caching](https://wiki.php.net/rfc/opcache.no_cache)

賛成10、反対13で却下。

Opcacheの機能は最適化とキャッシュです。
これはセットになっていて、キャッシュせずに最適化だけ使用することはできません。

ということで最適化だけ単体で使えるようにしようという提案。

しかし、これを実行するには[optimizerをopcacheからPHPコアに持っていかないといけない](https://github.com/php/php-src/pull/5504#issuecomment-622295467)みたいな話になり、話が広がりすぎてしまいました。


## [Match expression](https://wiki.php.net/rfc/match_expression)

賛成6、反対28で却下。

match式の提案です。

```php
$expressionResult = match ($condition) {
    1, 2 => foo(),
    3, 4 => bar(),
    default => baz(),
};
```

このRFCは当初からブロック構文を含んでいたのですが、[メモリリークバグ](https://github.com/php/php-src/pull/5448)などの問題があり[紆余曲折](https://externals.io/message/109590)の末に却下されました。

その後、ブロック構文を外すなど機能を制限してブラッシュアップされた[Match expression v2](https://wiki.php.net/rfc/match_expression_v2)として[提案・受理され](https://qiita.com/rana_kualu/items/38d2854d0575fb73f56a)、[PHP8.0から使えるようになりました](https://www.php.net/manual/ja/control-structures.match.php)。


## [Type casting in array destructuring expressions](https://wiki.php.net/rfc/typecast_array_desctructuring)

賛成6、反対26で却下。

型キャストの新文法の提案。

```php
// これを
[$now, $future] = explode(',', '2020,2021');
$now = (int) $now;
$future = (int) $future;

// こうしたい
[(int) $now, (int) $future] = explode(',', '2020,2021');

// これを
$users = [ ['id'=>'1', 'name'=>'ore'], ['id'=>'2', 'name'=>'omae'] ];
foreach($users as ['id'=>$id, 'name'=>$name]){
	$id = (int)$id;
	echo "{$name}のIDは{$id}です";
}

// こうしたい
foreach($users as ['id'=>(int)$id, 'name'=>$name]){
	echo "{$name}のIDは{$id}です";
}
```

PHPの場合、CSVやDB等から取ってきた値は全てstring型なので、取り出す時点でint型にしておきたいなどの需要はあるでしょう。
そんなときにあれば便利かもしれませんが、それだけのために新構文を追加する説得力が出せずに却下されました。


## [Compact Object Property Assignment](https://wiki.php.net/rfc/compact-object-property-assignment)

賛成2、反対48で却下。

オブジェクトのプロパティに一括で値を投入したいという提案。

```php
$foo->[
    a = 1,
    b = myfunc(),
    c = $foo->bar(),
];
```

驚きの反対率で却下されました。

```php
$foo->a = 1;
$foo->b = myfunc();
$foo->c = $foo->bar();
```

正直、現状の書き方と比べて長さもあまり変わらないですね。


## [Userspace operator overloading](https://wiki.php.net/rfc/userspace_operator_overloading)

賛成38、反対28で却下。

ユーザランドでの演算子オーバーロードの提案。

ここで言う演算子オーバーロードは、[PHPにおけるオーバーロード](https://www.php.net/manual/ja/language.oop5.overloading.php)ではなく一般的に言うところの演算子オーバーロードです。

PHPでは演算子を再定義することはできません。
`+`とか`<`とかの定義は不変ということです。
またPHP8以降、[オブジェクトへの算術演算](https://wiki.php.net/rfc/arithmetic_operator_type_checks)はTypeErrorになります。

しかし[GMP](https://www.php.net/manual/ja/class.gmp.php)や[DateTime](https://www.php.net/manual/ja/datetime.diff.php)など、独自の演算子オーバーロードが施されているオブジェクトも存在します。
ならばユーザランドでも同じようにオーバーロードできてもいいじゃない。


```php
class Vector3(){
    public static function __add($lhs, $rhs) {
        // 演算子'+'の定義
    }
 
    public static function __mul($lhs, $rhs): ?Vector3 {
        // 演算子'*'の定義
    }
}

$a = new Vector3();
$b = new Vector3();

$x = $a + $b;
$y = 3 * $b;
```

と、このように各演算子に対してマジックメソッドで独自の処理を実装できるという機能です。
しかし[-は実装できるが+が実装できない例](https://externals.io/message/108788#108805)など様々な問題が広がりまくった挙句却下となりました。


## [User Defined Operator Overloads](https://wiki.php.net/rfc/user_defined_operator_overloads)

賛成21、反対24で却下。

さきほどの`Userspace operator overloading`と似たような、演算子オーバーロードの提案です。
ほぼ同時期に出たというだけでオーサーは別人です。

```php
class Collection {
    // 演算子'+'の実装
    operator +(Collection $other, OperandPosition $operandPos) {}
}
```

新しいキーワード`operator`を追加し、演算子を直接定義できるようになります。

[わしゃこのRFCに100時間以上注ぎ込んでるんじゃよ](https://externals.io/message/115778#115785)とのことでしたが、[長い](https://externals.io/message/115764)[長い](https://externals.io/message/116611)[議論の果て](https://externals.io/message/116767)に却下となりました。



## [Write-Once Properties](https://wiki.php.net/rfc/write_once_properties)

賛成23、反対23で却下。

一回だけ書き込みが可能で、書き込んだらその後は変更できなくなるプロパティです。

```php
class Foo
{
    <keyword> public int $a;
    <keyword> public array $b;
    <keyword> public object $c;
 
    public function __construct()
    {
        $this->a = 1;
        $this->b = ["foo"];
    }
}
 
$foo = new Foo();

$foo->a = 2;              // EXCEPTION: 書き込み済なのでNG
var_dump($foo->b);        // publicなら読み込みはOK。
$foo->c = new stdClass(); // 一回目なので書き込める。
```

PHP8.1において、[ほぼ同じ内容で受理](https://qiita.com/rana_kualu/items/ae96400a0a6981eca64a)されました。
違いはクラス外から書き込み可能か否かくらいで、どうして明暗が分かれたのかはよくわかりません。


## [declare(function_and_const_lookup='global')](https://wiki.php.net/rfc/use_global_elements)

賛成2、反対35で却下。

PHPの関数呼び出しは、
・まず現在の名前空間に関数があるか探す
・なかったらグローバル名前空間の関数を探す
と2段階になっていて効率がよくありません。

そこで常時グローバル名前空間を探すようにするディレクティブ`declare(function_and_const_lookup='global')`を導入しようという提案。

```php
declare(
    strict_types=1,
    function_and_const_lookup='global'
);

namespace MyNS;

use function OtherNS\my_function;
use const OtherNS\OTHER_CONST;

if (version_compare(PHP_VERSION, '8.0.5') >= 0) {
    // このversion_compareはグローバル関数であることが保証される
}
```

当初は`use function *;`って[書きたかったけど書けなかったからどうにかしたい](https://externals.io/message/107877)って話から始まったようです。



## [Deprecate Backtick Operator (V2)](https://wiki.php.net/rfc/deprecate-backtick-operator-v2)

賛成11、反対26で却下。

[バッククォート](https://www.php.net/manual/ja/language.operators.execution.php)をE_DEPRECATEDにしようという提案。

バッククォートはシェル実行するという危険な機能にもかかわらず、わかりやすい目印がなく引用符`"`・`'`と紛れやすいため安全ではありません。
そもそも[shell_exec](https://www.php.net/manual/ja/function.shell-exec.php)と同一であるため、こちらに揃えた方がよいでしょう。

個人的にはさっさと消してしまっていいと思うのですが、しかしこのRFCが提出されたのは[Deprecate short open tags, again](https://wiki.php.net/rfc/deprecate_php_short_tags_v2)と同時期です。
すなわち[銀河に平和がもたらされた](https://qiita.com/tadsan/items/80cd844ee612c6cadabf)直後であり、やはりこちらも[Zeevが反対を表明](https://externals.io/message/107779#107407)しています。

このあたりの関係もあり、却下されたのだと思われます。


## [Object Initializer](https://wiki.php.net/rfc/object-initializer)

賛成3、反対26で却下。

新しいオブジェクトイニシャライザ構文の提案。

```php
// これを
$customer = new Customer();
$customer->id = 123;
$customer->name = "John Doe";

// こうしたい
$customer = new Customer {
  id = 123,
  name = "John Doe",
};
```

[この例ではファクトリーメソッドを使うべきだろ](https://externals.io/message/106947#107074)と突っ込まれていました。
まあそりゃそうだ。

## [Add array_group function](https://wiki.php.net/rfc/array_column_results_grouping)

賛成0、反対19で却下。

関数`array_group`がほしい、という提案。

```php
$array = [
    ['id' => 1, 'name' => 'hassan'],
    ['id' => 2, 'name' => 'sara'],
    ['id' => 3, 'name' => 'selim'],
    ['id' => 4, 'name' => 'chris'],
    ['id' => 5, 'name' => 'sara'],
];

var_dump(array_group($array, null, 'name')); // ↓こんなふうになる
[
    'hassan' => [ ['id' => 1, 'name' => 'hassan'] ],
    'sara' => [ ['id' => 2, 'name' => 'sara'], ['id' => 5, 'name' => 'sara'], ],
    'selim' => [ ['id' => 3, 'name' => 'selim'] ],
    'chris' => [ ['id' => 4, 'name' => 'chris'] ],
];
```

キーが同じ要素をまとめたいという要望ですね。
意外と需要があるようで、Qiitaでも[何度も](https://qiita.com/Jinta/items/3977d988a7104937ce3a)[再発明](https://qiita.com/ponsuke0531/items/405daf077640b6550d4f)[されて](https://qiita.com/suin/items/c524cf84368332db426b)います。

元々は[array_columnの引数で返り値を変えよう](https://externals.io/message/116564#116564)という意見であり、これには全員がやめろと突っ込んだ結果新たに関数を作るようにRFCを変更したのですが、それでも結局却下されてしまいました。


## [PHP Namespace in core](https://wiki.php.net/rfc/php-namespace-in-core)

賛成19、反対24で却下。

PHPプロジェクトは`\PHP`で始まる名前空間を[予約しています](https://www.php.net/manual/ja/language.namespaces.rationale.php)。
が、これがあんまり使われてないので言語コア機能は今後これを使っていこうぜという提案。

たとえば[\PhpTokenではなく\PHP\PhpTokenにしよう](https://wiki.php.net/rfc/token_as_object)といったかんじです。
なお、既に実装されているものについてはこのRFCでは変更しません。

どうして却下されたかよくわからないのですが、[ML](https://externals.io/message/109651)によると『言語コア機能は』がひっかかった人が多いように見えます。


## [PHP Namespace Policy](https://wiki.php.net/rfc/php_namespace_policy)

賛成13、反対17で却下。

上の`PHP Namespace in core`と同時期に作られたRFCです。
特に関連しているわけではなく、たまたま時期が被ったみたい。

PHPコア用に`\PHP`、拡張機能用に`\Ext`名前空間を予約します。
また名前空間の命名規則も規定します。
名前空間`\Foo\Bar\Baz`があれば、`Foo`が企業名、`Bar`がコンポーネント名となります。
なお、既に実装されているものについてはこのRFCでは変更しません。

規程までするのはやり過ぎと思われたのか、こちらも同様に拒否されました。

あと`\Ext`を使うには申請しなきゃいけないとかPEARを思い出すような仕様があったのも原因かもしれません。


# 感想

そんなの却下されて当たり前だろというものから、どうしてこれが却下されたんだ？というものまで、様々なRFCが提案されています。
これらの却下されたRFCと、[受理](https://qiita.com/rana_kualu/items/a6601b49e0591eb42200)[された](https://qiita.com/rana_kualu/items/fe7998fbe773544d5d25)RFCを比べて今後のPHPの進む先を探ってみるのも楽しいかもしれませんね。