• 50 hours
  • Hard

Free online content available in this course.

course.header.alt.is_video

course.header.alt.is_certifying

Got it!

Last updated on 7/29/19

La désérialisation

Log in or subscribe for free to enjoy all this course has to offer!

Travailler avec le body converter

 Nous savons comment sérialiser un objet, il faut maintenant savoir comment gérer les données envoyées par un client de l'API. Prenons un exemple simple : un client fait appel à l'API pour créer un article. Il faut que le client envoie toutes les informations nécessaires à la création de notre objet.

Grâce au ParamConverter spécial provenant du bundle FOSRestBundle, la conversion de JSON (ou XML) vers un objet PHP est faite automatiquement.

Il nous faut créer notre action en charge de la création d'un article. Dans la classe  ArticleController, ajoutons la méthode  CreateAction comme suit :

<?php

namespace AppBundle\Controller;

use AppBundle\Entity\Article;
use FOS\RestBundle\Controller\Annotations as Rest;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\ParamConverter;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;

class ArticleController extends Controller
{
    /**
     * @Rest\Post("/articles")
     * @Rest\View
     * @ParamConverter("article", converter="fos_rest.request_body")
     */
    public function createAction(Article $article)
    {
        dump($article); die;
    }
}

 Vous connaissez déjà les annotations @Rest\Post  &  @Rest\View. La nouveauté ici est donc l'annotation  @ParamConverter("article", converter="fos_rest.request_body") : nous indiquons ici vouloir utiliser le service  fos_rest.request_body pour convertir ce qui est reçu en requête en objet qui sera hydraté dans une variable appelée article. Il ne nous reste plus qu'à faire passer un objet de type  AppBundle\Entity\Article, ayant comme nom de variable  $article.

Dans notre cas, le JSON qui doit être envoyé à notre API doit être formaté comme suit :

{
	"title": "Le titre de mon article ",
	"content": "Le contenu de mon article.",
	"author": {
		"fullname": "Sarah Khalil",
		"biography": "Ma biographie."
	}
}
Conversion de JSON en objet
Conversion de JSON en objet

Il ne nous reste plus qu'à persister notre article !

<?php

namespace AppBundle\Controller;

use AppBundle\Entity\Article;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use FOS\RestBundle\Controller\Annotations as Rest;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\ParamConverter;

class ArticleController extends Controller
{
    /**
     * @Rest\Post(
     *    path = "/articles",
     *    name = "app_article_create"
     * )
     * @Rest\View(StatusCode = 201)
     * @ParamConverter("article", converter="fos_rest.request_body")
     */
    public function createAction(Article $article)
    {
        $em = $this->getDoctrine()->getManager();

        $em->persist($article);
        $em->flush();

        return $article;
    }
}

Utiliser le composant Form pour manipuler les informations postées

Le body converter se charge de convertir un objet JSON en objet PHP, ce qui nous facilite grandement la vie.

Ceci dit, il se trouve que le composant Form de Symfony s'en charge aussi très bien. Il est donc possible de passer par un formulaire pour faire cette conversion. Néanmoins, en choisissant cette solution, il y a plus de code à écrire : en effet, il faut créer un FormType, puis créer un formulaire dans l'action et enfin soumettre les informations.

Je vous propose de mettre en place le code nécessaire pour opérer la conversion du JSON envoyé par l'utilisateur à notre API. Commençons par créer les classes de FormType pour nos ressourcesArticleetAuthor:

<?php

namespace AppBundle\Form;

use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;

class ArticleType extends AbstractType
{
    public function buildForm(FormBuilderInterface $builder, array $options)
    {
        $builder
            ->add('title')
            ->add('content')
            ->add('author', AuthorType::class)
        ;
    }

    public function configureOptions(OptionsResolver $resolver)
    {
        $resolver->setDefaults(array(
            'data_class' => 'AppBundle\Entity\Article'
        ));
    }
}
<?php

namespace AppBundle\Form;

use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;

class AuthorType extends AbstractType
{
    public function buildForm(FormBuilderInterface $builder, array $options)
    {
        $builder
            ->add('fullname')
            ->add('biography')
        ;
    }

    public function configureOptions(OptionsResolver $resolver)
    {
        $resolver->setDefaults(array(
            'data_class' => 'AppBundle\Entity\Author'
        ));
    }
}

Une fois les classes de FormType créées, il suffit d'ajouter ce type de code dans l'action de création d'article :

<?php

namespace AppBundle\Controller;

use AppBundle\Entity\Article;
use AppBundle\Form\ArticleType;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use FOS\RestBundle\Controller\Annotations as Rest;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\ParamConverter;

class ArticleController extends Controller
{
    // …
    
    /**
     * @Rest\Post(
     *    path = "/articles",
     *    name = "app_article_create"
     * )
     * @Rest\View(StatusCode = 201)
     */
    public function createAction()
    {
        $data = $this->get('jms_serializer')->deserialize($request->getContent(), 'array', 'json');
        $article = new Article;
        $form = $this->get('form.factory')->create(ArticleType::class, $article);
        $form->submit($data);
        
        $em = $this->getDoctrine()->getManager();

        $em->persist($article);
        $em->flush();
        
        return $this->view($article, Response::HTTP_CREATED, ['Location' => $this->generateUrl('app_article_show', ['id' => $article->getId()])]);
    }
}

Et voilà, le tour est joué. Je vous avoue tout de même que je préfère le body converter du FOSRestBundle pour une raison simple : moins de code, moins de bugs. ;)

