• 8 heures
  • Difficile

Ce cours est visible gratuitement en ligne.

course.header.alt.is_video

course.header.alt.is_certifying

J'ai tout compris !

Mis à jour le 27/11/2023

Récupérez des données complexes avec les groupes

Récupérez des données complexes avec les groupes

Nous avons à présent la possibilité de récupérer des données très simples, à savoir des informations basiques sur les livres.

Mais que se passerait-il si nous ajoutions une nouvelle entité Author  pour gérer les autrices et auteurs des livres ?

Créez l’entité Author

Première étape, créons notre nouvelle entité. Rien de compliqué ici, c’est exactement la même procédure que lorsque nous avons créé l’entité Book  qui contient nos livres.

php bin/console make:entity

Ici, en plus du champ id qui est créé par défaut, nous allons également renseigner :

  • un firstName  ;

  • un lastName  ;

  • et un autre champ books  qui sera de type relation  . 

En effet, l’idée est de lier ici l’entité Author  à l’entité Books 

Lorsque nous choisissons relation , Symfony demande ensuite à quelle entité nous voulons lier notre Author , et quel est le type de lien.

Pour des raisons de simplification, nous allons considérer qu’un livre n’a qu’un seul auteur, mais qu’un auteur peut avoir écrit plusieurs livres. C’est donc, du point de vue de l’entité Author  , une relationOneToMany

En créant le champ books, de type relation de l’entité Author, il nous est demandé de renseigner le type de relation. On a pour choix ManyToOne, OneToMany, ManyToMany ou OneToOne.
Création du champ books, de type relation de l’entité Author

Maintenant que l’entité est créée, il ne reste plus qu’à mettre la base de données à jour grâce à :

php bin/console doctrine:schema:update --force

On vérifie que tout est bon.

Sur phpMyAdmin, cliquez sur la vue concepteur, ce qui vous permet de voir l’ensemble des tables et les relations entre les entités. Effectivement, nous pouvons constater que la table author  a été correctement créée, et qu’elle a bien une relation de type OneToMany  avec la table book  .

La vue concepteur de phpMyAdmin montre la relation entre les tables book et author.
Vérification de la création de la table author et de son lien avec la table book

Mettez à jour les fixtures

Maintenant que l’entité est créée, il faut mettre à jour les fixtures correspondantes pour pouvoir tester.

Tout d’abord, nous devons créer les auteurs dans le fichier AppFixtures.php  :

<?php
    // src\DataFixtures\AppFixtures.php
    // ...
    
    // Création des auteurs.
    $listAuthor = [];
    for ($i = 0; $i < 10; $i++) {
        // Création de l'auteur lui-même.
        $author = new Author();
        $author->setFirstName("Prénom " . $i);
        $author->setLastName("Nom " . $i);
        $manager->persist($author);
        // On sauvegarde l'auteur créé dans un tableau.
        $listAuthor[] = $author;
    }

Il faut maintenant mettre à jour la création des livres pour les lier à des auteurs pris au hasard.

<?php
    // src\DataFixtures\AppFixtures.php
    // ...
    
    // Création des livres
    for ($i = 0; $i < 20; $i++) {
        $book = new Book();
        $book->setTitle("Titre " . $i);
        $book->setCoverText("Quatrième de couverture numéro : " . $i);
        // On lie le livre à un auteur pris au hasard dans le tableau des auteurs.
         
        $book->setAuthor($listAuthor[array_rand($listAuthor)]);
        $manager->persist($book);
    }

On exécute ces fixtures grâce à :

php bin/console doctrine:fixtures:load

Et il ne reste plus qu’à vérifier que tout a bien fonctionné :

Nous voyons bien les books générés et la colonne author_id qui apparaît.
Vérification sur phpMyAdmin de la mise à jour des fixtures

Notez la colonne author_id  qui a été rajoutée automatiquement par Doctrine, et qui contient l’id de l’auteur qui a écrit le livre.

Récupérez des données

Relançons Postman pour récupérer un de nos livres (attention à bien choisir un id valide !) pour voir le résultat :

Postman affiche une erreur après la mise à jour des données : erreur de référence circulaire.
Test avec Postman après mise à jour des données : erreur de référence circulaire

Une erreur ! Alors qu’avant, tout fonctionnait bien. Que s’est-il passé ?

Il s’agit d’une référence circulaire.

En gros, lorsque le serializer a voulu récupérer des données du livre, il a pris, comme avant, les données de l’id, title  et coverText . Par contre, il a également pris les données de l’auteur.

Or, l’auteur possède comme donnée… une collection de livres qu’il a écrits. Ces livres ont des auteurs, qui ont des livres, qui ont des auteurs, qui ont des livres… vous voyez le problème.

Pour résoudre ce problème, il faudrait pouvoir spécifier quels champs on veut récupérer dans quel cas.

Cela tombe bien, Symfony a prévu un mécanisme pour ça, les Groups !

Reprenons l’entité Book dans le fichier Book.php :

<?php
//src\Entity\Book.php

namespace App\Entity;

use App\Repository\BookRepository;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Serializer\Annotation\Groups;

