10 - Symfony : Récupérer des données stockées avec Doctrine
Objectifs
- ✅ Récupérer une multitude d'objets stockés dans la base
- ✅ Récupérer un objet bien précis par un champ unique
- ✅ Récupérer plusieurs objets avec des filtres
Pré-requis
Avoir terminé la quête
Introduction
User stories 📋
en tant que visiteur, je souhaite récupérer les données d'une série
en tant que visiteur, je souhaite récupérer la liste des séries
en tant que contributeur, je souhaite ajouter une série
In progress 🤸
en tant que visiteur, je souhaite visualiser la fiche d'une série
Doing 🏗️
création de la méthode show dans le contrôleur CategoryController
Done ✅
mise en place de la relation entre les entités Program et Category
création des entités Program et Category
Dans les quêtes précédentes, tu as établi la relation ManyToOne de l'entité Program vers l'entité Category. Puis, via les fixtures, tu as pu ajouter des données (séries, catégories) dans tes tables. Dans cette quête, tu vas maintenant voir comment récupérer ces séries et comment sélectionner seulement celles qui appartiennent à une catégorie donnée.
Sommaire
Récupérer une multitude d'objets
Pour commencer, reprends ton dossier de la quête précédente et, plus précisément, le fichier ProgramController.php. Il devrait contenir la classe ProgramController avec deux méthodes : index() et show(int $id).
Tu vas en premier lieu sélectionner toutes les séries stockées afin de les afficher sur la route nommée program_index.
Pour cela, tu vas voir les méthodes de récupération d'objets par entité, à savoir :
findAll()findOneBy(...)findBy(...)
À chaque fois que tu vas requêter sur une entité pour créer des objets, tu vas utiliser une classe de repository qui lui est associée.
Par exemple, pour l'entité Program, c'est la classe ProgramRepository du fichier src/Repository/ProgramRepository.php qui va être utilisée.
Souviens-toi : lorsque tu as créé ton entité, Doctrine a directement généré ce fichier.
La classe ProgramRepository hérite de la classe ServiceEntityRepository. Cette dernière possède elle-même des méthodes génériques déjà créées, permettant de récupérer facilement des données. Évidemment, à l’appel de ces méthodes, ce sont des requêtes SQL qui sont exécutées.
Si tu as besoin de récupérer des données de manière plus complexe, en sortant des cas génériques, tu pourras créer tes propres méthodes dans la classe Repository de l'entité en question. Tu verras cela plus tard. Pour le moment, concentre-toi sur les méthodes déjà existantes.
findAll()
Cette méthode sert à lister toutes les séries présentes dans la base de données.
Reprends la méthode index() dans la classe ProgramController. Celle-ci doit récupérer tous les éléments présents dans la table program et les ressortir sous forme d'un tableau d'objets. Ce tableau sera envoyé à la vue program/index.html.twig que tu devras aussi modifier.
Essaie de ne pas copier coller ce code mais de l'écrire toi-même, afin que ton IDE t'aide à écrire les use dont tu peux avoir besoin.
💻 Dans ton ProgramController, récupère les programmes depuis la base et transmets-les à ta vue.
[...]
use Symfony\Component\Routing\Annotation\Route;
use App\Repository\ProgramRepository
#[Route('/program', name: 'program_')]
Class ProgramController extends AbstractController
{
#[Route('/', name: 'index')]
public function index(ProgramRepository $programRepository): Response
{
$programs = $programRepository->findAll();
return $this->render(
'program/index.html.twig',
['programs' => $programs]
);
}
}
Vérifie que tu as bien un use en haut de ton fichier pour l’entité Program.
Pour récupérer les programmes en base de données, tu vas devoir utiliser la classe ProgramRepository. Pour récupérer un objet $programRepository dans Symfony, tu ne vas pas faire un new directement, mais plutôt ProgramRepository $programRepository en paramètre de méthode d'un contrôleur. Symfony instancie alors "tout seul" l'objet nécessaire, ce mécanisme appelé "injection de dépendance" et très utilisé dans Symfony, te sera expliqué plus en détail dans une prochaine quête.
Ici, la variable $programs se voit affecter le retour de la méthode findAll() de ProgramRepository.
findAll() retourne tous les éléments présents dans la table (il fait un SELECT * FROM program en base de données). Contrairement aux autres méthodes find(), celle-ci ne prend aucun argument.
Remarque : Si tu regardes dans la classe ProgramRepository.php, tu ne trouveras pas la méthode que tu viens d’utiliser car, comme dit précédemment, celle-ci est héritée de la classe parente. D’ailleurs, il peut être intéressant de consulter le contenu des classes parentes. Tu peux ouvrir la classe parente (qui se situera dans ce cas dans le vendor) en faisant un CTRL+Clic sur le nom de la classe dans ton IDE. Dans le cas présent, tu peux constater que ProgramRepository hérite de ServiceEntityRepository qui hérite lui-même de EntityRepository. C’est dans cette dernière classe que l’on retrouve les méthodes find*().
💻 Affiche maintenant la liste des séries dans ta vue templates/program/index.html.twig.
{% extends 'base.html.twig' %}
{% block title %}All programs{% endblock %}
{% block body %}
<h1>Toutes les séries de la table program : </h1>
{% for program in programs %}
<div>
<h2>{{ loop.index }} / {{ program.title }} - Catégorie : {{ program.category.name }}</h2>
<p>{{ program.synopsis }}</p>
</div>
{% else %}
Aucune série trouvée.
{% endfor %}
<a href="{{ path('app_index') }}">
Retour à l'accueil
</a>
{% endblock %}
Après avoir placé les différents blocs Twig, tu peux constater qu'une structure de contrôle a été créée. Ici un for qui, pour chaque program (chaque ligne) du tableau d'objets programs, va itérer, afin d'afficher l’index courant de l’itération (représenté par {{ loop.index }}), le titre de la série, sa catégorie et, pour finir, son résumé. S'en suit un simple lien renvoyant à la page d'accueil.
Une erreur sur la redirection homepage ? Assure-toi d'avoir #[Route('/', name: 'app_index')] dans les attributs de la méthode index() du DefaultController.
findOneBy(...) : récupérer un objet unique selon différents paramètres
Tu as vu comment récupérer tous les objets d'une table mais, pour la suite, il pourrait être également intéressant de récupérer une seule série pour afficher ses détails, par exemple.
Tu te souviens de ta méthode show(int $id) de la classe ProgramController ? Eh bien tu vas la modifier afin de sélectionner la série qui se réfère à l'$id envoyé !
💻 Modifie la méthode show(int $id) du ProgramController comme suit :
#[Route('/show/{id<^[0-9]+$>}', name: 'show')]
public function show(int $id, ProgramRepository $programRepository):Response
{
$program = $programRepository->findOneBy(['id' => $id]);
// same as $program = $programRepository->find($id);
if (!$program) {
throw $this->createNotFoundException(
'No program with id : '.$id.' found in program\'s table.'
);
}
return $this->render('program/show.html.twig', [
'program' => $program,
]);
}
Comme pour l'exemple précédent, tu récupères ici le repository de la classe Program, afin d'utiliser cette fois-ci la méthode de classe findOneBy(...) qui a pour but de sélectionner strictement une seule ligne en base de données (un équivalent de SELECT * FROM program WHERE id=$id), en fonction de son id (qui est unique). Attention, ici tu récupères donc $program (directement un object Program correspondant à l'id) et non pas un tableau d'objets Program comme avec le findAll().
La méthode findOneBy() peut être utilisée de différentes manières. Tu peux sélectionner directement la propriété sur laquelle rechercher l’information, notamment grâce à la méthode magique __call() émulant un appel de méthode. On a pour exemples : findOneByNomDuChamp(), findOneById(), findOneByName().
Par conséquent, findOneBy(['id' => $id]) aurait pu s'écrire findOneById($id).
À ce tableau associatif, tu peux ajouter d’autres filtres ou champs comme un ordre de tri ou encore une limite. N’hésite pas à regarder les ressources !
De plus, il existe une méthode find($id) qui est un raccourci pour findOneById($id).
Dans le code que tu viens d'écrire, si aucun Program n'est retourné par la méthode findOneBy, on génère alors une erreur 404.
💻 Modifie maintenant la vue templates/program/show.html.twig afin d'afficher les détails de la série :
{% extends 'base.html.twig' %}
{% block title %}Série #{{ program.id }}{% endblock %}
{% block body %}
<article>
<img src="{{ program.poster }}" alt="{{ program.title }}">
<h1>{{ program.title }}</h1>
<p>{{ program.synopsis }}</p>
<p>Catégorie : {{ program.category.name }}</p>
</article>
<a href="{{ path('program_index') }}">
Retour à la liste des programmes
</a>
{% endblock %}
💻 Fais maintenant le lien entre la liste des séries et le détail d'une série en ajoutant un lien pour chaque série dans ta vue templates/program/index.html.twig :
{# ... #}
{% for program in programs %}
<div>
<h2>{{ loop.index }} / {{ program.title }} - Catégorie : {{ program.category.name }}</h2>
<p>{{ program.synopsis }}</p>
{# Lien vers la fiche de la série : #}
<a href="{{ path("program_show", { id: program.id }) }}">Voir la fiche</a>
</div>
{# ... #}
N'hésite pas à consulter les ressources avant d'attaquer le challenge, notamment sur les différentes utilités des findBy().
et également plus d'informations sur l'utilisation des objets, une fois récupérés.
💪 Challenge
findBy() : récupérer plusieurs objets avec des filtres
Tu as utilisé pour le moment les méthodes findAll() et findOneBy().
Il est temps pour toi de mettre en pratique la méthode findBy(). Le principe reste identique à la méthode findOneBy(), mais au lieu de récupérer strictement une seule ligne, tu en récupères plusieurs en fonction de paramètres donnés.
Objectif : Afficher les séries appartenant à une catégorie donnée.
Liste toutes les catégories
- Crée un nouveau fichier CategoryController.php qui contiendra la classe du même nom.
- toutes les routes de cette classe seront préfixées par
/categoryet leur nom parcategory_
- toutes les routes de cette classe seront préfixées par
- Crée une méthode
index()(route =/category/, route_name =category_index)- cette méthode récupère toutes les catégories présentes en base de données et les transmettra à une vue templates/category/index.html.twig.
- Crée une vue templates/category/index.html.twig qui affiche la liste des catégories que tu viens de récupérer.
Affiche les programmes d'une catégorie
- Dans ta classe
CategoryControllercrée une méthodeshow(string $categoryName)(route =/category/{categoryName}, route_name =category_show)- Dans un premier temps, cette méthode récupère l'objet Category dont le nom est passé en paramètre.
- Si aucune catégorie avec ce nom n'est présente en base, retourne une erreur 404.
- Si une catégorie existe, cette méthode récupère toutes les séries présentes en base de données qui appartiennent à cette catégorie.
- Au maximum, 3 séries devront être récupérées, ordonnées par ID décroissant.
- Ces séries seront transmises à une vue templates/category/show.html.twig
- Crée une vue templates/category/show.html.twig qui affiche la liste des séries que tu viens de récupérer :
- Tu dois afficher l'ID de la série, son titre et son résumé.
- Si aucune série n'est présente dans la catégorie, affiche un message "Aucune série trouvée".
- Pour chaque série, ajoute un lien pour pouvoir visualiser sa fiche (tu peux t'aider de ce que tu à fait précédemment lors du cours de la quête dans la vue program/index.html.twig)
- Enfin, depuis la liste des catégories dans ta vue category/index.html.twig ajoute un lien vers la fiche de chaque Category.
Critères de validation
- Un nouveau contrôleur
CategoryControllera été créé. - Il contient 2 méthodes :
index()etshow(string $categoryName) - Lorsqu'on se rend sur l'URL
/category/, la liste des catégories apparaît. - Si je clique sur une catégorie je suis redirigé vers la fiche de cette catégorie (l'URL :
/category/horreurpar exemple.) - Cette page affiche la liste des 3 dernières séries appartenant à la catégorie Horreur avec pour chacune, son ID, son titre et son résumé.
- Si je clique sur le titre d'une série je suis redirigé vers sa fiche.
- Si je tente d'accéder à la fiche d'une catégorie qui n'existe pas, j'obtiens une erreur 404 (exemple:
/category/Trucmuche=> 404 : "Aucune catégorie nommée TrucMuche") - Le code est disponible sur un repository GitHub, avec une branche correspondant à cette quête.
Ressources partagées sur cette quête
- a clear example that helps with the challenge for this questhttps://symfonycasts.com/screencast/doctrine-relations/fetching-relations