Kodokon kodokon.com

モダンなオブジェクト指向:クラス、プロパティプロモーション、可視性

PHP 8らしいクラスを書きましょう。プロパティプロモーション、readonly、名前付きコンストラクタ、そしてイミュータブルな値オブジェクトを学びます。

9 分 · 3 問

このレッスンを Kodokon で開く

あなたはもう長いことクラスを書いてきましたが、PHP 8はいくつかの習慣を過去のものにしました。もっとも目に見える変化が、コンストラクタでのプロパティプロモーションです。コンストラクタの引数の前に可視性キーワード(privateprotectedpublic)を付けるだけで、プロパティの作成とその代入が一度に行われます。旧来のスタイルにあった三つの繰り返し(宣言、引数、代入)が、ひとつにまとまるのです。

PHP
<?php

declare(strict_types=1);

class LegacyProduct
{
    private string $name;
    private int $priceCents;

    public function __construct(
        string $name,
        int $priceCents,
    ) {
        $this->name = $name;
        $this->priceCents = $priceCents;
    }
}

final class Product
{
    public function __construct(
        private string $name,
        private int $priceCents,
    ) {
    }

    public function label(): string
    {
        return sprintf(
            '%s (%.2f EUR)',
            $this->name,
            $this->priceCents / 100,
        );
    }
}

$product = new Product('Keyboard', 4990);
echo $product->label();
ビフォー/アフター:プロモーションは定型的な代入コードをすべて取り除く

可視性は、カプセル化のための第一の道具であり続けます。デフォルトはprivate、子クラスがアクセスを必要とするときだけprotectedpublicは最後の手段です。PHP 8.1以降、readonlyはより強い保証を加えます。プロパティは、それを宣言したクラスのスコープの中から、一度だけしか書き込めません。プロモーションと組み合わせれば、わずか数行でイミュータブルな値オブジェクトが作れます。あらゆる「変更」が新しいインスタンスを返すので、共有された副作用がなくなります。

PHP
<?php

declare(strict_types=1);

final class Money
{
    public function __construct(
        public readonly int $amount,
        public readonly string $currency,
    ) {
    }

    public function add(Money $other): self
    {
        if ($other->currency !== $this->currency) {
            throw new InvalidArgumentException(
                'Currency mismatch'
            );
        }

        return new self(
            $this->amount + $other->amount,
            $this->currency,
        );
    }
}

$total = (new Money(500, 'EUR'))
    ->add(new Money(250, 'EUR'));

echo $total->amount;
イミュータブルな値オブジェクト:add()は新しいインスタンスを返す

メソッドの側では、経験を積んだ開発者のあいだで二つのパターンが主流です。名前付きコンストラクタでは、privateなコンストラクタと静的ファクトリを組み合わせることで、それぞれの意図がはっきりし、一貫性のないインスタンス化を禁じられます。フルーエントインターフェースでは、$this(ミュータブル)またはnew self(...)(イミュータブル)を返すことで、呼び出しを連鎖させられます。トレードオフはよく知られています。イミュータブル版のほうが安全ですが、呼び出しごとにオブジェクトを一つ確保します。ほとんどの場面では無視できるものの、ホットループの中では測定できるほどになります。

PHP
<?php

declare(strict_types=1);

final class User
{
    private function __construct(
        public readonly string $email,
        public readonly string $role,
    ) {
    }

    public static function admin(string $email): self
    {
        return new self($email, 'admin');
    }

    public static function member(string $email): self
    {
        return new self($email, 'member');
    }
}

$user = User::admin('lea@example.com');
echo $user->role;
privateなコンストラクタ+静的ファクトリ:正当な状態しか構築できない

理解度チェック

このレッスンの要点をしっかり覚えているか確認しましょう。

  1. PHP 8のコンストラクタで、プロパティプロモーションは何をしますか?
    • すべてのプロパティを自動的にpublicにする
    • プロパティを宣言し、引数からその値を代入することを、一度に行う
    • すべてのプロパティに対してアクセサ(ゲッター)を生成する
    • プロパティをクラス定数に変換する
  2. readonlyプロパティは、いつ書き込めますか?
    • 決して書き込めない。宣言のときにデフォルト値を受け取らなければならない
    • 宣言したクラスのスコープの中から、ちょうど一度だけ
    • 書き込みがクラス自身から行われる限り、いつでも
  3. クラスとその子クラスだけが使うべきプロパティには、どの可視性を選ぶべきですか?
    • public
    • protected
    • private