プロパティにオブジェクトを持つオブジェクトをクローンする

つい最近の開発でハマったポイントだったので、こちらに記載しておきます。

とある変数にオブジェクトを格納した後、その変数を他の変数に代入したい。そんなときがあると思います。
しかしただ代入するだけだと、下記のような挙動をし、思っていたのとは違う結果を生みます。

<?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は同じになっています。

object(~~Class)の後の、[]に注目!

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の結果も、以下の通りです。

カテゴリーPHP

コメントを残す

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です