PHP8.4 / PHP8.3 / PHP8.2 / PHP8.1 / PHP8.0
2024/08/13、PHP8.4がフィーチャーフリーズしました。
言語機能に関わるような機能の追加・変更が締め切られたということです。
今後はデバッグを繰り返しながら完成度を高めていき、2024/11/21にPHP8.4.0がリリースされる予定です。
というわけでPHP8.4で実装されるRFCを見てみましょう。
RFC
Property access hooks
賛成42反対2で受理。
プロパティフックです。
class HOGE{
public string $tel{
set{
if(!ctype_digit($value)){
throw new ValueError("電話番号は数値のみ");
}
if(strlen($value) < 10){
throw new ValueError("電話番号は10文字以上");
}
$this->tel = $value;
}
get=> '電話番号は' . $this->tel;
}
}
$hoge = new HOGE();
$hoge->tel = '123456789012'; // OK
$hoge->tel = 'abcdefghijkl'; // Uncaught ValueError: 電話番号は数値のみ
$hoge->tel = '123'; // Uncaught ValueError: 電話番号は10文字以上
echo $hoge->tel; // "電話番号は123456789012"
プロパティに直接setter/getterを書くことができます。
これによって無駄なsetter/getterメソッドをとりあえず書いておく手間がなくなり、プロパティへの後からの検証機能の追加なども容易になります。
Property hook improvements
賛成33反対0で受理。
プロパティフックのパフォーマンス改善と、それに伴うトレードオフです。
class C
{
public string $val = 'a' {
get {
return $this->val . $this->suffix();
}
}
public function suffix(): string
{
return $this->val;
}
}
$c = new C();
print $c->val;
このコードはsuffix()
内でvalを読んでいるのでval()
が呼ばれ、val()
の中でsuffix()
が呼ばれているので再度valが読み込まれ、と無限ループになります。
プロパティフックの当初の実装では、このコードは専用のエラー検出を行っていました。
ところでプロパティフックのコード最適化をがんばっていたところ、劇的なパフォーマンス向上を成し遂げたかわりに上記コードが専用のエラーにならなくなりました。
かわりに無限ループでスタック上限にひっかかるという既存のPHPエラーになります。
そのトレードオフを押してでも導入する価値はあるだろうかというRFCですが、もちろん賛成多数で受理されました。
ちなみにこのRFCによって、プロパティフックのオーバーヘッドは『__get()と同じくらい』から『メソッドに比べると無きに等しい』まで改善されたそうです。
すごいですね。
Asymmetric Visibility v2
賛成24反対7で受理。
非対称可視性です。
かつてPHP8.3向けに提出されたものの却下されたRFCをブラッシュアップしたものであり、今回無事に受理となりました。
class Foo {
public private(set) string $bar = 'baz';
}
$foo = new Foo();
var_dump($foo->bar); // "baz"
$foo->bar = 'beep'; // Visibility error
読み込みはpublicなのでどこからでも可能ですが、書き込みはprivateなので自分自身からしか書き込むことができません。
setter/getterを使う理由の98%は『見られてもいいけど書き込ませたくない』なので、今後のPHPはきっとpublic private(set)
が定型文になっていくことでしょう。
なんとなく機能がプロパティフックと被っている気もしますが、プロパティフックは『setメソッドの動作を規定する』、可視性は『そもそもsetメソッドにアクセスできるかを規定する』であって、それぞれ独立した概念です。
両方同時に使うことももちろん可能です。
class Everything {
public protected(set) $answer {
get => 42;
set => print "Why?";
}
}
書き込みは子クラスからのみ可能ですが実際は書き込まれず、読み込みはどこからでもできますが42が返ってきます。
#[\Deprecated] Attribute
賛成23反対6で受理。
みんな大好き非推奨です。
#[\Deprecated]
function test() {
}
#[\Deprecated("use test() instead", since: "2.4")]
function test3() {
}
非推奨を表すアトリビュートで、関数、メソッド、クラス定数などに指定可能です。
これを書いておくと、呼び出したときにE_USER_DEPRECATED が発生します。
その点ではPHPDocの@Deprecatedより強制力を持っているといえますね。
他のアトリビュートって実際にユーザランドではあんまり使いそうにないものが多いわけですが、これに限っては出番が多くなりそうです。
new MyClass()->method() without parentheses
賛成25反対4で受理。
// PHP8.3
(new HOGE())->run();
// PHP8.4
new HOGE()->run();
インスタンス化からのメソッド即時実行は必ず(new HOGE())->run();
のように括弧で囲まないといけません。
これは括弧を書かないとnew (HOGE()->run());
と解釈されるからです。
が、newの場合は(new HOGE())->run();
と解釈しても文法が曖昧にならないことがわかったので優先順位を変更します。
個人的にはわりとよくこの書き方をするので、ちょっとだけ楽になりそうです。
Correctly name the rounding mode and make it an Enum
賛成34反対0で受理。
round関数の丸めモードを定数からENUMにしようという提案。
// PHP8.3
round(12.34, 1, PHP_ROUND_HALF_UP);
// PHP8.4
round(12.34, 1, RoundingMode::HalfAwayFromZero);
名前もより説明的になり、特にマイナス値だった場合の挙動がわかりやすくなります。
enum RoundingMode{
case HalfAwayFromZero; // PHP_ROUND_HALF_UP
case HalfTowardsZero; // PHP_ROUND_HALF_DOWN
case HalfEven; // PHP_ROUND_HALF_EVEN
case HalfOdd; // PHP_ROUND_HALF_ODD
case TowardsZero;
case AwayFromZero;
case NegativeInfinity;
case PositiveInfinity;
}
既存の定数を削除する予定は今のところありません。
なお後ろ4つは↓で新しく追加された丸めモードです。
New 4 rounding modes to round() function
賛成19反対0で受理。
round関数の丸めモードが4種類追加されます。
round(-12.34, 1, RoundingMode::PositiveInfinity); // 切り上げ
round(-12.34, 1, RoundingMode::NegativeInfinity); // 切り捨て
round(-12.34, 1, RoundingMode::TowardsZero); // 0に近づける切り捨て
round(-12.34, 1, RoundingMode::AwayFromZero); // 0から離れる切り上げ
切り捨て/切り上げの専用関数ceil/floorは桁数を指定できないので、こっちのほうが機能的にも優れていたりします。
これら新しい丸めモードには定数が用意されず、ENUMからのみアクセス可能となります。
実はこのRFCの時点では丸めモードごとにPHP_ROUND_TOWARD_ZERO
のような定数が用意されていたのですが、こちらが受理された後で↑のRFCも受理されたので、定数は日の目を見ることなく消滅しました。
Deprecate implicitly nullable parameter types
賛成26反対0で受理。
型のある引数のデフォルトnullが禁止されます。
function foo(int $x = null){}
// PHP8.3 OK
// PHP8.4 E_DEPRECATED
// PHP9 エラー
どういうことかというと、関数fooの型はintなので本来はintしか渡すことができません。
しかしここに=null
とデフォルト値が書いてある場合に限り、$x
にnullを入れることができてしまうのです。
これは元々PHPにnull許容型の引数がなかったからという歴史的経緯です。
ということで、今後は$x
がintであることが保証されます。
ところで今後、引数はintもしくは渡さないってしたいときはどうすればいいんだろう?
function foo(?int $x){}
foo(1); // OK
foo(null); // OK
foo(); // Fatal error ArgumentCountError Too few arguments
function foo(?int $x = null){}
foo(1); // OK
foo(null); // OK
foo(); // OK
引数なしはOKだけどnullはエラーにする方法がよくわかりませんでした。
Adding bcround, bcfloor and bcceil to BCMath
賛成13反対0で受理。
BCMathに丸め関数を導入します。
bcround('0.285', 2, PHP_ROUND_HALF_UP); // "0.29"
bcfloor('13.0915'); // "13"
bcceil('24.00001'); // "25"
PHPにはBCMathとGMPという2種類の任意精度演算があります。
GMPは整数しか使えないけどBCMathは小数も使えるので高機能、かと思いきや実はBCMathのほうが何故か機能が少ないです。
あと遅いらしい。
ということでPHP8.4ではBCMathにだいぶ高速化の梃入れが入る予定です。
Make the GMP class final
賛成28反対0で受理。
GMPクラスをfinalにします。
PHPでは、リソースという特殊な型が片っ端からObjectに置き換えられつつあります。
この置き換えは普通の新規クラス導入と異なり、文法やクラスの扱いがやや特殊です。
たとえばXMLParserはPHP8.0でリソースからObjectに変更されたのですが、その使い方は$xml->parse($data);
ではなくxml_parse($xml, $data);
です。
XMLParserはfinalクラスであり、直接newすることができず、キャストもシリアライズもできないという変なクラスです。
どうしてこのようなことになっているのかというと、置き換えの前後で互換性を壊さず全く同じ書き方ができるようにしたからです。
実際is_resource()とかで型チェックでも入れていない限り、XMLParserを使っているソースコードはPHP8.0の前後でそのまま動作します。
GMPもかつてリソースだったものがPHP5.6でObjectに置き換えられたのですが、GMPは上記のような移行方針が固まる前に移行されたものであるため(というかGMPが最初)他の置き換えクラスと形が揃っていません。
GMPクラスはfinalではなく、直接newが可能で、キャストもシリアライズも可能です。
ということで、GMPをせめてfinalにしようというRFCが提出されました。
GMPは演算子オーバーロードが実装されていて色々と特殊なので、extendsできないようにするのはまあ妥当だと思います。
がしかし、今までずっとそのままだったのにどうして今さらこんな話が出てきたのかというと、このスレッド・このRFCが原因です。
こちらはGMPを明示的にextends可能にするRFCであり、ほぼ演算子オーバーロードのユーザランド解放と同義です。
PHPでは演算子オーバーロードは様々な問題があって導入には慎重な姿勢です。
他の置き換えクラスと足並みを揃えるという理由ももちろんあるでしょうが、危険なRFCを事前に封じておこうという魂胆も見え隠れしますね。
Lazy Objects
賛成25反対4で受理。
遅延初期化です。
遅延初期化ってなにかというと、new XXX()
とか書いてあっても必要になるまで実際はインスタンスを生成しないというものです。
インスタンス生成は比較的重い処理なので、できるだけやりたくないし、やるにしてもなるべく後回しにしたいわけですね。
アイデアとしては引数のコピーオンライトと似たようなものです。
ただ現在使っている普通のクラスが遅延初期化されるわけではなく、使用するには専用の構文が必要です。
そのまま遅延初期化されると明らかに互換性がなくなりますしね。
この機能は基本的にユーザが使うものではなく、Fiberと同じようにライブラリやフレームワークが中で使うものになるでしょう。
class MyClass
{
public function __construct(private int $foo)
{
// 重い処理
}
}
$initializer = static function (MyClass $ghost): void {
$ghost->__construct(123);
};
$reflector = new ReflectionClass(MyClass::class);
// $objectが遅延初期化オブジェクト
$object = $reflector->newLazyGhost($initializer);
Symfonyでは既に独自実装をLazyObjectsに置き換えたバージョンで動作検証ができているそうです。
このRFCの提案者はSymfonyのCotribute数2位の人なので、まず間違いないでしょう。
Grapheme cluster for str_split function: grapheme_str_split
賛成19反対0で受理。
絵文字を正しく分割できる関数grapheme_str_splitを導入します。
grapheme_str_split("👨👩👦👦"); // ['👨👩👦👦']
mb_str_split("👨👩👦👦"); // ['👨', '', '👩', '', '👦', '', '👦']
str_split("👨👩👦👦"); // ['�', '�', '�', '�', '�', '�', '�', '�', '�', '�', '�', '�', '�', '�', '�', '�', '�', '�', '�', '�', '�', '�', '�', '�']
文字コードは魔境すぎてさっぱりだ。
Multibyte for trim function mb_trim, mb_ltrim and mb_rtrim
賛成15反対0で受理。
マルチバイト版trimです。
mb_trim(' あ '); // "あ"
trimでは無理だった全角スペースが消えてくれるようになります。
日本人としてはありがたい対応ですね。
Multibyte for ucfirst and lcfirst functions
賛成15反対0で受理。
マルチバイト版ucfirstです。
mb_ucfirst('ψη'); // "Ψη"
ucfirst('ψη'); // "ψη"
なおアルゴリズムはmb_strtoupperと同じであり、日本語には効きません。
残念ですね。
mb_ucfirst('ぁ'); // "ぁ"
Add http_(get|clear)_last_response_headers() function
賛成15反対1で受理。
HTTPレスポンスヘッダを取得する関数http_get_last_response_header()です。
現在file_get_contents()を使うと唐突に$http_response_headerという変数が生えてくるのですが、わかりにくいので同じ内容を取得する変数を追加します。
file_get_contents('xxx');
var_dump(http_get_last_response_header()); // $http_response_headerと同じ
このRFCでは$http_response_header
については何もしません。
今後PHP9かその次あたりで非推奨・削除になることでしょう。
RFC1867 for non-POST HTTP verbs
賛成23反対1で受理。
リクエストボディを取得する関数request_parse_body()です。
現在のPHPでは、POSTだとリクエストボディが$_POST
に入ってきます。
が、その他のリクエストメソッドGET
やPUT
等の場合$_POST
は空です。
そして何気にこれまでPHPには、リクエストボディを直接いいかんじに取ってくる方法がありませんでした。
かなり意外ですね。
ということでリクエストボディを取得する関数を実装します。
[$_POST, $_FILES] = request_parse_body();
これでリクエストメソッドに関係なく、リクエストボディを簡単に取得できるようになります。
A new JIT implementation based on IR Framework
賛成25反対0で受理。
中間表現を用いた新たなJITを実装します。
なんとこのために、JITフレームワークを一から作り上げました。
機能は現在のJITと全く同じで、動作は1割弱ほど高速化し、コンパイル速度は最大4倍になるそうです。
いったいどういうことだってばよ。
さらに、PHPのために作ったものですが、PHPに依存はしておらず他言語からも利用可能だということです。
PHP8.0でのJITの実装は900コミット5万行の追加というヤバい規模でしたが、今回は250コミット7万行です。
この人のバイタリティどうなってんだ。
Change how JIT is disabled by default
賛成17反対0で受理。
JITを無効化する方法を変更します。
現在、JIT関連設定のデフォルト値はこうなっています。
opcache.jit=tracing
opcache.jit_buffer_size=0
現在JITを無効化する方法はopcache.jit_buffer_size=0
です。
なんというかわかりにくいのでこうしましょう。
opcache.jit=disable
opcache.jit_buffer_size=64m
有効無効を普通にdisableで指定できるようにします。
これはデフォルト値を変更するだけなので、既存ユーザには影響はありません。
明らかにこちらのほうがわかりやすいですね。
Increasing the default BCrypt cost
賛成25反対0で受理。
PASSWORD_BCRYPTのコストのデフォルト値は10で、これはpassword_hashが実装された11年前からずっとそのままです。
当時からCPUのパワーもだいぶ上がったので、コストを上げようという提案です。
二次投票でデフォルト値11が12票、デフォルト値12が14票となり、デフォルト値は12になりました。
ハッシュアルゴリズムを変えたらこれまでのユーザがログインできなくなるのでは?と思うかもしれませんが、password_hashの特徴のひとつが完全な互換性であり、既存ユーザの認証には全く影響ありません。
PDO driver specific sub-classes
賛成23反対0で受理。
PDOサブクラスです。
// MySQL固有機能が使える
$pdo = new PdoMySql('mysql:host=localhost;dbname=test');
$pdo->getWarningCount();
// PostgreSQL固有機能が使える
$pdo = new PdoPgsql('pgsql:host=localhost;dbname=test');
$pdo->escapeIdentifier($name);
// SQLite固有機能が使える
$pdo = new PdoSqlite('sqlite:/path/to/sqlite_db.db');
$pdo->createCollation($collection);
固有機能を使う必要がない場合は、これまでどおりnew PDO()
としておけば何も変わりません。
こちら、元々PHP8.3向けで受理されたのですが、担当者が病気のため対応できなくなってしまいました。
その後、他のContributorたちの努力によって対応され、無事PHP8.4にマージされました。
PDO Driver specific SQL parser
賛成27反対0で受理。
PDOのSQLパーサは、歴史的理由によりMySQLを基準として作られています。
そんなわけでPosgresやSQL ServerなどでSQLを正しくパースできないことがあるという問題が発生していました。
そこでSQLドライバごとに個々のパーサを用意します。
むしろ今までパーサひとつでやってたことにびっくりだよ。
DOM HTML5 parsing and serialization
賛成16反対0で受理。
PHPのDOMはHTMLのパースにlibxmlを使用しています。
これがHTML4までしか対応していないので、HTML5を正しくパースすることができません。
libxmlにIssueも立っていますが対応は遅々として進んでいません。
ということで最新のHTMLにも対応したパーサLexborを使用した新たなクラス\DOM\HTMLDocument
を導入します。
互換性のため、既存のDOMDocumentを置き替えるのではなく新たなクラスを作ることにしたようです。
RFCはだいぶ長いので、詳しくは直接見てください。
Opt-in DOM spec-compliance
賛成14反対0で受理。
PHPのDOMはDOM Core Level 3 specification仕様に従っています。
しかし現在既にDOMの仕様はLiving Specificationに移行しており、さらに既存のDOM実装には多くのバグ(というかDOM Core Level 3仕様の不備による動作の差異)が含まれています。
$dom = new DOMDocument;
$outer = $dom->appendChild($dom->createElementNS("urn:a", "outer"));
$outer->appendChild($dom->createElement("inner"));
echo $dom->saveXML();
これは<outer xmlns="urn:a"><inner xmlns=""/></outer>
になるべきですが、PHPではDOMが導入された5.0から8.3までずっと<outer xmlns="urn:a"><inner/></outer>
になります。
もはや深く根付いてしまっており、今さらこの挙動を変えることは不可能でしょう。
ところでちょうどDOM\Document
のRFCが受理されたので、新しいDOMではついでにこのようなバグも全部修正してしまおうということになりました。
結果としてDOM\Node
とDOMNode
の動作が変わることになるので、ついでに古いDOMを新しい方にインポートするメソッドも用意します。
class DOM\Document {
public function importLegacyNode(DOMNode $node, bool $deep = false): DOM\Node;
}
DOMの既存バグが思ったよりたくさんあってびっくりだよ。
まあ大半はnullを返すべきところでfalseを返しているなど昔のPHPだなあってやつではありますが、たまに深刻なやつもあったりします。
New ext-dom features in PHP 8.4
賛成25反対0で受理。
ここまでで既にDOMがだいぶ変更されていますが、ついでだからということでさらなる改善を加えます。
// querySelector
$dom->querySelector('p ~ span')->textContent;
// closest
$dom->getElementById('div3')->closest('div')->getAttribute("xml:id");
// matches
$dom->getElementById('div3')->matches('div > div');
CSSのセレクタ、querySelector・closest・matchesの輸入です。
これは便利。
ほかにも現在PHPに存在しないDom\TokenListを追加しようといった様々な改善がなされています。
正直だいぶ使い辛かったDOMが、かなり便利になりそうですね。
XML_OPTION_PARSE_HUGE
賛成11反対0で受理。
DOMが使用しているlibxml2は、バージョン2.7.0で大きなデータの入力を拒否するようになりました。
これはDOS攻撃を防ぐためのものです。
で、同時にその上限を許可するオプションXML_PARSE_HUGE
も用意されまして、PHPからも使用可能です。
ところがこのオプション、DOMやsimplexmlには効くのですが、何故かxml_parseには使えませんでした。
ということでXMLパーサ関数でもこのオプションを使えるようにする提案です。
$parser = xml_parser_create();
xml_parser_set_option($parser, XML_OPTION_PARSE_HUGE, true);
$success = xml_parse($parser, $でかいXMLファイル);
Improve callbacks in ext/dom and ext/xsl
賛成15反対0で受理。
DOMXPathの改善です。
// registerPhpFunctionsに連想配列を渡す
$xpath->registerPhpFunctions([
"function_name" => $callable
]);
// registerPhpFunctionNSを追加
$xpath->registerPhpFunctionNS("http://example.com", "first_function", $callable);
実はDOMもXPathも全く使っていないので、これがどう便利なのかよくわかりませんでした。
誰かがコメントで親切に解説してくれるはず。
Add stream open functions to XML{Reader,Writer} (was: Add openStream() to XML{Reader,Writer})
賛成13反対0で受理。
XMLReaderおよびXMLWriterにストリーム読み書きのメソッドを追加します。
$stream = fopen('path/to/file', 'w+');
// 読み込み
$reader = XMLReader::fromStream($h);
while ($reader->read()) {
switch ($reader->nodeType) {
case XMLReader::ELEMENT:
echo "Element: ", $reader->name, "\n";
break;
case XMLReader::COMMENT:
echo "Comment: ", $reader->value, "\n";
break;
}
}
// 書き込み
$writer = XMLWriter::toStream($h);
$writer->startElement("root");
$writer->writeAttribute("align", "left");
$writer->writeComment("hello");
$writer->endElement();
$amount = $writer->flush();
XMLReader/Writerは内部的にはストリーム的な処理をやっているのに、fromStream
やtoStream
がありませんでした。
そのせいで一度文字列にしてから投入するなどの手間が必要でした。
今後はストリームとXMLReader/Writerを直接行き来できるようになります。
Dedicated StreamBucket class
賛成20反対0で受理。
ストリームフィルタ用クラスStreamBucketを導入します。
class simple_filter extends php_user_filter {
function filter($in, $out, &$consumed, $closing) {
while ($bucket = stream_bucket_make_writeable($in)) {
$consumed += $bucket->datalen;
stream_bucket_append($out, $bucket);
}
return PSFS_PASS_ON;
}
}
stream_filter_register("simple", "simple_filter")
PHPにはストリームに一律で処理を行えるストリームフィルタという機能があります。
しかしこれ、生成されるオブジェクトが何故かstdClass
なんですよね。
ということでStreamBucket
クラスを作ろうという提案です。
final class StreamBucket
{
/**
* @var resource
* @deprecated リソースからオブジェクトへ移行が終わったら
*/
public $bucket;
public string $data;
/** @deprecated 次バージョン */
public int $datalen;
public int $dataLength;
}
また、プロパティdatalen
は現在のPHP命名規則と合っていないので、中身の同じプロパティdataLength
を作り、今後どこかでdatalen
を削除します。
むしろどうして今までstdClass
だったのか謎。
Unbundle the imap, pspell, and oci8 extensions
賛成27反対0で受理。
問題を抱えているいくつかの拡張機能をバンドルから外します。
ext/imapは内部で使っているライブラリuw-imap/imap最終更新が2019年で、OAuthに対応していないのでGmail等に使えず存在価値がなくなりました。
送信だけならPHPMailer使っとけばいいし、受信も必要であればwebklex/php-imapなどの代替があります。
ext/pspellは、使っている辞書の最終更新が2001年です。
ext/oci8のOracleのライブラリは非常に多くのバグがあます。
PHP側の最近の修正は、ほとんどが通らないテストをスキップするようにしただけです。
ということでこれらはPHP本体に同梱されなくなります。
バンドルから外されてもPECLには残るので、当面はPECLからインストール可能です。
Raising zero to the power of negative number
賛成27反対0で受理。
0のマイナス乗をエラーにします。
var_dump(0 ** -1);
// PHP8.3 float(INF)
// PHP8.4 float(INF) Deprecated: Zero raised to a negative power is deprecated
// PHP9.0 DivisionByZeroError
x
のマイナス乗は1/x
のプラス乗です。
10 ** -3
=== 1/10 ** 3
ということですね。
つまり0をマイナス乗すると1/0 ** 1
になるわけで、従ってゼロ除算エラーにならないのはおかしいというわけです。
あとついでに、fmodに対応する算術演算関数fpowを追加します。
array_find
賛成21反対0で受理。
便利な配列検索関数array_findを追加します。
$array = [
'a' => 'dog',
'b' => 'cat',
'c' => 'cow',
'd' => 'duck',
'e' => 'goose',
'f' => 'elephant'
];
// 検索して最初の値を返す
var_dump(array_find($array, function (string $value) {
return strlen($value) > 4;
})); // string(5) "goose"
// 検索して最初のキーを返す
var_dump(array_find_key($array, function (string $value, $key) {
return preg_match('/^([a-f])$/', $key);
})); // string(1) "a"
// ひとつでもtrueであればtrue
var_dump(array_any($array, function (string $value) {
return strlen($value) < 3;
})); // bool(false)
// 全項目がtrueであればtrue
var_dump(array_all($array, function (string $value, $key) {
return is_string($key);
})); // bool(true)
配列の各項目を検索する関数が4つ追加されます。
一致する最初の値を返すarray_find
、一致する最初のキーを返すarray_find_key
、ひとつでも当てはまる項目が存在するかチェックするarray_any
、全て項目が当てはまるかをチェックするarray_all
です。
便利ですね。
感想
PHP8.4のα版は既に提供されています。
Linux / Windows
最も影響のありそうな変更点と言えば、やはりプロパティフックと非対称可視性ですね。
開発当初のPHPはプロパティへのアクセスがフリーパスだったわけですが、時代の移り変わりとともにどんどんプロパティ周りの機能が追加されてきました。
型宣言、NULL許容型、readonly、UNION型、交差型、オブジェクト初期化子、型パズル、そして今回のプロパティフックに非対称可視性。
今となっては、もう下手な言語より充実していると言って過言ではないでしょう。