Enums en Laravel 13: Tipado Fuerte sin Complejidad
Introducción
Los enumerados (Enums) son una de las características más poderosas de PHP 8.1+ que Laravel ha adoptado plenamente en su versión 13. Si aún no los utilizas en tus proyectos, te estás perdiendo la oportunidad de escribir código más seguro, autocompletable y fácil de mantener.
En este artículo exploraremos cómo integrar Enums en tus aplicaciones Laravel para evitar errores en tiempo de ejecución, mejorar la documentación implícita del código y reducir bugs difíciles de detectar.
¿Qué son los Enums y por qué importan?
El problema tradicional
Antes de los Enums, la mayoría de desarrolladores Laravel usaban constantes de clase o simples strings para representar estados o categorías:
class Order
{
const STATUS_PENDING = 'pending';
const STATUS_PROCESSING = 'processing';
const STATUS_COMPLETED = 'completed';
const STATUS_FAILED = 'failed';
public function getStatus()
{
return 'pending'; // ¿Es válido este valor?
}
}
Los problemas:
- No hay validación:
'pneding'(typo) se acepta sin error - IDE autocomplete limitado
- Sin type hints claros
- Mantener una lista de valores válidos es manual
La solución: Enums
Los Enums en PHP 8.1+ resuelven estos problemas proporcionando un tipo seguro:
enum OrderStatus: string
{
case Pending = 'pending';
case Processing = 'processing';
case Completed = 'completed';
case Failed = 'failed';
}
Tipos de Enums en Laravel
Enums “puros” (backed Enums con strings)
El tipo más común en Laravel, ideal para valores que se guardan en base de datos:
enum PaymentMethod: string
{
case CreditCard = 'credit_card';
case PayPal = 'paypal';
case BankTransfer = 'bank_transfer';
case Cryptocurrency = 'crypto';
}
Enums con valores enteros
Útil cuando trabajas con códigos numéricos:
enum HttpStatus: int
{
case OK = 200;
case Created = 201;
case BadRequest = 400;
case Unauthorized = 401;
case NotFound = 404;
case ServerError = 500;
}
Enums puros (sin backing)
Para cuando solo necesitas enumerar opciones sin valores específicos:
enum UserRole
{
case Admin;
case Editor;
case Viewer;
}
Integrando Enums en Modelos Eloquent
Casting automático
Laravel 13 permite castear automáticamente columnas a Enums:
<?php
namespace App\Models;
use App\Enums\OrderStatus;
use App\Enums\PaymentMethod;
use Illuminate\Database\Eloquent\Model;
class Order extends Model
{
protected $fillable = ['status', 'payment_method', 'total'];
protected $casts = [
'status' => OrderStatus::class,
'payment_method' => PaymentMethod::class,
'created_at' => 'datetime',
];
}
Ventajas:
- Acceso automático como objeto Enum, no string
- Type hints en el IDE
- Validación implícita en migraciones
Usando Enums con type hints
class OrderController extends Controller
{
public function store(Request $request)
{
$order = Order::create([
'status' => OrderStatus::Pending,
'payment_method' => PaymentMethod::CreditCard,
'total' => $request->total,
]);
return $order;
}
public function updateStatus(Order $order, OrderStatus $status)
{
$order->update(['status' => $status]);
return $order;
}
}
Métodos útiles en Enums
Acceder al valor backing
$status = OrderStatus::Pending;
// Obtener el valor de base de datos
echo $status->value; // 'pending'
// Obtener el nombre del caso
echo $status->name; // 'Pending'
Listar todos los casos
// Obtener todos los casos
$allStatuses = OrderStatus::cases();
// Resultado:
// [
// OrderStatus::Pending,
// OrderStatus::Processing,
// OrderStatus::Completed,
// OrderStatus::Failed,
// ]
Métodos personalizados en Enums
Puedes agregar métodos a tus Enums como en cualquier clase:
enum OrderStatus: string
{
case Pending = 'pending';
case Processing = 'processing';
case Completed = 'completed';
case Failed = 'failed';
public function label(): string
{
return match($this) {
self::Pending => 'Pendiente de pago',
self::Processing => 'En proceso',
self::Completed => 'Completada',
self::Failed => 'Falló',
};
}
public function isFinal(): bool
{
return in_array($this, [self::Completed, self::Failed]);
}
public function color(): string
{
return match($this) {
self::Pending => 'yellow',
self::Processing => 'blue',
self::Completed => 'green',
self::Failed => 'red',
};
}
}
Uso en controladores:
$order = Order::find(1);
echo $order->status->label(); // "Completada"
echo $order->status->color(); // "green"
if ($order->status->isFinal()) {
// Actualizar inventario, enviar notificación, etc.
}
Validación de Enums en formularios
Validar que una entrada sea un Enum válido
use Illuminate\Validation\Rules\Enum;
class StoreOrderRequest extends FormRequest
{
public function rules()
{
return [
'status' => ['required', new Enum(OrderStatus::class)],
'payment_method' => ['required', new Enum(PaymentMethod::class)],
'total' => ['required', 'numeric', 'min:0.01'],
];
}
}
Validación personalizada
class UpdateOrderRequest extends FormRequest
{
public function rules()
{
return [
'status' => [
'required',
new Enum(OrderStatus::class),
function ($attribute, $value, $fail) {
// Lógica de negocio: solo ciertos cambios de estado son válidos
$currentStatus = $this->order->status;
if ($currentStatus === OrderStatus::Completed) {
$fail('No puedes cambiar el estado de una orden completada.');
}
},
],
];
}
}
Trabajar con Enums en migraciones y seeders
Crear tabla con Enum
Schema::create('orders', function (Blueprint $table) {
$table->id();
$table->string('status')
->default(OrderStatus::Pending->value)
->index();
$table->string('payment_method');
$table->decimal('total', 10, 2);
$table->timestamps();
});
Seeder con Enums
<?php
namespace Database\Seeders;
use App\Enums\OrderStatus;
use App\Enums\PaymentMethod;
use App\Models\Order;
use Illuminate\Database\Seeder;
class OrderSeeder extends Seeder
{
public function run()
{
Order::factory()
->count(50)
->create([
'status' => OrderStatus::cases()[array_rand(OrderStatus::cases())],
'payment_method' => PaymentMethod::cases()[array_rand(PaymentMethod::cases())],
]);
}
}
Enums en Queries y Filtros
Filtrar por Enum en Eloquent
// Obtener todas las órdenes pendientes
$pendingOrders = Order::where('status', OrderStatus::Pending)->get();
// O más limpio:
$pendingOrders = Order::where('status', OrderStatus::Pending->value)->get();
// Con whereIn para múltiples valores
$inactiveOrders = Order::whereIn('status', [
OrderStatus::Failed->value,
OrderStatus::Completed->value,
])->get();
Scopes con Enums
class Order extends Model
{
public function scopeByStatus(Builder $query, OrderStatus $status)
{
return $query->where('status', $status);
}
public function scopeActive(Builder $query)
{
return $query->whereIn('status', [
OrderStatus::Pending->value,
OrderStatus::Processing->value,
]);
}
}
// Uso:
$pending = Order::byStatus(OrderStatus::Pending)->get();
$active = Order::active()->get();
Serialización JSON y APIs
Respuestas API con Enums
class OrderResource extends JsonResource
{
public function toArray($request)
{
return [
'id' => $this->id,
'status' => $this->status->value, // Convertir a string para JSON
'status_label' => $this->status->label(),
'payment_method' => $this->payment_method->value,
'total' => $this->total,
'created_at' => $this->created_at,
];
}
}
Request binding automático en rutas API
Laravel 13 puede vincular automáticamente Enums en route model binding:
Route::apiResource('orders', OrderController::class);
// En OrderController:
public function show(Order $order, OrderStatus $status)
{
// $status se convierte automáticamente desde la URL
}
Mejores prácticas
1. Organiza Enums en una carpeta dedicada
app/
├── Enums/
│ ├── OrderStatus.php
│ ├── PaymentMethod.php
│ ├── UserRole.php
│ └── NotificationType.php
2. Documenta los Enums con comentarios
/**
* Estados posibles de una orden
*
* @method static self Pending()
* @method static self Processing()
* @method static self Completed()
* @method static self Failed()
*/
enum OrderStatus: string
{
case Pending = 'pending';
case Processing = 'processing';
case Completed = 'completed';
case Failed = 'failed';
}
3. Usa Enums para constantes de negocio
En lugar de constantes dispersas en la aplicación, centraliza en Enums:
// ❌ Malo
class OrderService
{
const PENDING = 'pending';
const COMPLETED = 'completed';
}
// ✅ Bien
class OrderService
{
public function process(OrderStatus $status)
{
// Type hint seguro
}
}
4. Combina Enums con métodos para lógica de negocio
enum OrderStatus: string
{
case Pending = 'pending';
case Processing = 'processing';
case Completed = 'completed';
case Failed = 'failed';
public function canTransitionTo(OrderStatus $target): bool
{
return match($this) {
self::Pending => in_array($target, [self::Processing, self::Failed]),
self::Processing => in_array($target, [self::Completed, self::Failed]),
self::Completed => false,
self::Failed => false,
};
}
}
// Uso:
if ($order->status->canTransitionTo($newStatus)) {
$order->update(['status' => $newStatus]);
}
Enums en Laravel 13: Nuevas características
Laravel 13 introdujo soporte mejorado para Enums en varias áreas:
Soporte en LazyCollection
$statuses = collect(OrderStatus::cases())
->lazy()
->keyBy(fn($enum) => $enum->value)
->map(fn($enum) => $enum->label());
Soporte en ConcurrencyManager
// Los Enums funcionan mejor con validación de concurrencia
$concurrency = new ConcurrencyManager(
driver: ConcurrencyDriver::Process,
limit: 5
);
Conclusión
Los Enums son un cambio de juego para la seguridad de tipos en Laravel. Al usarlos correctamente, reduces bugs en tiempo de ejecución, mejoras el autocompletado del IDE y haces tu código más legible y mantenible.
Las ventajas se multiplican en proyectos medianos y grandes donde hay múltiples desarrolladores, ya que los Enums sirven como documentación ejecutable del código.
Comienza integrando Enums en nuevas features de tu aplicación y considera refactorizar código antiguo que use constantes de clase o strings mágicos.
Puntos clave
- Los Enums proporcionan tipado seguro para valores específicos, evitando typos y valores inválidos
- Laravel 13 integra Enums nativamente en modelos, validación y rutas
- Usa
protected $castspara convertir automáticamente columnas de base de datos a Enums - Agrega métodos a Enums para centralizar lógica de negocio relacionada con estados
- Valida con
new Enum(EnumClass::class)en tus Form Requests - Los Enums mejoran el autocompletado del IDE y sirven como documentación viva
- Organiza Enums en la carpeta
app/Enumspara mantener el proyecto estructurado - Combina Enums con scopes y validadores personalizados para máxima seguridad
- Accede al valor con
->valuey al nombre con->namecuando sea necesario - Los Enums funcionan perfectamente con APIs JSON si los conviertes a strings explícitamente