昨年12月にリリースされ,維持フェーズに移行したPHP 8.2は,引き続きバグ修正版のリリースがほぼ毎月1回リリースされています.また,次のマイナーバージョンである PHP 8.3については,リリースマネージャが決定するなど,リリースに向けた開発が本格化しています.今回は,前回に続きPHP 8.3に向けて開発中の新機能の一部について紹介します.
PHP 8.3の開発状況
PHP 8.3のリリースに向けた開発工程を統率するリリースマネージャがPHP開発者による選挙により4月16日に決定しました.近年の開発体制では,リリースマネージャの経験者1名と新人2名の3名体制としています.経験者(ベテラン)枠としては,PHP 8.2のリリースマネージャであったPierrick Charron氏が選ばれ,新人枠(2名)には,Jakub Zelenka氏とEric Mann氏が選ばれました.Jakub Zelenka氏は,PHP FoundationからPHPコア開発者として資金サポートを受けています.今後の開発工程は,Jakub Zelenka氏とEric Mann氏の2名が主としてまとめ,Pierrick Charron氏がサポートすることにより進められていきます.
PHP 8.3の正式リリースは今年(2023年)の11月23日に予定されており,最初のテスト版としてα1版が6月8日にリリースされる予定です.
以下,前回に続いて,PHP 8.3向けに実装済みの機能を順番に紹介していきます.
クラス定数の型宣言
PHPでは,クラス内でPHP定数(クラス定数)を宣言して,使用することができます.PHPでは,変数の型指定を行う機能がサポートされていますが,従来のバージョンのPHPでは,クラス定数の型指定はサポートされていませんでした.このため,以下のように継承先のクラスでクラス定数TESTの型を配列から文字列に変更するようなことが可能で,コードの見通しが悪くなるという課題がありました.
<?php
class Foo {
const TEST = [];
}
class Boo extends Foo {
const TEST = "Test2";
}
従来のPHPにおいても,クラス全体またはクラス定数にfinalを指定することで,代入される初期値と同じ型と値を指定することは可能でしたが,クラス定数の宣言時に直接型指定を行う機能はサポートされていませんでした.
PHP 8.3では,クラス定数における型指定が可能になります.以下のようにクラスFooのクラス定数TESTを配列として初期化することができます.継承先のBooクラスで文字列として同じクラス定数TESTを宣言しています.
<?php // PHP RFC: Typed class constants
class Foo {
const array TEST = [];
}
class Boo extends Foo {
const string TEST = "Test2";
}
この場合,継承先のクラス定数の型(文字列)が継承元の同名のクラス定数の型(配列)と一致しないため,以下のような致命的なエラーを発生します.
Fatal error: Type of Boo::TEST must be compatible with Foo::TEST of type array
クラス定数における型指定の追加は,クラスに関連する機能である,enum,trait,interfaceでも利用可能です.また,型指定ではunionやintersection(交差型), DNF記法もサポートしており,一部の型宣言(void,callable, never)を除く,すべての型をサポートしています.
以下のように,型指定に合わない型の初期値を代入するとエラーになります.
<?php
class Foo {
const string TEST = [];
}
この場合は,文字列として宣言されたクラス定数に配列を代入しており,型が不一致となっています.
なお,継承先のクラスでは,クラス定数の型に互換性があれば,初期値は変更できます.以下の例では,ともに文字列を代入しており,正しく動作します.
<?php // PHP RFC: Typed class constants
class Foo {
const string TEST = "Test1";
}
class Boo extends Foo {
const string TEST = "Test2";
}
クラスの型指定の原則に則っており,継承先における型指定では,継承元における型の集合を同等もしくは小さくする方向の指定のみが可能です.例えば,以下の例では,継承元のクラス定数の型宣言(array|string)に対して,継承先のクラス定数では文字列(string)として宣言されており,集合が小さくなる方向であるため,正常に動作します.
<?php
class Foo {
const array|string TEST = [];
}
class Boo extends Foo {
const string TEST = "Test2";
}
継承先のクラスBooにおいて型指定の集合を大きくすることはできません.例えば,以下のようにstringの代わりにstring|bool を指定するとエラーを発生します.
class Boo extends Foo {
const string|bool TEST = "Test2";
}
readonlyプロパティ変数のディープコピー
readonly宣言されたクラスのプロパティ変数に関する扱いの拡充が提案され,提案された2件の内,1件のみが採用されています.以下,その内容を紹介します.
従来,readonly宣言されたプロパティ変数をディープコピーすることはできませんでした.PHP 8.3では,特殊メソッド__clone()の中でのみ再初期化が可能となります.
具体的な例として,以下のコードを見てみましょう.
<?php
class Foo {
public function __construct(public readonly DateTime $bar) {}
public function __clone() {
$this->bar = clone $this->bar;
}
}
$foo = new Foo(new DateTime());
$moo = clone $foo;
最後の2行でクラスFooのインスタンスを作成して$fooに代入,cloneにより変数$mooへのディープコピーを行っています.Fooクラスのインスタンスを作成する際,引数としてDateTimeクラスのインスタンスを引数$barとして,コンストラクタがコールされます(4行目).この引数にreadonly属性が指定されていることに注意してください.この引数$barはクラスのプロパティ変数として自動的に保持されます.
ディープコピーを行う際,クラスの__cloneメソッドが自動的にコールされます(6行目).このメソッドの中で,cloneによりプロパティ$barのディープコピーを作成して,コピー先の同名の変数$barに代入しています(7行目).
この例をPHP 8.2で実行すると,以下のようなエラーとなります.
Fatal error: Uncaught Error: Cannot modify readonly property Foo::$bar
これは,7行目でreadonlyのプロパティ変数$barの値を変更しようとしたためです.PHP 8.2までのバージョンではこのような操作は許可されておらず,結果としてクラス内にreadonly属性を有する場合,そのクラスのインスタンスのディープコピーができなくなるという問題がありました.PHP 8.3では,この動作が修正され,上記のエラーを発生することがなくなりました.
ただし,再初期化できるのは,一つの変数について1回だけで,複数回初期化を行うとエラーになります.例えば,8行目に7行目と同じ行を追加してみましょう.
$this->bar = clone $this->bar;
すると,上記のPHP 8.2と同じエラーが発生します.通常は,同一の変数について複数回初期化を行うことは想定されませんが,注意は必要です.
今回は,前回に続いて,PHP 8.3で導入される予定の新機能の一部を紹介しました.この他にもいくつかの機能が提案されており,6月に予定されるα版のリリースに向けて,主な機能が出揃ってくると予想されます.次回以降もPHP 8.3の新機能を始め,PHP関連の話題を紹介いたします.