Valider les paramètres envoyés par l'utilisateur en GET et POST

Il vous est possible de travailler avec les éléments envoyés par l'utilisateur. L'utilisateur a deux moyens d'envoyer des données au serveur : via la query string (dans l'URL avec ?nameOfMyParameter=aValue)ou via une requête POST en plaçant les informations dans le corps de la requête.

Avec Symfony, nous avons bien évidemment la possibilité de travailler avec l'objet  Request que nous pouvons recevoir en paramètre d'une action à condition que l'argument passé soit typé avec  Symfony\Component\HttpFoundation\Request :

<?php

namspace AppBundle\Controller;

use Symfony\Component\HttpFoundation\Request;

class DefaultController
{
    public function indexAction(Request $request)
    {
        // …
    }
}

Néanmoins, nous n'avons pas de moyen facilité pour valider cet objet. :(

Récupérer les paramètres grâce au param fetcher listener

Pour pouvoir manipuler les paramètres envoyés par les utilisateurs de notre API, il nous faut tout d'abord configurer le listener Param Fetcher : c'est ce qui nous permet de récupérer les paramètres passés en GET ou en POST en paramètre de l'action et les valider grâce à un peu de configuration.

La variable de configuration est  fos_rest.param_fetcher_listener et peut prendre deux valeurs :  true ou  force.

  • si la valeur est à  true, nous devrons passer un argument ayant pour type  FOS\RestBundle\Request\ParamFetcherInterface à l'action pour y accéder, comme nous pouvons le voir dans ce qui suit :

    <?php
    
    namespace AppBundle\Controller;
    
    use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
    use FOS\RestBundle\Controller\Annotations as Rest;
    use FOS\RestBundle\Request\ParamFetcherInterface;
    
    class ArticleController
    {
        /**
         * @Rest\Get("/articles", name="app_article_list")
         * @Rest\QueryParam(name="order")
         */
        public function listAction(ParamFetcherInterface $paramFetcher)
        {
            dump($paramFetcher->get('order'));
        }
    }
  • si la valeur est à  force, nous sommes en mesure de recevoir les paramètres directement par leur nom, comme nous pouvons le voir dans ce qui suit :

    <?php
    
    namespace AppBundle\Controller;
    
    use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
    use FOS\RestBundle\Controller\Annotations as Rest;
    
    class ArticleController
    {
        /**
         * @Rest\Get("/articles", name="app_article_list")
         * @Rest\QueryParam(name="order")
         */
        public function listAction($order)
        {
            dump($order);
        }
    }

Voyons maintenant comment travailler avec les QueryParams et les RequestParams.

QueryParam - Les paramètres envoyés en GET

Il est possible de valider les paramètres que l'on attend en paramètre GET d'une requête. Prenons un exemple : l'un des points d'entrée de notre API permet de consulter la liste des articles. Ce point d'entrée attend un paramètre GET pour permettre l'ordonnancement des résultats. Nous souhaitons valider ce paramètre selon les critères suivants :

  • la valeur attendue ne peut être que  asc  ou  desc ;

  • dans le cas où le paramètre n'est pas renseigné, la valeur par défaut doit être asc ;

  • le paramètre a pour nom  order.

Pour répondre à toutes ces exigences, il est possible de passer par une annotation,@QueryParampour exprimer tout cela. L'annotation devra comporter les éléments suivants :

<?php

namespace AppBundle\Controller;

use FOS\RestBundle\Controller\Annotations\Get;
use FOS\RestBundle\Controller\Annotations\QueryParam;
use FOS\RestBundle\Controller\FOSRestController;

class DefaultController extends FOSRestController
{
    /**
     * @Get("/articles", name="app_article_list")
     * @QueryParam(
     *     name="order",
     *     requirements="asc|desc",
     *     default="asc",
     *     description="Sort order (asc or desc)"
     * )
     */
     public function listAction($order)
     {
        //…
     }
}

 RequestParam - Les paramètres envoyés en POST

 Il est également possible de valider les paramètres POST d'une requête. Le fonctionnement est similaire au QueryParam.

Prenons à nouveau un exemple : nous offrons la possibilité de rechercher des articles via un terme. Nous souhaitons valider ce paramètre selon les critères suivants :

  • la valeur attendue doit être des caractères allant de a à z et/ou de A à Z ;

  • dans le cas où le paramètre n'est pas renseigné, il doit être  null ;

  • la valeur de ce paramètre peut être null;

  • le paramètre a pour nom search.

Pour répondre à toutes ces exigences, l'annotation devra comporter les éléments suivants :

<?php

namespace AppBundle\Controller;

use FOS\RestBundle\Controller\Annotations\Get;
use FOS\RestBundle\Controller\Annotations\RequestParam;
use FOS\RestBundle\Controller\FOSRestController;

class DefaultController extends FOSRestController
{
    /**
     * @Get("/articles", name="app_article_list")
     * @RequestParam(
     *     name="search",
     *     requirements="[a-zA-Z0-9]",
     *     default=null,
     *     nullable=true
     *     description="Search query to look for articles"
     * )
     */
     public function listAction($search)
     {
        //…
     }
}

 

Ainsi, grâce au QueryParams et RequestParams, inutile d'effectuer votre validation dans le controller. Un peu de configuration et hop, le tour est joué ! ^^

 

Rendez-vous au chapitre suivant pour nous servir de ces éléments pour paginer la liste d'articles.

Example of certificate of achievement
Example of certificate of achievement