PHPでは、アプリケーションの規模が大きくなるほど、意図しないデータ変更によるバグを防ぐためにオブジェクトの状態管理が重要になります。そのために活用できる機能のひとつがPHP 8.1から導入されたreadonlyプロパティです。
readonlyプロパティを利用すると、一度設定したプロパティの値を後から変更できないよう制限できます。この記事では、readonlyプロパティがどのようにオブジェクトの不変性(イミュータブル)を実現するのか、具体例を交えながら解説します。
PHPのreadonlyプロパティとは何か
readonlyプロパティとは、PHP 8.1以降で利用できる、一度だけ値を代入できるプロパティです。通常のプロパティは、初期値を設定した後でも何度でも変更できますが、readonlyを指定すると再代入が禁止されます。
例えば、ユーザーIDや注文番号、作成日時など、一度決定した後に変更されるべきではないデータを管理する場合にreadonlyが役立ちます。
以下のようなクラスを考えてみます。
<?php
class User
{
public readonly int $id;
public function __construct(int $id)
{
$this->id = $id;
}
}
$user = new User(100);
$user->id = 200; // エラーになる
この例では、コンストラクタで設定したidプロパティは、その後変更できません。これにより、オブジェクトの重要な状態が意図せず書き換えられることを防げます。
readonlyプロパティがオブジェクトの不変性を保証する仕組み
オブジェクトの不変性とは、生成された後に内部状態が変化しない性質のことです。不変なオブジェクトは、どこから参照されても同じ状態を維持するため、プログラムの予測がしやすくなります。
readonlyプロパティは、プロパティへの再代入を禁止することで、不変性を実現するための基本的な仕組みを提供します。
例えば、住所情報を保持するValue Objectを作成する場合、都道府県や郵便番号が途中で変更されるとデータの整合性が崩れる可能性があります。
<?php
class Address
{
public function __construct(
public readonly string $postalCode,
public readonly string $prefecture
) {}
}
$address = new Address('100-0001', '東京都');
このAddressオブジェクトは作成後にpostalCodeやprefectureを変更できません。そのため、住所データを安全に扱うことができます。
readonlyだけでは完全な不変オブジェクトにならない理由
readonlyプロパティは便利な機能ですが、readonlyを付けるだけで完全な不変オブジェクトになるわけではありません。
注意点として、readonlyが保証するのはプロパティへの再代入禁止です。プロパティがオブジェクトや配列などの複雑な型を保持している場合、その内部状態までは自動的に保護されません。
例えば、次のようなコードでは問題が発生する可能性があります。
<?php
class UserProfile
{
public function __construct(
public readonly array $settings
) {}
}
$profile = new UserProfile(['theme' => 'dark']);
$profile->settings['theme'] = 'light';
この場合、settingsプロパティ自体への再代入は禁止されていますが、配列内部の値は変更できるため、完全な不変性は保証されません。
本当の意味で不変なオブジェクトを作る場合は、内部データも変更できない設計にする必要があります。
readonlyプロパティを使うメリット
readonlyプロパティを利用すると、プログラムの安全性や保守性を高めることができます。
- 意図しないデータ変更を防止できる
- オブジェクトの状態を理解しやすくなる
- デバッグ時に原因を特定しやすくなる
- Value ObjectやDTOの設計に適している
例えば、決済処理で利用する金額情報を通常のプロパティで管理すると、処理途中で金額が変更されるリスクがあります。readonlyを使えば、生成時に決定した金額を保持できます。
また、チーム開発では「この値は変更してはいけない」という意図をコード上で明確に表現できるため、他の開発者が安全にコードを扱えます。
readonlyプロパティを利用するときの注意点
readonlyプロパティにはいくつか制約があります。まず、readonlyプロパティは一度だけ代入可能で、初期化後の変更はできません。
また、初期化はクラス内部から行う必要があります。コンストラクタで値を設定する設計が一般的ですが、必要に応じて初期化専用の処理を用意することもあります。
さらに、readonlyプロパティを使う場合は「本当に変更不要なデータなのか」を考えることが重要です。後から変更される可能性があるデータに無理にreadonlyを付けると、設計変更時に扱いづらくなる場合があります。
まとめ
PHPのreadonlyプロパティは、プロパティへの再代入を禁止することで、オブジェクトの状態を安全に管理するための機能です。
特にユーザー情報、設定値、注文データ、Value Objectなど、一度作成した後に変更されるべきではないデータを扱う場合に有効です。
ただし、readonlyはオブジェクト全体の完全な不変性を自動的に保証するものではありません。内部データの設計も含めて変更できない構造を作ることで、より安全で保守性の高いPHPアプリケーションを構築できます。


コメント