クラスと変数定義の機能強化
まずは、クラス定義と変数定義の機能強化について紹介します。
Overrideアトリビュートによるオーバーライドメソッドの明示
PHP 8.3では、Overrideアトリビュートにより、オーバライドするメソッドを明示できるようになりました。メソッドのオーバライドとは、クラスやインタフェースのメソッドを派生クラス(実装クラス)のメソッドで上書きすることです。このとき、オーバライドするメソッドとオーバライドされるメソッドのシグネチャは、完全に一致する必要があります。JavaやPythonをはじめとする多くの言語のように、誤ったシグネチャによるメソッド定義を防止することができます。
以下のリストは、クラスとインタフェースのメソッド定義で、Overrideアトリビュートの付与がどのように働くか確認する例です。
class P { protected function cmethod(): void {} (1) } interface I { public function imethod(): void; (2) } class CGood extends P implements I { (3) #[\Override] public function cmethod(): void {} #[\Override] public function imethod(): void {} } class CBad extends P implements I { (4) #[\Override] public function cmesod(): void {} // エラー:CBad::cmesod() has #[\Override] attribute, but no matching parent method exists #[\Override] public function imesod(): void {} // エラー:Class CBad contains 1 abstract method and must therefore be declared abstract or implement the remaining methods (I::imethod) }
クラスPでは引数も戻り値もないメソッドcmethodを(1)、インタフェースIでも同様にメソッドimethodを定義しています(2)。クラスCGoodでは、オーバライドメソッドcmethodとimethodをOverrideアトリビュートを付与して定義しており、これらは問題なくオーバライドされます(3)。
これに対してクラスCBadでは、継承元にないメソッドcmesodとimesodにOverrideアトリビュートを付与して定義していますが、これは適切でないオーバライドとしてエラーになります(4)。
インタフェースでは、メソッドは無条件に抽象メソッドとなるので、実装クラスで定義がないとOverrideアトリビュートの有無にかかわらずエラーになります(上記でエラーメッセージの内容が異なるのはそのためです)。ただしOverrideアトリビュートの付与でオーバライドの意図が明確になるので、できるだけ指定した方がよいでしょう。
クラス定数への型の指定
PHP 8.3では、クラス定数に型を指定できるようになりました。従来は、クラス定数には型が指定できませんでした。そのため、別のコンテキストで定義された定数を参照して新たに定数を定義する場合、その型がよく分からないという問題がありました。
enum E1 { const NAME = "Non-typed YAMAUCHI Nao"; // こっちは文字列だと分かる } class C1 { const NAME = E1::NAME; // E1::NAMEの型は何? }
これが、以下のようにハッキリ分かりやすくなります。なお、互換性のない型を指定するとエラーになります。
enum E2 { const string NAME = "Typed YAMAUCHI Nao"; } class C2 { const string NAME = E2::NAME; const int INAME = E2::NAME; // TypeError }
readonlyプロパティの再初期化
PHP 8.3では、readonlyなプロパティをインスタンスのクローン時に1回だけ再初期化できるようになりました。具体的には、クローンのためのマジックメソッド__clone内において、readonlyなプロパティの変更が可能です。__cloneメソッドから呼び出された関数内でも変更できます。
class MyClass { // stringとDateTimeを持つクラス public function __construct( public readonly string $str, public readonly DateTime $dt ) {} // クローンメソッド。dtフィールドはクローンする。strフィールドは無効化 public function __clone() { $this->dt = clone $this->dt; (1) $this->unsetStr(); } // strフィールドを無効化する関数。__cloneメソッドから呼び出される場合のみ有効 private function unsetStr() { unset($this->str); (2) } } $c1 = new MyClass("Hello", new DateTime()); var_dump($c1->dt); // object(DateTime)#2 (3) { …… var_dump($c1->str); // string(5) "Hello" $c2 = clone $c1; // クローンだがstrフィールドは無効化されてしまう var_dump($c2->str); // 初期化されていないというError
従来は、readonlyなプロパティは書き換えることができなかったため、オブジェクトのディープコピーを目的としてプロパティをクローンしようとしても、それができないという問題がありました(シャローコピーでは問題ありません)。
例えば、リストの(1)のようにreadonlyプロパティであるdtをクローンしたくてもエラーとなります。PHP 8.3では、__cloneメソッド内に限定してreadonlyプロパティを変更できるようになったので、(1)のようにプロパティのクローンも、(2)のようにプロパティの値の破棄も可能になりました。
動的なクラス定数の参照
PHP 8.3では、文字列をキーにしてクラス定数を取得できるようになりました。これは、変数などでは可能であった記法を定数にも拡張したものです。これにより、取得したいクラス定数が実行時にならないと決まらない場合に対応できます。なお、従来はconstant関数を使って同様のことを行っていました(constant関数はPHP 8.3でも引き続き利用可能)。
class C { const string NAME = 'YAMAUCHI Nao'; } $constName = 'NAME'; echo constant("C::$constName") . "\n"; // 従来方式 echo C::{$constName} . "\n"; // PHP 8.3
また、この機能により、PHP 8.1で導入されたENUM型(列挙型)において文字列からの列挙子の値の取得も簡単になります。
enum Creature:string { case Insects = '昆虫'; case Birds = '鳥'; case Fishs = '魚'; case Animals = '動物'; } $creatureName = 'Insects'; echo Creature::{$creatureName}?->value; // 昆虫
静的変数(static変数)の任意の初期化
PHP 8.3では、静的変数(static変数)の初期化子に変数や関数の戻り値を指定することができるようになりました。静的変数とは、関数内でのみ有効で、かつプログラムの実行中に値を保持し続ける変数です。静的変数の初期化は、従来は定数による初期化のみが可能でした。
function init() { return (int)fgets(STDIN); // 標準入力から開始値(例えば101)を取得して返す } function countUp() { //static $count = 101; // 従来は定数による初期化のみが可能 static $count = init(); // 一度だけ初期化される echo "{$count}\n"; $count++; } countUp(); // 例えば101 countUp(); // 例えば102 countUp(); // 例えば103
関数countUpの静的変数countは、関数の呼び出しで一度だけinit関数の戻り値で初期化されます。以降の呼び出しでは、変数countの値をインクリメントしながら表示します。このような、開始値を外部から与えるような使い方が可能です。