第10回: イミュータブルオブジェクト — 「変えられない」ことが安全性を生む

章: 第1章: オブジェクト指向の深化

値を変更したら、別の場所でも変わっていた

$price->amount = 2000; と書いたら、全く別の計算処理でも価格が変わっていた——。オブジェクトの状態が外から自由に変更できると、こうした意図しない副作用が起きます。

イミュータブル(Immutable = 不変)オブジェクトは「一度作ったら変更できない」ことを設計で保証します。副作用のない安全なコードが書けるようになります。

イミュータブルオブジェクトの実装


<?php
readonly class Money {
    public function __construct(
        public int $amount,
        public string $currency,
    ) {}
    public function add(Money $other): self {
        return new self($this->amount + $other->amount, $this->currency);
    }
}
$price = new Money(1000, 'JPY');
$total = $price->add(new Money(500, 'JPY'));
echo $total->amount; // 1500
// $priceは1000のまま——変更されていない
echo $price->amount; // 1000

add()$this を変更せず、新しい Money インスタンスを返すのがポイントです。

ミュータブル vs イミュータブル

観点 ミュータブル(可変) イミュータブル(不変)
状態変更 外部からいつでも変更可 コンストラクタ以降は変更不可
副作用 意図しない変更が起きうる 呼び出し元が元の値を保持
スレッド安全性 競合状態が発生しうる 安全に共有できる
デバッグのしやすさ 値がどこで変わったか追いにくい 値は不変なので追いやすい

チェックポイント: PHP 8.1の readonly プロパティ、PHP 8.2の readonly class を使うとイミュータブルオブジェクトを宣言的に実装できます。

withパターン:一部だけ変えた新しいインスタンスを返す


<?php
readonly class UserProfile {
    public function __construct(
        public string $name,
        public string $email,
    ) {}

    public function withName(string $name): self {
        return new self($name, $this->email);
    }
}

$profile = new UserProfile('田中', 'tanaka@example.com');
$updated = $profile->withName('山田'); // 元のprofileは変わらない

チェックポイント: 「変更」ではなく「新しい値を作る」という発想の転換がイミュータブル設計の核心です。

まとめ & 次のステップ

  • イミュータブルオブジェクトは作成後に状態を変更できないことで副作用を排除する
  • readonly class を使えば PHP 8.2以降で宣言的に実装できる
  • 変更が必要なときは「新しいインスタンスを返す」withパターンで対応する

次回から第2章: データベース応用に入ります。まずはPDOトランザクションを学び、「複数のDB処理をまとめて成功か失敗にする」仕組みを理解します。

Related Articles