30 - Symfony : Live Component (Watchlist)
Objectifs
- ✅ Ajouter des items dans une liste de favoris
- ✅ Retirer des items d'une liste de favoris
Pré-requis
Avoir terminé la quête
Introduction
User stories 📋
In progress 🤸
en tant qu'utilisateur, je souhaite avoir un bouton « Watchlist » afin d'ajouter une série à ma liste « à voir »
Doing 🏗️
Done ✅
en tant que visiteur, je souhaite accéder aux catégories depuis la barre de navigation
en tant que visiteur, je souhaite avoir accès à un formulaire d'inscription afin de pouvoir me créer un compte
Dans cette quête, tu vas réaliser une nouvelle fonctionnalité avec un LiveComponent : rendre possible l'ajout de séries dans une "watchlist".
Sommaire
Modifier l'entité User
Dans un premier temps, il va falloir intervenir sur l'entité User afin de pouvoir enregistrer les informations en BDD.
Un User peut avoir plusieurs séries dans sa watchlist et un Program peut être ajouté à de nombreuses watchlists d'utilisateurs. C'est donc une relation ManyToMany entre les entités User et Program qui va nécessiter une table de jointure.
Rappel
- Pour modifier une entité, utilise la commande
php bin/console make:entity. - Lorsque la question te sera posée, ce sera à toi de décider si tu souhaites activer la bidirectionnalité entre
ProgrametUserqui te mènerait à créer une propriété$viewers. Ton choix n'aura pas d'impact sur la fonctionnalité que nous souhaitons implémenter dans cette quête.
Après avoir défini la nouvelle relation ManyToMany entre User et Program spécifiée par une propriété watchlist dans User, tu peux ensuite indiquer à Doctrine, directement dans la classe User, le nom de la table de jointure que tu souhaites créer.
Ajoute #[ORM\JoinTable(name:'watchlist')] dans les attributs de la propriété $watchlist comme ceci :
#[ORM\ManyToMany(targetEntity: Program::class)]
#[ORM\JoinTable(name:'watchlist')]
private $watchlist;
Pour information, il n'est pas obligatoire de préciser le nom d'une table de jointure, mais si tu ne le fais pas, Doctrine créera une table en utilisant les noms des entités. Cela donnerait ici la table user_program, ce qui n'est pas très explicite, convenons-en 🤓.
Concernant les méthodes générées lors de la commande make:entity, tu peux les renommer comme dans l'exemple suivant, afin que celles-ci soient également 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;
}
💪 Challenge
La réalisation du challenge va suivre la même procédure que la quête précédente mais il va falloir que tu étudies un peu la documentation, et notamment la section Actions.
Voici tout de même la marche à suivre :
- Utilise la console pour créer un LiveComponent
WatchtList. - Ce component aura besoin de 3 membres de classes :
- Une propriété
$programde typeProgramqui devra avoir l'attribut#[LiveProp] - Un constructeur car tu auras besoin d'injecter en dépendances
Security, pour récupérer l'utilisateur connecté, etEntityManagerInterfacepour le mettre à jour en BDD. - Une méthode
toggle()avec l'attribut#[LiveAction]tel que tu as pu le voir sur la documentation. Cette méthode devra permettre d'ajouter ou de retirer le$programà la$watchlistd'un utilisateur.
- Une propriété
💡 Tips : Tu pourras te servir de la méthode
contains()disponible sur la propriété$watchlistpour savoir si le programme est présent ou non.
- Place un bouton dans ton composant HTML qui va communiquer avec cette méthode
toggle()pour chaque programme. C'est ici que tu te serviras des attributsdata-actionetdata-live-action-paramexpliqués sur la documentation. - Enfin, fais en sorte que ton bouton affiche le texte add to watchlist ou remove from watchlist en fonction de l'état en BDD.
💡 Tips : Dans un template Twig, la méthode
containsest aussi accessible sur l'objetapp.user
Bonus
Remplace le texte par 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 ajoutant cette ligne :
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>
==$==
WatchList Component
src/Twig/Components/WatchtList.php
#[AsLiveComponent()]
final class WatchtList
{
use DefaultActionTrait;
#[LiveProp]
public Program $program;
public function __construct(
private readonly Security $security,
private readonly EntityManagerInterface $entityManager
) {
}
#[LiveAction]
public function toggle(): void
{
/** @var User $user */
$user = $this->security->getUser();
if ($user->getWatchlist()->contains($this->program)) {
$user->removeFromWatchlist($this->program);
}else {
$user->addToWatchlist($this->program);
}
$this->entityManager->flush();
}
}
templates/components/WatchtList.html.twig
<div{{ attributes }}>
<button class="btn" data-action="live#action" data-live-action-param="toggle">
{% if app.user %}
{% if app.user.watchlist.contains(this.program) %}
<i class="bi bi-heart-fill"></i>
{% else %}
<i class="bi bi-heart"></i>
{% endif %}
{% endif %}
</button>
</div>
somewhere on a program page or card
{{ component('WatchtList', {
program: program
}) }}