PHPで名前付き引数を使い、Scalaのcopyメソッドを模倣する
はじめに
オブジェクトをimmutableにしたい時、phpで書くならばreturn new self (...)
みたいにして「内部プロパティは変更せず、新しいオブジェクトを返す」という場面があると思います。
class Room
{
public function __construct(private RoomId $roomId, private Status $status, private ReservationName $name)
{
// 不変条件
}
public function reserve(): self
{
// 制約
return new self($this->roomId, $status->reserve(), $this->name);
}
}
$room = new Room(/* 引数 */);
$reservedRoom = $room->reserve();
この時new self(...)
の引数には、変更したいものはstatus
だけなのに、引数全てを渡す必要があります。こういうメソッドが増えてくると非常に冗長です。
一方で、Scalaにはcopyメソッドというものがあります。僕はPHPerなのでサンプルコードが書けないのですが、copy(status = 変更後の値)
の様に書くと、指定したプロパティだけ変更したcopyを作る事ができるそうです。
詳細の説明はドキュメントを参照してください。
PHPでも同じ様な事ができれば、先述の様なコードが非常にスッキリしそうです。
「名前付き引数」とは
php8から導入された機能です。
既にある、位置を指定した引数を拡張する形で、PHP 8.0.0 から名前付き引数が導入されました。 名前付き引数は、位置ではなく、名前ベースで引数を渡すことを可能にします。 これによって、引数の意味が自己文書化(self-documenting)され、 引数を任意の順番で渡せるようになり、任意のデフォルト値を持つ引数をスキップできるようになります。
出典: PHP: 関数の引数 - Manual > 名前付き引数
この「名前付き引数」と「デフォルト引数値」と「null合体演算子」を組み合わせると、Salaのcopyメソッドを模倣する事ができます。
どうやるか
Roomクラスを以下の様に変更します。
class Room
{
public function __construct(private RoomId $roomId, private Status $status, private ReservationName $name)
{
// 不変条件
}
public function reserve(): self
{
// 制約
// copy呼び出すよう変更↓
return $this->copy(status: $this->status->reserve());
}
// 追加
private function copy(?RoomId $roomId = null, ?Status $status = null, ?ReservationName $name = null): self
{
return new self(
$roomId ?? $this->roomId,
$status ?? $this->status,
$name ?? $this->name
);
}
}
新しいインスタンスを作成したい場合は変更したい値だけ「名前付き引数」に指定して渡せば、他の引数は元のプロパティが使用されます。
注意点として、このcopyメソッドをpublic
にしてしまうと、各メソッドで表現したい制約(=ビジネスルール)を無視したインスタンスが作られてしまう可能性があるので、private
にしておくことが望ましそうです。
Discussion