2020/08/04にPHP8.0がフィーチャーフリーズしました。
言語機能に関わるような機能の追加・変更が締め切られたということです。
今後はデバッグを繰り返しながら完成度を高めていき、2020/12/03にPHP8.0がリリースされる予定です。
というわけでPHP8.0で対応することが決まったRFCを見てみましょう。
RFC
JIT
賛成50反対2で受理。
PHP8の目玉、JITです。
PHPをネイティブコードにコンパイルし、さらにコンパイルした結果を次のリクエストに使い回すことができます。
平均的に1.3-1.5倍程度、さらにCPUバウンドな処理なら3倍以上という劇的な高速化が見込めます。
PHP7.4で入ったプリローディングと組み合わせれば、これってもうコンパイル言語なのでは?
Named Arguments
賛成57反対18で受理。
名前付き引数です。
// これまで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式です。
echomatch("1"){true=>'Foo',1=>'Bar',"1"=>'Baz',};// Baz
厳密な比較、フォールスルーしない、返り値を持つ式である、といった具合に既存のswitch文の欠点のほとんどを解消したナイスな構文です。
逆に分岐内部には1式しか書けないため、分岐内部での複雑な処理はできません。
今後はswitchを使わざるを得ないところ以外はmatchで書くとよいでしょう。
Nullsafe operator
賛成56反対2で受理。
ヌル安全オペレータです。
$country=$session?->user?->getAddress()?->country;
途中にnullが入ってくる可能性があるメソッドチェーンを簡単に書けるようになります。?
に引っかかった時点で実行が中断され、それより先は処理されません。
// bar()、baz()は実行されないnull?->foo(bar())->baz();
Union Types v2
賛成61反対5で受理。
UNION型のRFCです。
functionsetNumber(int|float$number):void{// $numberはintかfloat}
複数の型を受け取る、もしくは返すことができるようになります。
やりすぎると型システムの意味がなくなるので、程々に使っていくとよいでしょう。
Mixed type v2
賛成50反対11で受理。
なんでもあり型です。
classA{publicfunctionfoo(mixed$value):mixed{return$value;}}
var_dump()
の引数のように、あらゆる型を受け取りたい場合に使用する型です。
TypeScriptでいうところのanyです。
自発的に使用するのは基本的にやめておいたほうがよいでしょう。
Attributes (v2)
賛成51反対1で受理。
アトリビュートです。
<<>>
でメタデータを埋め込むことができるようになります。
<<WithoutArgument>><<SingleArgument(0)>><<FewArguments('Hello','World')>>functionfoo(){}
これまではPHPDocやPSR-5のようにコメントで書くしかなく、強制力もありませんでした。
アトリビュートは、PHPの構文として実効力のある方法でメタデータを書けるようになります。
Attribute Amendments
複数件の投票がありますが、全て受理されています。
アトリビュートの細かな追加仕様のRFCです。
アトリビュートをカンマ区切りで複数書けるようにする、PhpAttributeだったクラス名をAttributeにする、などの仕様追加です。
アトリビュート自体の大きな変更はないようです。
Shorter Attribute Syntax
アトリビュートの構文には幾つか問題があります。
<<Bar(2*(3+3)>>Baz,(4+5)*2)>><<JoinTable("User_Group",<<JoinColumn("User_id","id")>>,<<JoinColumn("Group_id","id")>>,)>>
ネストしたりビット演算子と混ざった場合に非常にわかりにくい、あと今後ジェネリクスが追加されたときに更に混乱する、などです。
ということで代替構文が@@
・#[]
・<<>>
の間で争われ、投票でもRedditでも@@
が最多数でした。
@@JoinTable("User_Group",@@JoinColumn("User_id","id"),@@JoinColumn("Group_id","id"),)
ただ決定はしたものの拒否反応を持つ人も多く、あと@@
構文にも後から問題が見つかったみたいな話をしてるっぽいので、もしかしたら特例で変更があるかもしれません。
Reclassifying engine warnings
賛成54反対3で受理。
警告レベルが厳しくなります。
$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です。
var_dump([]%[42]);var_dump(newstdClass>>tmpfile());
全く意味の分からない演算ですが、PHP7ではとりあえず動きます。
が、こんなものが動いたところで役には立たないため、PHP8.0以降、配列・リソース・オブジェクトへの算術演算は全てTypeErrorになります。
オブジェクトの場合、演算子オーバーロードが設定されている一部のクラス(GMPなど)は引き続き演算が可能です。
またstringやintなどのプリミティブ型については、このRFCでの影響はありません。
Consistent type errors for internal functions
賛成50反対2で受理。
内部関数の型引数についてのRFCです。
functionfoo(int$bar){}foo("not an int");// TypeErrorstrlen(newstdClass);// Warning: strlen() expects parameter 1 to be string, object givenvar_dump(DateTime::createFromFormat(newstdClass,"foobar"));// E_WARNINGvar_dump(DateTime::createFromFormat("foobar","foobar",newstdClass));// TypeError
正しくない型の引数を渡した場合、ユーザ定義関数では常にTypeErrorが発生します。
内部関数の場合、TypeErrorだったりE_WARNINGだったりします。
これはよくないのでTypeErrorに統一します。
従って、昔ながらのE_WARNINGを抑えるようなコードを書いていた場合はここで詰まります。
PHP本体のテストコードにも、この変更に引っかかるコード(わざとエラーを出すテスト)が1500ほどあるみたいです。
Saner string to number comparison
賛成44反対1で受理。
非厳密な比較演算子==
の挙動を変更するRFCです。
"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です。
PHP7までは、数値型文字列の定義が複数ありました。
・正規の数値型文字列:0個以上のスペース+数値
・非正規の数値型文字列:正規の数値型文字列+任意の文字列
・それ以外は全て数値型文字列ではない。
functionfoo(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
は数値型文字列ではなくなります。
functionfoo(int$i){var_dump($i);}foo("123");// int(123)foo(" 123");// int(123)foo("123 ");// int(123)foo("123abc");// TypeErrorfoo("string");// TypeError
ぶっちゃけSaner string to number comparison
なんかより、こちらのほうがよっぽど影響範囲が大きいです。
ただ数値型文字列ではなくなったとはいえ、(int)"123abc"
が0
になるとあまりにも被害甚大すぎるので、さすがにこれは123
にしてくれるようです。
Treat namespaced names as single token
賛成38反対4で受理。
namespaceのパーサトークンを変更するRFCです。
これまで名前空間Foo\Bar
は字句解析でT_STRING,T_NS_SEPARATOR,T_STRING
と分解されていましたが、今後はT_NAME_QUALIFIED
ひとつになります。
PHPを使う側としては、token_get_allを使っているような変態スクリプト以外は全く無関係です。
と思いきやしれっと、名前空間の空白が禁止されるとか書いてありました。
ちなみに違反するスクリプトは、Composer上位2000パッケージ中僅か5か所だけでです。
Allow trailing comma in parameter list
賛成58反対1で受理。
関数の引数の末尾カンマのRFCです。
PHPでは配列の末尾カンマは昔から使えていて、その後PHP7.3で関数呼び出しおよびほとんどのリストでも末尾カンマが使えるようになりました。
関数呼び出し側は対応したのに、関数の引数のほうは何故か対応していなかったので、その対応の追加です。
functionhoge($foo,$bar,// PHP8.0以降OK){}hoge(1,2,);// PHP7.3以降OK
Allow trailing comma in closure use lists
賛成49反対0で受理。
use内の末尾カンマのRFCです。
$longArgs_longVars=function($longArgument,$longerArgument,$muchLongerArgument,// ↑の引数末尾カンマでOKになった)use($longVar1,$longerVar2,$muchLongerVar3,// このRFC){};
use
の末尾にもカンマを置くことができなかったので、その対応の追加です。
この抜けの補完によって、おそらく全ての列挙箇所で末尾カンマを使えるようになったのではないでしょうか。
Ensure correct signatures of magic methods
賛成45反対2で受理。
マジックメソッドの引数のRFCです。
マジックメソッドには、これまでは正しくない型を書くことが可能でした。
classFoo{publicfunction__get(array$name):void{}}
今後はこれが禁止され、正しい型しか書けないようになります。
もしくは、単に何も書かないかです。
classFoo{publicfunction__get(string$name):mixed{}// ↓でもOKpublicfunction__get($name){}}
例によってNikitaがComposer上位1000パッケージを調べたところ、違反するスクリプトは僅か7か所だけでした。
Configurable string length in getTraceAsString()
賛成36反対2で受理。
例外の文字列展開の文字数を変更するRFCです。
Throwable::getTraceAsString()
などでエラーを文字列展開する際、スタックトレース中の文字列は15バイトで打ち切られてしまいます。
そのため、深く調査したい場合などに難しい状態でした。
そこでini設定zend.exception_string_param_max_len
を追加し、設定されたバイト数をスタックトレースに展開できるようにします。
デフォルトは既存の15です。
Remove inappropriate inheritance signature checks on private methods
賛成24反対11で受理。
privateメソッドの継承に関するRFCです。
classA{finalprivatefunctionfinalPrivate(){echo__METHOD__.PHP_EOL;}}classBextendsA{privatefunctionfinalPrivate(){echo__METHOD__.PHP_EOL;}}
privateメソッドは継承されないにも関わらず、この書き方は何故かCannot override final method
のFatal Errorになります。
その他幾つかの場合において、継承時にprivateメソッドのオーバーライドチェックが行われることがあります。
従って、PHP8.0以降privateメソッドは継承時にオーバーライドチェックを行わないようにします。
Abstract trait method validation
賛成52反対0で受理。
トレイトのabstractメソッドの挙動に関するRFCです。
トレイトにabstractメソッドを書いた場合、継承時のルールが何かおかしくなっています。
traitMyTrait{abstractpublicfunctionpublicFunction():string;abstractprivatefunctionprivateFunction():string;// cannot be declared private}classTraitUser{useMyTrait;// 何故かOKpublicfunctionpublicFunction():stdClass{}}
何故かシグネチャが異なっていても通ってしまうので、これを通常のクラス継承と同じように修正します。
またついでにabstract privateメソッドを定義できるようにします。
traitMyTrait{abstractprivatefunctionneededByTheTrait():string;publicfunctiondoSomething(){returnstrlen($this->neededByTheTrait());}}classTraitUser{useMyTrait;// OKprivatefunctionneededByTheTrait():string{}// 返り値の型が異なるのでNGprivatefunctionneededByTheTrait():stdClass{}// staticとnonstaticなのでNGprivatestaticfunctionneededByTheTrait():string{}}
traitのabstract privateメソッドは、useしたクラスで実装が必須となります。
privateメソッドの実装を強制させるような状況って、あまり考えたくありませんが。
Make sorting stable
賛成24反対11で受理。
ソートを安定ソートにするRFCです。
$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で受理。
オブジェクト初期化子です。
classPoint{// プロパティx,y,zが生えるpublicfunction__construct(publicfloat$x=0.0,publicfloat$y=0.0,publicfloat$z=0.0,){}}
コンストラクタにかぎり、引数に可視性を指定すると自動的にプロパティとして登録してくれます。
これまではオブジェクトの初期化に多数の定型文が必要でしたが、今後は楽に書けるようになります。
Always available JSON extension
賛成56反対0で受理。
JSONを常時有効にするRFCです。
実はこれまでJSONエクステンションを--disable-json
オプションでインストールしないようにできていたのですが、これができなくなります。
マニュアルにも書かれていないほどの隠し機能なので、無くなっても全く問題ないでしょう。
Unbundle ext/xmlrpc
賛成50反対0で受理。
ext/xmlrpcを外すRFCです。
xmlrpcは内部的にxmlrpc-epiを使っていますが、こちらが既に放棄されているためサポートから外します。
これによってxmlrpc_xxx関数が使えなくなります。
元より実験的なものである警告がなされていたモジュールなので、これが影響する人はほとんどいないでしょう。
Non-capturing catches
賛成48反対1で受理。
例外を受け取らないRFCです。
try{changeImportantData();}catch(PermissionException){echo"You don't have permission to do this";}
catchした例外を使用しない場合は、最初から受け取らないようにすることができます。
決して例外の握り潰しに使うための機能ではありません。
Locale-independent float to string cast
賛成42反対1で受理。
一部言語での(string)キャストに関するRFCです。
フランス語では小数点が,
です。
このためロケールをフランス語に設定した場合、(string)3.14
は"3,14"
になります。
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です。
PHP7までPDO::ATTR_ERRMODEのデフォルトはPDO::ERRMODE_SILENT
でした。
これはエラーが出ても何も言わないので非常によろしくない設定です。
PHP8.0からはPDO::ERRMODE_EXCEPTION
がデフォルトになります。
めでたし。
Add str_starts_with and str_ends_with to PHP
賛成51反対4で受理。
StartsWith/EndsWithです。
echostr_starts_with('abcdef','abc');echostr_ends_with('abcdef','def');
ユーザランドで容易く実装できそうに見えますが、実はこの関数の実装には罠が多く、下手に書くとすぐに非効率になります。
言語側で用意してくれるならそれにこしたことはないでしょう。
str_contains
賛成43反対9で受理。
str_containsです。
str_contains('放課後アトリエといろ','放課後');// true
何故かこれまでずっと存在せず、strpos($haystack, $needle)!==false
という謎の書式を強いられていた『〇〇を含む』が、ようやく簡潔に書けるようになります。
str_containsとstr_starts_with/str_ends_withが実装されたことにより、PHPのテキスト処理に対する関数は出揃ったと言えそうです。
throw expression
賛成46反対3で受理。
throw文がthrow式になります。
// PHP7if(!$nullableValue){thrownewInvalidArgumentException();}$value=$nullableValue;// 概ね同じ$value=$nullableValue??thrownewInvalidArgumentException();
throwはこれまで文だったので、式の間に含めることができませんでした。
今後はもっと気軽にthrowしていくことができるようになります。
Object-based token_get_all() alternative
賛成47反対0で受理。
token_get_allの代替構文のRFCです。
token_get_all関数は、パースした結果を配列で返してきていました。
このRFCでは新たにPhpToken
クラスを導入し、パースした結果をオブジェクトで受け取るようにします。
これによってコードがわかりやすくなり、メモリ消費量も減少します。
互換性のため、既存のtoken_get_allはそのままです。
Stringable
賛成29反対9で受理。
StringableインターフェイスのRFCです。
Countableインターフェイスを実装するとcountできるようになり、Traversableインターフェイスを実装するとforeachできるようになります。
同様に__toStringできるようになるインターフェイスStringableを実装しようという提案です。
interfaceStringable{publicfunction__toString():string;}
これを強制するとありとあらゆる実装がぶち壊れるため、__toString
を実装しているクラスは暗黙的にStringable
をimplementsします。
従って、使用する側としては特に気にする必要はありません。
どちらかというとライブラリでstring|Stringable
を受け取りたいときのためのマーカーインターフェースのようなものです。
実際のユースケースはSymfonyで見ることができます。
Allow ::class on objects
賛成60反対0で受理。
クラス名を取得するRFCです。
stdClass::class
でクラス名"stdClass"
を取得できますが、いったんオブジェクト化してしまうと何故か::class
できません。
get_classを使う必要があります。
PHP8では$object::class
と書けるようになります。
echostdClass::class;// classecho(newstdClass)::class;// PHP8.0以降OK
Static return type
賛成54反対0で受理。
返り値にstaticを書けるようにするRFCです。
PHP7では、返り値としてparent
やself
を書くことが可能です。
<?phpclassFUGA{}classHOGEextendsFUGA{publicfunctiona():parent{returnnewparent();}}(newHOGE())->a();// FUGAになる
同様にstatic
も書けるようにします。`
これは遅延静的束縛のほうのstaticです。
// PHP7までsyntax errorclassTest{publicfunctioncreateFromWhatever($whatever):static{returnnewstatic($whatever);}}
個人的には遅延静的束縛を全く使わないので、何が嬉しいのかよくわかりません。
Variable Syntax Tweaks
賛成47反対0で受理。
変数構文のRFCです。
PHP7.0において変数構文の大規模なリファインがありましたが、そのときに見過ごされていた一部の構文についての対応です。
とあるのですが、見過ごされていたくらいなのですごい細かくて気付かないくらいの変更です。
また、これまではシンタックスエラーだった構文が有効になるという変更なので、互換性のない変更はありません。
$bar="bar";echo"foobar"[0];// OKecho"foo$bar"[0];// PHP7までsyntax error、8からOKecho__FILE__[0];// PHP7までsyntax error、8からOK
DOM Living Standard API
賛成37反対0で受理。
DOMの更新についてのRFCです。
PHPのDOMは、かなり昔にW3 Groupが作ったDOM Level 3に準拠して作られています。
その後DOMはLiving Standardとなり、WHATWGが管理するようになりました。
当時よりだいぶ改善されているので、その変更を取り込みます。
まあでもDOM面倒臭いからあんまり使わないよね。
Always generate fatal error for incompatible method signatures
賛成39反対3で受理。
メソッドシグネチャのRFCです。
継承時にLSP原則に違反する書き方をすると、PHP7では致命的エラーもしくはE_WARININGのどちらかになります。
// こっちは致命的エラーinterfaceI{publicfunctionmethod(array$a);}classCimplementsI{publicfunctionmethod(int$a){}}// こっちはE_WARNINGclassC1{publicfunctionmethod(array$a){}}classC2extendsC1{publicfunctionmethod(int$a){}}
これを全ての場合において致命的エラーに統一します。
むしろ何故いままで致命的エラーになっていなかったんだ案件。
Arrays starting with a negative index
賛成17反対2で受理。
マイナススタートの配列インデックスが使えるようになります。
$arr=[-10=>'a'];$arr[]='b';// PHP7.4まで0、PHP8から-9
いったい誰が得するのかよくわかりません。
Weak maps
賛成25反対0で受理。
弱いマップです。
PHP7.4で弱い参照が導入されましたが、現実的には弱いマップのほうが使われてるよ、ってことで導入されるようです。
私には、普通のマップではなくこっちを使う場面が思いつかない。
get_debug_type
賛成42反対3で受理。
get_debug_typeです。
一言で言うとgettype + get_classです。
オブジェクトであればクラス名を、プリミティブ型であれば型名を取得できます。
gettype(1);// integerget_class(1);// TypeErrorget_debug_type(1);// intgettype(newstdClass);// objectget_class(newstdClass);// stdClassget_debug_type(newstdClass);// stdClass
いちいち使い分ける必要がなく、またinteger
とかいう正しくない型名が入ってきたりしないので便利です。
Don't automatically unserialize Phar metadata outside getMetadata()
賛成25反対0で受理。
Pharメタデータの処理方法に関するRFCです。
PHP7では、file_exists("phar://path/to/phar.ext")
のようにPharファイルを探すだけで、自動的にそのメタデータが展開されてしまいます。
これによって、いわゆる安全でないデシリアライゼーション攻撃が可能になります。
そこで、Phar::getMetadataを手動で呼び出さない限り、メタデータを展開しないように修正します。
またgetMetadataはメタデータの展開をunserialize
で行っています。
そこで引数$unserialize_options
を追加し、デシリアライズする方法を任意に変更できるようにします。
でも、Cookieなど外部で汚染できるデータではなく、サーバに置いてあるPharファイルが汚染されている時点でアウトな気がしてならないんだけどどうなのでしょうかね。
Add support for CMS
投票なし。
CMSサポートのRFCです。
CMSといってもContents Management SystemではなくRFC5652のCryptographic Message Syntaxです。
openssl_pkcs7_encryptに相当するopenssl_cms_encryptなどの暗号化・復号する関数が実装されます。
RFCを立てはしたものの、OpenSSLの基礎機能だし投票いらんじゃろということで投票なしに採用されたようです。
感想
おい誰だよPHP8たいしたことないんじゃねえのとか言った奴は。
相当にすごいことになっています。
アトリビュートのあたりは少々見通しが不透明ですが、それ以外は順当に実装されることでしょう。
これらを使うことで、PHP8では書きやすく、読みやすく、そして高速なプログラムを作ることができるようになります。
そのぶんだけ、これまで適当な書き方をしていたコードは移植がたいへんになりそうですね。
なお、このリストは、RFCが立って投票が受理され、導入されることが決定したものだけの一覧です。
これ以外にもバグ修正や、RFCを立てるほどでもないちょっとした変更なども多々入っているはずです。
PHP8によって、PHPはかなり完成に近づいてきた感があります。
これ以上に欲しい大きなものってジェネリクスくらいじゃね?
…なんて言ってたら次回もまた色々突っ込まれてきそうですけどね。
先日Microsoftが企業としてPHP8をサポートしないという残念なお知らせがありましたが、今のところは有志によってWindows版ビルドも迅速に提供されています。
PHP8.0ではさしたる影響はないでしょう。
まあ今後どうなるかはわかりませんし(Windows版固有バグなど)、とっととWSL2に移行しろって話なのかもしれませんが。
でもローカルなんかXAMPPで十分なんだよめんどくせーコマンドなんていちいち打ちたくねーんだよもっと手抜きさせろ。