💙

PHPのコピー(配列、オブジェクトの違い)

2022/03/07に公開

配列(スカラ値も同様)とオブジェクトのコピー方法による挙動の違いをまとめ。

変数に代入

配列:コピー元が参照されない。(ディープコピー)
オブジェクト:コピー元が参照される。(シャローコピー)

<?php
class Ingredient
{
    public $sugar;
    public $flour;
    public function __construct(string $sugar)
    {
        $this->sugar = $sugar;
    }
}

$ingredient = new Ingredient(300);
$cheeseCake = [
    'name' => 'cheese cake',
    'ingredient' => $ingredient,
];

// 代入(コピー)Copy on Write[※]
$spongeCake = $cheeseCake;

// 配列の値を変更
$spongeCake['name'] = 'sponge cake';
// オブジェクトの値を変更
$spongeCake['ingredient']->flour = 500;

echo "【実行結果】\n";
var_dump($cheeseCake);
var_dump($spongeCake);
【実行結果】
array(2) {
  // ディープコピー。配列はコピー元まで影響を受けていない。
  ["name"]=>
  string(11) "cheese cake"
  ["ingredient"]=>
  object(Ingredient)#1 (2) {
    ["sugar"]=>
    string(3) "300"
    // シャローコピー。オブジェクトはコピー元まで影響を受けてしまっている。
    ["flour"]=>
    int(500)
  }
}
array(2) {
  // 配列の値はコピー元まで影響を与えていない。
  ["name"]=>
  string(11) "sponge cake"
  ["ingredient"]=>
  object(Ingredient)#1 (2) {
    ["sugar"]=>
    string(3) "300"
    // オブジェクトの値はコピー元まで影響を与えてしまっている。
    ["flour"]=>
    int(500)
  }
}

[※]コピーオンライト (Copy-On-Write)
変数を代入しただけではメモリが新たに確保(コピー)されない。
この時点ではまだ同じメモリ領域をみている。
代入した変数を書き換えることによってコピーが実行される。

cloneメゾット

オブジェクトのコピーはcloneを使う。ただ、cloneだけではオブジェクト内の参照型のものはシャローコピーになる為、__clone()メゾット(※) を用いてディープコピーを実現します。

配列:cloneメソッドでコピーできない。(Fatal errorになる)
オブジェクト:コピー元が参照されない。(ディープコピー)

<?php
class Music {
	private $genre;
	private $song;

    public function __construct(string $genre)
    {
        $this->genre = $genre;
    }
    public function getGenre(){
        return $this->genre;
    }
    public function setGenre(string $genre){
        $this->genre = $genre;
    }
	
    public function getSong(){
        return $this->song;
    }
    public function setSong(Song $song=null){
        $this->song = $song;
    }
    // (※)
    public function __clone() {
	$this->song = clone $this->song;
    }
}

class Song {
	private $title;
	
    public function getTitle(){
        return $this->title;
    }
    public function setTitle(string $title=null){
        $this->title = $title;
    }
}

$hipHop = new Music('hip-hop');
$songH = new Song();
$songH->setTitle('deep in the drank');
$hipHop->setSong($songH);

$trap = clone $hipHop;
$trap->setGenre('trap');
$songT = new Song();
$songT->setTitle('save that shit');
$trap->setSong($songT);
// ↓これだと上手くいかない
// $trap->getSong->setTitle('save that shit');

echo "【実行結果】\n";
var_dump($hipHop);
var_dump($trap);
【実行結果】
object(Music)#1 (2) {
  ["genre":"Music":private]=>
  string(7) "hip-hop"
  ["song":"Music":private]=>
  object(Song)#2 (1) {
    ["title":"Song":private]=>
    string(17) "deep in the drank"
  }
}
object(Music)#3 (2) {
  ["genre":"Music":private]=>
  string(4) "trap"
  ["song":"Music":private]=>
  object(Song)#5 (1) {
    ["title":"Song":private]=>
    string(14) "save that shit"
  }
}

trapのsongが上書きされて、hipHopのsongには影響がない(コピー元に影響がない)ことが確認できた。

また、以下の方法でもオブジェクトのディープコピーが実現可能みたい。
cloneメゾットいちいち書かなくていいの?か確認中...

$copyObject = unserialize(serialize($object));

Discussion