BeginnerEngineerBlog
中の人
中の人

【php clone】オブジェクトのcloneてこんな仕様だったのね。。

公開: 2023-09-26 01:23
更新: 2023-09-27 11:59
283
php clone
最近cloneの仕様がわかったので紹介します。

こんにちは!

中の人です。

最近このオブジェクトコピーして、コピー元はそのままにしてコピーしたオブジェクトをコネコネしたいぜって時があって、こう言う時はcloneだよね!と意気揚々とcloneしたら全然ダメで、改めて調べたらはーそうなんですねーと感じたので自分のメモとして紹介します!


オブジェクト以外


コピーというと

$a = 1;
$b = $a;
$b = 2;

var_dump($a);
var_dump($b);

こういった処理をまず考えるかもしれません。

これの実行結果は


こうなります

いい感じです。


オブジェクトの場合


class Dog
{
    public $name;

    public $category;
}

$dog = new Dog();
$dog->name = 'ビギエン';
$dog->category = '柴犬';

$dog2 = $dog;// 👈 コピーしたつもり
$dog2->name = 'ドットコム';
$dog2->category = 'プードル';

var_dump($dog);
var_dump($dog2);

先ほどと同じようにコピーすると


コピー元の情報もコピー先の情報に上書きされています。

これは、phpではオブジェクトは参照渡しの扱い(シャローコピー)になっているからだそうです。
参照渡しとは、プログラムの見た目は別々の値として扱ってるように見えるけど、内部的に同じ値を操作していることを言います。
「上書きされている」と先に書きましたが、つまりは「上書きされているように見える(同じ値を参照している)」ということです。


ふむふむ(゚∀゚ )


ダメなやり方でオブジェクトをコピー


参照渡しね!了解了解!

clone

使えばオッケーだよね!知ってる知ってる!

ということでcloneを使ってコピーします。

<?php

class Dog
{
    public $name;

    public $category;

    /**
     * @var Owner
     */
    public $owner;// 👈 不穏なプロパティあるけど一旦無視
}

class Owner// 👈 不穏なクラスあるけど一旦無視
{
    public $name;

    public $gender;
}

$dog = new Dog();
$dog->name = 'ビギエン';
$dog->category = 'しば犬';

$dog2 = clone $dog;// 👈 クローン
$dog2->name = 'ドットコム';
$dog2->category = 'チワワ';

var_dump($dog);
var_dump($dog2);

この実行結果は


おーコピーされた。よしよし

なんですが、以下の場合は

<?php

class Dog
{
    public $name;

    public $category;
    
    /**
     *
     * @var Owner
     */
    public $owner;// 👈 オプジェクトをセットするプロパティ
}

/**
 * 飼い主クラス
 */
class Owner// 👈 こいつのオブジェクトをDogクラスのownerプロパティにセットする
{
    public $name;

    public $gender;
}

$owner = new Owner();// 👈 飼い主オブジェクトを生成
$owner->name = '中の人';
$owner->gender = '男';

$dog = new Dog();
$dog->name = 'ビギエン';
$dog->category = 'しば犬';
$dog->owner = $owner;// 👈 オブジェクトをセット

$dog2 = clone $dog;// 👈 クローン
$dog2->name = 'ドットコム';
$dog2->category = 'チワワ';
$owner2 = $dog2->owner;// 👈 飼い主クラスをコピーから取得
$owner2->name = 'モヒカン';
$owner2->gender = '女';
$dog2->owner = $owner2;// 👈 コピー先にオブジェクトをセット

var_dump($dog);
var_dump($dog2);

これの結果は


nameとcategoryは値が変わっていますが、ownerはコピー元の値が上書きされています。

やりたいこととちゃうでオラァ!(゚∀゚ )

つまりcloneでもプロパティがオブジェクトの場合は参照渡しのままになっているということっぽいです。


正しいオブジェクトのコピーの仕方


以下のようにすると正しくコピーされます

<?php

class Dog
{
    public $name;

    public $category;
    
    /**
     *
     * @var Owner
     */
    public $owner;

    // 👇 以下を追加
    public function __clone()
    {
        $this->owner = clone $this->owner;
    }
}

/**
 * 飼い主クラス
 */
class Owner
{
    public $name;

    public $gender;
}

$owner = new Owner();
$owner->name = '中の人';
$owner->gender = '男';

