🐘 PHP 8 moderno
Match, named arguments, attributes, enums, readonly, property promotion, nullsafe, tipos avanzados y Composer con PSR-4 explicados con código real.
PHP 8 moderno
PHP 8 convirtió un lenguaje históricamente criticado en una herramienta moderna de backend: enums de primera clase, attributes (anotaciones), types estrictos y una JIT. Si aún escribes PHP como en la era de PHP 5, te estás perdiendo el 80 % de las mejoras que esta versión aporta. Este artículo recorre cada feature con código real.
match vs switch
match es la evolución de switch: devuelve un valor, usa comparación estricta (===) y es exhaustivo (si no hay caso que coincida, lanza UnhandledMatchError).
function estadoTexto(int $estado): string
{
return match ($estado) {
0 => 'pendiente',
1 => 'procesando',
2 => 'completado',
default => 'desconocido',
};
}
echo estadoTexto(2); // completado
// switch: sin valor de retorno, comparación suelta (==), fácil de olvidar un break
switch ($estado) {
case 0:
$r = 'pendiente';
break;
case 1:
$r = 'procesando';
break;
}
switch |
match |
|
|---|---|---|
| Devuelve valor | ❌ (necesitas asignar) | ✅ expresión |
| Comparación | suelta (==) |
estricta (===) |
break |
obligatorio | no aplica |
| Exhaustivo | no | sí (sin default → excepción) |
| Múltiples condiciones | case 1: case 2: |
1, 2 => ... |
💡
matchtambién admite condiciones combinadas:match ($x) { 'a', 'b' => 'algo', default => 'otro' }.
Named arguments
Puedes pasar argumentos por nombre, en el orden que quieras y omitir los opcionales del medio:
function crearUsuario(
string $nombre,
string $email,
bool $activo = true,
array $roles = []
): void {
// ...
}
// Sin named args: tienes que recordar el orden y rellenar opcionales
crearUsuario('Ana', 'ana@x.com', true, ['admin']);
// Con named args: claro y con saltos
crearUsuario('Ana', 'ana@x.com', roles: ['admin']);
⚠️ Aplica el principio de último recurso: named args mejoran la legibilidad, pero sobreusarlos acopla tu código a los nombres de parámetros de terceros (si renombran uno, rompes). Úsalos para parámetros opcionales o booleanos confusos.
Attributes: los “decoradores” de PHP
Los attributes (PHP 8.0+) metadatos declarativos que se leen con reflexión. Son la base de cómo Laravel y Symfony configuran rutas, validación y más.
use Attribute;
#[Attribute(Attribute::TARGET_CLASS | Attribute::TARGET_METHOD)]
class Ruta
{
public function __construct(public string $metodo, public string $path) {}
}
#[Ruta('GET', '/usuarios')]
class ListarUsuarios
{
public function ejecutar(): array { return []; }
}
// Leerlos con reflexión
$ref = new ReflectionClass(ListarUsuarios::class);
$attr = $ref->getAttributes(Ruta::class)[0]->newInstance();
echo $attr->metodo . ' ' . $attr->path; // GET /usuarios
Enums: pure y backed
Los enums (PHP 8.1) son de primera clase: con métodos, constantes y casos:
// Pure enum: sin valor asociado
enum Estado
{
case Pendiente;
case Procesando;
case Completado;
public function etiqueta(): string
{
return match ($this) {
self::Pendiente => 'En espera',
self::Procesando => 'En proceso',
self::Completado => 'Terminado',
};
}
}
// Backed enum: asociado a un valor (int o string)
enum Rol: string
{
case Admin = 'admin';
case Editor = 'editor';
case Lector = 'lector';
public function esPrivilegiado(): bool
{
return $this === self::Admin;
}
}
echo Rol::Admin->value; // 'admin'
echo Rol::from('admin')->name; // 'Admin'
$rol = Rol::tryFrom('inexistente'); // null (no lanza)
💡 Un enum implementa una interface, puede tener métodos estáticos, y los casos se comparan con
===. Cuando necesites una “lista de constantes con comportamiento”, es un enum.
readonly properties y readonly classes
PHP 8.1 introdujo propiedades readonly; PHP 8.2 añadió clases readonly (todas sus propiedades son readonly). Solo se escriben una vez, y solo desde dentro del constructor:
readonly class Configuracion
{
public function __construct(
public string $host,
public int $puerto,
public bool $debug = false,
) {}
}
$cfg = new Configuracion('localhost', 3306);
// $cfg->host = 'otro'; // ❌ Error: no se puede modificar readonly
Constructor property promotion
Promueve parámetros del constructor a propiedades sin declararlas aparte:
// Antes (PHP 7)
class Usuario {
private string $nombre;
private int $edad;
public function __construct(string $nombre, int $edad) {
$this->nombre = $nombre;
$this->edad = $edad;
}
}
// Con promotion (PHP 8)
class Usuario {
public function __construct(
private string $nombre,
private int $edad,
) {}
}
💡 El promotion es azúcar sintáctico: la propiedad se crea, se declara su visibilidad y se asigna en el constructor, todo en una línea. Combinado con
readonlyy named args elimina mucho boilerplate.
Nullsafe operator
Encadena llamadas que pueden devolver null sin lanzar errores en el camino:
// PHP 7: feo y propenso a errores
$ciudad = null;
if ($usuario !== null) {
$perfil = $usuario->getPerfil();
if ($perfil !== null) {
$ciudad = $perfil->getDireccion()->ciudad ?? null;
}
}
// PHP 8: nullsafe
$ciudad = $usuario?->getPerfil()?->getDireccion()?->ciudad;
⚠️ Nullsafe corta la cadena devolviendo
nullen el primernull, pero no valida el tipo final: sigue necesitando??si el último eslabón puede sernull.
Union e intersection types
PHP 8.0 añadió unions (int|string); PHP 8.1 añadió intersections (A&B):
// Union: el valor puede ser cualquiera de los listados
function normalizar(int|string $valor): int
{
return is_string($valor) ? (int) $valor : $valor;
}
// Intersection: el valor DEBE cumplir todos los tipos
interface Guardable {}
interface Serializable {}
function persistir(Guardable&Serializable $entidad): void
{
// $entidad implementa ambas interfaces
}
⚠️ No puedes mezclar
A&ByA|Ben un mismo parámetro sin paréntesis; PHP lo rechaza con error de compilación.
mixed y never
mixed(8.0): “cualquier tipo” — la alternativa segura a no tipar (equivale aarray|bool|callable|int|float|null|object|resource|string).never(8.1): la función no devuelve nada porque siempre lanza o termina el proceso.
function debug(mixed $dato): void { var_dump($dato); }
function abortar(string $msg): never
{
throw new RuntimeException($msg);
}
First-class callable
Puedes pasar una función como objeto callable sin closures ni strings:
$fn = strlen(...); // first-class callable (8.1)
echo $fn('hola'); // 4
array_map(strtoupper(...), ['a', 'b']); // ['A', 'B']
str_contains, str_starts_with, str_ends_with
PHP 8 quitó por fin la necesidad de strpos(...) !== false:
$email = 'ana@example.com';
str_contains($email, '@'); // true
str_starts_with($email, 'ana'); // true
str_ends_with($email, '.com'); // true
// El clásico de PHP 5 que ya no necesitas
if (strpos($email, '@') !== false) { /* ... */ }
JIT y Opcache
- Opcache: cachea el bytecode compilado (los .php ya compilados) en memoria. Está activo en producción desde PHP 5.5.
- JIT (PHP 8.0): compila los hot paths de bytecode a código máquina en runtime. En la práctica beneficia CPU-bound; para backend típico (I/O), Opcache es lo que importa.
; php.ini
opcache.enable=1
opcache.memory_consumption=128
opcache.max_accelerated_files=10000
; JIT (solo PHP 8+)
opcache.jit=auto
opcache.jit_buffer_size=64M
💡 En un API/CRUD típico el JIT no cambia casi nada (el tiempo se va en I/O). Mide con
php -d opcache.jit=on script.phpcontra=offantes de tocar nada.
Composer y autoload PSR-4
Composer es el gestor de dependencias. El autoload PSR-4 mapea namespaces a directorios sin escanear archivos:
{
"name": "acme/app",
"require": {
"php": "^8.2",
"guzzlehttp/guzzle": "^7.0"
},
"autoload": {
"psr-4": {
"App\\": "src/"
}
}
}
src/
├── Usuario.php → namespace App; class Usuario
└── Dominio/
└── Repositorio.php → namespace App\Dominio; class Repositorio
composer install # instala dependencias
composer dump-autoload # regenera el autoloader (al añadir clases)
composer require laravel/framework
composer update guzzlehttp/guzzle
💡 En PSR-4 el namespace
App\Dominio\Repositoriose resuelve asrc/Dominio/Repositorio.phpautomáticamente. Cada clase en su propio archivo, con el namespace que coincida con la ruta.
Cheatsheet
| Feature | Versión | Una línea |
|---|---|---|
match, named args, attributes |
8.0 | expresión match que devuelve valor |
Union types, mixed, ?-> |
8.0 | int|string, nullsafe |
| Enums, readonly props, intersections | 8.1 | enum Rol: string |
| Readonly classes | 8.2 | readonly class Config |
| First-class callable | 8.1 | strlen(...) |
str_contains/starts/ends_with |
8.0 | adiós a strpos() !== false |
| JIT | 8.0 | bytecode → código máquina |
| Composer + PSR-4 | siempre | autoload por namespace |
Para profundizar
- PHP Manual — Language Reference: match, enums, readonly, attributes, documentados oficialmente.
- PHP 8.0 release notes y PHP 8.1 release notes: el changelog oficial explicado.
- PHP RFCs: donde se proponen y discuten los cambios del lenguaje.
- Composer — Autoloading (PSR-4): la referencia del autoload de Composer.
- PHP The Right Way: buenas prácticas modernas del ecosistema.
- Ruta completa: Backend con PHP.