PHP 8らしいクラスを書きましょう。プロパティプロモーション、readonly、名前付きコンストラクタ、そしてイミュータブルな値オブジェクトを学びます。
このレッスンを Kodokon で開くあなたはもう長いことクラスを書いてきましたが、PHP 8はいくつかの習慣を過去のものにしました。もっとも目に見える変化が、コンストラクタでのプロパティプロモーションです。コンストラクタの引数の前に可視性キーワード(private、protected、public)を付けるだけで、プロパティの作成とその代入が一度に行われます。旧来のスタイルにあった三つの繰り返し(宣言、引数、代入)が、ひとつにまとまるのです。
<?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、子クラスがアクセスを必要とするときだけprotected、publicは最後の手段です。PHP 8.1以降、readonlyはより強い保証を加えます。プロパティは、それを宣言したクラスのスコープの中から、一度だけしか書き込めません。プロモーションと組み合わせれば、わずか数行でイミュータブルな値オブジェクトが作れます。あらゆる「変更」が新しいインスタンスを返すので、共有された副作用がなくなります。
<?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;メソッドの側では、経験を積んだ開発者のあいだで二つのパターンが主流です。名前付きコンストラクタでは、privateなコンストラクタと静的ファクトリを組み合わせることで、それぞれの意図がはっきりし、一貫性のないインスタンス化を禁じられます。フルーエントインターフェースでは、$this(ミュータブル)またはnew self(...)(イミュータブル)を返すことで、呼び出しを連鎖させられます。トレードオフはよく知られています。イミュータブル版のほうが安全ですが、呼び出しごとにオブジェクトを一つ確保します。ほとんどの場面では無視できるものの、ホットループの中では測定できるほどになります。
<?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;readonlyプロパティは、いつ書き込めますか?