Глубокое погружение в клонирование объектов
Часть раздела Объектно-ориентированное программирование путешествия по 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 выполняет поверхностное копирование при клонировании объектов. Скалярные значения дублируются, но свойства объектов остаются ссылками на те же самые объекты.
Пример поверхностного клонирования:
<?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: Вывести информацию о клонированном документе
?>В этом уроке есть небольшой тест. Начните урок, чтобы ответить на вопросы и сохранить прогресс.
Все уроки раздела Объектно-ориентированное программирование
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