Asignación masiva y validación en el código generado por IA
Hola, bienvenido. En esta lección vamos a corregir la edición de clientes de invoices. El modelo Client tiene $guarded = [] y ClientController@update guarda $request->all() sin pasar por un form request. Con eso, un formulario que envíe un campo user_id de más le cambia el dueño al cliente, y un nombre vacío llega hasta la base de datos. Vamos a escribir los tests que lo demuestran, a aplicar la solución con $fillable, un UpdateClientRequest y validated(), y a sacar de aquí una regla para el agente.
A diferencia de la lección anterior, aquí la autorización sí está. update comprueba con abort_unless que el cliente es del usuario antes de guardar, así que nadie puede editar un cliente ajeno. El fallo está en qué datos se guardan del cliente propio.
Qué es la asignación masiva
La asignación masiva es guardar en un modelo un array de datos de golpe, con create(), fill() o update(), en lugar de asignar los campos uno a uno. Es la forma habitual de guardar datos en Laravel, y por eso Eloquent protege los modelos por defecto: hasta que no le dices qué campos se pueden asignar así, no deja asignar ninguno. La documentación de Laravel explica el riesgo con un ejemplo muy claro: si la petición trae un campo que no esperabas, como is_admin, y ese array llega entero al modelo, el usuario acaba cambiando una columna que nunca le pusiste en el formulario.
Hay 2 formas de decirle a Eloquent qué se puede asignar. Con $fillable listas los campos permitidos, y el resto se descarta. Con $guarded listas los prohibidos, y el resto se permite. $guarded = [] es la lista de prohibidos vacía, es decir, todo permitido. Para ese caso la documentación de Laravel es muy concreta: si desproteges el modelo, tienes que construir a mano los arrays que le pasas a fill, create y update.
En invoices pasa justo lo contrario. Este es el update que escribió el agente:
$request->all() devuelve todo lo que trae la petición. El formulario envía name, email y tax_id, además de _token y _method, que Eloquent ignora porque empiezan por guion bajo. Pero cualquier otro campo que coincida con una columna de clients se escribe, y user_id es una columna de clients. Con $guarded = [] en el modelo, no hay nada que lo pare.
Fíjate en que store, en el mismo controlador, está bien hecho: usa StoreClientRequest, guarda $request->validated() y crea el cliente a través de la relación del usuario, así que el dueño lo pone Laravel. El agente lo resolvió bien al crear y no al editar, y es un buen ejemplo de por qué no basta con revisar un método y dar por bueno el resto.
¿Y por qué un agente escribe $guarded = []? Porque funciona a la primera. Con un modelo recién creado, el primer create() lanza una MassAssignmentException que pide añadir el campo a $fillable. Para que el test pase hay 2 caminos: listar los campos o vaciar $guarded, y el segundo no vuelve a fallar nunca, ni cuando se añade una columna. Con $fillable, en cambio, un campo nuevo que no está en la lista se descarta sin avisar, y un agente que ve que una columna no se guarda tiende a la misma salida. $request->all() viene del mismo sitio: no hay que acordarse de nada. Si quieres el concepto de asignación masiva con más calma, lo tienes en el artículo Asignación masiva, ¿cómo protegerse en Laravel? (se abre en una pestaña nueva).
- 02Los tests: el dueño no cambia y el nombre es obligatorio
- 03La solución: $fillable, UpdateClientRequest y validated()
- 04El circuito y la regla para el agente