オブジェクトのクローン徹底解説
CoddyのPHPジャーニー「オブジェクト指向プログラミング」セクションの一部 — レッスン 61/91。
このコースのこれまでのレッスンで、__clone マジックメソッドについて学びました。ここでは、オブジェクトがプロパティとして他のオブジェクトを含んでいる場合の、浅い (shallow) コピーと 深い (deep) コピーの間の重要な違いについて探っていきましょう。
デフォルトでは、PHPはクローン時にシャローコピー(浅いコピー)を行います。スカラー値は複製されますが、オブジェクトのプロパティは同じオブジェクトへの参照のままとなります:
<?php
class Address {
public function __construct(public string $city) {}
}
class Person {
public function __construct(
public string $name,
public Address $address
) {}
}
$original = new Person("Alice", new Address("Paris"));
$clone = clone $original;
$clone->name = "Bob";
$clone->address->city = "London";
echo $original->name . " - " . $original->address->city;
出力:
Alice - London$clone->name を変更してもオリジナルには影響しませんでしたが、住所の都市を変更すると影響がありました!両方のオブジェクトは同じ Address インスタンスを共有しています。
ネストされたオブジェクトもクローンされるディープコピーを作成するには、__cloneを実装します:
<?php
class Person {
public function __construct(
public string $name,
public Address $address
) {}
public function __clone(): void {
$this->address = clone $this->address;
}
}
$original = new Person("Alice", new Address("Paris"));
$clone = clone $original;
$clone->address->city = "London";
echo $original->address->city;
出力:
Parisこれで、各 Person は独自の独立した Address を持つようになります。オブジェクトに複数のネストされたオブジェクトが含まれている場合、オリジナルとコピーの間で真の独立性を実現するために、__clone 内でそれぞれをクローンする必要があります。
チャレンジ
簡単シャローコピー(浅いコピー)とディープコピー(深いコピー)の違いを実証するドキュメント管理システムを構築しましょう。作成者情報を含むドキュメントを作成し、ドキュメントをクローンした際に完全に独立したコピーが作成されるようにします。
コードを3つのファイルに分けて構成します:
Author.php— ドキュメントの作成者を表すAuthorクラスを作成します。コンストラクタのプロパティ昇格(constructor promotion)を使用して、パブリックな$name(string) とパブリックな$email(string) を定義してください。Document.php— 作成者を含むDocumentクラスを作成します。Author ファイルを読み込んでください。このクラスは以下の要件を満たす必要があります:- コンストラクタのプロパティ昇格を使用して、パブリックな
$title(string) とパブリックな$author(Author) を定義する - ディープコピーを実行するために
__clone()マジックメソッドを実装する — ドキュメントがクローンされたとき、その作成者もクローンされるようにし、クローン側の作成者への変更がオリジナルに影響しないようにする "[title] by [author name] ([author email])"を返すgetInfo()メソッドを持つ
- コンストラクタのプロパティ昇格を使用して、パブリックな
main.php— Document ファイルを読み込みます。ドキュメントのタイトル、作成者名、作成者のメールアドレスの3つの入力を受け取ります。提供された入力を使用して、
Authorを持つDocumentを作成します。ドキュメントをクローンし、クローンしたドキュメントの作成者名に" (Copy)"を追加し、タイトルの先頭に"Copy of "を付けて変更してください。次の2行を出力します:
- オリジナルのドキュメントの情報
- クローンされたドキュメントの情報
ディープコピーが正しく実装されていれば、クローンの作成者を変更した後でも、オリジナルのドキュメントの作成者は変更されないはずです。
このチャレンジは、なぜディープコピーが重要なのかを実証します。__clone() の適切な実装がない場合、両方のドキュメントが同じ Author オブジェクトを共有してしまい、一方を変更するともう一方にも影響が及んでしまいます。
チートシート
デフォルトでは、PHPはオブジェクトをクローンする際に浅いコピー(shallow copy)を実行します。スカラー値は複製されますが、オブジェクトのプロパティは同じオブジェクトへの参照のままとなります。
浅いクローンの例:
<?php
class Address {
public function __construct(public string $city) {}
}
class Person {
public function __construct(
public string $name,
public Address $address
) {}
}
$original = new Person("Alice", new Address("Paris"));
$clone = clone $original;
$clone->name = "Bob";
$clone->address->city = "London";
echo $original->name . " - " . $original->address->city;
// 出力: Alice - London
$clone->name を変更してもオリジナルには影響しませんが、ネストされた address オブジェクトを変更すると影響します。これは、両方のオブジェクトが同じ Address インスタンスを共有しているためです。
ネストされたオブジェクトもクローンされる深いコピー(deep copy)を作成するには、__clone マジックメソッドを実装します:
<?php
class Person {
public function __construct(
public string $name,
public Address $address
) {}
public function __clone(): void {
$this->address = clone $this->address;
}
}
$original = new Person("Alice", new Address("Paris"));
$clone = clone $original;
$clone->address->city = "London";
echo $original->address->city;
// 出力: Paris
オブジェクトに複数のネストされたオブジェクトが含まれている場合、オリジナルとコピーの間で真の独立性を実現するために、__clone 内でそれぞれをクローンする必要があります。
自分で試してみよう
<?php
require_once 'Document.php';
// 入力を読み込む
$title = trim(fgets(STDIN));
$authorName = trim(fgets(STDIN));
$authorEmail = trim(fgets(STDIN));
// TODO: 提供された名前とメールアドレスを使用してAuthorを作成する
// TODO: 提供されたタイトルとAuthorを使用してDocumentを作成する
// TODO: ドキュメントを複製する
// TODO: 複製されたドキュメントの著者名に " (Copy)" を追加して修正する
// TODO: 複製されたドキュメントのタイトルの先頭に "Copy of " を追加して修正する
// TODO: オリジナルのドキュメントの情報を出力する
// TODO: 複製されたドキュメントの情報を出力する
?>このレッスンには短いクイズがあります。レッスンを始めて解答し、進捗を記録しましょう。
オブジェクト指向プログラミングのすべてのレッスン
10オブジェクト指向の高度な概念
コンポジション vs 継承依存性の注入 (Dependency Injection)匿名クラスEnum (PHP 8.1)ファイバー (PHP 8.1)オブジェクトのクローン徹底解説ジェネレータとイテレータ