Todo el mundo va a hablar del SDK de IA y de las passkeys, y es comprensible, porque lucen mucho en una demo. Pero la parte de Laravel 13 a la que sigo dando vueltas es bastante menos glamurosa: la release trae dos respuestas contradictorias a la misma pregunta, es decir, dónde se supone que debe vivir tu configuración. Ahora puedes decorar un job en cola con un atributo #[WithQueue] que declara su conexión, el nombre de la cola, los tries y el timeout directamente en la clase. O puedes dejar la clase limpia y declarar todo eso en un service provider con Queue::route(), mapeando clases de jobs a conexiones y colas en un único lugar central. Los mismos ajustes, dos casas, y el framework no tiene opinión sobre cuál eliges. Así que te ofrezco la mía.
<?php
// inside AppServiceProvider::boot()
use App\Jobs\GeneratePdfReport;
use App\Jobs\ProcessPodcast;
use Illuminate\Support\Facades\Queue;
Queue::route(ProcessPodcast::class, connection: 'redis', queue: 'media');
Queue::route(GeneratePdfReport::class, connection: 'sqs', queue: 'exports');Primero, reconozcamos lo que hay que reconocer: la historia de actualización es realmente buena. Si estás en PHP 8.3 o superior, el equipo core promete cero cambios rompedores en el código de tu aplicación y una actualización que cabe en lo que dura un café. Los atributos ahora funcionan en más de quince sitios del framework, así que los modelos de Eloquent pueden llevar #[Table], #[Fillable] y #[Hidden] en lugar de una pila de propiedades protected, y todo es opcional. Tus modelos existentes siguen funcionando sin tocarlos. Esa contención merece un aplauso, porque significa que las decisiones interesantes de esta release son cuestiones de estilo que puedes tomar con calma, y no migraciones forzadas por una fecha límite.
Esta es mi regla, y la defendería en cualquier code review: las decisiones de despliegue van en el provider, la forma del dominio va en la clase. En qué conexión corre un job, si las exportaciones a PDF acaban en SQS mientras el procesamiento de media se queda en Redis, eso cambia cuando cambia tu situación de operaciones, no cuando cambia tu lógica de negocio. Enrutarlo de forma centralizada con Queue::route() significa que el día que muevas las exportaciones a otro backend, tocas un provider y cero clases de jobs, y cada punto de despacho sigue siendo un simple ProcessPodcast::dispatch($podcast). La lista de fillable de un modelo, en cambio, es intrínseca a lo que esa cosa es. Pertenece a la clase, y la sintaxis de atributos por fin hace que se lea como una declaración y no como ruido.
El contraargumento honesto es la descubribilidad, y me he quemado con ella en otros ecosistemas. Cuando alguien nuevo en el equipo abre GeneratePdfReport y no ve nada sobre dónde se ejecuta, tiene que saber que el provider existe. Cualquiera que haya espeleologueado por una base de código de Spring buscando el único fichero XML que lo explica todo conoce ese dolor, y la versión con atributos mantiene toda la historia localizable con un grep en un solo archivo. Aun así me quedo con el enrutado central para la infra, porque el modo de fallo de la configuración dispersa es peor: cincuenta clases de jobs cada una con su propia cadena de conexión son cincuenta sitios que olvidar cuando migras colas, y he visto exactamente eso producir un incidente de viernes en el que la mitad de los jobs se fueron al clúster viejo.
La joya escondida de la release, para mi gusto, es Cache::touch(). Extender el TTL de una clave antes significaba leer el valor y volver a escribirlo, dos viajes de red más serialización, todo para actualizar un timestamp. Ahora Cache::touch('user_session:123', 3600) envía a Redis un único EXPIRE, ataca Memcached con su TOUCH nativo y ejecuta un simple UPDATE en el driver de base de datos. Si alguna vez has construido un rate limiter de ventana deslizante o mantenido caliente una métrica de un dashboard, sabes lo a menudo que aparece este patrón. Es una API pequeña que hace una sola cosa y encaja limpiamente con lo que cada store puede hacer de verdad. Más de esto, por favor.
Las novedades más vistosas también son reales, solo dudo que vayan a transformar tu diff diario tanto como la cuestión de la configuración. El Laravel AI SDK pasa a estable junto con la release, así que con PostgreSQL y pgvector tienes whereVectorSimilarTo() directamente en el query builder, con generación de embeddings conectada a OpenAI, Anthropic u Ollama. El login con passkeys vía WebAuthn ahora viene incluido en Fortify y en los starter kits, y como la clave privada nunca sale del dispositivo del usuario, el credential stuffing contra tu endpoint de login deja de ser una amenaza que le compense al atacante. Suma el driver de base de datos de Reverb para escalar websockets sobre MySQL o PostgreSQL sin una caja de Redis, Http::pool() con una concurrencia por defecto de 2 en lugar de arrastrarse en serie, y los teams de vuelta en los starter kits con enrutado aislado por pestaña, de modo que cambiar de equipo en una pestaña del navegador no corrompe otra.
Un apunte al margen sobre los atributos, porque es fácil pasar por alto lo lejos que ha llegado PHP aquí. Los desarrolladores de Go llevan una década embutiendo metadatos en struct tags, cadenas crudas que el compilador ignora alegremente hasta el runtime. Los atributos de PHP son clases de verdad con argumentos con nombre, tipados, autocompletables, refactorizables. Que Laravel apueste por ellos en todo el framework es menos una funcionalidad de Laravel que el ecosistema por fin cobrando un cheque que el lenguaje firmó allá por PHP 8.0. Ha tardado, pero la sintaxis que nos llega es más agradable que la que aceptaron varios lenguajes supuestamente más modernos.
Así que esa es mi línea: providers para todo lo que una decisión de operaciones pueda cambiar, atributos para todo lo que defina el propio objeto de dominio. Pero conozco equipos que rechazarán la división de plano y estandarizarán un solo estilo solo para simplificar las reviews, y no puedo decir que se equivoquen. ¿Tú dónde te posicionas? ¿Enrutas los jobs de forma centralizada, los decoras con #[WithQueue], o estás escribiendo una regla de Pint para prohibir la opción que perdió la discusión en tu standup? Cuéntamelo abajo, sobre todo si ya has probado a mezclar las dos en una misma base de código.




Comentarios
Aún no hay comentarios — escribe el primero.
Inicia la conversación
Sin cuenta ni contraseña — introduce tu correo y te enviamos un enlace de acceso de un solo uso. ¿Primera vez? Todo se configura automáticamente.
Tu valoración se aplicará automáticamente al iniciar sesión.
Revisa tu bandeja de entrada
Hemos enviado un enlace de acceso a …. Ábrelo en este dispositivo — esta pestaña te conectará automáticamente.
¿No llega nada? Mira en la carpeta de spam — y marca el correo como «No es spam» para que la próxima vez llegue directo.