PHP8.1でreadonlyプロパティが実装されましたが、このreadonly修飾子がクラスにも使えるようになります。
プロパティごとにreadonlyを設定するのではなく、クラス自体を厳格に運用することができるようになります。
以下は該当のRFC、Readonly classesの日本語訳です。
PHP RFC: Readonly classes
Introduction
PHP8.1でreadonlyプロパティがサポートされました。
しかしたとえば、多くのプロパティのある不変クラスの宣言は未だに面倒です。
そこで、このRFCではreadonlyなクラスのサポートを提案します。
Proposal
PHP8.1で追加されたreadonlyを、クラスにも適用できるようにします。
readonly class Test {
public string $prop;
}
これによって、クラス内の全てのプロパティがreadonlyになります。
また、動的プロパティの作成も禁止されます。
readonly class Foo{
public int $bar;
public function __construct() {
$this->bar = 1;
}
}
$foo = new Foo();
$foo->bar = 2; // Fatal Error: Uncaught Error: Cannot modify readonly property Foo::$bar
$foo->baz = 1; // Fatal Error: Uncaught Error: Cannot create dynamic property Foo::$baz
またDeprecate dynamic propertiesのRFCにおいては、#[AllowDynamicProperties]
を指定することで動的プロパティを引き続き使えるようにサポートされましたが、readonlyクラスに対しての指定は禁止されます。
#[AllowDynamicProperties]
readonly class Foo {
}
// Fatal error: Cannot apply #[AllowDynamicProperties] to readonly class Foo
Restrictions
型のついていないプロパティ、静的プロパティを宣言することはできません。
readonly class Foo{
public $bar; // Fatal error: Readonly property Foo::$bar must have type
public static int $baz; // Fatal error: Readonly class Foo cannot declare static properties
}
Inheritance
readonlyプロパティと同様、readonlyクラスをreadonlyクラスでextendsすることは可能です。
readonly class A {}
readonly class B extends A {} // これはOK
readonlyクラスをreadonlyでないクラスでextendsしたり、その逆は禁止されます。
readonly class A {}
class B extends A {}
// Fatal error: Non-readonly class B cannot extend readonly class A
class A {}
readonly class B extends A {}
// Fatal error: Readonly class B cannot extend non-readonly class A
Reflection
ReflectionClass::isReadOnly()
メソッドが追加され、クラスがreadonlyか否かを調べることができます。
またReflectionClass::getModifiers()
メソッドにReflectionClass::IS_READONLY
フラグが追加されます。
Backward Incompatible Changes
互換性のない変更はありません。
Vote
投票期間は2022/04/27から2022/05/11まで。
このRFCは、賛成28反対7の賛成多数で受理されました。
感想
一回だけ代入可能であり、代入したあとでの変更は不可能になります。
要するにimmutableオブジェクトです。
readonly class Foo{
public function __construct(
public int $bar
) {}
}
$foo = new Foo(1); // OK
$foo->bar = 2; // NG
readonlyを指定したクラスは、静的プロパティは設定できず、動的プロパティも使用できず、型指定は必須であり、一度設定した値を書き替えることはできないと、できることが非常に限定されるようになります。
そのぶん何処かでうっかり値を書き替えられるなどの事故を防ぐことができ、堅固で安全性の高いインスタンスの運用が可能になります。
未定義プロパティへのアクセスは最終的にPHP9.0で禁止されるのですが、readonlyクラスはそれのほぼ上位互換みたいなものです。