つい最近の開発でハマったポイントだったので、こちらに記載しておきます。
とある変数にオブジェクトを格納した後、その変数を他の変数に代入したい。そんなときがあると思います。
しかしただ代入するだけだと、下記のような挙動をし、思っていたのとは違う結果を生みます。
<?php
class classA {
public $name = 'classA';
public $info = '1つ目のクラス';
}
$a = new classA();
$b = $a;
$b->info .= ' 変更をしました';
echo $a->name . ' ' . $a->info . PHP_EOL; // classA 1つ目のクラス 変更をしました
echo $b->name . ' ' . $b->info . PHP_EOL; // classA 1つ目のクラス 変更をしました
(かなり細かい話になるので、ここは飛ばしても良いかもしれません。)
この現象の理由は、$aに入っているのは「オブジェクト」ではなく、「オブジェクトが入っているメモリへのアドレス」だからです。
$bにメモリへのアドレスが代入され、$b->infoの変更は、アドレス先にあるオブジェクトを変更するという意味になります。
$aと$bが指しているオブジェクトは同じものになるので、$bの変更が$aにも反映されているのです。
これを防ぐには、下記の「clone」を用います。
<?php
class classA {
public $name = 'classA';
public $info = '1つ目のクラス';
}
$a = new classA();
$b = clone $a; // ココがポイント!!!
$b->info .= ' 変更をしました';
echo $a->name . ' ' . $a->info . PHP_EOL; // classA 1つ目のクラス
echo $b->name . ' ' . $b->info . PHP_EOL; // classA 1つ目のクラス 変更をしました
さて、ここから今回の本題になります。
下記のコードは、先ほどのクラスにオブジェクトのプロパティを追加したものです。
<?php
class ParentClass {
public $name = 'ParentClass';
public $info = '親クラス';
public ChildClass $child;
public function __construct()
{
$this->child = new ChildClass();
}
}
class ChildClass {
public $name = 'ChildClass';
public $info = '子クラス';
}
$a = new ParentClass();
$b = clone $a;
$b->info .= ' 変更をしました';
$b->child->info .= ' 変更をしました';
echo $a->name . ' ' . $a->info . ' ' . $a->child->name . ' ' . $a->child->info . PHP_EOL;
// ParentClass 親クラス ChildClass 子クラス 変更をしました
echo $b->name . ' ' . $b->info . ' ' . $b->child->name . ' ' . $b->child->info . PHP_EOL;
// ParentClass 親クラス 変更をしました ChildClass 子クラス 変更をしました
clone後、$bのParentClassとChildClassに変更を加えています。
すると、なんとParentClassの変更は$aに反映していないのに、ChildClassの変更は$bに反映されています。
ちなみに、var_dumpの結果は以下のようになります。(上:$a、下:$b)
ParentClassは$aと$bで違っていますが、中身のChildClassは同じになっています。
ChildClassもcloneしたい場合、下記のようにします。
<?php
class ParentClass {
public $name = 'ParentClass';
public $info = '親クラス';
public ChildClass $child;
public function __construct()
{
$this->child = new ChildClass();
}
// ココがポイント!!!
public function __clone()
{
$this->child = clone $this->child;
}
}
class ChildClass {
public $name = 'ChildClass';
public $info = '子クラス';
}
$a = new ParentClass();
$b = clone $a;
$b->info .= ' 変更をしました';
$b->child->info .= ' 変更をしました';
echo $a->name . ' ' . $a->info . ' ' . $a->child->name . ' ' . $a->child->info . PHP_EOL;
// ParentClass 親クラス ChildClass 子クラス
echo $b->name . ' ' . $b->info . ' ' . $b->child->name . ' ' . $b->child->info . PHP_EOL;
// ParentClass 親クラス 変更をしました ChildClass 子クラス 変更をしました
ParentClassに__clone()というメソッドを追加します。これにより、ParentClassのインスタンスがcloneされるときに、$childもクローンしてねという指示を出すことができます。
var_dumpの結果も、以下の通りです。