PHP moderno en 2026: enums, fibers, match y el sistema de tipos que cambió el lenguaje

PHP 8.0 salió a finales de 2020 y desde entonces el lenguaje no ha parado. Enums en 8.1, readonly classes en 8.2, mejoras de rendimiento en 8.3 y 8.4. Si llevas un tiempo sin tocar PHP o llegas de otro lenguaje, puede que lo que conoces tenga poco que ver con lo que hay ahora. Este artículo repasa las características más importantes, con código real, para que las entiendas y las uses de inmediato.

declare(strict_types=1): el primer paso

Por defecto PHP intenta convertir los tipos automáticamente. Si tienes una función que espera un int y le pasas la cadena "3", PHP la convierte sin quejarse. Eso puede ser cómodo al principio, pero en proyectos con algo de tamaño se convierte en un problema difícil de depurar.

Con declare(strict_types=1) al inicio del fichero, PHP rechaza esa conversión y lanza un TypeError si el tipo no coincide exactamente:

<?php
declare(strict_types=1);

function suma(int $a, int $b): int {
    return $a + $b;
}

suma(2, 3);    // OK
suma(2, "3");  // TypeError en modo estricto

Ponlo siempre al inicio de cada fichero PHP moderno. Es una línea, y te ahorra horas de depuración.

Union types e intersection types

A veces un argumento puede ser de más de un tipo. Los union types resuelven eso de forma explícita:

function procesar(int|string $id): void {
    // $id puede ser int o string
}

Si el valor puede ser null, null|string y ?string son equivalentes. La primera sintaxis es más explícita cuando combinas null con otro tipo que no sea un único tipo base:

function buscar(int|string|null $id): void { ... }

Los intersection types (PHP 8.1) van un paso más allá: el argumento debe implementar todas las interfaces indicadas, no solo una:

function paginar(Iterator&Countable $coleccion): void {
    // $coleccion debe ser Iterator Y Countable a la vez
}

Esto te permite hacer contratos muy precisos sin necesidad de crear interfaces nuevas solo para agrupar otras.

Enums: valores finitos con tipo

Antes de PHP 8.1 la forma de representar un conjunto finito de valores era con constantes de clase o con una constante por archivo. Funcionaba, pero no tenías garantías de tipo ni autocompletado útil. Los enums lo solucionan.

Pure enum

enum Estado {
    case Activo;
    case Inactivo;
    case Suspendido;
}

$estado = Estado::Activo;

if ($estado === Estado::Activo) {
    echo 'El usuario está activo';
}

El tipo de $estado es Estado, no un string ni un int. Si intentas pasarle otra cosa donde se espera Estado, PHP lo rechaza en modo estricto.

Backed enum

Cuando necesitas que cada caso tenga un valor escalar asociado (para guardar en base de datos o enviar por API), usas un backed enum:

enum Color: string {
    case Rojo  = 'red';
    case Verde = 'green';
    case Azul  = 'blue';
}

$color = Color::from('red');        // Color::Rojo
$desconocido = Color::tryFrom('rosa'); // null, sin excepción

from() lanza una excepción si el valor no existe. tryFrom() devuelve null. Cuál usar depende de si el valor viene de una fuente de confianza o de entrada externa.

Los enums también pueden tener métodos, implementar interfaces y usar traits. Son mucho más que constantes agrupadas.

match expression: el switch que debería haber existido siempre

El problema con switch es que hace comparación débil (==), requiere break en cada rama y no devuelve un valor directamente. match corrige los tres problemas:

$resultado = match($codigo) {
    200      => 'OK',
    301, 302 => 'Redirección',
    404      => 'No encontrado',
    500      => 'Error del servidor',
    default  => 'Código desconocido',
};

Comparación estricta (===), sin break, devuelve un valor. Si ninguna rama coincide y no hay default, lanza UnhandledMatchError. Eso es bueno: falla rápido en vez de devolver un resultado inesperado sin avisar.

Puedes usarlo en cualquier contexto donde se espera una expresión: asignaciones, return, argumentos de función.

Named arguments: llamadas más claras

¿Cuántas veces has mirado la firma de array_slice para recordar el orden de los parámetros? Con named arguments ya no hace falta:

$parte = array_slice(
    array: $elementos,
    offset: 0,
    length: 5,
    preserve_keys: true
);

