# Symfony REST api : validating data that will be stored in different entities

REST is great. But, most of the time, it is quite hard to think your web services architecture in a way that each WS is stateless. Let’s think about a common use case.

## The problem

If you have to design a personal data form and wish to ask the user to complete some fields such as the name, the firstname, the birthdate, and two addresses with, for each, an addresse, a city, a zipcode, and a country.

Using Symfony and Doctrine, you will probably have to create 3 entities :

  1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28   

In that case, your database model will probably looks like that :

firstname city
birthdate zipcode
country

Now, if you wish to strictly use REST standards, you would expose 2 WS :

• One that POST a user
• One that POST an address (using user id)

And then, for each, populate your objects, check some rules such as the name is only composed of letters, using the Validator Symfony Component.

  1 2 3 4 5 6 7 8 9 10 11 12 13 14  setName($request->get('name'));$errors = $this->validator->validate($user); if($errors) { //... } //...$user->persist(); $em->flush(); return new JsonResponse($user->id, 201); ?> 
  1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21  setAddress($request->get('address'));$errors = $this->validator->validate($address); if($errors) { //... } //...$user = $this->get('em')->getRepository()->findById($request->get('user_id')); $userAddress = new UserAddress();$userAddress->setUser($user);$userAddress->setAddress($address);$userAddress->persist(); $address->persist();$em->flush(); ?> 

However, most of the time, this is probably not what you’ll do. There are some reasons why :

• Making at least 3 calls to post a simple form could be a bad idea if you wish to improve performances
• That way, it will be quite difficult to check, for example, that the 2 required addresses are different. (You will probably have to post the first one and then check the DB to compare with the second one)
• Some of your WS will be stateless but won’t make any sens as a domain.

A common way to avoid such problems is using the Symfony Form Builder. Then, you need to populate your form with the request data and test the Symfony validator method isValid() on the form.

This is not a good way to deal with REST data validation.

• First of all, Symfony forms are design to actually generate HTML form.
• Why would you create a form object (using a factory) when you already have a form on the front app?
• Form validation is great when you need to actually show (html way) errors. If your goal is to send an error message to your frontend app… well… that’s not the purpose.

## My solution

I have just released a little bundle that provide a good solution to answer that kind of dilemma.

Instead of exposing 2 WS, we will only expose one. we will be able to send all datas as a Json object and validate the Json object instead of validate each domain object apart.

Here is what the Json object could looks like in our example :

  1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19  { "name":"Doe", "firstname":"John", "birthdate":"11/24/1988", "addresses":[ { "address":"36, Disney Road", "city":"Paradize", "zipcode":"764576", "country":"France" }, { "address":"37, Mickey Road", "city":"Paradize", "zipcode":"764576", "country":"France" } ] }

Then, we will validate all that object using the Symfony Parameter Bag and the Validator Component.

Let’s try.

Using composer :

 1  $composer require seblegall/api-validator-bundle Then, set up the bundle in your Symfony’s AppKernel.php file :   1 2 3 4 5 6 7 8 9 10 11 12 13    Finally, import the bundle routing by inserting those lines in the app/config/routing.yml file :  1 2  api_validator_bundle: resource: "@ApiValidatorBundle/Resources/config/routing.yml" Once the bundle is installed, here is how to do so. ### Create a dedicated Parameter Bag for your web service • Create a directory in the bundle structure. The directory can be named as you wish. For example Api or ParameterBag. You now should have something like that :  1 2 3 4 5 6 7  src\ MyBundle\ Api\ Controller\ DependencyInjection\ Resources\ MyBundle.php • Create a PHP Class that extends Seblegall\ApiValidatorBundle\Request\ApiParameterBag :   1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33    ### Enable your new parameter bag. Using yml, in the bundle routing.yml file :  1 2 3  example_api: path: /example/api/ws-route defaults: { _controller: MyBundle:Api:ws , _api_bag: MyBundle\Api\ExampleApiParameterBag } Or, if you prefer, using annotations, directly in the controller :   1 2 3 4 5 6 7 8 9 10 11 12 13 14 15  attributes->get('api_parameters'); return new JsonResponse($apiParameters->all()); } ?> 

If not yet create, add a validation.yml file in the Resources directory.

  1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16  MyBundle\Api\ExampleApiParameterBag: properties: parameters: - Collection: fields: firstname: - Type: type: string name: - Type: type: string birthdate: - Type: type: datetime # This could be a custom constraint allowMissingFields: true allowExtraFields: true

### Enable validation

Using the routing.yml file :

 1 2 3  example3_api: path: /example/api/ws-route-validation defaults: { _controller: MyBundle:Api:ws , _api_bag:MyBundle\Api\ExampleApiParameterBag, _api_validation: true }

Or, using annotations :

  1 2 3 4 5 6 7 8 9 10 11 12 13 14 15  attributes->get('api_parameters'); return new JsonResponse(\$apiParameters->all()); } ?> 

### You’re done

Now, any request send to your web service will be catch and the request data will be validate before calling the controller in a way that you don’t need to validate anything in the controller or in any service one the controller is called.

### What if datas are not valid ?

If datas are not valid, the bundle will return a status code 400 and an array containing an errors key in which you will find all the errors catch by the validator component.