Kodokon kodokon.com

POO moderna: clases, propiedades promovidas, visibilidad

Escribe clases idiomáticas de PHP 8: promoción de propiedades, readonly, constructores con nombre y objetos de valor inmutables.

9 min · 3 preguntas

Abrir esta lección en Kodokon

Llevas mucho tiempo escribiendo clases, pero PHP 8 ha dejado obsoletos varios hábitos. El más visible: la promoción de propiedades en el constructor. Añadir una palabra clave de visibilidad (private, protected, public) delante de un parámetro del constructor crea la propiedad y la asigna en un solo paso. Los tres bloques repetitivos del estilo antiguo - declaración, parámetro, asignación - se funden en uno solo.

PHP
<?php

declare(strict_types=1);

class LegacyProduct
{
    private string $name;
    private int $priceCents;

    public function __construct(
        string $name,
        int $priceCents,
    ) {
        $this->name = $name;
        $this->priceCents = $priceCents;
    }
}

final class Product
{
    public function __construct(
        private string $name,
        private int $priceCents,
    ) {
    }

    public function label(): string
    {
        return sprintf(
            '%s (%.2f EUR)',
            $this->name,
            $this->priceCents / 100,
        );
    }
}

$product = new Product('Keyboard', 4990);
echo $product->label();
Antes / después: la promoción elimina todo el código repetitivo de asignación.

La visibilidad sigue siendo tu primera herramienta de encapsulación: private por defecto, protected solo si una clase hija necesita acceso, public como último recurso. Desde PHP 8.1, readonly añade una garantía más fuerte: la propiedad solo se puede escribir una vez, desde dentro del ámbito de la clase que la declara. Combinada con la promoción, produce objetos de valor inmutables en apenas unas líneas: cualquier "cambio" devuelve una nueva instancia, lo que elimina los efectos secundarios compartidos.

PHP
<?php

declare(strict_types=1);

final class Money
{
    public function __construct(
        public readonly int $amount,
        public readonly string $currency,
    ) {
    }

    public function add(Money $other): self
    {
        if ($other->currency !== $this->currency) {
            throw new InvalidArgumentException(
                'Currency mismatch'
            );
        }

        return new self(
            $this->amount + $other->amount,
            $this->currency,
        );
    }
}

$total = (new Money(500, 'EUR'))
    ->add(new Money(250, 'EUR'));

echo $total->amount;
Objeto de valor inmutable: add() devuelve una nueva instancia.

En cuanto a los métodos, dos patrones dominan entre los desarrolladores con experiencia. Los constructores con nombre: un constructor private combinado con fábricas estáticas hace explícita cada intención y prohíbe las instanciaciones inconsistentes. Las interfaces fluidas: devolver $this (mutable) o new self(...) (inmutable) te permite encadenar llamadas. El compromiso es bien conocido: la versión inmutable es más segura pero reserva un objeto por llamada - insignificante casi siempre, medible en un bucle intensivo.

PHP
<?php

declare(strict_types=1);

final class User
{
    private function __construct(
        public readonly string $email,
        public readonly string $role,
    ) {
    }

    public static function admin(string $email): self
    {
        return new self($email, 'admin');
    }

    public static function member(string $email): self
    {
        return new self($email, 'member');
    }
}

$user = User::admin('lea@example.com');
echo $user->role;
Constructor privado + fábricas estáticas: solo se pueden construir estados válidos.

Prueba de conocimientos

Comprueba que has retenido los puntos clave de esta lección.

  1. ¿Qué hace la promoción de propiedades en un constructor de PHP 8?
    • Hace que todas las propiedades sean públicas automáticamente
    • Declara la propiedad y la asigna a partir del parámetro, en un solo paso
    • Genera accesores (getters) para cada propiedad
    • Convierte las propiedades en constantes de clase
  2. ¿Cuándo se puede escribir una propiedad readonly?
    • Nunca: debe recibir un valor por defecto en la declaración
    • Exactamente una vez, desde dentro del ámbito de la clase que la declara
    • En cualquier momento, siempre que la escritura provenga de la propia clase
  3. ¿Qué visibilidad deberías elegir para una propiedad que solo deben usar la clase y sus clases hijas?
    • public
    • protected
    • private