Durante años, en PHP un estado se guardaba como
const PAGADO = 'pagado' y se pasaba por ahí como un string
cualquiera. Funcionaba hasta que alguien escribía 'pagao'. Desde PHP 8.1 el
lenguaje tiene enums: un tipo con una lista cerrada de valores que el
motor comprueba por ti. Vamos a verlos con ejemplos cortitos.
El problema: los strings mágicos
function cambiarEstado(string $estado) { /* ... */ }
cambiarEstado('pagado'); // bien
cambiarEstado('pagao'); // también «bien»… y es un bug
La función pide un string, así que cualquier string vale. El
error no aparece aquí, sino mucho después, cuando algo no encuentra el valor que
esperaba.
Tu primer enum
enum Estado
{
case Pendiente;
case Pagado;
case Enviado;
}
function cambiarEstado(Estado $estado) { /* ... */ }
cambiarEstado(Estado::Pagado); // bien
cambiarEstado('pagao'); // TypeError
Ahora la función solo acepta uno de los tres casos. Ni typos ni valores inventados, y el
editor te autocompleta Estado:: con las opciones válidas.
Para comparar, ===:
$estado = Estado::Pagado;
$estado === Estado::Pagado; // true
$estado->name; // 'Pagado'
Enums con valor (backed)
Si el estado tiene que guardarse en una base de datos o viajar en un JSON, necesitas un valor que lo represente. Se lo das así:
enum Estado: string
{
case Pendiente = 'pendiente';
case Pagado = 'pagado';
case Enviado = 'enviado';
}
Estado::Pagado->value; // 'pagado'
Y para ir al revés, del texto al caso:
Estado::from('pagado'); // Estado::Pagado
Estado::from('pagao'); // ValueError
Estado::tryFrom('pagado'); // Estado::Pagado
Estado::tryFrom('pagao'); // null
Usa from() cuando el valor tiene que ser válido, y
tryFrom() cuando viene de fuera (un formulario, la URL) y puede traer
cualquier cosa. Con ??
pones un valor por defecto en una línea:
$estado = Estado::tryFrom($_GET['estado'] ?? '') ?? Estado::Pendiente;
Y cases() te da todos los casos, por ejemplo para pintar un
<select>:
foreach (Estado::cases() as $estado) {
echo $estado->value; // pendiente, pagado, enviado
}
Los enums pueden tener métodos
Aquí está lo mejor: la lógica que depende del estado puede vivir dentro del propio enum.
enum Estado: string
{
case Pendiente = 'pendiente';
case Pagado = 'pagado';
case Enviado = 'enviado';
public function color(): string
{
return match ($this) {
Estado::Pendiente => 'gris',
Estado::Pagado => 'verde',
Estado::Enviado => 'azul',
};
}
}
Estado::Pagado->color(); // 'verde'
Fíjate en que el match no tiene default. Si mañana añades
case Cancelado y te olvidas de darle color, PHP lanza un error en vez de
devolver cualquier cosa en silencio. Mejor enterarte así.
Enums en Symfony y Doctrine
Todo el ecosistema los entiende sin que tengas que hacer nada especial.
Doctrine guarda el value en la columna y te devuelve el caso
al leer:
#[ORM\Column(enumType: Estado::class)]
private Estado $estado = Estado::Pendiente;
En una ruta, Symfony convierte el parámetro solo. Si el valor no existe, devuelve un 404:
#[Route('/pedidos/{estado}')]
public function listar(Estado $estado): Response
{
// /pedidos/pagado → Estado::Pagado
// /pedidos/pagao → 404
}
En un formulario, EnumType crea el desplegable con todos los
casos:
$builder->add('estado', EnumType::class, ['class' => Estado::class]);
Y en Twig llamas a sus métodos como a los de cualquier objeto:
<span class="badge {{ pedido.estado.color }}">{{ pedido.estado.value }}</span>
¿Con valor o sin él?
| Si el enum… | Usa |
|---|---|
| se guarda en base de datos o sale en una API | enum Estado: string |
| solo se usa dentro del código | enum Estado |
Ante la duda, con string: un 'pagado' en la base de datos se
entiende a simple vista; un 2, no.
Lo que se rompe si no lo sabes
- No sirven como clave de array.
$totales[Estado::Pagado]da error. Usa$totales[Estado::Pagado->value]. - No guardan datos. Un caso no puede tener propiedades que cambien. Para eso, una clase normal.
- Un enum sin valor no va a JSON.
json_encode(Estado::Pagado)devuelvefalsesi el enum no tiene valor. from()con datos del usuario es un error 500 esperando. Si viene de fuera,tryFrom().- Cambiar un
valuerompe los datos viejos. Las filas que ya tenían el valor antiguo dejan de poder leerse. Migra antes.
Y ya está
Si tienes un grupo de constantes con el mismo prefijo, o un string que en
realidad solo puede valer tres o cuatro cosas, ahí hay un enum esperando. Empieza por
uno y verás cuántos if y validaciones a mano desaparecen.