【PHP8.x】__cloneメソッドの使い方
__cloneメソッドの使い方について、初心者にもわかりやすく解説します。
基本的な使い方
__cloneメソッドは、PHPの標準ライブラリであるExceptionクラスに属し、オブジェクトの複製(クローン)時に実行される特殊なメソッドです。
通常、PHPではオブジェクトを複製する際にcloneキーワードを使用します。このとき、複製元のオブジェクトに__cloneメソッドが定義されていれば、複製後に自動的に呼び出され、複製後のオブジェクトに対する追加の初期化処理などを記述できます。
しかし、Exceptionクラスの__cloneメソッドは、その設計思想と目的から特殊な挙動を示します。Exceptionオブジェクトは、プログラム実行中に発生した特定のエラー状況やスタックトレースなどの情報を含んでおり、これらはその発生時の文脈と密接に結びついています。Exceptionオブジェクトを複製しようとすると、これらの情報が元の例外発生時の状態を正確に引き継げない可能性があり、デバッグの混乱や予期せぬ挙動につながる恐れがあります。
そのため、Exceptionクラスでは、オブジェクトの不変性と一意性を保ち、例外処理の信頼性を確保するために、__cloneメソッドをprivateとして定義しています。外部からExceptionオブジェクトをcloneしようとすると、このprivateな__cloneメソッドが呼び出されようとし、結果としてErrorがスローされます。これは、Exceptionオブジェクトが複製されるべきではない、というPHPの設計思想を反映したものであり、開発者が意図せず例外の状態を曖昧にしてしまうことを防ぐための重要な仕組みです。
構文(syntax)
1final public function __clone(): void 2{}
引数(parameters)
引数なし
引数はありません
戻り値(return)
void
このメソッドは、例外オブジェクトのコピーを作成するために使用されます。このメソッドには戻り値はありません。
サンプルコード
PHP __cloneでディープコピーする
1<?php 2 3/** 4 * サブオブジェクトクラス 5 * ディープコピーの例で参照されるオブジェクトとして使用します。 6 */ 7class SubObject 8{ 9 public int $id; 10 public string $name; 11 12 public function __construct(int $id, string $name) 13 { 14 $this->id = $id; 15 $this->name = $name; 16 } 17 18 public function __toString(): string 19 { 20 return "SubObject[ID: {$this->id}, Name: {$this->name}]"; 21 } 22} 23 24/** 25 * メインオブジェクトクラス 26 * 参照型のプロパティ(SubObject)を持ち、__clone メソッドでディープコピーを実装する例です。 27 * 28 * PHPの組み込みクラスであるExceptionには、内部的に__cloneメソッドが存在しますが、 29 * 通常、ユーザーがExceptionオブジェクトをクローンしたり、その__cloneメソッドをオーバーライドしたりすることはありません。 30 * このサンプルコードは、__cloneマジックメソッドが、オブジェクトの「ディープコピー」を 31 * 実現するためにどのように使用されるかを示す汎用的な例です。 32 */ 33class MyClass 34{ 35 public int $value; 36 public SubObject $subObject; 37 38 public function __construct(int $value, SubObject $subObject) 39 { 40 $this->value = $value; 41 $this->subObject = $subObject; 42 } 43 44 /** 45 * オブジェクトがクローンされた直後に呼び出されるマジックメソッドです。 46 * PHPの標準的なクローンは「シャローコピー」(浅いコピー)であり、 47 * 参照型のプロパティは元のオブジェクトとクローンされたオブジェクトで同じインスタンスを共有します。 48 * このメソッド内で参照型のプロパティも個別にクローンすることで、 49 * 元のオブジェクトと完全に独立した「ディープコピー」を作成できます。 50 */ 51 public function __clone() 52 { 53 // subObject プロパティがオブジェクトであるため、 54 // このプロパティも別途クローンして新しいインスタンスを割り当てます。 55 // これにより、元のオブジェクトとクローンされたオブジェクトが 56 // 異なる SubObject インスタンスを参照するようになり、ディープコピーが実現されます。 57 $this->subObject = clone $this->subObject; 58 } 59 60 public function __toString(): string 61 { 62 return "MyClass[Value: {$this->value}, SubObject: {$this->subObject}]"; 63 } 64} 65 66// --- サンプル実行 --- 67 68// 元のオブジェクトを作成 69$originalSub = new SubObject(1, "Original Sub"); 70$original = new MyClass(100, $originalSub); 71 72echo "--- 初期状態 ---\n"; 73echo "元のオブジェクト: " . $original . "\n"; 74// SubObjectのインスタンスが同じであることをハッシュ値で確認 75echo "元のSubObjectのハッシュ: " . spl_object_hash($original->subObject) . "\n\n"; 76 77// オブジェクトをクローン(__clone メソッドが実行され、ディープコピーされる) 78$cloned = clone $original; 79 80echo "--- クローン後 ---\n"; 81echo "クローンされたオブジェクト: " . $cloned . "\n"; 82// __cloneによってSubObjectも新しくクローンされたため、異なるハッシュ値になることを確認 83echo "クローンされたSubObjectのハッシュ: " . spl_object_hash($cloned->subObject) . "\n\n"; 84 85// 元のオブジェクトとクローンされたオブジェクトにそれぞれ変更を加えて、 86// ディープコピーされたことを確認します。 87$original->value = 200; 88$original->subObject->name = "Modified Original Sub"; 89$original->subObject->id = 2; 90 91$cloned->value = 300; 92$cloned->subObject->name = "Modified Cloned Sub"; 93$cloned->subObject->id = 3; 94 95echo "--- 変更後 ---\n"; 96echo "変更後の元のオブジェクト: " . $original . "\n"; 97echo "変更後のクローンされたオブジェクト: " . $cloned . "\n"; 98 99// 出力から、元のオブジェクトとクローンされたオブジェクトの SubObject が 100// それぞれ独立して変更されていることが確認できます。 101// これは、__clone メソッドによって SubObject も個別にクローンされ、 102// 完全に独立したディープコピーが実現されたためです。
__cloneメソッドは、PHPでオブジェクトをcloneキーワードを使って複製した直後に自動的に呼び出される特殊な(マジック)メソッドです。
PHPにおける標準のオブジェクト複製は「シャローコピー」(浅いコピー)であり、オブジェクト内の参照型のプロパティ(別のオブジェクトなど)は、元のオブジェクトとクローンされたオブジェクトとで同じインスタンスを共有します。このため、片方のオブジェクトで参照型プロパティを変更すると、もう一方にも影響が及びます。
__cloneメソッドをクラス内で定義することで、この標準の挙動をカスタマイズし、より深いコピーを実現できます。特に、サンプルコードのようにメソッド内で参照型プロパティを改めてcloneすることで、元のオブジェクトと完全に独立した「ディープコピー」(深いコピー)を作成することが可能です。これにより、クローン後の各オブジェクトが持つ参照型プロパティもそれぞれ独立したインスタンスとなるため、片方の変更がもう一方に影響することはありません。
このメソッドは引数を受け取らず、値を返すこともありません(void)。サンプルコードでは、MyClassが持つSubObjectプロパティを__cloneメソッド内で個別にクローンしており、その結果、元のMyClassオブジェクトとクローンされたMyClassオブジェクトが独立したSubObjectを持つディープコピーが実現されていることを確認できます。
なお、ご提示のリファレンスにあるExceptionクラスの__cloneはPHPの内部で利用されるものであり、通常、開発者がExceptionオブジェクトを直接クローンしたり、その__cloneメソッドをオーバーライドしたりすることはありません。このサンプルコードは、一般的なPHPオブジェクトにおいて__cloneがディープコピーのためにどのように機能するかを説明する汎用的な例です。
PHPのclone演算子はデフォルトで「シャローコピー」(浅いコピー)となり、参照型のプロパティは元のオブジェクトとクローン先で同じものを共有します。完全に独立した「ディープコピー」(深いコピー)が必要な場合は、__cloneマジックメソッドを実装し、参照型プロパティも個別にcloneしてください。本サンプルは一般的なディープコピーの実装例です。なお、PHP内部のExceptionクラスの__cloneは、通常ユーザーが直接利用・オーバーライドするものではありません。__cloneは引数も戻り値も持ちません。
PHP Exception __clone でカスタムする
1<?php 2 3/** 4 * カスタム例外クラスを定義し、オブジェクトのクローン時に動作する 5 * `__clone` マジックメソッドの例を示します。 6 * Exception クラス自体にはユーザーが `__clone` をオーバーライドする機会はありませんが、 7 * その子クラスではクローン時の挙動をカスタマイズできます。 8 */ 9class MyCustomException extends Exception 10{ 11 private string $context; 12 13 /** 14 * コンストラクタ 15 * 16 * @param string $message 例外メッセージ 17 * @param int $code 例外コード 18 * @param Throwable|null $previous 前の例外 19 * @param string $context 例外が発生したコンテキスト情報 20 */ 21 public function __construct(string $message, int $code = 0, ?Throwable $previous = null, string $context = 'default') 22 { 23 parent::__construct($message, $code, $previous); 24 $this->context = $context; 25 } 26 27 /** 28 * 例外のコンテキストを取得します。 29 * 30 * @return string 31 */ 32 public function getContext(): string 33 { 34 return $this->context; 35 } 36 37 /** 38 * オブジェクトが `clone` キーワードによって複製されたときに自動的に呼び出されるマジックメソッドです。 39 * 引数はなく、戻り値は `void` です。 40 * このメソッドが呼び出される前に、新しいオブジェクトには元のオブジェクトのプロパティがすべてコピーされています。 41 * ここでは、クローンされたオブジェクトのカスタムプロパティを初期化または変更する例を示します。 42 * 43 * PHPの内部的な `Exception::__clone()` も同様に引数なしで `void` を返します。 44 */ 45 public function __clone() 46 { 47 // 親クラス(Exception)のプロパティは自動的にシャローコピーされます。 48 // `$this->message` や `$this->code` などは元のオブジェクトと同じ値を持っています。 49 50 // ここでは、カスタムプロパティである `$context` をクローン用に変更します。 51 // これは、クローンされた例外が元の例外とは異なる用途やコンテキストで 52 // 扱われる場合に、その状態を調整するのに役立ちます。 53 $this->context = 'cloned_context'; 54 55 // 必要であれば、他のプロパティも変更できます。 56 // 例: $this->message = "Cloned: " . $this->getMessage(); 57 } 58} 59 60// ---------------------------------------------------------------------------------------------------- 61// サンプルコードの実行部分 62// ---------------------------------------------------------------------------------------------------- 63 64// 1. 元の例外オブジェクトを作成します。 65$originalException = new MyCustomException("オリジナルエラー発生", 500, null, '処理A'); 66 67echo "--- 元の例外オブジェクト ---" . PHP_EOL; 68echo "メッセージ: " . $originalException->getMessage() . PHP_EOL; 69echo "コード: " . $originalException->getCode() . PHP_EOL; 70echo "コンテキスト: " . $originalException->getContext() . PHP_EOL; 71echo "オブジェクトID: " . spl_object_id($originalException) . PHP_EOL; // オブジェクトのユニークID 72echo PHP_EOL; 73 74// 2. `clone` キーワードを使って元の例外オブジェクトを複製(クローン)します。 75// この操作により、`MyCustomException` クラスで定義した `__clone()` メソッドが 76// 自動的に呼び出されます。 77$clonedException = clone $originalException; 78 79echo "--- クローンされた例外オブジェクト ---" . PHP_EOL; 80echo "メッセージ: " . $clonedException->getMessage() . PHP_EOL; 81echo "コード: " . $clonedException->getCode() . PHP_EOL; 82echo "コンテキスト: " . $clonedException->getContext() . PHP_EOL; // `__clone` で変更された値 83echo "オブジェクトID: " . spl_object_id($clonedException) . PHP_EOL; // 元とは異なるID 84echo PHP_EOL; 85 86// 3. 元のオブジェクトとクローンされたオブジェクトが異なるインスタンスであることを確認します。 87echo "元の例外とクローンされた例外は同じオブジェクトですか? " . ($originalException === $clonedException ? "はい" : "いいえ") . PHP_EOL; 88 89// 出力から、`clone` 操作によって新しいオブジェクトが生成され、 90// その際に `MyCustomException` の `__clone` メソッドが呼ばれて 91// `$context` プロパティが 'cloned_context' に変更されたことが分かります。 92// 標準の `Exception` クラスの `__clone` はPHP内部で同様に動作しますが、 93// その挙動はユーザーからは変更できません。
PHPのException::__cloneは、Exceptionオブジェクトが複製される際に内部で動作する特殊なメソッドです。Exceptionクラス自体で直接オーバーライドすることはできませんが、その子クラスでは、オブジェクト複製時の挙動をカスタマイズするために利用できます。cloneキーワードを用いてオブジェクトを複製すると、新しいオブジェクトが生成された直後に、この__cloneマジックメソッドが自動的に呼び出されます。このメソッドは引数を取らず、戻り値もありません(void)。__cloneが実行される前には、新しいオブジェクトに元のオブジェクトのすべてのプロパティがシャローコピーされています。そのため、__cloneメソッド内では、複製されたオブジェクトの特定のプロパティを再初期化したり、元のオブジェクトとは異なる状態に調整したりする処理を記述します。サンプルコードでは、MyCustomExceptionクラスが__cloneを実装し、カスタムプロパティである$contextの値を「cloned_context」に変更しています。これにより、オリジナルの例外とクローンされた例外は、メッセージやコードは同じでも、$contextの値が異なる、完全に独立した新しいオブジェクトとして扱われることを示しています。このように__cloneは、オブジェクトの複製時に特別な初期化や状態変更が必要な場合に活用できる重要な仕組みです。
PHPのExceptionクラス自体は__cloneマジックメソッドを直接オーバーライドできませんが、その子クラスでは定義することで、オブジェクトのクローン時の挙動をカスタマイズできます。このメソッドはcloneキーワードでオブジェクトが複製される際に自動的に呼び出され、引数はなく戻り値はvoidです。__cloneが実行される前に、元のオブジェクトのプロパティはすべて新しいオブジェクトにシャローコピーされています。そのため、__clone内では、参照型のプロパティのディープコピー処理や、クローン後の状態に応じたプロパティ値の変更を行うのが一般的です。サンプルコードのように、クローンされた例外オブジェクトのカスタムプロパティを初期化し直すことで、元の例外とは異なる用途で利用する際に有効活用できます。