#7. 9 php ООП Копирование объектов: clone, __clone()

Копирование объектов

При выполнении строки

$lion = new Animal;

в переменной $lion сохраняется ссылка на объект класса Animal. Если присвоить переменную $lion какой-то другой переменной:

$lion2 = $lion

новый объект не будет создан, а лишь продублирована ссылка на уже существующий. Если нам нужно создать новый объект, который будет являться копией существуещего, необходимо использовать ключевое слово clone:

<?php
$lion = new Animal;
$lion2 = clone $lion;

Теперь $lion и $lion2 — это два разных объекта.

Переопределение операции копирования

По умолчанию операция clone выполняет побитовое копирование всех свойств объекта. В некоторых случаях это может быть не тем, что требуется. Например, если объект сам хранит ссылки на другие объекты, то, возможно, эти данные должны быть скопированы с помощью того же ключевого слова clone. Рассмотрим пример:

class Animal
{
    private $name;
    private $children = [];
}

$lion = new Animal;

Свойство $children соответствует списку детей животного, каждый из которых является объектом этого же класса Animal. При выполнении копирования с помощью clone

$lion2 = clone $lion;

для объекта, на который ссылается переменная $lion, будет создана его копия. Однако, если список children объекта $lion не пуст, то новый объект $lion2 будет хранить в своем свойстве children ссылки на те же самые объекты. Если нам это не подходит, то в классе должен быть описан специальный метод __clone, который вызывается всякий раз при использовании ключевого слова clone по отношению к объекту этого класса.

Пример 1. Переопределение операции копирования объектов

<?php
<?php
class Animal
{
    private $name;
    private $children = [];
    
    public function __construct($text) 
    {
        $this->name = $text;
    }
 
    public function set_name($text)
    {
        $this->name = $text;
    }

    public function add_child($child)
    {
        $this->children[] = $child;
    }
 
    public function get_children()
    {
        return $this->children;
    }
    
    public function print_children()
    {
        foreach ($this->children as $value) {
            echo $value->name, " ";
        }
    }
     
    public function __clone()
    {
        foreach ($this->children as $key => $value) {
            $this->children[$key] = clone $value;
        }
    }
}
 
$lion = new Animal("Бонифаций");
$lion->add_child(new Animal("Симба"));

$lion2 = clone $lion;

$child2 = $lion2->get_children()[0];
$child2->set_name("Другой Симба");

$lion->print_children();
echo "<BR>";
$lion2->print_children();

В данном примере мы создаём объект с именем "Бонифаций" и добавляем ему одного ребенка — объект с именем "Симба":

$lion = new Animal("Бонифаций");
$lion->add_child(new Animal("Симба"));

Затем копируем объект "Бонифаций" с помощью ключевого слова clone:

$lion2 = clone $lion;

в результате чего вызывается описанный в классе специальный метод __clone. В момент вызова __clone уже выполнено побитовое копирование свойства $children объекта $lion в $this нового объекта. В методе выполняется обход массива $this->children нового объекта и перезапись каждого элемента этого массива (каждый элемент заменяется его копией, сделанной с помощью clone):

foreach ($this->children as $key => $value) {
    $this->children[$key] = clone $value;
}

После завершения копирования мы меняем имя ребенка объекта $lion2 и убеждаемся в том, что имя ребенка $lion при этом не изменилось.

Результатом выполнения примера будут строки:
Симба
Другой Симба

Запрет клонирования

Если объявить метод __clone с модификатором доступа private:

class Animal
{
    private $name;

    private function __clone()
    {
    }
}

то в программе нельзя будет сделать копию объекта никакими способами. Этот подход применяется, если объект должен существовать в программе в единственном экземпляре.

Key Words for FKN + antitotal forum (CS VSU):