Puedes pasar los argumentos en el orden que quieras y omitir los opcionales del medio sin pasar null como marcador. También funcionan en constructores:

$fecha = new DateTimeImmutable(
    datetime: 'now',
    timezone: new DateTimeZone('Europe/Madrid')
);

Son especialmente útiles al llamar funciones nativas con muchos parámetros opcionales como implode, str_pad o number_format.

Nullsafe operator: cadenas sin null checks

Antes de PHP 8.0, encadenar llamadas a métodos cuando alguno podía devolver null requería anidar condiciones:

$ciudad = null;
if ($usuario !== null) {
    $direccion = $usuario->getDireccion();
    if ($direccion !== null) {
        $ciudad = $direccion->getCiudad();
    }
}

Con el operador nullsafe (?->) eso se reduce a una línea:

$ciudad = $usuario?->getDireccion()?->getCiudad();

Si $usuario es null, la cadena entera devuelve null sin lanzar excepción. El operador no funciona en el lado izquierdo de una asignación, solo en lecturas y llamadas a métodos.

Fibers: coroutines en PHP

Las Fibers son la parte más técnica de esta lista, pero merece la pena entender qué son antes de usarlas. Una Fiber es una función que puede pausarse y reanudarse desde fuera:

$fiber = new Fiber(function(): void {
    $valor = Fiber::suspend('primero');
    echo 'Recibí: ' . $valor . PHP_EOL;
});

$suspendido = $fiber->start();   // Ejecuta hasta Fiber::suspend(), devuelve 'primero'
echo $suspendido . PHP_EOL;      // primero
$fiber->resume('hola');          // Reanuda, $valor = 'hola'

start() ejecuta la función hasta el primer Fiber::suspend(). resume() la reanuda pasándole un valor. Puedes pausar y reanudar tantas veces como quieras.

Las Fibers en sí no hacen I/O asíncrono. Son la base sobre la que se construyen los event loops de ReactPHP y Amp v3. Si quieres hacer peticiones HTTP en paralelo o leer archivos sin bloquear, necesitas una de esas librerías encima. Lo que aportan las Fibers es que ya no hace falta el sistema de callbacks y promesas de antes: el código parece síncrono aunque por dentro se está cooperando con el event loop.

readonly properties y readonly classes

Una propiedad readonly solo se puede asignar una vez, normalmente en el constructor. Cualquier intento posterior de sobreescribirla lanza un Error:

class Usuario {
    public function __construct(
        public readonly string $nombre,
        public readonly string $email
    ) {}
}

$usuario = new Usuario('Ana', '[email protected]');
$usuario->nombre = 'Otra'; // Error: Cannot modify readonly property

Con PHP 8.2 puedes marcar la clase entera como readonly, lo que hace que todas sus propiedades lo sean por defecto. Es muy útil para objetos de valor (value objects) donde quieres garantizar que el estado no cambia después de la construcción:

readonly class Coordenada {
    public function __construct(
        public float $lat,
        public float $lng
    ) {}
}

Junto con los enums y los tipos de intersección, las readonly classes hacen que modelar el dominio en PHP sea mucho más cercano a lo que se puede hacer en lenguajes como Kotlin o Rust.

Juntarlo todo

Estas características no son piezas sueltas: se complementan. Un enum como tipo de retorno combinado con match te da un flujo de control sin ambigüedad. Un value object readonly con constructor promotion y nullsafe operator en la capa de acceso a datos reduce los nulos sueltos por el código. Strict types encima de todo eso hace que los errores aparezcan en el momento equivocado, no tres capas más abajo.

Si tienes código en PHP 7.x o en PHP 8.0 sin estas características, no hace falta reescribirlo todo de golpe. Puedes empezar por declare(strict_types=1) en los ficheros nuevos, sustituir los switch por match donde los encuentres y convertir los arrays de constantes a enums cuando toquen esos módulos.

Para profundizar en cómo el tipado afecta a la calidad del código de prueba, mira enums y tipos en PHP: cómo el tipado mejora el testing. Y si te interesa el modelado de dominio con objetos inmutables, readonly classes y el modelado de dominio en PHP va directo al grano.

Imagen: Pexels / Bibek ghosh

COMPARTE ESTE ARTÍCULO

COMPARTIR EN FACEBOOK
COMPARTIR EN TWITTER
COMPARTIR EN LINKEDIN
COMPARTIR EN WHATSAPP