Leccion 37 de 75 10 min de lectura

Clonacion y Comparacion de Objetos

Los objetos en PHP se pasan por referencia. Aprende a crear copias independientes con clone y a comparar objetos correctamente.

Objetos y referencias

Cuando asignas un objeto a otra variable, ambas apuntan al mismo objeto:

PHP
<?php
declare(strict_types=1);

class Producto
{
    public string $nombre;
    public float $precio;

    public function __construct(string $nombre, float $precio)
    {
        $this->nombre = $nombre;
        $this->precio = $precio;
    }
}

$producto1 = new Producto('Laptop', 999);
$producto2 = $producto1; // NO es una copia, es la misma referencia

$producto2->precio = 899;

echo $producto1->precio; // 899 (cambio en ambos!)
echo $producto2->precio; // 899

Clonar objetos

Usa clone para crear una copia independiente:

PHP
<?php
declare(strict_types=1);

class Producto
{
    public string $nombre;
    public float $precio;

    public function __construct(string $nombre, float $precio)
    {
        $this->nombre = $nombre;
        $this->precio = $precio;
    }
}

$producto1 = new Producto('Laptop', 999);
$producto2 = clone $producto1; // Crea una copia independiente

$producto2->precio = 899;
$producto2->nombre = 'Laptop Gaming';

echo $producto1->precio; // 999 (sin cambios)
echo $producto1->nombre; // Laptop

echo $producto2->precio; // 899
echo $producto2->nombre; // Laptop Gaming

El método __clone

Puedes personalizar la clonacion implementando __clone():

PHP
<?php
declare(strict_types=1);

class Documento
{
    private string $id;
    private string $titulo;
    private string $creadoEn;

    public function __construct(string $titulo)
    {
        $this->id = uniqid();
        $this->titulo = $titulo;
        $this->creadoEn = date('Y-m-d H:i:s');
    }

    public function __clone(): void
    {
        // Al clonar, generar nuevo ID y nueva fecha
        $this->id = uniqid();
        $this->creadoEn = date('Y-m-d H:i:s');
        $this->titulo = $this->titulo . ' (copia)';
    }

    public function getId(): string
    {
        return $this->id;
    }

    public function getTitulo(): string
    {
        return $this->titulo;
    }
}

$doc1 = new Documento('Informe anual');
sleep(1); // Esperar 1 segundo
$doc2 = clone $doc1;

echo $doc1->getId();     // 65a1b2c3...
echo $doc1->getTitulo(); // Informe anual

echo $doc2->getId();     // 65a1b2c4... (diferente)
echo $doc2->getTitulo(); // Informe anual (copia)
Clonacion superficial

Por defecto, clone hace una copia superficial. Si el objeto tiene propiedades que son otros objetos, estos no se clonan automaticamente.

Clonacion profunda

PHP
<?php
declare(strict_types=1);

class Direccion
{
    public string $ciudad;

    public function __construct(string $ciudad)
    {
        $this->ciudad = $ciudad;
    }
}

class Persona
{
    public string $nombre;
    public Direccion $direccion;

    public function __construct(string $nombre, Direccion $direccion)
    {
        $this->nombre = $nombre;
        $this->direccion = $direccion;
    }

    public function __clone(): void
    {
        // Clonar tambien los objetos internos
        $this->direccion = clone $this->direccion;
    }
}

$persona1 = new Persona('Ana', new Direccion('Madrid'));
$persona2 = clone $persona1;

$persona2->direccion->ciudad = 'Barcelona';

echo $persona1->direccion->ciudad; // Madrid (sin cambios)
echo $persona2->direccion->ciudad; // Barcelona

Comparar objetos

PHP tiene dos formas de comparar objetos:

PHP
<?php
declare(strict_types=1);

class Punto
{
    public int $x;
    public int $y;

    public function __construct(int $x, int $y)
    {
        $this->x = $x;
        $this->y = $y;
    }
}

$a = new Punto(1, 2);
$b = new Punto(1, 2);
$c = $a;

// == compara valores de propiedades
var_dump($a == $b);  // true (mismos valores)
var_dump($a == $c);  // true

// === compara si son el MISMO objeto
var_dump($a === $b); // false (objetos diferentes)
var_dump($a === $c); // true (misma referencia)

// Con clone
$d = clone $a;
var_dump($a == $d);  // true (mismos valores)
var_dump($a === $d); // false (objetos diferentes)

Ejemplo practico: Carrito de compras

PHP
<?php
declare(strict_types=1);

class ItemCarrito
{
    public string $nombre;
    public float $precio;
    public int $cantidad;

    public function __construct(string $nombre, float $precio, int $cantidad = 1)
    {
        $this->nombre = $nombre;
        $this->precio = $precio;
        $this->cantidad = $cantidad;
    }

