__clone y clonación de objetos
Parte de la sección Programación Orientada a Objetos del Journey de PHP de Coddy — lección 48 de 91.
Cuando asignas un objeto a otra variable en PHP, no creas una copia; ambas variables apuntan al mismo objeto. Para crear una copia independiente real, necesitas usar la palabra clave 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;
Salida:
999.99 - 899.99Por defecto, PHP realiza una copia superficial: copia todos los valores de las propiedades directamente. Esto funciona bien para valores escalares, pero los objetos dentro de su objeto siguen haciendo referencia a la misma instancia. El método mágico __clone() le permite personalizar este comportamiento:
<?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;
Salida:
Original: 101, Clone: 0El método __clone() se llama en el nuevo objeto después de que se realiza la copia superficial. Aquí es donde se restablecen los IDs, se crean copias nuevas de los objetos anidados o se realiza cualquier limpieza necesaria para que el clon sea verdaderamente independiente.
Desafío
FácilVamos a construir un sistema de versiones de documentos que demuestre cómo la clonación crea copias independientes y cómo el método mágico __clone() te permite personalizar lo que sucede cuando se clona un objeto.
Organizarás tu código en dos archivos:
Document.php— Crea una claseDocumentque represente un documento con versiones. Usa la promoción de constructor para definir un$titlepúblico (string), un$contentpúblico (string) y un$versionpúblico (integer, con valor por defecto 1). Añade una propiedad privada$createdAtque almacene la marca de tiempo actual (usadate('Y-m-d H:i:s')en el constructor). Tu documento necesita:getCreatedAt()— devuelve la marca de tiempo de creación__clone()— cuando se clona un documento, la versión debe incrementarse en 1, y el$createdAtdebe restablecerse a la marca de tiempo actual (el clon es una nueva versión creada "ahora")
main.php— Incluye el archivo Document. Recibirás tres entradas: un título, un contenido y una nueva cadena de contenido para la versión clonada. Crea unDocumentcon el título y el contenido. Luego clónalo para crear una nueva versión y actualiza el contenido del clon con la nueva cadena de contenido. Imprime cuatro líneas:- La versión del documento original
- La versión del documento clonado
- El contenido del documento original
- El contenido del documento clonado
Este sistema muestra cómo __clone() te permite controlar lo que sucede durante la clonación. El documento original mantiene su versión y hora de creación, mientras que el clon se convierte automáticamente en una nueva versión con una marca de tiempo fresca, ideal para el seguimiento del historial de documentos donde cada clon representa una nueva revisión.
Hoja de referencia
Para crear una copia independiente de un objeto, utiliza la palabra clave 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;
// Salida: 999.99 - 899.99
Por defecto, PHP realiza una copia superficial (shallow copy) de todos los valores de las propiedades. Para personalizar el comportamiento de la clonación, utiliza el método mágico __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;
// Salida: Original: 101, Clone: 0
El método __clone() se llama en el nuevo objeto después de que se realiza la copia superficial, lo que permite restablecer propiedades o crear copias independientes de objetos anidados.
Pruébalo tú mismo
<?php
require_once 'Document.php';
// Leer entradas
$title = trim(fgets(STDIN));
$content = trim(fgets(STDIN));
$newContent = trim(fgets(STDIN));
// TODO: Crear un Document con el título y el contenido
// TODO: Clonar el documento para crear una nueva versión
// TODO: Actualizar el contenido del clon con la nueva cadena de contenido
// TODO: Imprimir cuatro líneas:
// 1. La versión del documento original
// 2. La versión del documento clonado
// 3. El contenido del documento original
// 4. El contenido del documento clonado
?>Esta lección incluye un breve cuestionario. Empieza la lección para responderlo y registrar tu progreso.
Todas las lecciones de Programación Orientada a Objetos
1Fundamentos de OOP
Archivos externosIntroducción a OOPClases vs ObjetosLa palabra clave $thisMétodosPropiedadesConstructor (__construct)Destructor (__destruct)Resumen - Calculadora simple4Herencia
Herencia básicaLa palabra clave parent::Sobrescritura de métodosLa palabra clave finalClases abstractasResumen - Jerarquía de empleados7Encapsulamiento
Público, Protegido, PrivadoModificadores de acceso a fondoGetters y SettersOcultamiento de informaciónPromoción de constructor (8.0)Resumen - Sistema de registros de estudiantes10Conceptos avanzados de POO
Composición vs. HerenciaInyección de dependenciasClases anónimasEnums (PHP 8.1)Fibers (PHP 8.1)Clonación de objetos a fondoGeneradores e Iteradores13Patrones de diseño Parte 2
Patrón CommandPatrón AdapterPatrón DecoratorPatrón Template MethodPatrón StatePatrón CompositePatrón Repository2Espacios de nombres y Autoloading
Introducción a los espacios de nombresLa palabra clave useEstándar de Autoloading PSR-4Autoloader de ComposerResumen - Proyecto organizado5Interfaces y Contratos
Introducción a las InterfacesImplementación de InterfacesImplementación de Múltiples InterfacesInterfaz vs Clase AbstractaType Hinting con InterfacesResumen - Calculadora de Formas8Métodos mágicos
Introducción a los métodos mágicos__toString y __debugInfo__get, __set, __isset, __unset__call y __callStatic__clone y clonación de objetos__serialize y __unserializeResumen - Colección personalizada3Propiedades de clase
Propiedades de instancia vs. estáticasConstantes en clasesMétodos y propiedades estáticosPropiedades privadas y protegidasPropiedades de solo lectura (PHP 8.1)Resumen - Gestor de cuenta bancaria6Polimorfismo
Sobrescritura de métodos revisadaPolimorfismo mediante interfacesType Hinting y tipos de uniónEnlace estático tardíoResumen - Procesador de pagos9Traits
Introducción a los TraitsUso de múltiples TraitsResolución de conflictos de TraitsMétodos abstractos en TraitsTraits frente a herencia12Patrones de diseño Parte 1
Introducción a los patrones de diseñoPatrón SingletonPatrón FactoryPatrón ObserverPatrón Strategy