【PHP8.2】PHP8.2の新機能

[PHP8.3](https://qiita.com/rana_kualu/items/91111443105a75a55e2f) / [PHP8.2](https://qiita.com/rana_kualu/items/fc4b02e2daaf102aa92f) / [PHP8.1](https://qiita.com/rana_kualu/items/a6601b49e0591eb42200) / [PHP8.0](https://qiita.com/rana_kualu/items/fe7998fbe773544d5d25)


**[2022/12/08にリリース](https://qiita.com/rana_kualu/items/64bd73ad3d314a1cccdc)されました。**

2022/07/19、PHP8.2が[フィーチャーフリーズ](https://wiki.php.net/todo/php82)しました。
言語機能に関わるような機能の追加・変更が締め切られたということです。
今後はデバッグを繰り返しながら完成度を高めていき、2022/11/24にPHP8.2.0がリリースされる予定です。

というわけでPHP8.2で実装されるRFCを見てみましょう。

# RFC

## [Disjunctive Normal Form Types](https://wiki.php.net/rfc/dnf_types)

賛成25、反対1で受理。

[選言標準形](https://qiita.com/rana_kualu/items/ddadd32756afdefbe99f)です。
UNION型と交差型を同時に使えるようになります。
思う存分型パズルで遊べますね。

```php
// A型、もしくはB型かつC型、もしくはint
function hoge( A | (B & C) | int $param){}
```


## [Allow null and false as stand-alone types](https://wiki.php.net/rfc/null-false-standalone-types)

賛成38、反対0の全会一致で受理。

[null型とfalse型](https://qiita.com/rana_kualu/items/2ef15cfa2dea8ba0f061)です。

null型にはnullしか入れることができず、false型にはfalseしか入れることができません。

```php
class Nil {
    public null $nil = null;

    public function foo(false $falsy): false{
        return false;
    }
}
```

元々[UNION型](https://qiita.com/rana_kualu/items/f75e254553157d8eb0b7)を導入する際にnull疑似型とfalse疑似型が導入されました。
これはUNION型の一部としてのみ記述可能で、単独では使えないという特殊な型でした。

当時としてはそうする合理的理由があったわけですが、その後交差型などで型システムが拡張された結果、単独でも使えた方がいいだろうということになり疑似型から通常の型に昇格しました。


## [Add true type](https://wiki.php.net/rfc/true-type)

賛成33、反対0の全会一致で受理。

[true型](https://qiita.com/rana_kualu/items/2e64ecb8740d21f4fd34)です。

true型にはtrueしか入れることができません。

```php
function foo(true $true): true{
    return true;
}
```

false型が導入されたので、そうなるとtrue型が無いのは片手落ちになってしまいます。
また静的解析ツールにとっても便利なこと、実際にtrueだけを返す関数が存在するなど、他にも利点があるため導入が決定しました。


## [Readonly classes](https://wiki.php.net/rfc/readonly_classes)

賛成28、反対7で受理。

[readonlyクラス](https://qiita.com/rana_kualu/items/0d8cfb5d0f83ae24f943)です。

```php
readonly class Test {
    public int $foo;
}

$t = new Test();
$t->foo = 1; // OK
$t->foo = 2; // NG 上書きは不可
$t->bar = 3; // NG 動的プロパティは禁止
```

readonlyクラスは、静的プロパティは設定できず、動的プロパティも使用できず、型指定は必須であり、一度設定したプロパティは書き替えることができません。
できることが非常に限定されるかわりに、堅固な運用が可能になります。



## [Random Extension 5.x](https://wiki.php.net/rfc/rng_extension)

賛成21、反対0の全会一致で受理。

PHPの乱数は、これまで様々な問題がありました。
有名どころでは[メルセンヌツイスタの実装が間違ってる](https://sucrose.hatenablog.com/entry/2016/02/19/235506)とか[範囲によっては奇数しか出てこない](https://news.ycombinator.com/item?id=9941364)とか。

このあたりはある程度個別に解消されたのですが、もっと深刻な問題として[状態がグローバル](https://speakerdeck.com/colopl/colopltech-04-02?slide=12)というものがあります。
これのせいで外部ライブラリで乱数が乱れたり、状況再現ができないみたいな問題が発生するみたいです。
正直そんなコード書いたことないからよくわかりませんが。
他にもRFCには様々な問題点が書かれていました。

ということで、そのあたりを解決する`Random\Randomizer`クラスを導入します。

```php
$engine = new Random\Engine\Secure();
$randomizer = new Random\Randomizer($engine);

$randomizer->getInt(1, 10);           // mt_rand
$randomizer->getBytes(10);            // random_bytes
$randomizer->shuffleArray([1, 2, 3]); // shuffle
$randomizer->shuffleString('abc');    // str_shuffle
```



## [Random Extension Improvement](https://wiki.php.net/rfc/random_extension_improvement)

`Random Extension`の補足RFCです。
投票開始後に発覚した問題点の改善や、不足していた機能の追加などです。

```php
$randomizer->pickArrayKeys([1, 2, 3], 1); // array_rand
$randomizer->shuffleBytes('abc');         // shuffleStringから改名
```

`shuffleString`をマルチバイト文字に使うと壊れるので、適切な`shuffleBytes`に改名する、array_randを実装する、当初のRFCに含まれていた`Random\Engine\CombinedLCG`は乱数として低品質であるため削除する、等が含まれています。


## [Redacting parameters in back traces](https://wiki.php.net/rfc/redact_parameters_in_back_traces)

賛成24、反対1で受理。

引数をスタックトレースに出力しないようにするアトリビュート`#[\SensitiveParameter]`です。

```php
function test(
    $foo = null,
    #[\SensitiveParameter]
    $bar = null,
    $baz = null
) {
    throw new \Exception('Error');
}
 
test(
    bar: 'bar',
    baz: 'baz',
);
 
/*  エラーメッセージは以下のようになる

	Fatal error: Uncaught Exception: Error in test.php:8
	Stack trace:
	#0 test.php(13): test(NULL, Object(SensitiveParameterValue), 'baz')
	#1 {main}
	  thrown in test.php on line 8
*/
```

アトリビュート`#[\SensitiveParameter]`を設定した引数`$bar`の値は、例外スタックトレースや[var_dump](https://www.php.net/var_dump)等に出力されなくなります。

どうしてこんなものが必要かというと、たとえば[PDO::__construct()](https://www.php.net/manual/ja/pdo.construct.php)は失敗すると例外を吐くのですが、[display_errors](https://www.php.net/manual/ja/errorfunc.configuration.php#ini.display-errors)がオンだと思いっきりDBパスワードが表示されます。
これがうっかり設定ミスとかのせいで本番環境で発生したら大惨事です。
またクレカ番号等のセンシティブな情報がアクセスログに流れてそこから流出という可能性もありえます。

そういうミスがあった場合でも大丈夫なように、という安全策ですね。


## [Deprecate dynamic properties](https://wiki.php.net/rfc/deprecate_dynamic_properties)

賛成52、反対25の僅差で受理。

[動的プロパティ禁止](https://qiita.com/rana_kualu/items/53012c8f84e0f92220c1)です。

```php
class Test {}

$t = new Test();
$t->foo = 1; // PHP <= 8.1: $t->fooが生える
             // PHP    8.2: E_DEPRECATE
             // PHP    9.0: Exception
```

未定義のプロパティは、PHP8.2ではE_DEPRECATEを出すようになり、そしてPHP9以降は例外になります。

私は今までたくさんのPHP RFCを見てきましたが、このRFCが、これまでのPHPの思想から最も異質なものであると感じました。


```php
#[AllowDynamicProperties]
class Test {}

$t = new Test();
$t->foo = 1; // OK
```

これまでの動作を行わせたい場合、アトリビュート`#[AllowDynamicProperties]`を指定することで互換します。


## [Deprecate partially supported callables](https://wiki.php.net/rfc/deprecate_partially_supported_callables)

賛成32、反対0の全会一致で受理。

[一部callableの構文削除](https://qiita.com/rana_kualu/items/aa03bcc5e6595137c237)です。

```php
$callable = 'self::hoge';
is_callable($callable);    // OK
call_user_func($callable); // OK
$callable();               // NG ←
```

これまで、[is_callable](https://www.php.net/manual/ja/function.is-callable.php)や[call_user_func](https://www.php.net/manual/ja/function.call-user-func.php)ではtrueが返ってくるのに実際は[コールバック](https://www.php.net/manual/ja/language.types.callable.php)できない、みたいな矛盾した構文が幾つか存在しました。

よって、これらの動作を揃えるようにします。
PHP8.2でE_DEPRECATEとなり、そしてPHP9で削除されます。


## [Expand deprecation notice scope for partially supported callables](https://wiki.php.net/rfc/partially-supported-callables-expand-deprecation-notices)

`Deprecate partially supported callables`のフォローアップです。

該当のRFCでは『is_callable()やcallableは、サポートされなくなるまで非推奨の警告は発生しません』と、エラーを出さないことを明示していましたが、これだと危ない挙動が存在することが判明しました。

```php
class Foo {
    public function bar() {
        if (is_callable('static::methodName')) {
            static::methodName();
        }
    }
}

(new Foo())->bar();
```

[このコード](https://3v4l.org/VeVYC)、PHP8.xでは何のエラーも出ないのに、PHP9.0で突然動作が変わることになります。

ということで、対応しなくなる書式を`is_callable`等に渡した場合にもE_DEPRECATEDが発生するようにします。


## [Locale-independent case conversion](https://wiki.php.net/rfc/strtolower-ascii)

賛成29、反対1で受理。

[strtolower](https://www.php.net/manual/ja/function.strtolower.php)などの関数は、なんと[ロケール](https://www.php.net/manual/ja/function.setlocale.php)の影響を受けます。

![01.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/26088/63c8df1c-8882-5df4-28c8-e6dbcbdd3628.png)

マニュアルには『たとえばデフォルトの「C」ロケールである場合は、 A ウムラウト (Ä) のような文字は変換されません。』とありますが、そもそもロケールを変えたら変更できる、という挙動のほうがわかりにくくて危険です。
そのため、これらの関数はロケールの影響を受けないようにします。
具体的には`C`ロケールであるものとして扱います。


## [Deprecate ${} string interpolation](https://wiki.php.net/rfc/deprecate_dollar_brace_string_interpolation)

賛成31、反対1で受理。

文字列内変数展開の文法を一部廃止します。

```php
$foo = 'value';
$bar = 'foo';

var_dump(
    "$foo",   // value
    "{$foo}", // value
    "${foo}", // value
    "${$bar}" // value
);
```

この4種類の書式のうち、後者2つを廃止します。
PHP8.2でE_DEPRECATED、そしてPHP9.0でエラーになります。

2番目と3番目は同じに見えますが、実は2番目のほうでは使えるのに3番目では駄目という書式が存在します。

```php
var_dump("{$foo->bar()}");        // OK
var_dump("{$foo['bar']->baz()}"); // OK

var_dump("${foo->bar()}");        // NG!
var_dump("${foo['bar']->baz()}"); // NG!
```

たいへんわかりにくいので片方しか使えないようにします。

また4番目の可変変数は3番目と書式が被るうえ、[全く使われていない](https://gist.github.com/iluuu1994/05427dd74100af8e41ebff3d4201742c)ので削除します。
`${$bar}`は今後`{${$bar}}`と書くことになります。

まあ、文字列内可変変数なんてややこしいことするんじゃねえと言いたいですが。

RFCには色々と細かい例が書かれていて頭が混乱します。


## [Deprecate and remove utf8_decode() and utf8_encode()](https://wiki.php.net/rfc/remove_utf8_decode_and_utf8_encode)

賛成33、反対2で受理。

[utf8_decode](https://www.php.net/manual/ja/function.utf8-decode.php)と[utf8_encode](https://www.php.net/manual/ja/function.utf8-encode.php)を削除します。

この関数は、任意の文字列を`UTF-8`にエンコードデコードできるかと思いきや、変換相手は`Latin 1`固定です。
非常に誤解を招く関数名であり、容易に[間違った実装](https://stackoverflow.com/questions/910793/detect-encoding-and-make-everything-utf-8/3479658#3479658)をしてしまいがちです。

従ってこの関数を削除し、代替として間違えた理解をしづらい[mb_convert_encoding](https://www.php.net/manual/ja/function.mb-convert-encoding.php)を推奨します。


## [MySQLi Execute Query](https://wiki.php.net/rfc/mysqli_execute_query)

賛成24、反対0で受理。


MySQLiに新しい関数[mysqli_execute_query](https://www.php.net/manual/ja/function.mysqli.execute-query)を追加する提案。

```php
// これまで
$statement = $db->prepare('SELECT * FROM user WHERE name LIKE ? AND type_id IN (?, ?)');
$statement->bind_param('sii', $name, $type1, $type2);
$statement->execute();
foreach ($statement->get_result() as $row) {
    print_r($row);
}

// 今後
foreach ($db->execute_query('SELECT * FROM user WHERE name LIKE ? AND type_id IN (?, ?)', [$name, $type1, $type2]) as $row) {
    print_r($row);
}
```

MySQLiで[SELECTする](https://www.php.net/manual/ja/mysqli.quickstart.prepared-statements.php)には、`mysqli_prepare()`して`mysqli_execute()`して`mysqli_stmt_get_result()`するという面倒な手順が必要です。
これらを単純にまとめただけなのですが、たったこれだけでも使い方がぐっと楽になりますね。

これ同様の機能は[PDOにもない](https://www.php.net/manual/ja/pdo.query.php)ので、そっちにも欲しいですね。



## [Remove support for libmysql from mysqli](https://wiki.php.net/rfc/mysqli_support_for_libmysql)

賛成30、反対0の全会一致で受理。

MySQLのモジュールは[mysqlndとlibmysqlの2種類が存在](https://www.php.net/manual/ja/mysqlinfo.library.choosing.php)します。
このうちlibmysqlを削除しようという提案。

mysqlndはPHP5.4以降ずっとデフォルトであり、PHP同梱のため別途インストールする必要がなく、パフォーマンス上有利であり、[libmysqlには存在しない機能](https://www.php.net/manual/ja/mysqli-stmt.get-result.php)もあったりと、基本的にlibmysqlを選ぶ利点はほぼありません。
ということでlibmysqlを削除し、mysqlndに一本化します。

## [Make the iterator_*() family accept all iterables](https://wiki.php.net/rfc/iterator_xyz_accept_array)

[iterator_to_array](https://www.php.net/iterator_to_array)と[iterator_count](https://www.php.net/manual/ja/function.iterator-count.php)に[iterable](https://www.php.net/manual/ja/language.types.iterable.php)を渡せるようにするRFC。

`iterator_to_array`は名前に反して[Traversable](https://www.php.net/manual/ja/class.traversable.php)しか受け付けません。

```php
iterator_to_array([1, 2]); // Fatal error: Uncaught TypeError
```

とてもわかりにくいので、foreach同様iterableを受け付けられるようにします。

なお`iterable = array|Traversable`なので、実質的には配列を渡せるようになるだけです。

## [Fetch properties of enums in const expressions](https://wiki.php.net/rfc/fetch_property_in_const_expressions)


賛成24、反対11の僅差で受理。

定数式の中でENUMの`->`を使えるようにします。

```php
enum E: string {
    case Foo = 'foo';
}

const C = E::Foo->name; // 8.2からOK
```


Nikitaが[これサポートしていいんじゃない](https://github.com/php/php-src/issues/8344#issuecomment-1095474193)と言ったことで方向性が決まったみたいです。
いまだに多大な影響力を持ってますねNikita。

当初は[ENUMだけでなく全てのオブジェクトに対応する](https://github.com/php/php-src/issues/8344#issuecomment-1096873244)ようにしたかったみたいですが、こちらについてはパフォーマンス等の理由で中止されました。

```php
class X {
    public $name = 'value';
}

const C = (new X())->name; // こっちはPHP8.2以降も駄目
```



## [Constants in Traits](https://wiki.php.net/rfc/constants_in_traits)

賛成28、反対12で受理。

[トレイト](https://www.php.net/manual/ja/language.oop5.traits.php)に定数を書けるようになります。

```php
trait T {
    public const CONSTANT = 42;
}

class C1 {
    use T;

    public function doSomething(): void {
        // OK
        echo self::CONSTANT;
        echo static::CONSTANT;
        echo $this::CONSTANT;

        // Fatal Error
        echo T::CONSTANT;
    }
}
```

具象化したクラスにおいて宣言されたのと同じように、トレイト定数を扱うことができます。
`T::CONSTANT`のように、トレイトを直接参照することはできません。
また、`as`演算子によるエイリアスや`insteadof`による衝突回避は禁止され、定数が被るとエラーになります。

そもそもトレイトが実装されたときにどのような意図で定数が対応されなかったのかというと、実装者によると[特に考えてなかった](https://externals.io/message/118039#118059)そうです。


# 感想

PHP8.1に引き続き、型に関連するRFCが多いですね。
PHP7の後半あたりからどんどん型宣言でできることが増え、そしてついに今回の選言標準形において、あらゆる型を表現できるまでになってしまいました。
ここまでできる言語はそう多くないのではないでしょうか。

そして新機能以外では、よくわからなかったり曖昧だったりする機能の削除が目立ちます。


さてRFCの総量自体は、[PHP8.1](https://wiki.php.net/rfc#php_81)や[PHP8.0](https://wiki.php.net/rfc#php_80)に比べると格段に減ってしまいました。
これはやはり[Nikitaの離脱](https://qiita.com/rana_kualu/items/eaf97694f25a62f93a7e)という事件の影響が大きいと思われます。
実際この後しばらくRFCの提出や投票が滞ったりしましたからね。

しかしその後[Nikitaの後を継ぐ者たちが現れ](https://qiita.com/rana_kualu/items/0479edbd936447572c3b)、締め切り間際には彼らによって作られたRFCも幾つも投票にかけられるまでになりました。
まあ概ね却下されてしまいましたが。

今後は彼らも成長し、再びPHPの進化の速度は上がっていくに違いありません。


PHP8.2は、[既にα版が提供されており](https://downloads.php.net/~sergey/)、[Windows版もしっかりあります](https://windows.php.net/qa/)。
今回紹介した機能の一部はまだ搭載されてなかったりしますが、これらの新機能を早々と試してみたい人はダウンロードしてみてはいかがでしょうか。