#[ORM\Entity(repositoryClass: BookRepository::class)]
class Book
{
    #[ORM\Id]
    #[ORM\GeneratedValue]
    #[ORM\Column(type: 'integer')]
    #[Groups(["getBooks"])]
    private $id;

    #[ORM\Column(type: 'string', length: 255)]
    #[Groups(["getBooks"])]
    private $title;

    #[ORM\Column(type: 'text', nullable: true)]
    #[Groups(["getBooks"])]
    private $coverText;

    #[ORM\ManyToOne(targetEntity: Author::class, inversedBy: 'Books')]
    #[Groups(["getBooks"])]
    private $author;

    // ...

Nous pouvons voir une nouvelle annotation :

#[Groups(["getBooks"])]

Celle-ci définit que le champ qui suit sera pris en compte si on demande à récupérer les éléments qui appartiennent à ce groupe. Les crochets indiquent que l’on peut spécifier plusieurs groupes différents (nous verrons cela plus loin).

Notez également le use  qui permet d’expliquer à Symfony comment interpréter cette annotation Groups  .

Ici, pas de problème, nous prenons tous les champs. Regardons à présent le fichier Author.php  :

<?php
// src\Entity\Author.php

namespace App\Entity;

use App\Repository\AuthorRepository;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Serializer\Annotation\Groups;

#[ORM\Entity(repositoryClass: AuthorRepository::class)]
class Author
{
    #[ORM\Id]
    #[ORM\GeneratedValue]
    #[ORM\Column(type: 'integer')]
    #[Groups(["getBooks"])]
    private $id;
    
    #[ORM\Column(type: 'string', length: 255, nullable: true)]
    #[Groups(["getBooks"])]
    private $firstName;

    #[ORM\Column(type: 'string', length: 255)]
    #[Groups(["getBooks"])]
    private $lastName;

    #[ORM\OneToMany(mappedBy: 'author', targetEntity: Book::class)]
    private $books;
   
    // ...

Ici, nous récupérons l’id, le prénom, le nom, mais PAS la liste des livres. De ce fait, nous “cassons” la référence circulaire.

Lorsque l’on va demander des informations sur un livre, nous aurons les informations sur le livre, dont l’auteur, mais en récupérant les informations sur l’auteur, on s’arrête puisqu’on ne récupère pas la propriété $books .

Il nous reste maintenant à spécifier dans le contrôleur que nous voulons les informations qui sont dans ce nouveau groupe getBooks  que nous venons de créer.

<?php
// src\Controller\BookController.php

namespace App\Controller;

use App\Entity\Book;
use App\Repository\BookRepository;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\Serializer\SerializerInterface;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;

class BookController extends AbstractController
{
    #[Route('/api/books', name: 'books', methods: ['GET'])]
    public function getAllBooks(BookRepository $bookRepository, SerializerInterface $serializer): JsonResponse
    {
        $bookList = $bookRepository->findAll();

        $jsonBookList = $serializer->serialize($bookList, 'json', ['groups' => 'getBooks']);
        return new JsonResponse($jsonBookList, Response::HTTP_OK, [], true);
    }

    #[Route('/api/books/{id}', name: 'detailBook', methods: ['GET'])]
    public function getDetailBook(Book $book, SerializerInterface $serializer): JsonResponse 
    {
        $jsonBook = $serializer->serialize($book, 'json', ['groups' => 'getBooks']);
        return new JsonResponse($jsonBook, Response::HTTP_OK, [], true);
    }
}

Ici, la seule chose qui a changé est que nous passons une option supplémentaire au serializer :  [‘groups’ => ‘getBooks’]   .

Voyons maintenant le résultat :

Nos données sont bien disponibles dans le Body de Postman après la mise en place du Group.
Vérification que notre Group fonctionne

Et voilà, lorsque nous demandons l’ensemble des livres, nous obtenons aussi des informations sur les auteurs. Nous avons bien cassé la référence circulaire !

Notez également qu’il est possible de se servir de ce système pour cacher d’autres informations. Par exemple, nous aurions pu décider de ne pas afficher l’id des auteurs, mais seulement leurs nom et prénom.

Exercez-vous

Ici, nous nous sommes occupés des livres. Mais il est important de récupérer également des informations sur l’entité Author !

Créez un contrôleur AuthorController  ainsi que les méthodes :

  • getAuthor , pour récupérer les informations d’un auteur en particulier (y compris les livres qui lui sont associés) ; 

  • getAllAuthor , permettant de récupérer tous les auteurs (y compris les livres qui leur sont associés).

En vous inspirant de ce qui a été réalisé pour les livres, vous ne devriez pas rencontrer de problème majeur. N’hésitez pas à relire certains passages du cours si nécessaire !

En résumé

  • Un champ de type Relation permet de créer une relation entre deux entités.

  • Une référence circulaire est un problème lié à deux entités qui se référencent entre elles.

  • Pour éviter le problème des références circulaires, il est possible d'utiliser les groupes. Ils vont définir quels sont les champs que l'on veut récupérer en fonction du groupe demandé.

Cependant, jusqu’à présent, nous n’avons fait que récupérer des données : rendez-vous au chapitre suivant pour l’écriture, avec la mise en place d’un CRUD (Create, Remove, Update, Delete) complet ! 

Exemple de certificat de réussite
Exemple de certificat de réussite