Flag of Ukraine
SymfonyCasts stands united with the people of Ukraine

Validación

Keep on Learning!

If you liked what you've learned so far, dive in!
Subscribe to get access to this tutorial plus
video, code and script downloads.

Start your All-Access Pass
Buy just this tutorial for $12.00

With a Subscription, click any sentence in the script to jump to that part of the video!

Login Subscribe

Hay un montón de formas diferentes en las que un cliente de la API puede enviar datos erróneos: puede enviar un JSON mal formado... o enviar un campo title en blanco... quizás porque un usuario se olvidó de rellenar un campo en el frontend. El trabajo de nuestra API es responder a todas estas situaciones de forma informativa y coherente, de modo que los errores se puedan entender, analizar y comunicar fácilmente a los humanos.

Manejo de JSON inválido

Ésta es una de las áreas en las que la Plataforma API realmente destaca. Hagamos algunos experimentos: ¿qué ocurre si enviamos accidentalmente un JSON no válido? Elimina la última llave.

¡Pruébalo! ¡Woh! ¡Qué bien! Esto nos devuelve un nuevo "tipo" de recurso: un hydra:error. Si un cliente de la API entiende a Hydra, sabrá al instante que esta respuesta contiene detalles del error. E incluso si alguien no ha oído hablar nunca de Hydra, ésta es una respuesta súper clara. Y, lo más importante, todos los errores tienen la misma estructura.

El código de estado también es 400 -lo que significa que el cliente ha cometido un error en la petición- y hydra:description dice "Error de sintaxis". Sin hacer nada, la Plataforma API ya está gestionando este caso. Ah, y el trace, aunque puede ser útil ahora mismo durante el desarrollo, no aparecerá en el entorno de producción.

Validación de campos

¿Qué pasa si simplemente borramos todo y enviamos una petición vacía? Ah... eso sigue siendo técnicamente un JSON no válido. Prueba sólo con {}.

Ah... esta vez obtenemos un error 500: la base de datos está explotando porque algunas de las columnas no pueden ser nulas. Ah, y como he mencionado antes, si utilizas Symfony 4.3, es posible que ya veas un error de validación en lugar de un error de base de datos debido a una nueva función en la que las reglas de validación se añaden automáticamente al leer las reglas de la base de datos de Doctrine.

Pero, tanto si ves un error 500, como si Symfony añade al menos una validación básica por ti, los datos de entrada permitidos son algo que queremos controlar: quiero decidir las reglas exactas para cada campo.

Tip

En realidad, la auto-validación no estaba habilitada por defecto en Symfony 4.3, pero puede que lo esté en Symfony 4.4.

Añadir reglas de validación es... oh, tan bonito. Y, a menos que seas nuevo en Symfony, esto te parecerá deliciosamente aburrido. Por encima de title, para que sea obligatorio, añade@Assert\NotBlank(). Añadamos también aquí @Assert\Length() con, por ejemplo,min=2 y max=50. Incluso pongamos en maxMessage

Describe tu queso en 50 caracteres o menos

... lines 1 - 14
use Symfony\Component\Validator\Constraints as Assert;
... lines 16 - 37
class CheeseListing
{
... lines 40 - 46
/**
... lines 48 - 49
* @Assert\NotBlank()
* @Assert\Length(
* min=2,
* max=50,
* maxMessage="Describe your cheese in 50 chars or less"
* )
*/
private $title;
... lines 58 - 175
}

¿Qué más? Por encima de description, añade @Assert\NotBlank. Y para el precio,@Assert\NotBlank(). También podrías añadir una restricción GreaterThan para asegurarte de que está por encima de cero.

... lines 1 - 37
class CheeseListing
{
... lines 40 - 58
/**
... lines 60 - 61
* @Assert\NotBlank()
*/
private $description;
... line 65
/**
... lines 67 - 70
* @Assert\NotBlank()
*/
private $price;
... lines 74 - 175
}

Bien, vuelve a cambiar y prueba a no enviar datos de nuevo. ¡Woh! ¡Es increíble! ¡El @type es ConstraintViolationList! ¡Es uno de los tipos descritos por nuestra documentación JSON-LD!

Ve a /api/docs.jsonld. Debajo de supportedClasses, está EntryPoint y aquí están ConstraintViolation y ConstraintViolationList, que describen el aspecto de cada uno de estos tipos.

