clone
Узнайте, как ключевое слово PHP clone копирует object, в чём разница между поверхностным и глубоким копированием, и как использовать магический метод __clone().
Ключевое слово PHP clone
В PHP объекты передаются по ссылке. Когда вы присваиваете одну переменную-object другой с помощью =, обе переменные указывают на один и тот же object — изменив один, вы измените и другой. Ключевое слово clone разрывает эту связь: оно создаёт совершенно новый object, который является копией существующего, поэтому вы можете изменять копию, не затрагивая оригинал.
На этой странице рассматриваются синтаксис clone, разница между поверхностным и глубоким копированием (главный источник ошибок при использовании clone), а также магический метод __clone(), позволяющий управлять процессом клонирования.
Если вы только начинаете работать с объектами, сначала прочитайте Классы и объекты PHP и Конструкторы.
Синтаксис
$copy = clone $original;clone возвращает новый object. Оригинал остаётся нетронутым, а $copy является независимым экземпляром, свойства которого скопированы из $original.
Присваивание vs. clone
Именно для этого существует clone. При обычном присваивании обе переменные ссылаются на один object:
<?php
class Counter
{
public int $value = 0;
}
$a = new Counter();
$b = $a; // same object, NOT a copy
$b->value = 10;
echo $a->value . PHP_EOL; // 10 — $a changed tooИспользуйте clone, чтобы получить по-настоящему отдельный object:
<?php
class Counter
{
public int $value = 0;
}
$a = new Counter();
$b = clone $a; // independent copy
$b->value = 10;
echo $a->value . PHP_EOL; // 0 — original is untouched
echo $b->value . PHP_EOL; // 10Простой пример
<?php
class Car
{
public string $make;
public string $model;
public int $year;
public function __construct(string $make, string $model, int $year)
{
$this->make = $make;
$this->model = $model;
$this->year = $year;
}
}
$original = new Car("Ford", "Mustang", 2022);
$copy = clone $original;
$copy->make = "Chevrolet";
$copy->model = "Corvette";
echo "Original: {$original->make} {$original->model} {$original->year}" . PHP_EOL;
echo "Copy: {$copy->make} {$copy->model} {$copy->year}" . PHP_EOL;
// Output:
// Original: Ford Mustang 2022
// Copy: Chevrolet Corvette 2022Изменение $copy не затрагивает $original, поскольку они являются двумя отдельными объектами.
Поверхностное копирование: главный подводный камень
По умолчанию clone создаёт поверхностную копию. Скалярные свойства (string, int, boolean) копируются по значению, но если свойство содержит другой object, копируется только ссылка — и оригинал, и клон в итоге указывают на один и тот же вложенный object.
<?php
class Engine
{
public function __construct(public int $horsepower) {}
}
class Car
{
public function __construct(public Engine $engine) {}
}
$original = new Car(new Engine(300));
$copy = clone $original;
// Both cars still share ONE Engine object
$copy->engine->horsepower = 500;
echo "Original engine: {$original->engine->horsepower} hp" . PHP_EOL;
echo "Copy engine: {$copy->engine->horsepower} hp" . PHP_EOL;
// Output:
// Original engine: 500 hp
// Copy engine: 500 hpИзменение двигателя клона также изменило двигатель оригинала — почти никогда это не то, чего вы хотите.
Глубокое копирование с помощью __clone()
Магический метод __clone() автоматически выполняется на новом объекте сразу после его дублирования. Используйте его для клонирования вложенных объектов, чтобы копия получила собственные независимые экземпляры (глубокое копирование):
<?php
class Engine
{
public function __construct(public int $horsepower) {}
}
class Car
{
public function __construct(public Engine $engine) {}
public function __clone()
{
// Give the clone its own Engine instead of sharing the original's
$this->engine = clone $this->engine;
}
}
$original = new Car(new Engine(300));
$copy = clone $original;
$copy->engine->horsepower = 500;
echo "Original engine: {$original->engine->horsepower} hp" . PHP_EOL;
echo "Copy engine: {$copy->engine->horsepower} hp" . PHP_EOL;
// Output:
// Original engine: 300 hp
// Copy engine: 500 hpТеперь у двух автомобилей отдельные двигатели, поэтому изменение одного не влияет на другой. __clone() — подходящее место для любых исправлений при копировании: сброс автоматически сгенерированного ID, очистка кешированного значения или глубокое копирование вложенных объектов и массивов объектов.
Когда использовать clone
- Рабочие копии / черновики — дублируйте object, чтобы пользователь мог редактировать копию, пока оригинал сохраняется (по аналогии с «Сохранить как»).
- Паттерн «Прототип» — настройте один object и клонируйте его при необходимости получить новый преднастроенный экземпляр, вместо того чтобы каждый раз запускать затратный конструктор.
- Вспомогательные средства неизменяемости — возвращайте изменённый клон вместо мутации
$this, что является распространённым приёмом в объектах-значениях и DTO.
Прибегайте к clone, когда вам конкретно нужен второй независимый object; если вы лишь хотите читать один и тот же object из двух мест, вполне подойдёт обычная ссылка.
Важно помнить
cloneпо умолчанию создаёт поверхностную копию — вложенные объекты разделяются, а не дублируются.- Определите
__clone()для выполнения глубокого копирования или любых других постобработочных действий. __clone()выполняется на новом объекте, где$this— это клон.cloneпредназначен для объектов. Массивы (array) в PHP уже копируются по значению при присваивании, поэтому имcloneне нужен.