PHP 8.3 では、オブジェクトのクローン作成中に読み取り専用プロパティ値を上書きできる機能が追加されました。 ただし、誤解しないでください。オブジェクトのクローンを作成したり、その読み取り専用の値をどこからでも上書きしたりすることはできません。 この機能は、非常に特殊な (ただし重要な) エッジケースのみに対応します。
見てみましょう!
理想的な世界では、ユーザー定義の値のセットに基づいて、読み取り専用プロパティを持つクラスのクローンを作成できるでしょう。 いわゆる clone with
構文 (存在しません):
readonly class Post
{
public function __construct(
public string $title,
public string $author,
public DateTime $createdAt,
) {}
}
$post = new Post(
title: 'Hello World',
);
$updatedPost = clone $post with {
title: 'Another One!',
};
現在の RFC のタイトルを読むと、「読み取り専用プロパティはクローン作成中に再初期化できる」というようになります。次のようなことを考えるかもしれません。 clone with
これが可能になりました。 しかし…そうではありません。 RFC では、マジックで読み取り専用の値を上書きするという 1 つの特定の操作のみが許可されています。 __clone
方法:
readonly class Post
{
public function __construct(
public string $title,
public string $author,
public DateTime $createdAt,
) {}
public function __clone()
{
$this->createdAt = new DateTime();
}
}
これは役に立ちますか? そうです! ネストされたオブジェクトを使用してオブジェクトのクローンを作成したいとします。別名「ディープ クローン」を作成します。 この RFC では、それらのネストされたオブジェクトのクローンを作成し、それらが読み取り専用プロパティである場合でも、新しく作成したクローンで上書きすることができます。
readonly class Post
{
public function __clone()
{
$this->createdAt = clone $this->createdAt;
}
}
この RFC がなければ、クローンを作成できます。 $post
、ただし、オリジナルへの参照は保持されます。 $createdAt
物体。 そのオブジェクトに変更を加えるとします (これは可能です。 readonly
割り当てられたプロパティの変更を防ぐだけであり、内部値の変更は防ぎません):
$post = new Post();
$otherPost = clone $post;
$post->createdAt->add(new DateInterval('P1D'));
$otherPost->createdAt === $post->createdAt;
そうすれば、最終的には $createdAt
両方のオブジェクトの日付が変更されました。
この RFC のおかげで、これらのプロパティが読み取り専用の場合でも、すべてのネストされたプロパティも複製された実際のクローンを作成できます。
$post = new Post();
$otherPost = clone $post;
$post->createdAt->add(new DateInterval('P1D'));
$otherPost->createdAt === $post->createdAt;
#個人的なメモとして
PHP 8.3 で読み取り専用プロパティのディープ クローン作成が可能になったのは良いことだと思います。 しかし、私は 持っている この実装については複雑な気持ちです。 ちょっと想像してみてください clone with
PHP に存在する場合、上記のすべては不要です。 ご覧ください:
$updatedPost = clone $post with {
createdAt: clone $post->createdAt,
};
さあ、想像してみてください clone with
PHP 8.4 で追加される — もちろん、純粋な憶測です。 これは、PHP で同じことを行うには 2 つの方法があることを意味します。 あなたはどうか知りませんが、私は言語やフレームワークが同じことを行う複数の方法を提供するのが好きではありません。 私の知る限り、これはせいぜい次善の言語設計です。
もちろんこれは次のことを前提としています clone with
マッピング ロジックを手動で実装する必要がなく、値をプロパティに自動的にマッピングできます。 __clone
。 私もそれを想定しています clone with
プロパティの可視性を扱うことができます。外部からはパブリック プロパティのみを変更できますが、クラス内で使用される場合は保護されたプロパティとプライベート プロパティを変更できます。
少し前に、PHP の内部がどのように分裂しているようで、あるグループが 1 つの解決策を考え出す一方で、別のグループが別のアプローチをとろうとしていることについて書きました。 私にとって、これは委員会による設計の明らかな欠点です。
完全な開示 – RFC に記載 clone with
将来の範囲として:
将来に向けて想定されているアイデアはいずれも、この RFC の提案と衝突しません。 したがって、それらは後で個別に検討することができます。
しかし、少なくとも次のように仮定すると、私はこの意見に反対する傾向があります。 clone with
ユーザーランドコードを実装しなくても機能します。 この現在の RFC の傾向に従えば、誰かが次のように追加することを提案するのが想像できます。 clone with
データを渡す方法としてのみ __clone
そしてユーザー自身に対処してもらいます。
readonly class Post
{
public function __clone(...$properties)
{
foreach ($properties as $name => $value) {
$this->$name = $value;
}
}
}
しかし、そうでないことを心から願っています clone with
実装される。 を追加する必要があるため、 __clone
すべての読み取り専用クラスに実装します。
したがって、最良のケースを想定すると、 clone with
追加され、値を自動的にマッピングできる場所。 その場合、この現在の RFC の機能は無効になり、同じことを行う 2 つの方法ができるようになります。 コーディング時にさらに別の決定を迫られるため、ユーザーは混乱するでしょう。 PHP はこのままでも十分にわかりにくくなっていると思うので、その変化を見てみたいと思っています。
一方で、私はこの RFC 自体に反対しているわけではないことにも言及しておきたいと思います。 ニコラとマテは、現実の問題に対してしっかりとした解決策を考え出すという素晴らしい仕事をしたと思います。
PS: 実装するだけで済むため、誰かが現在の RFC について議論したい場合に備えて __clone
オブジェクトごとに 1 回だけ実行すれば、呼び出しサイトではもう心配する必要はありません。 これらの例単独では、非常に重要な詳細が 1 つ欠けています。それは、単純なサンプルではディープ コピーは発生しないということです。 clone
電話。 ほとんどの場合、ディープコピーのようなパッケージが使用されるため、潜在的なオーバーヘッドが発生します。 clone with
example はこれらのパッケージによってすでに処理されているため、エンドユーザーを煩わせることはありません。