Readonly, visibilidad asimetrica y property hooks

Avanzado
PHP
PHP
Actualizado: 19/04/2026

PHP ha evolucionado hacia un modelo de objetos mas cercano al que se encuentra en Java, C# o Kotlin. Las novedades que llegaron con PHP 8.1, PHP 8.2 y PHP 8.4 permiten expresar invariantes del dominio directamente en la declaracion de la clase, eliminando comprobaciones manuales en setters y metodos. Esta leccion cubre las cuatro piezas que vertebran ese cambio.

Propiedades readonly

Una propiedad readonly, disponible desde PHP 8.1, solo puede asignarse una unica vez dentro del alcance declarativo de la clase. Tras la primera escritura, cualquier intento de modificarla lanza Error.

<?php

final class Usuario
{
    public function __construct(
        public readonly int $id,
        public readonly string $email,
    ) {}
}

$u = new Usuario(1, "alicia@example.com");
echo $u->email;
// $u->email = "otro@example.com"; // Error: Cannot modify readonly property

El modificador readonly se combina a la perfeccion con la promocion de propiedades en el constructor. El parametro tipado del constructor se convierte directamente en una propiedad de la clase, sin necesidad de declarar y asignar por separado.

Las propiedades readonly se inicializan una sola vez y no tienen que ser obligatoriamente en el constructor. Cualquier metodo de la misma clase puede asignarlas siempre que aun no esten inicializadas.

<?php

final class ResultadoPago
{
    public readonly string $referencia;

    public function marcarReferencia(string $ref): void
    {
        $this->referencia = $ref;
    }
}

Una propiedad readonly no es una constante. Su valor se fija en tiempo de ejecucion durante la construccion, no en tiempo de compilacion, lo que permite usar valores dinamicos sin perder la garantia de inmutabilidad posterior.

Clases readonly

PHP 8.2 introduce la posibilidad de marcar una clase entera como readonly. Todas sus propiedades heredan automaticamente la restriccion, y la clase no puede declarar propiedades no tipadas ni dinamicas.

<?php

readonly class Coordenada
{
    public function __construct(
        public float $latitud,
        public float $longitud,
    ) {}

    public function distanciaA(self $otro): float
    {
        return sqrt(
            ($this->latitud - $otro->latitud) ** 2 +
            ($this->longitud - $otro->longitud) ** 2
        );
    }
}

Esta sintaxis es ideal para value objects, esos objetos que solo existen por los datos que contienen y que el dominio compara por valor. Al declarar la clase entera como readonly, cualquier intento de modificar un campo queda detectado en analisis estatico y en tiempo de ejecucion.

Para crear una version modificada de un value object se usa el operador clone with, disponible a partir de PHP 8.3, que devuelve una nueva instancia con las propiedades indicadas actualizadas.

<?php

$origen = new Coordenada(40.4168, -3.7038);
$destino = clone($origen) with { latitud: 41.3851, longitud: 2.1734 };

Este patron respeta la inmutabilidad, devolviendo siempre un objeto nuevo en lugar de mutar el original.

Visibilidad asimetrica

Hasta PHP 8.3, la visibilidad de una propiedad debia ser la misma para lectura y escritura. La visibilidad asimetrica de PHP 8.4 permite declarar un nivel mas restrictivo para la escritura que para la lectura, mediante la sintaxis public private(set).

<?php

final class Cuenta
{
    public function __construct(
        public private(set) float $saldo = 0.0,
    ) {}

    public function ingresar(float $importe): void
    {
        if ($importe <= 0) {
            throw new InvalidArgumentException("Importe debe ser positivo");
        }
        $this->saldo += $importe;
    }
}

$c = new Cuenta();
$c->ingresar(100);
echo $c->saldo; // 100.0 (lectura publica)
// $c->saldo = 999; // Error: solo la clase puede escribir

La combinacion public private(set) evita el ritual de declarar una propiedad privada y exponerla con un getter explicito, manteniendo al mismo tiempo el control total sobre las mutaciones. Esto reduce boilerplate sin ceder encapsulacion.

Tambien existe la variante public protected(set), que permite a las subclases modificar la propiedad sin exponerla publicamente.

La visibilidad asimetrica tiene su reflejo natural en otros lenguajes: en C# es el patron public int Saldo { get; private set; }, y en Kotlin equivale a var saldo: Double private set.

Property hooks

