Глубокое погружение в клонирование объектов
Часть раздела Объектно-ориентированное программирование путешествия по 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, чтобы достичь истинной независимости между оригиналом и копией.
Задание
ЛегкоДавайте создадим систему управления документами, которая демонстрирует разницу между поверхностным и глубоким клонированием. Вы создадите документы, содержащие информацию об авторе, и обеспечите, чтобы клонирование документа создавало по-настоящему независимую копию.
Вы организуете свой код в трех файлах:
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
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: Вывести информацию о клонированном документе
?>В этом уроке есть небольшой тест. Начните урок, чтобы ответить на вопросы и сохранить прогресс.
Все уроки раздела Объектно-ориентированное программирование
1Основы ООП
Внешние файлыВведение в ООПКлассы и объектыКлючевое слово $thisМетодыСвойстваКонструктор (__construct)Деструктор (__destruct)Итоги — Простой калькулятор4Наследование
Основы наследованияКлючевое слово parent::Переопределение методовКлючевое слово finalАбстрактные классыИтоги — Иерархия сотрудников7Инкапсуляция
Public, Protected, PrivateПодробный разбор модификаторов доступаГеттеры и сеттерыСокрытие данныхConstructor Promotion (8.0)Итоги — Система учета студентов10Продвинутые концепции ООП
Композиция против наследованияВнедрение зависимостейАнонимные классыПеречисления (PHP 8.1)Файберы (PHP 8.1)Глубокое погружение в клонирование объектовГенераторы и итераторы13Паттерны проектирования. Часть 2
Паттерн КомандаПаттерн АдаптерПаттерн ДекораторПаттерн Шаблонный методПаттерн СостояниеПаттерн КомпоновщикПаттерн Репозиторий2Пространства имен и автозагрузка
Введение в пространства именКлючевое слово useСтандарт автозагрузки PSR-4Автозагрузчик ComposerИтоги: структурированный проект5Интерфейсы и контракты
Введение в интерфейсыРеализация интерфейсовРеализация нескольких интерфейсовИнтерфейс против абстрактного классаType Hinting с интерфейсамиИтоги — Калькулятор фигур8Магические методы
Введение в магические методы__toString и __debugInfo__get, __set, __isset, __unset__call и __callStatic__clone и клонирование объектов__serialize и __unserializeИтоги — Кастомная коллекция11Система типов и обработка ошибок
Объявление типовNullable-типыТипы объединения и пересеченияКлассы исключенийИерархия пользовательских исключенийTry, Catch, FinallyИтоги — Валидатор форм14Проект: Управление библиотекой
Обзор проектаКлассы Book и User3Свойства классов
Свойства экземпляра и статические свойстваКонстанты в классахСтатические методы и свойстваПриватные и защищенные свойстваСвойства Readonly (PHP 8.1)Итоги — Менеджер банковского счета6Полиморфизм
Снова о переопределении методовПолиморфизм через интерфейсыType Hinting и Union TypesПозднее статическое связываниеИтоги — Payment Processor9Трейты
Введение в трейтыИспользование нескольких трейтовРазрешение конфликтов трейтовАбстрактные методы в трейтахТрейты и наследование12Паттерны проектирования. Часть 1
Введение в паттерны проектированияПаттерн SingletonПаттерн FactoryПаттерн ObserverПаттерн Strategy