Tipa la entrada, y valídala igualmente
Las herramientas tienen dos descripciones de su entrada, y ambas son necesarias porque se dirigen a públicos distintos.
El esquema le dice al modelo qué enviar:
'options' => $schema->array()
->items($schema->object([
'label' => $schema->string()
->description('The answer itself, as the user would say it. Keep it to a few words.')
->required(),
'description' => $schema->string()
->description('One short line explaining the option. Optional, but it is what makes the card readable at a glance.'),
]))
->min(2)
->max(6)
->description('Two to six options you generated for this specific business. Order them most to least likely.')
->required(),
Fíjate en que cada campo lleva prosa, no sólo un tipo. ->description('Ordénalas de más a menos probable') no es comprobable por máquina y es exactamente el tipo de instrucción que mejora la salida. El esquema también es superficie de prompt —el argumento del capítulo 3, llegando donde más rinde.
Y luego validas igualmente, porque un esquema es una petición y no una garantía:
$validated = $request->validate([
'question' => ['required', 'string', 'max:160'],
'field' => ['sometimes', 'string', 'in:services,hours,other'],
'options' => ['required', 'array', 'min:2', 'max:6'],
'options.*.label' => ['required', 'string', 'max:60'],
'multi' => ['sometimes', 'boolean'],
]);
Las mismas reglas, dichas dos veces, en dos idiomas. El esquema es cómo pides; el validador es cómo insistes. Sáltate el segundo y una carga con buena pinta, con siete opciones y una etiqueta de doscientos caracteres, llega a tu capa de vistas.
Trata la frontera exactamente como una frontera HTTP —porque lo es. Sólo que el llamante es raro.
Los argumentos planos ganan a los anidados
Esta es la lección más portable del capítulo.
Un sitio puede ser un negocio o un evento, y los eventos necesitan inicio, fin y zona horaria. El modelado natural es un objeto anidado:
event: { start_at, end_at, timezone, venue: { address, phone } }
Los modelos emiten eso de forma poco fiable. Se saltan un nivel, invierten el anidamiento o ponen venue arriba del todo. Así que la herramienta lo toma plano:
// Event-only. Taken flat and assembled into the nested `event` object below —
// flat arguments are far more reliable for a model to emit correctly than
// nested JSON.
'event_start_at' => ['required_if:category,event', 'date'],
'event_end_at' => ['required_if:category,event', 'date', 'after_or_equal:event_start_at'],
'event_timezone' => ['required_if:category,event', 'string', 'timezone'],
'event_venue_address' => ['sometimes', 'string', 'max:500'],
…y monta la forma anidada en el servidor, donde es una función pura de la entrada validada y no puede salir mal.
La regla: haz que el trabajo del modelo sea lo más plano posible, y hazte tú el trabajo estructural. Una lista plana de escalares bien nombrados con descripciones claras es algo que un modelo acierta casi siempre. Cualquier cosa más profunda es un impuesto de fiabilidad que eliges pagar.
required_if:category,event es la otra mitad. Los requisitos condicionales permiten que una herramienta sirva a las dos formas sin partirse en dos herramientas que luego habría que describir, ordenar y mantener.