30 - Symfony : Watchlist
⚠️ Avant de commencer cette quête, tu dois avoir terminé les quêtes suivantes :
Introduction

Dans cette quête, tu vas rendre possible l'ajout de séries dans une "watchlist", d'abord de la manière traditionnelle que tu connais déjà. Dans la quête suivante, tu iras encore plus loin à l’aide d’une requête asynchrone Javascript, également appelée :def[AJAX]{value="Asynchronous JavaScript And Xml"}.
🤓 À la fin de cette quête, tu seras capable de :
- ✅ Ajouter des items dans une liste de favoris
- ✅ Retirer des items d'une liste de favoris
Modification des entités
Tu vas modifier l'entité User. Tout d’abord, quelques informations importantes à lire attentivement :
-
Un
Userpeut avoir plusieurs séries dans sa watchlist et unProgrampeut être ajouté à de nombreuses watchlists d'utilisateurs. CotéProgramles utilisateurs seront appelés ses viewers. C'est donc une relationManyToManyentre les entitésUseretProgram. -
Attention, dans une quête précédente, tu as déjà créé une relation
ManyToManyentreUseretProgramafin de gérer le propriétaire (owner). Il faut donc préciser à Doctrine que l'on souhaite utiliser une table de jointure supplémentaire pour cette relation.
Rappel : Pour modifier une entité, utilise la commande php bin/console make:entity.
Après avoir définie la nouvelle relation ManyToMany entre User et Program spécifiée par la propriété watchlist dans User, tu peux maintenant spécifier directement dans la class de l'entité User le nom de la table de jointure par #[ORM\JoinTable(name:'watchlist')] dans les attributs de la propriété watchlist :
#[ORM\ManyToMany(targetEntity: Program::class, inversedBy:'viewers')]
#[ORM\JoinTable(name:'watchlist')]
private $watchlist;
Tu dois aussi retrouver les méthodes générées lors de la commande make:entity, tu peux les renommer comme l'exemple suivant, pour que celles-ci soient plus parlantes 😉.
public function getWatchlist(): Collection
{
return $this->watchlist;
}
public function addToWatchlist(Program $program): self
{
if (!$this->watchlist->contains($program)) {
$this->watchlist[] = $program;
}
return $this;
}
public function removeFromWatchlist(Program $program): self
{
$this->watchlist->removeElement($program);
return $this;
}
Vérifie également que c'est bien "cablé" du coté de notre entité Program.
#[ORM\ManyToMany(targetEntity: User::class, mappedBy:'watchlist')]
private Colleciton $viewers;
Ajout dans la "watchlist"
Si, depuis ton application, tu as une interaction spécifique à avoir avec l'utilisateur, il te faut une route spécifique à cette opération, ici l'ajout d'une série dans une watchlist d'un utilisateur précis :
-
Créer une méthode
addToWatchlist()dans le controllerProgramController -
L'url de la route :
program/{id}/watchlist
Rappel : la syntaxe de l'annotation Route est #[Route('/url_de_la_route', name: 'nom_de_la_route', methods: ["GET","POST"])]
Maintenant que cette route est créée, tu dois écrire le comportement de la méthode associée. Elle devra :
- Récupérer le programme dont l'identifiant
{id}est dans la route - Ajouter la série à la watchlist de l'utilisateur connecté :
$this->getUser()->addToWatchlist($program) - Enregistrer les modifications :
$entityManager->flush() - Rediriger vers la route de la page détails de la série.
Un bouton qui a du cœur
La route est en place, mais tu n'as pas encore d'interface pour déclencher l'action. Ajoute un bouton associé à la notion de "watchlist" sur la page de détail d’une série. Tu auras une icône, un cœur par exemple, qui sera vide si la série n'est pas déjà dans la liste et plein si elle l'est déjà.

