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

[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) / [PHP7.4](https://qiita.com/rana_kualu/items/50f9f5735321fe995ff5)

[2020/11/26に **リリースされました** ](https://qiita.com/rana_kualu/items/c110cb244c3ee38c6859)。

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

というわけで[PHP8.0で対応することが決まったRFC](https://wiki.php.net/rfc#php_80)を見てみましょう。


# RFC

## JIT

賛成50反対2で受理。
PHP8の目玉、[JIT](https://qiita.com/rana_kualu/items/ba312d2789bd228f887a)です。

PHPをネイティブコードにコンパイルし、さらにコンパイルした結果を次のリクエストに使い回すことができます。
速度はOpcacheがオンの状態から平均的に1.3-1.5倍程度、さらに[CPUバウンドな処理なら3倍以上](https://qiita.com/rana_kualu/items/73c346b3feadfa3a5b21)という劇的な高速化が見込めます。
Opcache無しからだと、1分かかっていた処理が8秒になります。

PHP7.4で入った[プリローディング](https://qiita.com/rana_kualu/items/afe544b0f5680e81fabc)と組み合わせれば、これってもうコンパイル言語なのでは？


## Named Arguments

賛成57反対18で受理。
[名前付き引数](https://qiita.com/rana_kualu/items/13bc0c30192ee22c1396)です。

```php
// これまで
array_fill(0, 100, 50);
// 名前付き引数
array_fill(start_index: 0, num: 100, value: 50);

// 同じ
htmlspecialchars($string, ENT_COMPAT | ENT_HTML401, 'UTF-8', false);
htmlspecialchars($string, double_encode: false);
```

Pythonのやつとだいたい一緒です。

引数の多い関数で最後の引数だけ指定したいといった場合に有用です。
また、これまでは引数の順番を知らないとどの引数が何を表しているのかわかりませんでしたが、引数名で書くことで読みやすくなります。


## Match expression v2

賛成43反対2で受理。
[match式](https://qiita.com/rana_kualu/items/38d2854d0575fb73f56a)です。

```php
echo match ("1") {
    true => 'Foo',
    1 => 'Bar',
    "1" => 'Baz',
}; // Baz
```

厳密な比較、フォールスルーしない、返り値を持つ式である、といった具合に既存のswitch文の欠点のほとんどを解消したナイスな構文です。
逆に分岐内部には1式しか書けないため、分岐内部での複雑な処理はできません。
今後はswitchを使わざるを得ないところ以外はmatchで書くとよいでしょう。


## Nullsafe operator

賛成56反対2で受理。
[ヌル安全オペレータ](https://qiita.com/rana_kualu/items/c967d58582ef75e73d7d)です。

```php
$country = $session?->user?->getAddress()?->country;
```

途中にnullが入ってくる可能性があるメソッドチェーンを簡単に書けるようになります。
`?`に引っかかった時点で実行が中断され、それより先は処理されません。

```php
// bar()、baz()は実行されない
null?->foo(bar())->baz();
```


## Union Types v2

賛成61反対5で受理。
[UNION型のRFC](https://qiita.com/rana_kualu/items/f75e254553157d8eb0b7)です。

```php
function setNumber(int|float $number): void {
	// $numberはintかfloat
}
```

複数の型を受け取る、もしくは返すことができるようになります。
やりすぎると型システムの意味がなくなるので、程々に使っていくとよいでしょう。


## Mixed type v2

賛成50反対11で受理。
[なんでもあり型](https://qiita.com/rana_kualu/items/4685f2c7693ce0394fe8)です。

```php
 class A
{
    public function foo(mixed $value): mixed {
    	return $value;
    }
}
```

`var_dump()`の引数のように、あらゆる型を受け取りたい場合に使用する型です。
TypeScriptでいうところのanyです。
リファクタリング時には威力を発揮するでしょう。
新規で自発的に使用するのは基本的にやめておいたほうがよいでしょう。


## Attributes (v2)

賛成51反対1で受理。
[アトリビュート](https://qiita.com/rana_kualu/items/749c389a275f207a8e58)です。

`<<>>`でメタデータを埋め込むことができるようになります。

```php
<<WithoutArgument>>
<<SingleArgument(0)>>
<<FewArguments('Hello', 'World')>>
function foo() {}
```

これまでは[PHPDoc](https://www.phpdoc.org/)や[PSR-5](https://qiita.com/rana_kualu/items/8b2d9d42b81e9f6f0fd1)のようにコメントで書くしかなく、強制力もありませんでした。
アトリビュートは、PHPの構文として実効力のある方法でメタデータを書けるようになります。


## Attribute Amendments

複数件の投票がありますが、全て受理されています。
[アトリビュートの細かな追加仕様のRFC](https://wiki.php.net/rfc/attribute_amendments)です。

アトリビュートをカンマ区切りで複数書けるようにする、PhpAttributeだったクラス名をAttributeにする、などの仕様追加です。
アトリビュート自体の大きな変更はないようです。


## Shorter Attribute Syntax

[アトリビュートの構文変更のRFC](https://wiki.php.net/rfc/shorter_attribute_syntax)です。

アトリビュートの構文には幾つか問題があります。

```php
<<Bar(2 * (3 + 3)>>Baz, (4 + 5) * 2)>>

<<JoinTable(
    "User_Group",
    <<JoinColumn("User_id", "id")>>,
    <<JoinColumn("Group_id", "id")>>,
)>>
```

ネストしたりビット演算子と混ざった場合に非常にわかりにくい、あと今後ジェネリクスが追加されたときに更に混乱する、などです。
ということで代替構文が`@@`・`#[]`・`<<>>`の間で争われ、投票でも[Reddit](https://www.reddit.com/r/PHP/comments/h06bra/community_poll_attribute_syntax/)でも`@@`が最多数でした。

```php
@@JoinTable(
    "User_Group",
    @@JoinColumn("User_id", "id"),
    @@JoinColumn("Group_id", "id"),
)
```

ただ決定はしたものの`@@`構文には[色々と問題が発覚した](https://externals.io/message/111101)ため、さらにRFCが提出されました。

## Shorter Attribute Syntax Change

`@@`構文の問題解決のために再度行われた[アトリビュート構文変更のRFC](https://wiki.php.net/rfc/shorter_attribute_syntax_change)です。
このRFCは特例でフィーチャーフリーズ後に投票が始まり、2020/09/02に終了しました。

構文変更自体は賛成50反対11で受理されました。
変更後のアトリビュート構文は`@@Attr`、`#[Attr]`、`@[Attr]`、`<<Attr>>`、`@:Attr`、`@{Attr}`の中から選択します。
投票結果は`#[Attr]`が32票で首位となりました。

色々と紆余曲折のあったアトリビュートですが、最終的に以下の構文に決定しました。

```php
#[
  ORM\Entity,
  ORM\Table("user")
]
class User
{
    #[ORM\Id, ORM\Column("integer"), ORM\GeneratedValue]
    private $id;
 
    #[ORM\Column("string", ORM\Column::UNIQUE)]
    #[Assert\Email(["message" => "The email '{{ value }}' is not a valid email."])]
    private $email;
}
```

## Reclassifying engine warnings

賛成54反対3で受理。
[警告レベルが厳しくなります](https://qiita.com/rana_kualu/items/2d71a88aab59b35cdcb8)。

```php:PHP7
	$a = 1;
	$a->b++;
```

こんな訳のわからないコードでもPHP7ではE_WARNING止まりで動いていたのですが、今後はErrorExceptionになります。
あまり正しくない20以上の書式について、これまでE_NOTICEだった警告の一部がE_WARNINGに、E_WARNINGだった警告の一部が例外にと、厳しくなる方向に変更されます。


## Stricter type checks for arithmetic/bitwise operators

賛成57反対0で受理。
[プリミティブでない値の算術演算のRFC](https://wiki.php.net/rfc/arithmetic_operator_type_checks)です。

```php:PHP7
var_dump( [] % [42] );
var_dump( new stdClass >> tmpfile() );
```

全く意味の分からない演算ですが、PHP7ではとりあえず動きます。

が、こんなものが動いたところで役には立たないため、PHP8.0以降、配列・リソース・オブジェクトへの算術演算は全てTypeErrorになります。

オブジェクトの場合、演算子オーバーロードが設定されている一部のクラス([GMP](https://www.php.net/manual/ja/book.gmp.php)など)は引き続き演算が可能です。
またstringやintなどのプリミティブ型については、このRFCでの影響はありません。


## Consistent type errors for internal functions

賛成50反対2で受理。
[内部関数の型引数についてのRFC](https://wiki.php.net/rfc/consistent_type_errors)です。

```php
function foo(int $bar) {}
foo("not an int"); // TypeError

strlen(new stdClass); // Warning: strlen() expects parameter 1 to be string, object given

var_dump(DateTime::createFromFormat(new stdClass, "foobar"));           // E_WARNING
var_dump(DateTime::createFromFormat("foobar", "foobar", new stdClass)); // TypeError
```

正しくない型の引数を渡した場合、ユーザ定義関数では常にTypeErrorが発生します。
内部関数の場合、TypeErrorだったりE_WARNINGだったりします。

これはよくないのでTypeErrorに統一します。

従って、昔ながらのE_WARNINGを抑えるようなコードを書いていた場合はここで詰まります。

PHP本体のテストコードにも、この変更に引っかかるコード(わざとエラーを出すテスト)が1500ほどあるみたいです。


## Saner string to number comparison

賛成44反対1で受理。
[非厳密な比較演算子`==`の挙動を変更するRFC](https://qiita.com/rana_kualu/items/82cc8295d2102d14b88a)です。

```php
    "true" == 0; // true  PHP7まで
    "true" == 0; // false PHP8
```

数字と数値型でない文字列を比較する場合、これまでは文字列を数値に変換してから比較していましたが、今後は数値を文字列に変換してから比較するようになります。
数値と数値型文字列の比較や、数値以外の比較はこれまでと同じです。

一見非常に危なそうな変更ですが、この変更で挙動がおかしくなる品質のプログラムは、まず間違いなくその前に`Reclassifying engine warnings`や`Consistent type errors for internal functions`や`Stricter type checks for arithmetic/bitwise operators`あたりに引っかかって動かなくなるので、実害はほぼ無いと思います。


## Saner numeric strings

賛成30反対4で受理。
[数値型文字列の定義を変更するRFC](https://wiki.php.net/rfc/saner-numeric-strings)です。

PHP7までは、数値型文字列の定義が複数ありました。

・正規の数値型文字列：0個以上のスペース+数値
・非正規の数値型文字列：正規の数値型文字列+任意の文字列
・それ以外は全て数値型文字列ではない。

```php
function foo(int $i) { var_dump($i); }
foo("123");    // int(123)
foo("   123"); // int(123)
foo("123   "); // int(123) with E_NOTICE "A non well formed numeric value encountered"
foo("123abc"); // int(123) with E_NOTICE "A non well formed numeric value encountered"
foo("string"); // TypeError
```

このせいで色々と面倒なことになっていました。
特に`　123`と`123　`が異なるのはとても違和感がありますね。

ということで、PHP8では数値型文字列の定義をひとつにします。

・数値型文字列：0個以上のスペース+数値+0個以上のスペース
・それ以外は全て数値型文字列ではない。

これにより`　123`と`123　`は緩やかに同じ値になります。
そして、`123abc`は数値型文字列ではなくなります。

```php
function foo(int $i) { var_dump($i); }
foo("123");    // int(123)
foo("   123"); // int(123)
foo("123   "); // int(123)
foo("123abc"); // TypeError
foo("string"); // TypeError
```

ぶっちゃけ`Saner string to number comparison`なんかより、こちらのほうがよっぽど影響範囲が大きいです。
ただ数値型文字列ではなくなったとはいえ、`(int)"123abc"`が`0`になるとあまりにも被害甚大すぎるので、さすがにこれは`123`にしてくれるようです。


## Treat namespaced names as single token

賛成38反対4で受理。
[namespaceのパーサトークンを変更するRFC](https://wiki.php.net/rfc/namespaced_names_as_token)です。

これまで名前空間`Foo\Bar`は字句解析で`T_STRING,T_NS_SEPARATOR,T_STRING`と分解されていましたが、今後は`T_NAME_QUALIFIED`ひとつになります。

PHPを使う側としては、[token_get_all](https://www.php.net/manual/ja/function.token-get-all.php)を使っているような変態スクリプト以外は全く無関係です。
と思いきやしれっと、[名前空間の空白](https://qiita.com/rana_kualu/items/7fbdd520a7e355599f7d#%E6%84%9F%E6%83%B3)が[禁止される](https://wiki.php.net/rfc/namespaced_names_as_token#backward_incompatible_changes)とか書いてありました。
ちなみに違反するスクリプトは、Composer上位2000パッケージ中僅か5か所だけでです。


## Allow trailing comma in parameter list

賛成58反対1で受理。
[関数の引数の末尾カンマのRFC](https://wiki.php.net/rfc/trailing_comma_in_parameter_list)です。

PHPでは配列の末尾カンマは昔から使えていて、その後PHP7.3で[関数呼び出し](https://wiki.php.net/rfc/trailing-comma-function-calls)および[ほとんどのリスト](https://wiki.php.net/rfc/list-syntax-trailing-commas)でも末尾カンマが使えるようになりました。

関数呼び出し側は対応したのに、関数の引数のほうは何故か対応していなかったので、その対応の追加です。

```php
function hoge(
	$foo,
	$bar, // PHP8.0以降OK
){}

hoge(1, 2, ); // PHP7.3以降OK
```

## Allow trailing comma in closure use lists

賛成49反対0で受理。
[use内の末尾カンマのRFC](https://wiki.php.net/rfc/trailing_comma_in_closure_use_list)です。

```php
$longArgs_longVars = function (
    $longArgument,
    $longerArgument,
    $muchLongerArgument, // ↑の引数末尾カンマでOKになった
) use (
    $longVar1,
    $longerVar2,
    $muchLongerVar3, // このRFC
) {
};
```

`use`の末尾にもカンマを置くことができなかったので、その対応の追加です。
この抜けの補完によって、おそらく全ての列挙箇所で末尾カンマを使えるようになったのではないでしょうか。


## Ensure correct signatures of magic methods

賛成45反対2で受理。
[マジックメソッドの引数のRFC](https://wiki.php.net/rfc/magic-methods-signature)です。

マジックメソッドには、これまでは正しくない型を書くことが可能でした。

```php:PHP7までOK
class Foo {
    public function __get(array $name): void{}
}
```

今後はこれが禁止され、[正しい型](https://wiki.php.net/rfc/magic-methods-signature#proposal)しか書けないようになります。
もしくは、単に何も書かないかです。

```php:PHP8.0
class Foo {
    public function __get(string $name): mixed{}
    // ↓でもOK
    public function __get($name){}
}
```

例によってNikitaがComposer上位1000パッケージを調べたところ、違反するスクリプトは[僅か7か所だけ](https://gist.github.com/carusogabriel/e0b36e7cd9e6846e04f79008cb7e35d6)でした。


## Configurable string length in getTraceAsString()

賛成36反対2で受理。
[例外の文字列展開の文字数を変更するRFC](https://wiki.php.net/rfc/throwable_string_param_max_len)です。

`Throwable::getTraceAsString()`などでエラーを文字列展開する際、スタックトレース中の文字列は15バイトで打ち切られてしまいます。
そのため、深く調査したい場合などに難しい状態でした。

そこでini設定`zend.exception_string_param_max_len`を追加し、設定されたバイト数をスタックトレースに展開できるようにします。
デフォルトは既存の15です。

## Remove inappropriate inheritance signature checks on private methods

賛成24反対11で受理。
[privateメソッドの継承に関するRFC](https://wiki.php.net/rfc/inheritance_private_methods)です。

```php
class A 
{ 
    final private function finalPrivate() {
        echo __METHOD__ . PHP_EOL;
    }
}
 
class B extends A 
{ 
    private function finalPrivate() {
        echo __METHOD__ . PHP_EOL;
    }
}
```

privateメソッドは継承されないにも関わらず、この書き方は何故か`Cannot override final method`のFatal Errorになります。
その他幾つかの場合において、継承時にprivateメソッドのオーバーライドチェックが行われることがあります。

従って、PHP8.0以降privateメソッドは継承時にオーバーライドチェックを行わないようにします。


## Abstract trait method validation

賛成52反対0で受理。
[トレイトのabstractメソッドの挙動に関するRFC](https://wiki.php.net/rfc/abstract_trait_method_validation)です。

トレイトにabstractメソッドを書いた場合、継承時のルールが何かおかしくなっています。

```php:PHP7までOK
trait MyTrait {
    abstract public function publicFunction(): string;
    abstract private function privateFunction(): string; // cannot be declared private
}

class TraitUser {
    use MyTrait;
 
    // 何故かOK
    public function publicFunction(): stdClass { }
}
```

何故かシグネチャが異なっていても通ってしまうので、これを通常のクラス継承と同じように修正します。
またついでにabstract privateメソッドを定義できるようにします。

```php:PHP8.0
trait MyTrait {
    abstract private function neededByTheTrait(): string;
 
    public function doSomething() {
        return strlen($this->neededByTheTrait());
    }
}
 
class TraitUser {
    use MyTrait;
 
    // OK
    private function neededByTheTrait(): string { }
 
    // 返り値の型が異なるのでNG
    private function neededByTheTrait(): stdClass { }
 
    // staticとnonstaticなのでNG
    private static function neededByTheTrait(): string { }
}
```

traitのabstract privateメソッドは、useしたクラスで実装が必須となります。
privateメソッドの実装を強制させるような状況って、あまり考えたくありませんが。


## Make sorting stable

賛成24反対11で受理。
[ソートを安定ソートにするRFC](https://wiki.php.net/rfc/stable_sorting)です。

```php
$array = [
    'c' => 1,
    'd' => 1,
    'a' => 0,
    'b' => 0,
];
asort($array);
```

PHPのソートはこれまで不安定ソートだったので、ソート後に`a`と`b`の順番、`c`と`d`の順番が保証されませんでした。
PHP8.0以降はソート後の順番が`['a' => 0, 'b' => 0, 'c' => 1, 'd' => 1]`で保証されます。


## Constructor Property Promotion

賛成46反対10で受理。
[オブジェクト初期化子](https://qiita.com/rana_kualu/items/122a1da3b6089b125cb0)です。

```php
class Point {
    // プロパティx,y,zが生える
    public function __construct(
        public float $x = 0.0,
        public float $y = 0.0,
        public float $z = 0.0,
    ) {}
}
```

コンストラクタにかぎり、引数に可視性を指定すると自動的にプロパティとして登録してくれます。
これまではオブジェクトの初期化に多数の定型文が必要でしたが、今後は楽に書けるようになります。


## Always available JSON extension

賛成56反対0で受理。
[JSONを常時有効にするRFC](https://wiki.php.net/rfc/always_enable_json)です。

実はこれまで[JSONエクステンション](https://www.php.net/manual/ja/book.json.php)を`--disable-json`オプションでインストールしないようにできていたのですが、これができなくなります。

[マニュアル](https://www.php.net/manual/ja/json.installation.php)にも書かれていないほどの隠し機能なので、無くなっても全く問題ないでしょう。


## Unbundle ext/xmlrpc

賛成50反対0で受理。
[ext/xmlrpcを外すRFC](https://wiki.php.net/rfc/unbundle_xmlprc)です。

[xmlrpc](https://www.php.net/manual/ja/book.xmlrpc.php)は内部的に[xmlrpc-epi](https://sourceforge.net/projects/xmlrpc-epi/)を使っていますが、こちらが既に[放棄されている](https://sourceforge.net/projects/xmlrpc-epi/files/xmlrpc-epi-base/)ためサポートから外します。
これによって[xmlrpc_xxx](https://www.php.net/manual/ja/ref.xmlrpc.php)関数が使えなくなります。

元より[実験的なものである警告](https://www.php.net/manual/ja/intro.xmlrpc.php)がなされていたモジュールなので、これが影響する人はほとんどいないでしょう。


## Non-capturing catches

賛成48反対1で受理。
[例外を受け取らないRFC](https://qiita.com/rana_kualu/items/7edbe66492a3018d90cf)です。

```php
try {
    changeImportantData();
} catch (PermissionException) {
    echo "You don't have permission to do this";
}
```

catchした例外を使用しない場合は、最初から受け取らないようにすることができます。
決して例外の握り潰しに使うための機能ではありません。


## Locale-independent float to string cast

賛成42反対1で受理。
[一部言語での(string)キャストに関するRFC](https://wiki.php.net/rfc/locale_independent_float_to_string)です。

フランス語では小数点が`,`です。
このためロケールをフランス語に設定した場合、`(string)3.14`は`"3,14"`になります。

```php
setlocale(LC_ALL, "de_DE");

$f = 3.14;        // float(3,14)
$s = (string) $f; // string(4) "3,14"
$f = (float) $s;  // float(3)
```

しかし`"3,14"`を小数に戻すことはできず`3`になります。
この矛盾を解消するため、またそもそも文字列表現が異なる値になる時点でわかりにくいので、stringキャストはロケールに関わらず常に`"3.14"`と変換することにします。

英語・日本語など小数点が`.`のロケールには全く影響ありません。


## Change default PDO error mode

賛成49反対2で受理。
[PDOのデフォルトエラーモードのRFC](https://wiki.php.net/rfc/pdo_default_errmode)です。

PHP7まで[PDO::ATTR_ERRMODE](https://www.php.net/manual/ja/pdo.error-handling.php)のデフォルトは`PDO::ERRMODE_SILENT`でした。
これはエラーが出ても何も言わないので非常によろしくない設定です。

PHP8.0からは`PDO::ERRMODE_EXCEPTION`がデフォルトになります。
めでたし。


## Add str_starts_with and str_ends_with to PHP

賛成51反対4で受理。
[StartsWith/EndsWith](https://qiita.com/rana_kualu/items/71e2980da92b49736001)です。

```php
echo str_starts_with ( 'abcdef' , 'abc' );
echo str_ends_with ( 'abcdef' , 'def' );
```

ユーザランドで容易く実装できそうに見えますが、実はこの関数の実装には罠が多く、下手に書くとすぐに非効率になります。
言語側で用意してくれるならそれにこしたことはないでしょう。


## str_contains

賛成43反対9で受理。
[str_contains](https://qiita.com/rana_kualu/items/cb758cdc21d60e632491)です。


```php
str_contains('放課後アトリエといろ', '放課後'); // true
```

何故かこれまでずっと存在せず、`strpos($haystack, $needle)!==false`という謎の書式を強いられていた『〇〇を含む』が、ようやく簡潔に書けるようになります。

str_containsとstr_starts_with/str_ends_withが実装されたことにより、PHPのテキスト処理に対する関数は出揃ったと言えそうです。


## throw expression

賛成46反対3で受理。
[throw文がthrow式になります](https://qiita.com/rana_kualu/items/44e87a22957fab4732c3)。

```php
// PHP7
if(!$nullableValue){
	throw new InvalidArgumentException();
}
$value = $nullableValue;

// 概ね同じ
$value = $nullableValue ?? throw new InvalidArgumentException();
```

throwはこれまで文だったので、式の間に含めることができませんでした。
今後はもっと気軽にthrowしていくことができるようになります。


## Object-based token_get_all() alternative

賛成47反対0で受理。
[token_get_allの代替構文のRFC](https://qiita.com/rana_kualu/items/44e87a22957fab4732c3)です。

[token_get_all](https://www.php.net/manual/ja/function.token-get-all.php)関数は、パースした結果を配列で返してきていました。

このRFCでは新たに`PhpToken`クラスを導入し、パースした結果をオブジェクトで受け取るようにします。
これによってコードがわかりやすくなり、メモリ消費量も減少します。

互換性のため、既存のtoken_get_allはそのままです。


## Stringable

賛成29反対9で受理。
[StringableインターフェイスのRFC](https://wiki.php.net/rfc/stringable)です。

[Countableインターフェイス](https://www.php.net/manual/ja/class.countable.php)を実装すると[count](https://www.php.net/manual/ja/function.count)できるようになり、[Traversableインターフェイス](https://www.php.net/manual/ja/class.traversable.php)を実装すると[foreach](https://www.php.net/manual/ja/control-structures.foreach.php)できるようになります。

同様に[__toString](https://www.php.net/manual/ja/language.oop5.magic.php#object.tostring)できるようになるインターフェイス[Stringable](https://www.php.net/manual/ja/class.stringable.php)を実装しようという提案です。

```php
interface Stringable
{
   public function __toString(): string;
}
````

これを強制するとありとあらゆる実装がぶち壊れるため、`__toString`を実装しているクラスは暗黙的に`Stringable`をimplementsします。
従って、使用する側としては特に気にする必要はありません。

どちらかというとライブラリで`string|Stringable`を受け取りたいときのためのマーカーインターフェースのようなものです。
実際のユースケースは[Symfony](https://github.com/symfony/symfony/search?q=%22%40param+string%7Cstringable%22)で見ることができます。


## Allow ::class on objects

賛成60反対0で受理。
[クラス名を取得するRFC](https://wiki.php.net/rfc/class_name_literal_on_object)です。

`stdClass::class`でクラス名`"stdClass"`を取得できますが、いったんオブジェクト化してしまうと何故か`::class`できません。
[get_class](https://www.php.net/manual/ja/function.get-class.php)を使う必要があります。

PHP8では`$object::class`と書けるようになります。

```php
	echo stdClass::class; // class
	echo (new stdClass)::class; // PHP8.0以降OK
```


## Static return type

賛成54反対0で受理。
[返り値にstaticを書けるようにするRFC](https://wiki.php.net/rfc/static_return_type)です。

PHP7では、返り値として`parent`や`self`を書くことが可能です。

```php
<?php

class FUGA{}

class HOGE extends FUGA{
    public function a(): parent{
        return new parent();
    }
}

(new HOGE())->a(); // FUGAになる
```

同様に`static`も書けるようにします。`
これは[遅延静的束縛のほうのstatic](https://www.php.net/manual/ja/language.oop5.late-static-bindings.php)です。

```php
// PHP7までsyntax error
class Test {
    public function createFromWhatever($whatever): static {
        return new static($whatever);
    }
}
```

個人的には遅延静的束縛を全く使わないので、何が嬉しいのかよくわかりません。


## Variable Syntax Tweaks

賛成47反対0で受理。
[変数構文のRFC](https://wiki.php.net/rfc/variable_syntax_tweaks)です。

PHP7.0において[変数構文の大規模なリファイン](https://wiki.php.net/rfc/uniform_variable_syntax)がありましたが、そのときに見過ごされていた一部の構文についての対応です。
とあるのですが、見過ごされていたくらいなのですごい細かくて気付かないくらいの変更です。
また、これまではシンタックスエラーだった構文が有効になるという変更なので、互換性のない変更はありません。

```php
$bar  = "bar";
echo "foobar"[0];  // OK
echo "foo$bar"[0]; // PHP7までsyntax error、8からOK

echo __FILE__[0]; // PHP7までsyntax error、8からOK
```

## DOM Living Standard API

賛成37反対0で受理。
[DOMの更新についてのRFC](https://wiki.php.net/rfc/dom_living_standard_api)です。

PHPの[DOM](https://www.php.net/manual/ja/book.dom.php)は、かなり昔にW3 Groupが作ったDOM Level 3に準拠して作られています。
その後DOMは[Living Standard](https://dom.spec.whatwg.org/)となり、WHATWGが管理するようになりました。
当時よりだいぶ改善されているので、その変更を取り込みます。

まあでもDOM面倒臭いからあんまり使わないよね。


## Always generate fatal error for incompatible method signatures

賛成39反対3で受理。
[メソッドシグネチャのRFC](https://wiki.php.net/rfc/lsp_errors)です。

継承時にLSP原則に違反する書き方をすると、PHP7では致命的エラーもしくはE_WARININGのどちらかになります。

```php
// こっちは致命的エラー
interface I {
    public function method(array $a);
}
class C implements I {
    public function method(int $a) {}
}

// こっちはE_WARNING
class C1 {
    public function method(array $a) {}
}
class C2 extends C1 {
    public function method(int $a) {}
}
```

これを全ての場合において致命的エラーに統一します。

むしろ何故いままで致命的エラーになっていなかったんだ案件。


## Arrays starting with a negative index

賛成17反対2で受理。
[マイナススタートの配列インデックス](https://qiita.com/rana_kualu/items/3f47ed79f18aa6427010)が使えるようになります。

```php
$arr = [
	-10 => 'a'
];
$arr[] = 'b'; // PHP7.4まで0、PHP8から-9
```

いったい誰が得するのかよくわかりません。


## Weak maps

賛成25反対0で受理。
[弱いマップ](https://wiki.php.net/rfc/weak_maps)です。

PHP7.4で[弱い参照](https://wiki.php.net/rfc/weakrefs)が導入されましたが、現実的には弱いマップのほうが使われてるよ、ってことで導入されるようです。

私には、普通のマップではなくこっちを使う場面が思いつかない。


## get_debug_type

賛成42反対3で受理。
[get_debug_type](https://qiita.com/rana_kualu/items/aa4438e57e2ed8112c60)です。

一言で言うと[gettype](https://www.php.net/manual/ja/function.gettype.php) + [get_class](https://www.php.net/manual/ja/function.get-class.php)です。
オブジェクトであればクラス名を、プリミティブ型であれば型名を取得できます。

```php
gettype(1);        // integer
get_class(1);      // TypeError
get_debug_type(1); // int

gettype(new stdClass);        // object
get_class(new stdClass);      // stdClass
get_debug_type(new stdClass); // stdClass
```

いちいち使い分ける必要がなく、また`integer`とかいう正しくない型名が入ってきたりしないので便利です。


## Don't automatically unserialize Phar metadata outside getMetadata()

賛成25反対0で受理。
[Pharメタデータの処理方法に関するRFC](https://wiki.php.net/rfc/phar_stop_autoloading_metadata)です。

PHP7では、`file_exists("phar://path/to/phar.ext")`のようにPharファイルを探すだけで、自動的にそのメタデータが展開されてしまいます。
これによって、いわゆる[安全でないデシリアライゼーション](https://blog.tokumaru.org/2017/09/introduction-to-object-injection.html)攻撃が可能になります。

そこで、[Phar::getMetadata](https://www.php.net/manual/ja/phar.getmetadata.php)を手動で呼び出さない限り、メタデータを展開しないように修正します。

またgetMetadataはメタデータの展開を`unserialize`で行っています。
そこで引数`$unserialize_options`を追加し、デシリアライズする方法を任意に変更できるようにします。

でも、Cookieなど外部で汚染できるデータではなく、サーバに置いてあるPharファイルが汚染されている時点でアウトな気がしてならないんだけどどうなのでしょうかね。


## Add support for CMS

投票なし。
[CMSサポートのRFC](https://wiki.php.net/rfc/add-cms-support)です。

CMSといってもContents Management SystemではなくRFC5652の[Cryptographic Message Syntax](https://www.rfc-editor.org/rfc/rfc5652.html)です。
[openssl_pkcs7_encrypt](https://www.php.net/manual/ja/function.openssl-pkcs7-encrypt.php)に相当する[openssl_cms_encrypt](https://www.php.net/manual/ja/function.openssl-cms-encrypt.php)などの暗号化・復号する関数が実装されます。

RFCを立てはしたものの、[OpenSSLの基礎機能だし投票いらんじゃろ](https://externals.io/message/110127#110147)ということで投票なしに採用されたようです。


# 感想

おい誰だよ[PHP8たいしたことないんじゃねえの](https://qiita.com/rana_kualu/items/993fff9662726a004660#%E6%84%9F%E6%83%B3)とか言った奴は。
相当にすごいことになっています。

アトリビュートのあたりは少々見通しが不透明ですが、それ以外は順当に実装されることでしょう。
これらを使うことで、PHP8では書きやすく、読みやすく、そして高速なプログラムを作ることができるようになります。
そのぶんだけ、これまで適当な書き方をしていたコードは移植がたいへんになりそうですね。

なお、このリストは、RFCが立って投票が受理され、導入されることが決定したものだけの一覧です。
これ以外にもバグ修正や、RFCを立てるほどでもないちょっとした変更なども多々入っているはずです。
PHP8によって、PHPはかなり完成に近づいてきた感があります。
これ以上に欲しい大きなものってジェネリクスくらいじゃね？

…なんて言ってたら次回もまた色々突っ込まれてきそうですけどね。

先日[Microsoftが企業としてPHP8をサポートしない](https://externals.io/message/110907)という残念なお知らせがありましたが、今のところは有志によって[Windows版ビルド](https://windows.php.net/qa/)も迅速に提供されています。
PHP8.0では[さしたる影響はないでしょう](https://www.reddit.com/r/PHP/comments/ho9dgq/microsoft_not_going_to_officially_support_php_8/)。
まあ今後どうなるかはわかりませんし(Windows版固有バグなど)、とっととWSL2に移行しろって話なのかもしれませんが。
でもローカルなんかXAMPPで十分なんだよめんどくせーコマンドなんていちいち打ちたくねーんだよもっと手抜きさせろ。