    public function getSubtotal(): float
    {
        return $this->precio * $this->cantidad;
    }
}

class Carrito
{
    /** @var ItemCarrito[] */
    private array $items = [];

    public function agregar(ItemCarrito $item): void
    {
        $this->items[] = $item;
    }

    public function getTotal(): float
    {
        $total = 0;
        foreach ($this->items as $item) {
            $total += $item->getSubtotal();
        }
        return $total;
    }

    public function __clone(): void
    {
        // Clonar cada item para tener copia profunda
        $itemsClonados = [];
        foreach ($this->items as $item) {
            $itemsClonados[] = clone $item;
        }
        $this->items = $itemsClonados;
    }
}

$carrito = new Carrito();
$carrito->agregar(new ItemCarrito('Laptop', 999));
$carrito->agregar(new ItemCarrito('Mouse', 29, 2));

// Crear carrito de prueba
$carritoPrueba = clone $carrito;
// Modificar el carrito de prueba no afecta al original

Ejercicios

Ejercicio 1: Configuracion clonable

Crea una clase Configuracion con un array de opciones. Implementa __clone() para que al clonar, el array se copie correctamente y no se comparta entre instancias.

Ver solucion
PHP
<?php
declare(strict_types=1);

class Configuracion
{
    private array $opciones;

    public function __construct(array $opciones = [])
    {
        $this->opciones = $opciones;
    }

    public function set(string $clave, mixed $valor): void
    {
        $this->opciones[$clave] = $valor;
    }

    public function get(string $clave): mixed
    {
        return $this->opciones[$clave] ?? null;
    }

    public function __clone(): void
    {
        // El array ya se copia por valor en PHP
        // Pero si tuviera objetos dentro, habria que clonarlos
        $this->opciones = $this->opciones;
    }
}

$config1 = new Configuracion(['debug' => true, 'version' => '1.0']);
$config2 = clone $config1;

$config2->set('debug', false);
$config2->set('version', '2.0');

echo $config1->get('debug');   // true (sin cambios)
echo $config2->get('debug');   // false
echo $config1->get('version'); // 1.0
echo $config2->get('version'); // 2.0

Ejercicio 2: Historial de versiones

Crea una clase Documento con contenido y version. Cada vez que se clone, incrementa la version. Crea un metodo para obtener una copia con version incrementada.

Ver solucion
PHP
<?php
declare(strict_types=1);

class Documento
{
    private string $titulo;
    private string $contenido;
    private int $version;

    public function __construct(string $titulo, string $contenido)
    {
        $this->titulo = $titulo;
        $this->contenido = $contenido;
        $this->version = 1;
    }

    public function getVersion(): int
    {
        return $this->version;
    }

    public function getContenido(): string
    {
        return $this->contenido;
    }

    public function setContenido(string $contenido): void
    {
        $this->contenido = $contenido;
    }

    public function __clone(): void
    {
        $this->version++;
    }

    public function crearNuevaVersion(): self
    {
        return clone $this;
    }
}

$doc1 = new Documento('Manual', 'Contenido inicial');
echo $doc1->getVersion(); // 1

$doc2 = $doc1->crearNuevaVersion();
$doc2->setContenido('Contenido actualizado');
echo $doc2->getVersion(); // 2

$doc3 = $doc2->crearNuevaVersion();
echo $doc3->getVersion(); // 3

Ejercicio 3: Comparador de usuarios

Crea una clase Usuario con id, email y nombre. Crea un metodo esIgual(Usuario $otro): bool que compare por email.

Ver solucion
PHP
<?php
declare(strict_types=1);

class Usuario
{
    private int $id;
    private string $email;
    private string $nombre;

    public function __construct(int $id, string $email, string $nombre)
    {
        $this->id = $id;
        $this->email = strtolower($email);
        $this->nombre = $nombre;
    }

    public function getId(): int
    {
        return $this->id;
    }

    public function getEmail(): string
    {
        return $this->email;
    }

    public function esIgual(Usuario $otro): bool
    {
        return $this->email === $otro->email;
    }

    public function esMismaInstancia(Usuario $otro): bool
    {
        return $this === $otro;
    }
}

$usuario1 = new Usuario(1, 'Ana@Email.com', 'Ana Garcia');
$usuario2 = new Usuario(2, 'ana@email.com', 'Ana G.');
$usuario3 = new Usuario(3, 'luis@email.com', 'Luis');

var_dump($usuario1->esIgual($usuario2)); // true (mismo email)
var_dump($usuario1->esIgual($usuario3)); // false (diferente email)

$usuario4 = $usuario1;
var_dump($usuario1->esMismaInstancia($usuario4)); // true
var_dump($usuario1->esMismaInstancia($usuario2)); // false

¿Te está gustando el curso?

Tenemos cursos premium con proyectos reales y soporte personalizado.

Descubrir cursos premium