Y los datos de la respuesta son realmente útiles: una matriz violations en la que cada error tiene un propertyPath -para que sepamos de qué campo procede ese error- y message. Así que... ¡todo funciona!

Y si intentas pasar un title de más de 50 caracteres... y se ejecuta, ahí está nuestro mensaje personalizado.

Validación para pasar tipos no válidos

¡Perfecto! ¡Ya hemos terminado! Pero espera... ¿no nos falta un poco de validación en el campoprice? Tenemos @NotBlank... pero ¿qué nos impide enviar el texto de este campo? ¿Algo?

¡Vamos a intentarlo! Establece el precio en apple, y ejecuta.

¡Ja! ¡Falla con un código de estado 400! ¡Es increíble! Dice:

El tipo del atributo precio debe ser int, cadena dada

Si te fijas, está fallando durante el proceso de deserialización. Técnicamente no es un error de validación, es un error de serialización. Pero para el cliente de la API, parece casi lo mismo, excepto que esto devuelve un tipo de error en lugar de un ConstraintViolationList... lo que probablemente tiene sentido: si algún JavaScript está haciendo esta petición, ese JavaScript probablemente debería tener algunas reglas de validación incorporadas para evitar que el usuario añada texto al campo del precio.

La cuestión es: La Plataforma API, bueno, en realidad, el serializador, conoce los tipos de tus campos y se asegurará de que no se pase nada insano. En realidad, sabe que el precio es un entero por dos fuentes: los metadatos de Doctrine @ORM\Column sobre el campo y la sugerencia de tipo de argumento en setPrice().

De lo único que tenemos que preocuparnos realmente es de añadir la validación de "reglas de negocio": añadir las restricciones de validación de @Assert para decir que este campo es obligatorio, que ese campo tiene una longitud mínima, etc. Básicamente, la validación en la Plataforma API funciona exactamente igual que la validación en cualquier aplicación Symfony. Y la Plataforma API se encarga del aburrido trabajo de asignar los fallos de serialización y validación a un código de estado 400 y a respuestas de error descriptivas y coherentes.

A continuación, ¡creemos un segundo Recurso API! ¡Un usuario! Porque las cosas se pondrán realmente interesantes cuando empecemos a crear relaciones entre recursos.

Leave a comment!

Este tutorial funciona muy bien para Symfony 5 y la Plataforma API 2.5/2.6.

What PHP libraries does this tutorial use?

// composer.json
{
    "require": {
        "php": "^7.1.3",
        "ext-ctype": "*",
        "ext-iconv": "*",
        "api-platform/core": "^2.1", // v2.4.3
        "composer/package-versions-deprecated": "^1.11", // 1.11.99
        "doctrine/annotations": "^1.0", // 1.10.2
        "doctrine/doctrine-bundle": "^1.6", // 1.11.2
        "doctrine/doctrine-migrations-bundle": "^2.0", // v2.0.0
        "doctrine/orm": "^2.4.5", // v2.7.2
        "nelmio/cors-bundle": "^1.5", // 1.5.5
        "nesbot/carbon": "^2.17", // 2.19.2
        "phpdocumentor/reflection-docblock": "^3.0 || ^4.0", // 4.3.1
        "symfony/asset": "4.2.*|4.3.*|4.4.*", // v4.3.11
        "symfony/console": "4.2.*", // v4.2.12
        "symfony/dotenv": "4.2.*", // v4.2.12
        "symfony/expression-language": "4.2.*|4.3.*|4.4.*", // v4.3.11
        "symfony/flex": "^1.1", // v1.17.6
        "symfony/framework-bundle": "4.2.*", // v4.2.12
        "symfony/security-bundle": "4.2.*|4.3.*", // v4.3.3
        "symfony/twig-bundle": "4.2.*|4.3.*", // v4.2.12
        "symfony/validator": "4.2.*|4.3.*", // v4.3.11
        "symfony/yaml": "4.2.*" // v4.2.12
    },
    "require-dev": {
        "symfony/maker-bundle": "^1.11", // v1.11.6
        "symfony/stopwatch": "4.2.*|4.3.*", // v4.2.9
        "symfony/web-profiler-bundle": "4.2.*|4.3.*" // v4.2.9
    }
}