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 typerelation
.
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
.
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
.
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é :
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 :
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 :
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 !