Archives Wild Code School
Symfony

30 - Symfony : Live Component (Watchlist)

Quête 33 sur 35 · 1 image archivée

Objectifs

  • ✅ Ajouter des items dans une liste de favoris
  • ✅ Retirer des items d'une liste de favoris

Pré-requis

​ Avoir terminé la quête

Quêtes liées
title-book
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.

Info

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 Program et User qui 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 :

php
#[ORM\ManyToMany(targetEntity: Program::class)]
#[ORM\JoinTable(name:'watchlist')]
private $watchlist;
Info

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 😉.

php
    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é $program de type Program qui devra avoir l'attribut #[LiveProp]
    • Un constructeur car tu auras besoin d'injecter en dépendances Security, pour récupérer l'utilisateur connecté, et EntityManagerInterface pour 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 $watchlist d'un utilisateur.

💡 Tips : Tu pourras te servir de la méthode contains() disponible sur la propriété $watchlist pour 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 attributs data-action et data-live-action-param expliqué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 contains est aussi accessible sur l'objet app.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à.

coeur bootstrap

Pour cela, tu peux utiliser Bootstrap icons. Pour l'utiliser, tu peux lire la documentation et pour l'installer, utiliser directement Yarn.

bash
yarn add bootstrap-icons

Tu devras également importer Bootstrap-icons dans ton fichier /assets/app.js en ajoutant cette ligne :

css
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

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

html
<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

html
{{ component('WatchtList', {
    program: program
}) }}