Sébastien Le Gall

Blog Notes

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

php

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 :


<?php
class User {
  protected $id;
  protected $name;
  protected $firstname;
  protected $birthdate;

  // getters and setters
}

class Address {
  protected $id;
  protected $address;
  protected $city;
  protected $zipcode;
  protected $country;

  // getters and setters
}

class UserAddress {
  protected $user;
  protected $address;

  // getters and setters
}

?>

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

user user_address address
id_user id_user id_adress
name id_address address
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.

<?php
$user = new User();
$user->setName($request->get('name'));

$errors = $this->validator->validate($user);
if($errors) {
  //...
}
//...
$user->persist();
$em->flush();

return new JsonResponse($user->id, 201);
?>
<?php
$address = new Address();
$address->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 :

{
   "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.

Installation

Using composer :

$ composer require seblegall/api-validator-bundle

Then, set up the bundle in your Symfony’s AppKernel.php file :

<?php
class AppKernel extends Kernel
{
    public function registerBundles()
    {
        return array(
            new Symfony\Bundle\FrameworkBundle\FrameworkBundle(),
            // ...
            new Seblegall\ApiValidatorBundle\ApiValidatorBundle(),
        );
    }
// ....
?>

Finally, import the bundle routing by inserting those lines in the app/config/routing.yml file :

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 :

src\
  MyBundle\
    Api\
    Controller\
    DependencyInjection\
    Resources\
    MyBundle.php
  • Create a PHP Class that extends Seblegall\ApiValidatorBundle\Request\ApiParameterBag :
<?php

namespace MyBundles\Api;

use Seblegall\ApiValidatorBundle\Request\ApiParameterBag;

class ExampleApiParameterBag extends ApiParameterBag
{

    // return the parameters you need to catch.
    // It could be  :
    //static::PARAMETERS_TYPE_HEADERS
    //static::PARAMETERS_TYPE_QUERY
    //static::PARAMETERS_TYPE_REQUEST
    //static::PARAMETERS_TYPE_COOKIES
    //static::PARAMETERS_TYPE_FILES
    //static::PARAMETERS_TYPE_ATTRIBUTES

    public function getFilteredType()
    {
        return array(
            static::PARAMETERS_TYPE_QUERY,
            static::PARAMETERS_TYPE_REQUEST,
        );
    }

    // return the parameters key you need to catch.
    public function getFilteredKeys()
    {
        return array('firstname', 'name', 'birthdate');
    }
}
?>

Enable your new parameter bag.

Using yml, in the bundle routing.yml file :

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 :

<?php
/**
 * @ApiParameters(bag="MyBundle\Api\ExampleApiParameterBag")
 *
 * @param Request $request
 *
 * @return JsonResponse
 */
public function ws2Action(Request $request)
{
    $apiParameters = $request->attributes->get('api_parameters');

    return new JsonResponse($apiParameters->all());
}
?>

Add validation rules

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

Add your own validation rules. Here is an example :

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 :

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

Or, using annotations :

<?php
/**
 * @ApiParameters(bag="MyBundle\Api\ExampleApiParameterBag", validation=true)
 *
 * @param Request $request
 *
 * @return JsonResponse
 */
public function ws3Action(Request $request)
{
    $apiParameters = $request->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.

Learn More

See the github project.

Read more

As a developer in 2016, you need to learn Emacs (or Vi)

developer-tips

How to improve your productivity writing code and force yourself to think twice.

This post is not another Emacs vs Vi troll. My point is to explain why (deeply) learning to use a low level text editor is one of the first things any software engineer should do. I have personally chose Emacs because I found common shortcuts easier to memorize. As you will see later, it doesn’t matter which one of those editors you like to use, as long as you have learned to use it.

Editors like Emacs may seems old but they have been thought to be productive tools. Shortcuts are optimized with your hands place on the keyboard and the most used key. They let you do your work as quickly as you can think about it but still, they force you to think about what you’re doing. Not only they let you learn how to be more productive, they make you understand what you do instead of writing code with crappy CRLF.

Master your working environment.

I often meet developers working on a software or a web application development trying to master the programming language and the framework they use. This is a good idea, for sure. It let you being more productive with time. But, at some point, programming a new feature in your development environment is not enough to get things done. More effective and more comfortable you become with the software code, more features you should be able to design, code, and make run into production. And yet, most of the time, this doesn’t go as well as you expected. There could be thousands of management and technical reasons which explain why you don’t succeed at getting things done. Obviously, if your company culture is that only one of the lead developers is in charge of pushing new features into production, your possible improvement is limited (And you probably should think about quitting you job). However, most of the time, this is not the reason why you can’t push the feature you just get reviewed and validated into production. Most of the time, you just don’t want to work (or people don’t want you to work..) on the production environment because you don’t master it.

So, as (not) expected, mastering your working environment doesn’t mean mastering MS Windows. It means, however, mastering your VCS, mastering the deployment tools you use, mastering the server configuration (such as Apache or Nginx), mastering the programming language configuration, mastering Linux configuration and the Linux logging system.

As you probably know, all those tools configuration is made trough text files you need to edit directly on the server. At this point, no chance to open any of the graphical text editors you use everyday. Here is where comes Emacs (or Vi).

Emacs

Read more

Docker : Remove all images

docker

If you have ever add to work with docker and and docker-compose, you probably often have mistaken in you Dockerfile. Specially when you work with multiple container build from multiple Dockerfile and tag differently.

Here is a simple way to clean all your docker images.

Show images :

$ docker images

Delete all container :

$ docker rm $(docker ps -a -q)

Delete all images :

docker rmi $(docker images -q)
Read more

Estimation des projets au forfait : la contractualisation itérative, une piste de réflexion

agility

L’état des projets informatiques en 2015

Le rapport Big Bang Boom publié en 2014 par le Standish Group met en exergue de façon très pragmatique la principale cause de l’échec d’un projet informatique :

A Big Bang theory for software and information technology projects is that everything needs to come together at once to have a working solution that is universal to all stakeholders.

Il explique, notamment, que sur un panel représentatif de projet IT :

  • 42% échouent purement et simplement;
  • 52% ne répondent pas aux contraintes imposées (coût, délais, qualité, périmètre, etc.);
  • 6% seulement réussissent.

Par ailleurs, ce même institut fait une analyse plus globale de l’évolution des projets IT au fil du temps dans son fameux Chaos report.

RSE

On y constate que la réussite d’un projet informatique est un challenge que peu d’entreprises réussissent.

Read more

Utiliser un listener de test avec PhpUnit

php

RSE

Si vous avez déjà utilisé PHPUnit, vous avez forcément en tête la succession de points qui s’affichent dans la console pour signifier que les tests passent. Sans doute aussi avez-vous l’habitude de vous ronger les ongles de peur qu’un joli F rouge apparaisse. Avec PHPUnit, cette sortie de console est gérée par un listener qui implémente l’interface :

PHPUnit_Framework_TestListener

Pour rajouter des listeners à ceux implémentés par défaut, il suffit donc de créer une classe qui implémente cette interface et de déclarer votre listener directement dans le fichier de configuration PHPUnit : phpunit.xml(.dist)

Read more