Lazy Enums en Laravel: Optimiza tu Aplicación con Enumeraciones Perezosas
Lazy Enums en Laravel: Optimiza tu Aplicación con Enumeraciones Perezosas
Los enums en PHP y Laravel se han convertido en una herramienta fundamental para escribir código más seguro y tipado. Sin embargo, cuando trabajas con enumeraciones que contienen datos asociados complejos, cargar todo en memoria puede ser ineficiente. En este artículo, te mostraré cómo implementar Lazy Enums para cargar datos solo cuando realmente los necesites, mejorando significativamente el rendimiento de tu aplicación.
¿Qué son los Lazy Enums y por qué importan?
Un Lazy Enum es una enumeración que difiere la carga de sus datos asociados hasta el momento exacto en que se accede a ellos. En lugar de cargar toda la información en memoria cuando se instancia el enum, solo se carga lo necesario.
El problema que resuelven
Imagina un enum con múltiples casos, cada uno con datos asociados complejos:
enum OrderStatus: string
{
case Pending = 'pending';
case Processing = 'processing';
case Shipped = 'shipped';
case Delivered = 'delivered';
public function label(): string
{
return match($this) {
self::Pending => 'Pedido Pendiente',
self::Processing => 'En Procesamiento',
self::Shipped => 'Enviado',
self::Delivered => 'Entregado',
};
}
public function color(): string
{
return match($this) {
self::Pending => '#fbbf24',
self::Processing => '#60a5fa',
self::Shipped => '#a78bfa',
self::Delivered => '#34d399',
};
}
public function description(): string
{
return match($this) {
self::Pending => 'Tu pedido está esperando confirmación',
self::Processing => 'Estamos preparando tu pedido',
self::Shipped => 'Tu pedido está en camino',
self::Delivered => 'Tu pedido ha llegado',
};
}
}
Cada vez que usas OrderStatus::cases() o iteras sobre todos los casos, se cargan y calculan todos estos datos, aunque solo necesites acceder a algunos.
Implementando Lazy Enums en Laravel
La mejor práctica es crear un trait que implemente la lógica perezosa:
<?php
namespace App\Enums;
trait LazyEnumData
{
private static array $cache = [];
public static function lazyData(self $case): array
{
$key = static::class . '::' . $case->value;
if (!isset(self::$cache[$key])) {
self::$cache[$key] = static::loadData($case);
}
return self::$cache[$key];
}
abstract protected static function loadData(self $case): array;
}
Ahora creamos nuestro enum optimizado:
<?php
namespace App\Enums;
enum OrderStatus: string
{
use LazyEnumData;
case Pending = 'pending';
case Processing = 'processing';
case Shipped = 'shipped';
case Delivered = 'delivered';
protected static function loadData(self $case): array
{
return match($case) {
self::Pending => [
'label' => 'Pedido Pendiente',
'color' => '#fbbf24',
'description' => 'Tu pedido está esperando confirmación',
'icon' => 'clock',
],
self::Processing => [
'label' => 'En Procesamiento',
'color' => '#60a5fa',
'description' => 'Estamos preparando tu pedido',
'icon' => 'box',
],
self::Shipped => [
'label' => 'Enviado',
'color' => '#a78bfa',
'description' => 'Tu pedido está en camino',
'icon' => 'truck',
],
self::Delivered => [
'label' => 'Entregado',
'color' => '#34d399',
'description' => 'Tu pedido ha llegado',
'icon' => 'check-circle',
],
};
}
public function label(): string
{
return self::lazyData($this)['label'];
}
public function color(): string
{
return self::lazyData($this)['color'];
}
public function description(): string
{
return self::lazyData($this)['description'];
}
public function icon(): string
{
return self::lazyData($this)['icon'];
}
}
Casos de uso prácticos en una aplicación real
Usando Lazy Enums en modelos Eloquent
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use App\Enums\OrderStatus;
class Order extends Model
{
protected $casts = [
'status' => OrderStatus::class,
];
public function getStatusLabelAttribute(): string
{
return $this->status->label();
}
public function getStatusColorAttribute(): string
{
return $this->status->color();
}
}
En controladores y vistas
<?php
namespace App\Http\Controllers;
use App\Models\Order;
use Illuminate\View\View;
class OrderController extends Controller
{
public function show(Order $order): View
{
// Solo carga los datos del status cuando los necesita
return view('orders.show', [
'order' => $order,
'statusLabel' => $order->status->label(),
'statusColor' => $order->status->color(),
'statusIcon' => $order->status->icon(),
]);
}
public function index(): View
{
// Aquí no se cargan los datos de los status hasta renderizar la vista
$orders = Order::paginate();
return view('orders.index', compact('orders'));
}
}
En tus vistas Blade
<div class="orders-list">
@foreach($orders as $order)
<div class="order-card">
<span class="badge" style="background-color: {{ $order->status->color() }}">
<i class="icon-{{ $order->status->icon() }}"></i>
{{ $order->status->label() }}
</span>
<p>{{ $order->status->description() }}</p>
<p class="order-id">#{{ $order->id }}</p>
</div>
@endforeach
</div>
Lazy Enums con datos de base de datos
Para casos más complejos, puedes cargar datos desde la base de datos:
<?php
namespace App\Enums;
use Illuminate\Support\Facades\Cache;
trait LazyDatabaseEnumData
{
private static array $cache = [];
public static function lazyData(self $case): array
{
$key = static::class . '::' . $case->value;
if (!isset(self::$cache[$key])) {
self::$cache[$key] = Cache::remember(
"enum.{$key}",
now()->addDay(),
fn () => static::loadData($case)
);
}
return self::$cache[$key];
}
abstract protected static function loadData(self $case): array;
}
Implementación con base de datos:
<?php
namespace App\Enums;
use App\Models\StatusConfiguration;
enum PaymentMethod: string
{
use LazyDatabaseEnumData;
case CreditCard = 'credit_card';
case BankTransfer = 'bank_transfer';
case PayPal = 'paypal';
case Cryptocurrency = 'crypto';
protected static function loadData(self $case): array
{
$config = StatusConfiguration::where('key', $case->value)->first();
return [
'label' => $config?->label ?? ucfirst(str_replace('_', ' ', $case->value)),
'description' => $config?->description ?? '',
'fee' => $config?->fee ?? 0,
'enabled' => $config?->enabled ?? false,
'processing_time' => $config?->processing_time ?? null,
];
}
public function label(): string
{
return self::lazyData($this)['label'];
}
public function fee(): float
{
return self::lazyData($this)['fee'];
}
public function isEnabled(): bool
{
return self::lazyData($this)['enabled'];
}
public function processingTime(): ?string
{
return self::lazyData($this)['processing_time'];
}
}
Comparación de rendimiento
Veamos la diferencia en un benchmark real:
<?php
namespace App\Commands;
use App\Enums\OrderStatus;
use Illuminate\Console\Command;
class BenchmarkLazyEnums extends Command
{
protected $signature = 'benchmark:lazy-enums';
public function handle()
{
$iterations = 10000;
// Sin Lazy Enums - Carga todo siempre
$startWithout = microtime(true);
for ($i = 0; $i < $iterations; $i++) {
$status = OrderStatus::Shipped;
$label = $status->label();
$color = $status->color();
$description = $status->description();
}
$timeWithout = microtime(true) - $startWithout;
// Con Lazy Enums - Carga solo lo necesario
$startWith = microtime(true);
for ($i = 0; $i < $iterations; $i++) {
$status = OrderStatus::Shipped;
$label = $status->label(); // Solo se calcula esto
}
$timeWith = microtime(true) - $startWith;
$this->info("Sin optimización: {$timeWithout}s");
$this->info("Con Lazy Enums: {$timeWith}s");
$this->info("Mejora: " . round(($timeWithout / $timeWith - 1) * 100, 2) . "%");
}
}
Lazy Enums en APIs y Resources
Cuando usas Lazy Enums en API Resources, obtienen automáticamente mejor rendimiento:
<?php
namespace App\Http\Resources;
use Illuminate\Http\Request;
use Illuminate\Http\Resources\Json\JsonResource;
class OrderResource extends JsonResource
{
public function toArray(Request $request): array
{
return [
'id' => $this->id,
'number' => $this->number,
'status' => [
'value' => $this->status->value,
'label' => $this->status->label(),
'color' => $this->status->color(),
'icon' => $this->status->icon(),
],
'total' => $this->total,
'created_at' => $this->created_at,
];
}
}
Mejores prácticas al usar Lazy Enums
1. Mantén los datos simples y predecibles
// ✅ Bien: Datos simples y claros
protected static function loadData(self $case): array
{
return match($case) {
self::Active => [
'label' => 'Activo',
'color' => '#10b981',
],
self::Inactive => [
'label' => 'Inactivo',
'color' => '#6b7280',
],
};
}
// ❌ Evita: Cálculos complejos en loadData
protected static function loadData(self $case): array
{
return match($case) {
self::Active => [
'label' => User::where('status', 'active')->count(),
'queries' => User::where('status', 'active')->get(),
],
};
}
2. Usa métodos privados para datos auxiliares
public function getTranslatedLabel(string $locale = 'es'): string
{
$labels = $this->getTranslations();
return $labels[$locale] ?? $this->label();
}
private function getTranslations(): array
{
return [
'es' => $this->label(),
'en' => $this->englishLabel(),
'fr' => $this->frenchLabel(),
];
}
3. Combina con Enums Backed para máxima tipado
<?php
namespace App\Enums;
enum UserRole: string
{
use LazyEnumData;
case Admin = 'admin';
case Manager = 'manager';
case User = 'user';
protected static function loadData(self $case): array
{
return match($case) {
self::Admin => [
'permissions' => ['read', 'write', 'delete', 'manage_users'],
'description' => 'Acceso total a la aplicación',
],
self::Manager => [
'permissions' => ['read', 'write', 'delete'],
'description' => 'Gestión de recursos',
],
self::User => [
'permissions' => ['read'],
'description' => 'Solo lectura',
],
};
}
public function hasPermission(string $permission): bool
{
return in_array($permission, self::lazyData($this)['permissions']);
}
}
Puntos clave
- Los Lazy Enums cargan datos solo cuando se accede, no cuando se instancian, mejorando el rendimiento
- El caché en memoria evita recalcular los mismos datos múltiples veces en la misma request
- Combina con Cache::remember() para persistir datos entre requests cuando sea necesario
- Mantén los datos simples: usa Lazy Enums para métodos y propiedades, no para cálculos complejos
- Funciona perfecto en Eloquent: el casting automático + métodos perezosos es una combinación poderosa
- Ideal para enumeraciones con muchos casos: cuantos más casos tenga tu enum, más se beneficia de la carga perezosa
- Tipo-seguro: PHP valida la existencia de los casos en tiempo de compilación
- Compatible con APIs y Resources: renderiza JSON optimizado sin cargar datos innecesarios
- Usa junto con Blade: aprovecha los métodos en tus vistas sin preocuparte por el rendimiento
- Extiende con traits reutilizables: crea patterns reutilizables para diferentes tipos de datos