Menu
Coddy logo textTech

Глубокое погружение в клонирование объектов

Часть раздела Объектно-ориентированное программирование путешествия по PHP на Coddy — урок 61 из 91.

Вы узнали о магическом методе __clone ранее в этом курсе. Теперь давайте изучим критическую разницу между поверхностным и глубоким клонированием, когда объекты содержат другие объекты в качестве свойств.

По умолчанию PHP выполняет поверхностное копирование при клонировании. Скалярные значения дублируются, но свойства объектов остаются ссылками на те же самые объекты:

<?php
class Address {
    public function __construct(public string $city) {}
}

class Person {
    public function __construct(
        public string $name,
        public Address $address
    ) {}
}

$original = new Person("Alice", new Address("Paris"));
$clone = clone $original;

$clone->name = "Bob";
$clone->address->city = "London";

echo $original->name . " - " . $original->address->city;

Вывод:

Alice - London

Обратите внимание, что изменение $clone->name не повлияло на оригинал, но изменение города в адресе — повлияло! Оба объекта используют один и тот же экземпляр Address.

Чтобы создать глубокую копию, в которой вложенные объекты также клонируются, реализуйте __clone:

<?php
class Person {
    public function __construct(
        public string $name,
        public Address $address
    ) {}
    
    public function __clone(): void {
        $this->address = clone $this->address;
    }
}

$original = new Person("Alice", new Address("Paris"));
$clone = clone $original;
$clone->address->city = "London";

echo $original->address->city;

Вывод:

Paris

Теперь каждый Person имеет свой собственный независимый Address. Когда ваши объекты содержат несколько вложенных объектов, вы должны клонировать каждый из них в __clone, чтобы достичь истинной независимости между оригиналом и копией.

challenge icon

Задание

Легко

Давайте создадим систему управления документами, которая демонстрирует разницу между поверхностным и глубоким клонированием. Вы создадите документы, содержащие информацию об авторе, и обеспечите, чтобы клонирование документа создавало по-настоящему независимую копию.

Вы организуете свой код в трех файлах:

  • Author.php — Создайте класс Author, который представляет автора документа. Используйте продвижение параметров конструктора (constructor promotion) для определения публичного свойства $name (string) и публичного свойства $email (string).
  • Document.php — Создайте класс Document, который содержит автора. Подключите файл Author. Класс должен:
    • Использовать продвижение параметров конструктора для публичного свойства $title (string) и публичного свойства $author (Author)
    • Реализовать магический метод __clone() для выполнения глубокого клонирования — когда документ клонируется, его автор также должен быть клонирован, чтобы изменения автора клона не влияли на оригинал
    • Иметь метод getInfo(), который возвращает "[title] by [author name] ([author email])"
  • main.php — Подключите файл Document. Вы получите три входных значения: заголовок документа, имя автора и электронную почту автора.

    Создайте объект Document с объектом Author, используя предоставленные входные данные. Клонируйте документ, затем измените имя автора клона, добавив к нему " (Copy)", и измените заголовок клона, добавив в начало "Copy of ".

    Выведите две строки:

    • Информацию об оригинальном документе
    • Информацию о клонированном документе

    Если ваше глубокое клонирование реализовано правильно, автор оригинального документа должен остаться неизменным даже после изменения автора клона.

Эта задача демонстрирует, почему важно глубокое клонирование — без правильной реализации __clone() оба документа будут использовать один и тот же объект Author, и изменение одного из них повлияет на другой.

Шпаргалка

По умолчанию PHP выполняет поверхностное копирование при клонировании объектов. Скалярные значения дублируются, но свойства объектов остаются ссылками на те же самые объекты.

Пример поверхностного клонирования:

<?php
class Address {
    public function __construct(public string $city) {}
}

class Person {
    public function __construct(
        public string $name,
        public Address $address
    ) {}
}

$original = new Person("Alice", new Address("Paris"));
$clone = clone $original;

$clone->name = "Bob";
$clone->address->city = "London";

echo $original->name . " - " . $original->address->city;
// Вывод: Alice - London

Изменение $clone->name не влияет на оригинал, но изменение вложенного объекта address влияет, так как оба объекта используют один и тот же экземпляр Address.

Чтобы создать глубокую копию, при которой вложенные объекты также клонируются, реализуйте магический метод __clone:

<?php
class Person {
    public function __construct(
        public string $name,
        public Address $address
    ) {}
    
    public function __clone(): void {
        $this->address = clone $this->address;
    }
}

$original = new Person("Alice", new Address("Paris"));
$clone = clone $original;
$clone->address->city = "London";

echo $original->address->city;
// Вывод: Paris

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

Попробуйте сами

<?php

require_once 'Document.php';

// Чтение входных данных
$title = trim(fgets(STDIN));
$authorName = trim(fgets(STDIN));
$authorEmail = trim(fgets(STDIN));

// TODO: Создать автора с указанным именем и электронной почтой

// TODO: Создать документ с указанным заголовком и автором

// TODO: Клонировать документ

// TODO: Изменить имя автора клона, добавив к нему " (Copy)"

// TODO: Изменить заголовок клона, добавив в начало "Copy of "

// TODO: Вывести информацию об оригинальном документе

// TODO: Вывести информацию о клонированном документе

?>
quiz iconПроверьте себя

В этом уроке есть небольшой тест. Начните урок, чтобы ответить на вопросы и сохранить прогресс.

Все уроки раздела Объектно-ориентированное программирование