__clone и клонирование объектов
Часть раздела Объектно-ориентированное программирование путешествия по PHP на Coddy — урок 48 из 91.
Когда вы присваиваете один объект другой переменной в PHP, вы не создаете копию — обе переменные указывают на один и тот же объект. Чтобы создать настоящую независимую копию, вам нужно использовать ключевое слово clone:
<?php
class Product {
public function __construct(
public string $name,
public float $price
) {}
}
$original = new Product("Laptop", 999.99);
$copy = clone $original;
$copy->price = 899.99;
echo $original->price . " - " . $copy->price;
Вывод:
999.99 - 899.99По умолчанию PHP выполняет поверхностное копирование — он копирует все значения свойств напрямую. Это хорошо работает для скалярных значений, но объекты внутри вашего объекта по-прежнему ссылаются на тот же экземпляр. Магический метод __clone() позволяет настроить это поведение:
<?php
class Order {
public function __construct(
public int $id,
public array $items = []
) {}
public function __clone(): void {
$this->id = 0;
$this->items = [];
}
}
$order1 = new Order(101, ["Book", "Pen"]);
$order2 = clone $order1;
echo "Original: " . $order1->id . ", Clone: " . $order2->id;
Вывод:
Original: 101, Clone: 0Метод __clone() вызывается для нового объекта после того, как была создана поверхностная копия. Здесь вы сбрасываете идентификаторы (ID), создаете новые копии вложенных объектов или выполняете любую очистку, необходимую для того, чтобы клон стал по-настоящему независимым.
Задание
ЛегкоДавайте создадим систему версионности документов, которая демонстрирует, как клонирование создает независимые копии и как магический метод __clone() позволяет настраивать действия, происходящие при клонировании объекта.
Вы организуете свой код в двух файлах:
Document.php— Создайте классDocument, который представляет документ с версиями. Используйте продвижение параметров конструктора (constructor promotion), чтобы определить публичное свойство$title(string), публичное$content(string) и публичное$version(integer, по умолчанию 1). Добавьте приватное свойство$createdAt, которое хранит текущую метку времени (используйтеdate('Y-m-d H:i:s')в конструкторе). Вашему документу необходимы:getCreatedAt()— возвращает метку времени создания__clone()— когда документ клонируется, версия должна увеличиться на 1, а$createdAtдолжен быть сброшен на текущую метку времени (клон — это новая версия, созданная "сейчас")
main.php— Подключите файл Document. Вы получите три входных значения: заголовок, содержимое и новую строку содержимого для клонированной версии. Создайте объектDocumentс заголовком и содержимым. Затем клонируйте его, чтобы создать новую версию, и обновите содержимое клона на новую строку содержимого. Выведите четыре строки:- Версию оригинального документа
- Версию клонированного документа
- Содержимое оригинального документа
- Содержимое клонированного документа
Эта система показывает, как __clone() позволяет вам контролировать процесс клонирования. Оригинальный документ сохраняет свою версию и время создания, в то время как клон автоматически становится новой версией со свежей меткой времени — это идеально подходит для отслеживания истории документов, где каждый клон представляет собой новую ревизию.
Шпаргалка
Чтобы создать независимую копию объекта, используйте ключевое слово clone:
<?php
class Product {
public function __construct(
public string $name,
public float $price
) {}
}
$original = new Product("Laptop", 999.99);
$copy = clone $original;
$copy->price = 899.99;
echo $original->price . " - " . $copy->price;
// Вывод: 999.99 - 899.99
По умолчанию PHP выполняет поверхностное копирование всех значений свойств. Чтобы настроить поведение при клонировании, используйте магический метод __clone():
<?php
class Order {
public function __construct(
public int $id,
public array $items = []
) {}
public function __clone(): void {
$this->id = 0;
$this->items = [];
}
}
$order1 = new Order(101, ["Book", "Pen"]);
$order2 = clone $order1;
echo "Original: " . $order1->id . ", Clone: " . $order2->id;
// Вывод: Original: 101, Clone: 0
Метод __clone() вызывается для нового объекта после выполнения поверхностного копирования, что позволяет сбросить значения свойств или создать независимые копии вложенных объектов.
Попробуйте сами
<?php
require_once 'Document.php';
// Чтение входных данных
$title = trim(fgets(STDIN));
$content = trim(fgets(STDIN));
$newContent = trim(fgets(STDIN));
// TODO: Создать Document с заголовком и содержимым
// TODO: Клонировать документ для создания новой версии
// TODO: Обновить содержимое клона на новую строку содержимого
// TODO: Вывести четыре строки:
// 1. Версия оригинального документа
// 2. Версия клонированного документа
// 3. Содержимое оригинального документа
// 4. Содержимое клонированного документа
?>В этом уроке есть небольшой тест. Начните урок, чтобы ответить на вопросы и сохранить прогресс.
Все уроки раздела Объектно-ориентированное программирование
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