11
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

PHP の配列は変数に代入するだけで中身がコピーされる (ただしオブジェクトは clone されない)

Last updated at Posted at 2018-04-09

オブジェクトのコピーには clone を使う

サンプルコード

<?php

class Detail
{
  private $message;
  public function __construct(string $message)
  {
    $this->message = $message;
  }
  public function getMessage(): string
  {
    return $this->message;
  }
  public function setMessage(string $message)
  {
    $this->message = $message;
  }
}

$d1 = new Detail("hello");

echo "オブジェクトを変数に代入してもコピーにはならない。\n";
$d2 = $d1;

echo "clone でシンプルなクラスのオブジェクトをコピーできる。\n";
$d3 = clone $d1;

echo "d2 のオブジェクトを変更すると d1 にも影響する。\n";
$d2->setMessage("goodbye");

echo "d3 のオブジェクトを変更しても d1 には影響しない\n";
$d3->setMessage("seeyou");

echo "d1=", $d1->getMessage(), "\n";
echo "d2=", $d2->getMessage(), "\n";
echo "d3=", $d3->getMessage(), "\n";
echo "\n";

実行結果

オブジェクトを変数に代入してもコピーにはならない。
clone でシンプルなクラスのオブジェクトをコピーできる。
d2 のオブジェクトを変更すると d1 にも影響する。
d3 のオブジェクトを変更しても d1 には影響しない
d1=goodbye
d2=goodbye
d3=seeyou

参考資料

PHP: オブジェクトのクローン作成 - Manual http://php.net/manual/ja/language.oop5.cloning.php

オブジェクトのクローンが作成される際、PHP 5 は、そのオブジェクトのプロパティを 全てシャローコピーします。他の変数へのリファレンスを保持する全てのプロパティは、 リファレンスのままとなります。

クローンの作成が完了すると、 __clone() メソッドが定義された場合、新規の作成されたオブジェクトの __clone() メソッドがコールされるため、この中で、プロパティに 必要な変更を行うことができます。

PHP: オブジェクトと参照 - Manual http://php.net/manual/ja/language.oop5.references.php

PHP の参照は一種のエイリアスで、ふたつの異なる変数に同じ値を書き込めるものです。 PHP 5 以降、オブジェクト変数の値にオブジェクト自身は含まれなくなりました。 含まれるのはオブジェクトの ID のみで、 これを用いて実際のオブジェクトにアクセスできるようになっています。 オブジェクトが引数として渡されたり返り値となったり あるいは別の変数に代入されたりした場合、 それはエイリアスではありません。ID のコピーを保持し、 同じオブジェクトを指すようになるのです。

配列は変数に代入するだけで中身がコピーされる

  • 配列は変数に代入するだけで中身がコピーされる
  • ただし、オブジェクトは clone されずに同じものが参照されるので注意
  • また、配列はオブジェクトではないので clone できない

サンプルコード

<?php

class Detail
{
  private $message;
  public function __construct(string $message)
  {
    $this->message = $message;
  }
  public function getMessage(): string
  {
    return $this->message;
  }
  public function setMessage(string $message)
  {
    $this->message = $message;
  }
}

$user1 = [
  "name" => "Aiko",
  "age" => 16,
  "detail" => new Detail("hello"),
];

echo "PHP配列を変数に代入するとコピーが作られる(参照ではなく)。\n";
echo "正確には Copy-On-Write (COW) という仕組みで、";
echo "どちらかの中身を変更したときにはじめてコピーが作られるらしい(公式情報見つからず)。\n";
$user2 = $user1;
echo "\n";

echo "コピーした配列の中身を書き換えてもコピー元には影響しない。\n";
$user2["name"] = "Bob";
echo "\n";

echo "コピーした配列の中身がオブジェクトの場合はコピー元に影響する。\n";
$user2["detail"]->setMessage("goodbye");
echo "\n";

var_dump($user1);
echo "\n";
var_dump($user2);
echo "\n";

echo "配列はオブジェクトではないので clone できない\n";
$user3 = clone $user1;

実行結果

PHP配列を変数に代入するとコピーが作られる(参照ではなく)。
正確には Copy-On-Write (COW) という仕組みで、どちらかの中身を変更したときにはじめてコピーが作られるらしい(公式情報見つからず)。

コピーした配列の中身を書き換えてもコピー元には影響しない。

コピーした配列の中身がオブジェクトの場合はコピー元に影響する。

array(3) {
  ["name"]=>
  string(4) "Aiko"
  ["age"]=>
  int(16)
  ["detail"]=>
  object(Detail)#1 (1) {
    ["message":"Detail":private]=>
    string(7) "goodbye"
  }
}

array(3) {
  ["name"]=>
  string(3) "Bob"
  ["age"]=>
  int(16)
  ["detail"]=>
  object(Detail)#1 (1) {
    ["message":"Detail":private]=>
    string(7) "goodbye"
  }
}

配列はオブジェクトではないので clone できない
PHP Fatal error:  Uncaught Error: __clone method called on non-object in /Users/alice/hoge.php:46
Stack trace:
#0 {main}
  thrown in /Users/alice/hoge.php on line 46

Fatal error: Uncaught Error: __clone method called on non-object in /Users/alice/hoge.php:46
Stack trace:
#0 {main}
  thrown in /Users/alice/hoge.php on line 46

「アイコ16歳」をコピーして名前だけ変えたら「ボブ16歳」になったが、 detail は同じオブジェクトを指している。

参考資料

PHP: 配列 - Manual
http://php.net/manual/ja/language.types.array.php

配列への代入においては、常に値がコピーされることに注意してください。 配列をリファレンスでコピーする場合には、 リファレンス演算子を使う必要があります。

Memory management — PHP Internals Book
http://www.phpinternalsbook.com/zvals/memory_management.html#reference-counting-and-copy-on-write

To avoid doing so, PHP employs the copy-on-write paradigm: A zval can be shared by multiple variables/functions/etc as long as it’s only read from and not modified. If one of the holders wants to modify it, the zval needs to be copied before applying any changes.

今回の動作確認環境

  • macOS Sierra 10.12.6
  • PHP 7.1.16
$ uname -mrsv
Darwin 16.7.0 Darwin Kernel Version 16.7.0: Tue Jan 30 11:27:06 PST 2018; root:xnu-3789.73.11~1/RELEASE_X86_64 x86_64

$ php --version
PHP 7.1.16 (cli) (built: Mar 31 2018 02:27:44) ( NTS )
Copyright (c) 1997-2018 The PHP Group
Zend Engine v3.1.0, Copyright (c) 1998-2018 Zend Technologies
    with Zend OPcache v7.1.16, Copyright (c) 1999-2018, by Zend Technologies
11
3
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
11
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?