Propiedades Readonly
PHP 8.1 introduce readonly, un modificador que permite
crear propiedades que solo pueden asignarse una vez, ideal para
objetos inmutables.
Propiedades readonly
Una propiedad readonly solo puede asignarse una vez,
normalmente en el constructor. Cualquier intento posterior de
modificarla lanza un error:
<?php
declare(strict_types=1);
class Usuario
{
public readonly string $id;
public readonly string $email;
public function __construct(string $email)
{
$this->id = uniqid('user_');
$this->email = $email;
}
}
$usuario = new Usuario('ana@example.com');
echo $usuario->id; // user_6574a3f2...
echo $usuario->email; // ana@example.com
// Error: Cannot modify readonly property
// $usuario->email = 'otro@example.com';
Readonly con constructor promotion
readonly se combina perfectamente con constructor
property promotion para crear clases muy concisas:
<?php
declare(strict_types=1);
class Producto
{
public function __construct(
public readonly string $sku,
public readonly string $nombre,
public readonly float $precio
) {}
}
$producto = new Producto('SKU-001', 'Laptop', 999.99);
echo $producto->nombre; // Laptop
// Las propiedades son publicas pero inmutables
// $producto->precio = 899.99; // Error!
Clases readonly (PHP 8.2)
PHP 8.2 permite marcar toda la clase como readonly.
Todas sus propiedades seran automaticamente readonly:
<?php
declare(strict_types=1);
readonly class Coordenadas
{
public function __construct(
public float $latitud,
public float $longitud
) {}
public function distanciaA(Coordenadas $otra): float
{
// Calculo simplificado
$dLat = $otra->latitud - $this->latitud;
$dLon = $otra->longitud - $this->longitud;
return sqrt($dLat ** 2 + $dLon ** 2);
}
}
$madrid = new Coordenadas(40.4168, -3.7038);
$barcelona = new Coordenadas(41.3851, 2.1734);
echo $madrid->distanciaA($barcelona);
Caso de uso: Value Objects
Los Value Objects son objetos que representan un valor y deben
ser inmutables. readonly es perfecto para ellos:
<?php
declare(strict_types=1);
readonly class Dinero
{
public function __construct(
public float $cantidad,
public string $moneda = 'EUR'
) {
if ($cantidad < 0) {
throw new \InvalidArgumentException('Cantidad negativa');
}
}
public function sumar(Dinero $otro): Dinero
{
if ($this->moneda !== $otro->moneda) {
throw new \InvalidArgumentException('Monedas diferentes');
}
// Retorna un NUEVO objeto (inmutabilidad)
return new Dinero($this->cantidad + $otro->cantidad, $this->moneda);
}
public function formatear(): string
{
return number_format($this->cantidad, 2) . ' ' . $this->moneda;
}
}
$precio = new Dinero(100.00);
$iva = new Dinero(21.00);
$total = $precio->sumar($iva);
echo $total->formatear(); // 121.00 EUR
Cuando necesites "modificar" un objeto readonly, crea uno nuevo con los valores actualizados. Esto garantiza que el objeto original nunca cambie.
Ejercicios
Ejercicio 1: Clase Email readonly
Crea una clase Email readonly que valide el formato
del email en el constructor y tenga un metodo getDominio().
Ver solucion
<?php
declare(strict_types=1);
readonly class Email
{
public function __construct(
public string $direccion
) {
if (!filter_var($direccion, FILTER_VALIDATE_EMAIL)) {
throw new \InvalidArgumentException('Email invalido');
}
}
public function getDominio(): string
{
return explode('@', $this->direccion)[1];
}
}
$email = new Email('ana@example.com');
echo $email->getDominio(); // example.com
Ejercicio 2: Clase Punto con método mover
Crea una clase Punto readonly con coordenadas x, y.
Incluye un metodo mover() que retorne un nuevo Punto
con las coordenadas desplazadas.
Ver solucion
<?php
declare(strict_types=1);
readonly class Punto
{
public function __construct(
public float $x,
public float $y
) {}
public function mover(float $dx, float $dy): Punto
{
return new Punto($this->x + $dx, $this->y + $dy);
}
}
$punto = new Punto(10, 20);
$nuevo = $punto->mover(5, -3);
echo "{$nuevo->x}, {$nuevo->y}"; // 15, 17
¿Has encontrado un error o tienes una sugerencia?
Escribenos