PHP 8 trajo dos características que reducen drásticamente el código repetitivo en clases: el constructor promotion, que convierte los parámetros del constructor en propiedades automáticamente, y las propiedades readonly, que garantizan inmutabilidad tras la inicialización. PHP 8.2 añade además clases readonly completas.
Constructor promotion: antes y después
<?php
// Sin constructor promotion: 12 líneas para 3 propiedades
class PuntoAntiguo
{
public float $x;
public float $y;
public float $z;
public function __construct(float $x, float $y, float $z)
{
$this->x = $x;
$this->y = $y;
$this->z = $z;
}
}
// Con constructor promotion (PHP 8.0): 5 líneas
class Punto
{
public function __construct(
public float $x,
public float $y,
public float $z = 0.0,
) {}
}
$p = new Punto(1.0, 2.0, 3.0);
echo $p->x; // 1.0
?>
Mezclar parámetros normales y promovidos
<?php
class Producto
{
public readonly string $id;
public function __construct(
public readonly string $nombre,
public readonly float $precio,
string $id = '', // parámetro normal, no se convierte en propiedad
) {
// Lógica adicional en el cuerpo del constructor
$this->id = $id !== '' ? $id : uniqid('prod_');
}
}
$p = new Producto('Teclado', 89.99);
echo $p->nombre; // Teclado
echo $p->id; // prod_6640f1a2c1234 (aleatorio)
?>
Propiedades readonly: inmutabilidad controlada
<?php
class DireccionPostal
{
public function __construct(
public readonly string $calle,
public readonly string $ciudad,
public readonly string $codigoPostal,
public readonly string $pais = 'ES',
) {}
// Para "modificar", crea una nueva instancia con los datos cambiados
public function conCiudad(string $nuevaCiudad): static
{
return new static(
$this->calle,
$nuevaCiudad,
$this->codigoPostal,
$this->pais,
);
}
}
$dir = new DireccionPostal('Calle Mayor 1', 'Madrid', '28001');
echo $dir->ciudad; // Madrid
// $dir->ciudad = 'Barcelona'; // Fatal error: Cannot modify readonly property
$dir2 = $dir->conCiudad('Barcelona');
echo $dir2->ciudad; // Barcelona
echo $dir->ciudad; // Madrid (la original no cambia)
?>
Clases readonly en PHP 8.2
En PHP 8.2, una clase marcada como readonly convierte automáticamente todas sus propiedades en readonly sin tener que declararlo una a una:
<?php
// PHP 8.2: readonly class
readonly class Coordenada
{
public function __construct(
public float $latitud,
public float $longitud,
public float $altitud = 0.0,
) {}
public function distanciaA(Coordenada $otra): float
{
// Fórmula de Haversine simplificada
$dlat = deg2rad($otra->latitud - $this->latitud);
$dlon = deg2rad($otra->longitud - $this->longitud);
$a = sin($dlat/2)**2
+ cos(deg2rad($this->latitud))
* cos(deg2rad($otra->latitud))
* sin($dlon/2)**2;
return 6371 * 2 * atan2(sqrt($a), sqrt(1 - $a)); // km
}
}
$madrid = new Coordenada(40.4168, -3.7038);
$barcelona = new Coordenada(41.3851, 2.1734);
echo round($madrid->distanciaA($barcelona)) . ' km'; // 505 km
?>
Combinar constructor promotion con validación
<?php
class RangoPrecio
{
public function __construct(
public readonly float $min,
public readonly float $max,
) {
if ($this->min < 0) {
throw new InvalidArgumentException('El mínimo no puede ser negativo');
}
if ($this->max < $this->min) {
throw new InvalidArgumentException('El máximo debe ser mayor que el mínimo');
}
}
public function contiene(float $precio): bool
{
return $precio >= $this->min && $precio <= $this->max;
}
}
$rango = new RangoPrecio(10.0, 100.0);
var_dump($rango->contiene(50.0)); // true
var_dump($rango->contiene(5.0)); // false
?>
La documentación oficial de constructor promotion y la de propiedades readonly detallan las restricciones de uso en herencia y con tipos de unión.