$dog = new Dog();
$dog->name = 'ビギエン';
$dog->category = 'しば犬';
$dog->owner = $owner;

$dog2 = clone $dog;
$dog2->name = 'ドットコム';
$dog2->category = 'チワワ';
$owner2 = $dog2->owner;
$owner2->name = 'モヒカン';
$owner2->gender = '女';
$dog2->owner = $owner2;

var_dump($dog);
var_dump($dog2);

Dogクラスに

    // 👇 以下を追加
    public function __clone()
    {
        $this->owner = clone $this->owner;
    }

これを追加します。

これの実行結果は


別々のオブジェクトとしてようやくうまくいった

__clone関数


これはcloneが呼び出された時に自動で実行されるマジックメソッドです

cloneが呼び出された時に何か処理を加えたい時に利用するっぽいです

Document



こちらに

class MyCloneable
{
    public $object1;
    public $object2;

    function __clone()
    {
        // this->object のコピーを作成します。こうしないと、
        // 同じオブジェクトを指すことになってしまいます。
        $this->object1 = clone $this->object1;
    }
}

なーるほど!いいですかみなさん、こうしないと、同じオブジェクトを指すことになってしまうということです。
つまり、同じオブジェクトを指すということです。納得できる説明だと感じる努力が必要です!
(どっかの政治家の構文みたいになってしまってますが、つまり、どっかの政治家の構文みたいになっているということです!)

つまりは仕様ってことですね(゚∀゚ )< オッケー


ちなみに


<?php

class Dog
{
    public $name;

    public $category;
    
    /**
     *
     * @var Owner
     */
    public $owner;

    public function __clone()
    {
        $this->owner = clone $this->owner;
    }
}

/**
 * 飼い主クラス
 */
class Owner
{
    public $name;

    public $gender;

    /**
     * 親
     *
     * @var OwnerParent
     */
    public $ownerParent;// 👈 飼い主の親クラスのオブジェクトをセット
}

/**
 * 飼い主の親クラス
 */
class OwnerParent// 👈 飼い主の親クラスを追加
{
    public $name;

    public $gender;
}

$owner = new Owner();
$owner->name = '中の人';
$owner->gender = '男';

// 👇 飼い主の親オブジェクトを飼い主にセット
$ownerParent = new OwnerParent();
$ownerParent->name = '松ノ信吾郎';
$ownerParent->gender = '男';
$owner->ownerParent = $ownerParent;

$dog = new Dog();
$dog->name = 'ビギエン';
$dog->category = 'しば犬';
$dog->owner = $owner;

$dog2 = clone $dog;
$dog2->name = 'ドットコム';
$dog2->category = 'チワワ';
$owner2 = $dog2->owner;
$owner2->name = 'モヒカン';
$owner2->gender = '女';

// 👇 飼い主の親をコピー先から取得してコネってセット
$owner2Parent = $owner2->ownerParent;
$owner2Parent->name = 'お茶がもっぱら好きでねぇ';
$owner2Parent->gender = 'お茶';
$owner2->ownerParent = $owner2Parent;
$dog2->owner = $owner2;

var_dump($dog);
var_dump($dog2);

この実行結果は


おー

飼い主クラスが上書きされたのと同様に上書きされました。

cloneをちゃんとしたオブジェクトでも、そのオブジェクト(Dog)のプロパティのオブジェクト(owner)のプロパティにオブジェクト(ownerParent)がある場合はやっぱりコピーされないみたいです。

つまり

/**
 * 飼い主クラス
 */
class Owner
{
    public $name;

    public $gender;

    /**
     * 親
     *
     * @var OwnerParent
     */
    public $ownerParent;

        // 👇 こいつが必要ということ
    public function __clone()
    {
        $this->ownerParent = clone $this->ownerParent;
    }
}

これが必要になります。


正直めんどくせぇ(゚∀゚ )

終わりに


まぁぁぁぁぁぁぁぁぁぁぁぁぁぁぁ
仕様だからしゃーない!

普通にcloneだけでやってくれると助かるんですが、マジックメソッド実装しないとダメなんですね

というか、cloneのためだけ?にマジックメソッドが用意されているってのも???と感じます。

まぁしゃーない!
こういうものだと割り切るのが重要ですね!

ということで
乙ですー
1
0
0
0
通信エラーが発生しました。
似たような記事