別に記事での説明も上手ではないんですけど…説明スキルが欲しいです
PHP の clone について
PHP の clone はシャローコピー(浅いコピー)です!
シャローコピー(浅いコピー)って何だ!?
シャローコピー(浅いコピー)とディープコピー(深いコピー)
シャローコピー
プリミティブプロパティの値(int, string, float, bool, ...etc)は新しいオブジェクトに複製し、別アドレスを指します。
オブジェクトプロパティの値は clone 元のオブジェクトと同じアドレスを指します。つまり、clone 先のオブジェクトプロパティの値が書き換えられると、clone 元のオブジェクトプロパティの値も書き換わります。
ディープコピー
プリミティブプロパティの値もオブジェクトプロパティの値も新しいオブジェクトに複製して、別アドレスを指します。
文字だけだと口頭と同様で意味わからないため、下記サンプルコードです。
サンプルコード(clone 元のクラス)
namespace App\ValueObject;
/**
* 氏名クラス
*
* @property string $lastName 姓
* @property string $firstName 名
* @property LastName $lastNameObject 姓オブジェクト
* @property FirstName $firstNameObject 名オブジェクト
*/
final class FullName
{
/**
* 姓
*
* @var string
*/
public string $lastName;
/**
* 名
*
* @var string
*/
public string $firstName;
/**
* 姓オブジェクト
*
* @var LastName
*/
public LastName $lastNameObject;
/**
* 名オブジェクト
*
* @var FirstName
*/
public FirstName $firstNameObject;
/**
* @param string $lastName 姓
* @param string $firstName 名
*/
public function __constract(
string $lastName,
string $firstName,
) {
$this->lastName = $lastName;
$this->firstName = $firstName;
$this->lastNameObject = new LastName($lastName);
$this->firstNameObject = new FirstName($firstName);
}
/**
* プリミティブプロパティから氏名を取得
*
* @return string
*/
public function getMutableFullName(): string
{
return $this->lastName . $this->firstName;
}
/**
* オブジェクトプロパティから氏名を取得
*
* @return string
*/
public function getObjectFullName(): string
{
return $this->lastNameObject->value . $this->firstNameObject->value;
}
}
namespace App\ValueObject;
/**
* 名クラス
*/
final class FirstName
{
/**
* @var string 名
*/
public string $value;
/**
* @param string $value 名
*/
public function __constract(
string $value,
) {
$this->value = $value;
}
}
namespace App\ValueObject;
/**
* 姓クラス
*/
final class LastName
{
/**
* @var string 姓
*/
public string $value;
/**
* @param string $value 名
*/
public function __constract(string $value)
{
$this->value = $value;
}
}
「姓と名を別クラスにするとかプロパティが readonly じゃないとか設計としてどうなんだ!?」と思っていそうですが、説明に必要なのでそこは考えないで下さい
シャローコピーサンプルコード
$baseFullName = new FullName('池田', 'エライザ');
$baseFullName->getMutableFullName(); // 池田エライザ
$baseFullName->getObjectFullName(); // 池田エライザ
// clone 実行
$cloneFillName = clone $baseFullName();
// clone 先のミュータブルプロパティの値を書き換え
$cloneFullName->lastName = '馬場';
$cloneFullName->firstName = 'ふみか';
// clone 先のオブジェクトプロパティの値を書き換え
$cloneFullName->lastNameObject->value = '馬場';
$cloneFullName->firstNameObject->value = 'ふみか';
// clone 先 を出力
$cloneFullName->getMutableFullName(); // 馬場ふみか
$cloneFullName->getObjectFullName(); // 馬場ふみか
// clone 元 を出力
// プリミティブプロパティは別アドレスで複製されるため、clone 元には影響が無い
$baseFullName->getMutableFullName(); // 池田エライザ
// オブジェクトプロパティは同じアドレスを指すため
// clone 先の値が書き換わると clone 元の値も書き換わる
$baseFullName->getObjectFullName(); // 馬場ふみか
どうしてPHPはシャローコピーを採用しているかというと、メモリを節約したいのと、プリミティブプロパティの複製と比較してオブジェクトプロパティの複製は重たくて時間もかかるというのが理由かなと思ってます。
多分ですが。
どうしてもディープコピー(深いコピー)をしたい場合は、__clone() を利用する
「どうしてもシャローコピーは嫌だ!ディープコピーが良い!」というわがままなエンジニアの要望を満たすために、PHP には __clone() マジックメソッドが用意されています。
clone 元の氏名クラスに __clone() マジックメソッドを追加して、オブジェクトプロパティを clone するようにコードを追加します。
/**
* 氏名クラス
*
* @property string $lastName 姓
* @property string $firstName 名
* @property LastName $lastNameObject 姓オブジェクト
* @property FirstName $firstNameObject 名オブジェクト
*/
final class FullName
{
/**
* 姓
*
* @var string
*/
public string $lastName;
/**
* 名
*
* @var string
*/
public string $firstName;
/**
* 姓オブジェクト
*
* @var LastName
*/
public LastName $lastNameObject;
/**
* 名オブジェクト
*
* @var FirstName
*/
public FirstName $firstNameObject;
/**
* @param string $lastName 姓
* @param string $firstName 名
*/
public function __constract(
string $lastName,
string $firstName,
) {
$this->lastName = $lastName;
$this->firstName = $firstName;
$this->lastNameObject = new LastName($lastName);
$this->firstNameObject = new FirstName($firstName);
}
/**
* プリミティブプロパティから氏名を取得
*
* @return string
*/
public function getMutableFullName(): string
{
return $this->lastName . $this->firstName;
}
/**
* オブジェクトプロパティから氏名を取得
*
* @return string
*/
public function getObjectFullName(): string
{
return $this->lastNameObject->value . $this->firstNameObject->value;
}
+ /**
+ * clone 時に実行されるマジックメソッド
+ */
+ public function __clone()
+ {
+ $this->lastNameObject = clone $this->lastNameObject;
+ $this->firstNameObject = clone $this->firstNameObject;
+ }
ディープコピーサンプルコード
$baseFullName = new FullName('池田', 'エライザ');
$baseFullName->getMutableFullName(); // 池田エライザ
$baseFullName->getObjectFullName(); // 池田エライザ
// clone 実行
$cloneFillName = clone $baseFullName();
// clone 先のミュータブルプロパティの値を書き換え
$cloneFullName->lastName = '馬場';
$cloneFullName->firstName = 'ふみか';
// clone 先のオブジェクトプロパティの値を書き換え
$cloneFullName->lastNameObject->value = '馬場';
$cloneFullName->firstNameObject->value = 'ふみか';
// clone 先 を出力
$cloneFullName->getMutableFullName(); // 馬場ふみか
$cloneFullName->getObjectFullName(); // 馬場ふみか
// clone 元 を出力
$baseFullName->getMutableFullName(); // 池田エライザ
// ディープコピーの場合、オブジェクトプロパティの値も別のアドレスを指すため
// clone 元が更新されても影響はない
+ $baseFullName->getObjectFullName(); // 池田エライザ
- $baseFullName->getObjectFullName(); // 馬場ふみか
オブジェクトはイミュータブル(不変)であることが望ましいとされているので、正直ディープコピーの出番はあまり無いかなと思ってます。イミュータブルであれば値を更新出来ないので、同じアドレスを指していても関係ありませんし。
知識としてシャローコピーとディープコピーの違いだけ知っていれば良いと思います!
以上
参考