Pour cela, tu peux utiliser Bootstrap icons. Pour l'utiliser, tu peux lire la documentation et pour l'installer, utiliser directement Yarn.
yarn add bootstrap-icons
Tu devras également importer Bootstrap-icons dans ton fichier `/assets/app.js` en rajoutant ces lignes (et en faisant un nouveau build avec webpack ;) ) :
import 'bootstrap-icons/font/bootstrap-icons.css';
Puis lance un nouveau build avec webpack.
Tu peux maintenant utiliser les icônes de bootstrap :
- Icône pour ajouter à la watchlist (cœur vide) :
<i class="bi bi-heart"></i> - Icône déjà dans sa watchlist(cœur plein) :
<i class="bi bi-heart-fill"></i>Il est temps d'afficher une icône ou l'autre dans le template de détail d'un programme. Attention, pour cela, il te faudra savoir si la série à afficher est déjà dans la watchlist de l'utilisateur ou non. Tu dois effectuer une modification dans l'entitéUser.
Ajoute une méthode isInWatchlist(Program $program) à la classe User. Elle retournera true si la série en paramètre est dans la watchlist de l'utilisateur, et false sinon. À toi d'écrire la méthode et de l’utiliser dans ton template Twig de détails d’une série pour afficher la bonne icône : app.user.isInWatchlist(program)
Ton lien devrait ressembler à cela :
<a href="{{ path('program_watchlist', {id: program.id})}}">
<i class="bi {{ app.user.isInWatchlist(program) ? 'bi-heart-fill' : 'bi-heart' }}"></i>
</a>
Suppression de la watchlist
Actuellement, tu peux ajouter une série à ta watchlist, mais tu ne peux pas l'enlever. Le comportement voulu est :
-
Si la série n'est pas dans la liste, il faut l'ajouter et changer l'icône
-
Si la série est déjà dans la liste, il faut la retirer de la liste et on change l'icône
Pour mettre ou enlever une série d'une watchlist, la route sera la même. Tu dois modifier la méthode ProgramController::addToWatchlist pour enlever la série si elle est déjà dans la liste. Si elle n'y est pas, tu l'ajoutes comme précédemment. Ta méthode devrait ressembler à cela :
#[Route('/{id}/watchlist', methods: ['GET', 'POST'], name: 'watchlist')]
public function addToWatchlist(Program $program, UserRepository $userRepository): Response
{
if (!$program) {
throw $this->createNotFoundException(
'No program with this id found in program\'s table.'
);
}
/** @var \App\Entity\User */
$user = $this->getUser();
if ($user->isInWatchlist($program)) {
$user->removeFromWatchlist($program);
} else {
$user->addToWatchlist($program);
}
$userRepository->save($user, true);
return $this->redirectToRoute('program_show', ['slug' => $program->getSlug()], Response::HTTP_SEE_OTHER);
}
Tu remarqueras une ligne /** @var \App\Entity\User */ au dessus de $this->getUser(). C'est un bloc phpDoc, qui sert à documenter le code. Il n'est pas interprété par PHP, mais est compris par ton IDE ou des outils d'analyse de code. De base, $this->getUser() renvoie un UserInterface et non un User. De ce fait, l'IDE n'arrive pas à reconnaître les méthode removeFromWatchlist() ou addToWatchlist() qui ne sont pas dans l'interface (mais ton code marche quand même !).
Ce bloc phpDoc permet que l'IDE prenne bien $user pour un objet de type User et corriger ainsi le souci d'affichage.
💪 Challenge
Ajout à la "Watchlist"
Le code réalisé lors de cette quête est fonctionnel. Tu peux ainsi permettre à un utilisateur, depuis la page descriptive d'une série, de l'ajouter / retirer de sa watchlist. C'était déjà un challenge en soi. Réalise une vidéo à partir de ton projet pour partager ta réussite ;)
Critères de validation
- L'ajout à la watchlist sur la page de détails fonctionne,
- Le code est disponible sur un repository GitHub, avec une branche correspondant à cette quête.