Los property hooks son quizas la novedad mas transformadora de PHP 8.4. Permiten interceptar la lectura y escritura de una propiedad directamente en su declaracion, sin declarar metodos getX() y setX() separados. El resultado es un codigo mas cercano al dominio y una API limpia para los consumidores.

<?php

final class Persona
{
    public string $nombre = "";
    public string $apellidos = "";

    public string $nombreCompleto {
        get => trim("{$this->nombre} {$this->apellidos}");
    }
}

$p = new Persona();
$p->nombre = "Ana";
$p->apellidos = "Gomez";
echo $p->nombreCompleto; // "Ana Gomez"

El hook get convierte $nombreCompleto en una propiedad derivada. No hay almacenamiento para ella, se calcula bajo demanda cada vez que se lee, igual que una propiedad computada en otros lenguajes.

Los hooks tambien admiten interceptores de escritura con validacion incorporada. La variable $value representa el valor entrante y set => permite transformarlo antes de guardar.

<?php

final class Producto
{
    public string $slug {
        set (string $value) {
            $normalizado = strtolower(trim($value));
            if ($normalizado === "") {
                throw new InvalidArgumentException("Slug vacio");
            }
            $this->slug = str_replace(" ", "-", $normalizado);
        }
    }
}

$p = new Producto();
$p->slug = "  Mi Producto  ";
echo $p->slug; // "mi-producto"

Los hooks se pueden combinar con readonly, con visibilidad asimetrica y con tipos. Esto permite modelar con precision cuando una propiedad admite escritura externa, cuando se calcula, y cuando su valor se almacena tras transformarlo.

El diagrama resume los elementos de modelado de objetos introducidos en las ultimas versiones.

flowchart LR
    A[Objeto PHP 8.4] --> B[Constructor property promotion]
    A --> C[readonly]
    A --> D[Asymmetric visibility]
    A --> E[Property hooks]
    C --> C1[Propiedad readonly]
    C --> C2[Clase readonly]
    D --> D1[public private set]
    D --> D2[public protected set]
    E --> E1[get]
    E --> E2[set]

Patron practico: DTO inmutable

La combinacion de todas estas herramientas ofrece un estilo compacto para definir Data Transfer Objects que viajan entre capas. El objeto solo se construye una vez, sus datos no se modifican, y las propiedades derivadas se calculan al vuelo.

<?php

readonly class UsuarioDTO
{
    public function __construct(
        public int $id,
        public string $email,
        public string $nombre,
        public string $apellidos,
    ) {}

    public string $iniciales {
        get => strtoupper($this->nombre[0] . $this->apellidos[0]);
    }
}

$dto = new UsuarioDTO(1, "alicia@example.com", "Alicia", "Martinez");
echo $dto->iniciales; // "AM"

Este enfoque sustituye a los antiguos arrays asociativos y a las clases POPO repletas de getters escritos a mano. El resultado es menos codigo, mas seguridad de tipos y una intencion mas clara para quien lo lee.

Cuando preferir mutabilidad controlada

No todo objeto debe ser inmutable. Las entidades del dominio que cambian de estado a lo largo de su ciclo de vida se benefician de la visibilidad asimetrica combinada con metodos que encapsulan las transiciones validas. El readonly total conviene para los value objects, los DTO y los parametros de configuracion que se construyen una vez al arrancar el proceso.

Una regla util: si dos objetos con los mismos valores son intercambiables para el negocio, son value objects y merecen ser readonly. Si tienen identidad propia y un ciclo de vida, son entidades y normalmente exponen metodos mutadores controlados.

Alan Sastre - Autor del tutorial

Alan Sastre

Ingeniero de Software y formador, CEO en CertiDevs

Ingeniero de software especializado en Full Stack y en Inteligencia Artificial. Como CEO de CertiDevs, PHP es una de sus áreas de expertise. Con más de 15 años programando, 6K seguidores en LinkedIn y experiencia como formador, Alan se dedica a crear contenido educativo de calidad para desarrolladores de todos los niveles.

Más tutoriales de PHP

Explora más contenido relacionado con PHP y continúa aprendiendo con nuestros tutoriales gratuitos.

Aprendizajes de esta lección

Aprender a usar propiedades readonly, clases readonly de PHP 8.2, la visibilidad asimetrica y los property hooks de PHP 8.4 para expresar invariantes en tiempo de compilacion.