Ir al contenido principal
Laravel, shipping fast.

Dos campos y tres reglas bastan para una licencia. Las peticiones reales se complican, y los FormRequests tienen un lugar para cada tipo de complicación. Conocerlos es lo que impide que la validación vuelva a colarse en el controlador.

Limpia primero la entrada

Los consumidores envían " Example.COM ". Tú quieres example.com, y quieres que las reglas juzguen el valor ya limpio. Laravel ha hecho la mitad antes de que tu código se ejecute: su middleware TrimStrings recorta todas las cadenas de todas las peticiones. Pasarlo a minúsculas te toca a ti:

// app/Http/Requests/StoreLicenseRequest.php
protected function prepareForValidation(): void
{
    if (is_string($this->domain)) {
        $this->merge([
            'domain' => Str::lower($this->domain),
        ]);
    }
}

prepareForValidation() se ejecuta antes que las reglas. Lo que fusione es lo que se valida, y lo que validated() devuelve después. La comprobación is_string importa: un consumidor puede enviar un array donde esperabas texto, y deben rechazarlo las reglas, no un error de tipos. Normalizar aquí, una sola vez, significa que el controlador, el driver y la clave de caché ven todos la misma cadena.

Arrays y campos anidados

El Capítulo 18 añade un endpoint que crea varias licencias a la vez. Las reglas alcanzan cada elemento con *:

// app/Http/Requests/StoreLicenseBatchRequest.php
return [
    'licenses' => ['required', 'array', 'min:1', 'max:100'],
    'licenses.*.name' => ['required', 'string', 'max:100'],
    'licenses.*.domain' => [
        'required', 'string', 'max:100', 'distinct',
    ],
];

distinct rechaza una petición que lista el mismo dominio dos veces. Un error en el tercer elemento vuelve con la clave licenses.2.domain, así que el consumidor sabe exactamente qué elemento corregir. Y el max:100 sobre el propio array no es opcional: un array sin techo es una entrada cuyo costo lo decide quien llama.

Una regla propia

Cuando una comprobación no está en la lista de Laravel, no la escribas suelta en el request. Dale un nombre:

php artisan make:rule RegistrableDomain
// app/Rules/RegistrableDomain.php
class RegistrableDomain implements ValidationRule
{
    private const PATTERN = '/^([a-z0-9-]+\.)+[a-z]{2,}$/';

    public function validate(
        string $attribute,
        mixed $value,
        Closure $fail,
    ): void {
        $valid = is_string($value)
            && preg_match(self::PATTERN, $value) === 1;

        if (! $valid) {
            $fail('validation.domain')->translate();
        }
    }
}

Un objeto regla se reutiliza entre requests, se prueba por separado, y su mensaje pasa por los mismos archivos de idioma que cualquier regla incorporada. Úsalo como new RegistrableDomain en el array de reglas. (Documentación de Laravel: Validation › Custom Validation Rules.)

Reglas que dependen de otros campos

Algunos campos son obligatorios solo a causa de otro. En el Capítulo 18 a un consumidor se le puede asignar un webhook, y un consumidor con webhook debe tener también una dirección de contacto, para que se pueda avisar a alguien cuando las entregas fallan:

// app/Http/Requests/UpdateConsumerRequest.php, en rules()
'webhook_url' => ['nullable', 'url:https'],
'contact_email' => ['required_with:webhook_url', 'email'],

El Capítulo 18 añade dos reglas más a la primera de esas líneas: un límite de longitud y una comprobación de que la dirección es pública.

Laravel tiene una familia de estas reglas: required_with, required_if, prohibited_unless, exclude_if, y Rule::when() para cualquier cosa condicional. Recurre a ellas antes de escribir código. Cuando una comprobación sí necesita código, el método after() de un FormRequest devuelve closures que se ejecutan después de las reglas de los campos, hayan pasado o no, y añaden sus errores al mismo 422. (Documentación de Laravel: Validation › Conditionally Adding Rules y Validation › Performing Additional Validation.)

Reserva ambas cosas para las comprobaciones que tienen que ver con si la petición es aceptable. Si una comprobación es en realidad una regla de negocio, una que puede fallar por razones que el consumidor no puede arreglar editando la petición, lanza una excepción desde el código que hace el trabajo, como hace el Capítulo 7. La prueba es esta: ¿podría el consumidor corregirlo enviando algo distinto? Si la respuesta es sí, es validación.

Detenerse en el primer fallo, a veces

Por defecto Laravel ejecuta todas las reglas e informa de todos los fallos, que es lo que quiere un consumidor: corregirlo todo en una sola ronda. Cuando una regla posterior es cara (una consulta a la base de datos, por ejemplo), pon bail al principio de ese campo, y el resto se omite en cuanto una falla. El Capítulo 5 da a los consumidores nombres que deben ser únicos, con esta regla:

// app/Http/Requests/StoreConsumerRequest.php, en rules()
'name' => [
    'bail', 'required', 'string', 'max:100',
    Rule::unique('consumers', 'name'),
],

La consulta de unicidad ahora solo se ejecuta para valores que al menos estaban presentes, eran una cadena y tenían una longitud aceptable.

No se pudo cargar el audio. Inténtalo de nuevo en un momento.