PHPでオブジェクトをクローン(複製)する方法 ― 代入・clone・__clone()の違いを解説
はじめに
PHPでは、単純な代入($obj2 = $obj1;)によってオブジェクトを「コピー」しても、実際にはメモリ上の同一オブジェクトへの参照がもう一つ作られるだけです。そのため、一方のオブジェクトのプロパティを変更すると、元のオブジェクトと複製側のオブジェクトの両方に変更が反映されてしまいます。
PHPには、オブジェクトの浅いコピー(shallow copy)を作成するための clone キーワードが用意されています。しかし、元のオブジェクトが別のオブジェクトをプロパティとして保持している場合、cloneで作成したコピーも同じ内部オブジェクトを参照し続けます。完全に独立した深いコピー(deep copy)を作成したい場合は、クラス内にマジックメソッド __clone() を定義する必要があります。
本記事では、以下の3つの方法の違いを具体例とともに解説します。
- 単純な代入によるコピー
- cloneキーワードによる浅いコピー
- __clone()メソッドを使った深いコピー
1. 代入によるコピー
次のコードでは、myclass のプロパティの一つとして address クラスのオブジェクトを持たせています。代入によってオブジェクトを複製し、プロパティの値を変更すると、その変更が両方のオブジェクトに反映されることを確認できます。
コード例
<?php
class address{
var $city="Nanded";
var $pin="431601";
function setaddr($arg1, $arg2){
$this->city=$arg1;
$this->pin=$arg2;
}
}
class myclass{
var $name="Raja";
var $obj;
function setname($arg){
$this->name=$arg;
}
}
$obj1=new myclass();
$obj1->obj=new address();
echo "original object\n";
print_r($obj1);
$obj2=$obj1;
$obj1->setname("Ravi");
echo "after change:\n";
print_r($obj1);
print_r($obj2);
?>出力結果
original object
myclass Object(
[name] => Raja
[obj] => address Object(
[city] => Nanded
[pin] => 431601
)
)
after change:
original object:
myclass Object(
[name] => Ravi
[obj] => address Object(
[city] => Nanded
[pin] => 431601
)
)
copied object:
myclass Object(
[name] => Ravi
[obj] => address Object(
[city] => Nanded
[pin] => 431601
)
)$obj2 = $obj1; という代入は新しいオブジェクトを作成せず、$obj1 と $obj2 は同じインスタンスを指しているため、setname() による変更が両方に出力されています。
2. cloneキーワードによる浅いコピー
clone キーワードを使用すると、オブジェクトの浅いコピーが作成されます。スカラー値のプロパティ(nameなど)は独立して複製されるため、クローン元での変更はクローン先に反映されません。しかし、プロパティとして保持されている埋め込みオブジェクト(addressオブジェクトなど)は依然として同じインスタンスを共有しており、その内部を変更すると元のオブジェクトとクローンの両方に影響します。
コード例
<?php
class address{
var $city="Nanded";
var $pin="431601";
function setaddr($arg1, $arg2){
$this->city=$arg1;
$this->pin=$arg2;
}
}
class myclass{
var $name="Raja";
var $obj;
function setname($arg){
$this->name=$arg;
}
}
$obj1=new myclass();
$obj1->obj=new address();
echo "original object:\n";
print_r($obj1);
$obj2=clone $obj1;
$obj1->setname("Ravi");
$obj1->obj->setaddr("Mumbai", "400001");
echo "after change:\n";
echo "original object:\n";
print_r($obj1);
echo "cloned object:\n";
print_r($obj2);
?>出力結果
original object:
myclass Object(
[name] => Raja
[obj] => address Object(
[city] => Nanded
[pin] => 431601
)
)
after change:
original object:
myclass Object(
[name] => Ravi
[obj] => address Object(
[city] => Mumbai
[pin] => 400001
)
)
cloned object:
myclass Object(
[name] => Raja
[obj] => address Object(
[city] => Mumbai
[pin] => 400001
)
)出力を見ると、name プロパティはクローン側で「Raja」のまま独立していますが、address オブジェクトの city と pin はクローン元での変更(Mumbai / 400001)がクローン側にも反映されています。これが「浅いコピー」の挙動です。
3. __clone()メソッドによる深いコピー
完全に独立したコピー(深いコピー)を作成するには、クラス内にマジックメソッド __clone() を定義します。このメソッドは clone キーワードが実行された直後に自動的に呼び出されるため、ここで埋め込みオブジェクトも明示的にクローンすれば、すべてのプロパティが独立したコピーになります。
コード例
<?php
class address{
var $city="Nanded";
var $pin="431601";
function setaddr($arg1, $arg2){
$this->city=$arg1;
$this->pin=$arg2;
}
}
class myclass{
var $name="Raja";
var $obj;
function setname($arg){
$this->name=$arg;
}
public function __clone() {
$this->obj = clone $this->obj ;
}
}
$obj1=new myclass();
$obj1->obj=new address();
echo "original object:\n";
print_r($obj1);
$obj2=clone $obj1;
$obj1->setname("Ravi");
$obj1->obj->setaddr("Mumbai", "400001");
echo "after change:\n";
echo "original object:\n";
print_r($obj1);
echo "cloned object:\n";
print_r($obj2);
?>出力結果
original object:
myclass Object(
[name] => Raja
[obj] => address Object(
[city] => Nanded
[pin] => 431601
)
)
after change:
original object:
myclass Object(
[name] => Ravi
[obj] => address Object(
[city] => Mumbai
[pin] => 400001
)
)
cloned object:
myclass Object(
[name] => Raja
[obj] => address Object(
[city] => Nanded
[pin] => 431601
)
)今回は __clone() 内で $this->obj = clone $this->obj; を実行しているため、埋め込みの address オブジェクトも別インスタンスとして複製されます。その結果、クローン元で住所を変更しても、クローン側は初期値(Nanded / 431601)のまま維持されています。
まとめ
| 方法 | コピーの種類 | プロパティ(スカラー値) | 埋め込みオブジェクト |
|---|---|---|---|
| 代入($obj2 = $obj1) | 参照の共有 | 共有される | 共有される |
| clone キーワード | 浅いコピー | 独立する | 共有される |
| clone + __clone() | 深いコピー | 独立する | 独立する |
補足: サンプルコードで使われている var はPHP 4時代の古い宣言方法で、現在は非推奨です。PHP 5以降では public、private、protected などのアクセス修飾子を使用することが推奨されています。また、ネストが深いオブジェクト構造の場合は、__clone() 内で再帰的にクローン処理を行うか、シリアライズ(serialize()/unserialize())を利用した深いコピーの手法も検討するとよいでしょう。
-
PHPのget_class()関数とは?使い方とサンプルコードを解説
PHPのget_class()関数は、オブジェクトが属するクラス名を取得するための関数です。引数に渡された値がオブジェクトでない場合にはFALSEを返します。また、クラス内部でobject引数を省略して呼び出した場合には、そのクラス自身の名前が返されます。構文get_class(object)パラメータobject − 検査対象となるオブジェクトです。クラスの内部で呼び出す場合は、このパラメータを省略できます。戻り値get_class()関数は、指定されたオブジェクトのクラス名を文字列として返します。引数がオブジェクトでない場合はFALSEを返します。クラス内部
-
PHPのget_object_vars()関数の使い方を徹底解説
PHPのget_object_vars()関数は、指定されたオブジェクトが持つプロパティを取得するための関数です。オブジェクト内で定義されているプロパティを、プロパティ名をキーとした連想配列の形式で返します。 構文 get_object_vars(object) パラメータ object − プロパティを取得したいオブジェクトのインスタンスを指定します。 戻り値 get_object_vars()関数は、指定されたオブジェクトの定義済みプロパティを連想配列として返します。まだ値が代入されていないプロパティについては、NULLが返されます。 なお、この関数をクラスの外部から呼び出した場合、取