Création d'une wishlist avec SyliusResourceBundle

Dans ce tutoriel nous allons créer une entity wishlist, et nous allons ajouter des produits sur celle-ci. Nous verrons de cette façon comment utiliser SyliusResourceBundle pour résoudre notre problématique. Pour ajouter un article l'utilisateur devra être connecté et pour rester concis dans le tutoriel nous ferons une page qui liste les produits de la wishlist et une action qui ajoute un produit à la liste.

Commencons en créant notre entité src/Entity/WishList.php

<?php

namespace App\Entity;

use App\Entity\Product\Product;
use App\Entity\User\ShopUser;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
use Doctrine\ORM\Mapping as ORM;
use Sylius\Component\Resource\Model\ResourceInterface;

/**
 * @ORM\Entity
 */
class WishList implements WishListInterface
{
    /**
     * @ORM\Id()
     * @ORM\GeneratedValue()
     * @ORM\Column(type="integer")
     */
    private $id;

    /**
     * @ORM\OneToOne(targetEntity="App\Entity\User\ShopUser", cascade={"persist", "remove"})
     */
    private $user;

    /**
     * @ORM\ManyToMany(targetEntity="App\Entity\Product\Product")
     */
    private $products;

    public function __construct()
    {
        $this->products = new ArrayCollection();
    }

    public function getId(): ?int
    {
        return $this->id;
    }

    public function getUser(): ?ShopUser
    {
        return $this->user;
    }

    public function setUser(?ShopUser $user): self
    {
        $this->user = $user;

        return $this;
    }

    /**
     * @return Collection|Product[]
     */
    public function getProducts(): Collection
    {
        return $this->products;
    }

    public function addProduct(Product $product): self
    {
        if (!$this->products->contains($product)) {
            $this->products[] = $product;
        }

        return $this;
    }

    public function removeProduct(Product $product): self
    {
        if ($this->products->contains($product)) {
            $this->products->removeElement($product);
        }

        return $this;
    }

}

Ainsi que son interface src/Entity/WishListInterface.php

<?php

namespace App\Entity;

use App\Entity\Product\Product;
use App\Entity\User\ShopUser;
use Doctrine\Common\Collections\Collection;
use Sylius\Component\Resource\Model\ResourceInterface;

interface WishListInterface extends ResourceInterface
{
    public function getUser(): ?ShopUser;

    public function setUser(?ShopUser $user): \App\Entity\WishList;

    /**
     * @return Collection|Product[]
     */
    public function getProducts(): Collection;

    public function addProduct(Product $product): \App\Entity\WishList;

    public function removeProduct(Product $product): \App\Entity\WishList;
}

Notre interface hérite de ResourceInterface

Nous pouvons mettre à jour la base de données avec php bin/console doctrine:migrations:diff et php bin/console doctrine:migrations:migrate

Nous déclarons maintenant notre resource dans config/packages/sylius_resource.yaml

sylius_resource:
    resources:
        app.wishlist:
            classes:
                model: App\Entity\WishList

Maintenant si on se rend sur notre page /wishlists on obtient une erreur qui nous indique qu'il ne trouve pas la vue pour l'action index

Unable to find template "/index.html.twig" (looked into: ...).

On va indiquer d'utiliser nos templates, pour ça il faut modifier notre ressource indiquer où sont nos templates :

app_wishlist:
    resource: |
        alias: app.wishlist
        templates: "Wishlist" #ici on indique de regarder dans le dossier templates/Wishlist
    type: sylius.resource

Dans le dossier il cherchera les vues :

  • Wishlist:show.html.twig
  • Wishlist:index.html.twig
  • Wishlist:create.html.twig
  • Wishlist:update.html.twig

Pour commencer on va juste créer la vue templates/Wishlist/index.html.twig avec le contenu suivant :

{% extends '@SyliusShop/layout.html.twig' %}

{% block content %}
    <h1>index wishlist</h1>
{% endblock %}

Image Title

On souhaite afficher la liste de notre wishlist (pour le moment vide)

Pour connaitre les variables qui sont a notre disposition avec twig on peut simplement mettre dans la vue {{ dump() }} et il nous retourne tout ce qui est disponible

Image Title

la variable wishlists nous retourne la liste des entrées sur l'entité, on reviendra sur notre vue pour boucler dessus.

Maintenant on va créer un bouton qu'on va rajouter sur une page produit qui nous ajoutera le produit à notre wishlist

Pour ça on se rend dans config/routes.yaml

app_wishlist_add:
    path: /wishlist/add/{id}
    methods: [POST]
    defaults:
        _controller: App\Controller\AddWishListAction

On créer notre action src/Controller/AddWishListAction.php

<?php

namespace App\Controller;

use Doctrine\Common\Persistence\ObjectManager;
use Sylius\Component\Resource\Factory\FactoryInterface;
use Sylius\Component\Resource\Repository\RepositoryInterface;
use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpFoundation\Session\Session;
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;

class AddWishListAction
{
    /** @var RepositoryInterface */
    private $wishListRepository;

    /** @var RepositoryInterface */
    private $productRepository;

    /** @var FactoryInterface */
    private $wishListFactory;

    /** @var ObjectManager */
    private $objectManager;

    /** @var TokenStorageInterface */
    private $tokenStorage;

    /** @var UrlGeneratorInterface  */
    private $router;

    public function __construct(RepositoryInterface $wishListRepository,
                                RepositoryInterface $productRepository,
                                FactoryInterface $wishListFactory,
                                ObjectManager $objectManager,
                                TokenStorageInterface $tokenStorage,
                                UrlGeneratorInterface $router)
    {
        $this->wishListRepository = $wishListRepository;
        $this->productRepository = $productRepository;
        $this->wishListFactory = $wishListFactory;
        $this->objectManager = $objectManager;
        $this->tokenStorage = $tokenStorage;
        $this->router = $router;
    }

    public function __invoke(Request $request, Session $session): Response
    {
        $productId = $request->attributes->getInt('id');
        $product = $this->productRepository->find($productId);

        $tokenUser = $this->tokenStorage->getToken();
        if (!is_null($tokenUser) && $tokenUser == 'anon.') {
            $session->getFlashBag()->add('error', 'You have to be logged to add to the wishlist');

            $productRoute = $this->router->generate('sylius_shop_product_show',
                array('slug' => $product->getSlug())
            );
            return new RedirectResponse($productRoute);
        }
        $user = $tokenUser->getUser();

        $wishList = $this->wishListRepository->findOneBy(array('user' => $user->getId()));
        if (is_null($wishList)) {
            $wishList = $this->wishListFactory->createNew();
            $wishList->setUser($user);
        }
        $wishList->addProduct($product);
        $this->objectManager->persist($wishList);
        $this->objectManager->flush();

        $session->getFlashBag()->add('success', 'Product added to the wishlist');

        $productRoute = $this->router->generate('sylius_shop_product_show',
            array('slug' => $product->getSlug())
        );
        return new RedirectResponse($productRoute);
    }
}

Ensuite on la déclare comme service config/services.yaml

services:
    #...
    App\Controller\AddWishListAction:
        arguments:
            - '@app.repository.wishlist'
            - '@sylius.repository.product'
            - '@app.factory.wishlist'
            - '@app.manager.wishlist'
            - '@security.token_storage'
        public: true

Puis on créer la vue du bouton templates/Wishlist/_addWishList.html.twig

{% if app.user is not null %}
    <form action="{{ path('app_wishlist_add', {id: product.id }) }}" method="post">
        <button type="submit" class="ui huge red icon button"><i class="heart icon"></i></button>
    </form>
{% endif %}

On ajoute notre bouton sur les pages produits, on va utiliser les evenements des rendus des vues, pour ça on se rend dans le fichier config/packages/sylius_ui.yaml et on ajoute :

sylius_ui:
    events:
        sylius.shop.product.show.right_sidebar:
            blocks:
                add_wishlist: 'Wishlist/_addWishList.html.twig'

Maintenant, quand on se rend sur une page produit nous avons le résultat suivant :

Image Title

Ajoutez deux ou trois produits à votre wishlist

Maintenant pour retourner uniquement les produits de l'utilisateur connecté il va nous falloir surcharger le repository.

On créer donc notre repository src/Repository/WishlistRepository.php

<?php

namespace App\Repository;

use App\Entity\WishList;
use Sylius\Bundle\ResourceBundle\Doctrine\ORM\EntityRepository;

class WishlistRepository extends EntityRepository implements WishListRepositoryInterface
{
    public function findProductInWishList($userId): ?WishList
    {
        return $this->createQueryBuilder('w')
            ->innerJoin('w.user', 'u', 'WHERE', 'u.id = :userId')
            ->setParameter('userId', $userId->getId())
            ->getQuery()
            ->getOneOrNullResult()
        ;
    }
}

Son interface src/Repository/WishlistRepositoryInterface.php

<?php

namespace App\Repository;

use App\Entity\WishList;
use Sylius\Component\Resource\Repository\RepositoryInterface;

interface WishlistRepositoryInterface extends RepositoryInterface
{
    public function findProductInWishList($user): ?WishList;

}

On indique notre nouveau repository à notre resource, config/packages/resources.yaml

sylius_resource:
    resources:
        app.wishlist:
            classes:
                model: App\Entity\WishList
                repository: App\Repository\WishlistRepository # on rajoute notre repository ici

Et enfin on crée notre route dans config/routes.yaml

app_wishlist_index:
    path: /wishlists
    methods: [GET]
    defaults:
        _controller: app.controller.wishlist:indexAction
        _sylius:
            template: Wishlist/index.html.twig
            repository:
                method: findProductInWishList
                arguments:
                   - "expr:service('sylius.context.customer').getCustomer()"

On indique la méthode qu'on veut utiliser, donc la méthode findProductInWishList qui demande comme argument l'utilisateur en cours, et on l'envoie grâce au service de Sylius : expr:service('sylius.context.customer').getCustomer()

Enfin, on va modifer notre vue index afin d'afficher nos articles templates/Wishlist/index.html.twig

{% extends '@SyliusShop/layout.html.twig' %}

{% block content %}
    <h1>index wishlist</h1>
    <div class="ui four doubling cards">
        {% for product in wishlists.products %}
            {% include '@SyliusShop/Product/_box.html.twig' %}
        {% endfor %}
    </div>
{% endblock %}

Et on peut se rendre sur la page /wishlists pour retrouver nos articles de notre wishlist :

Image Title

Voilà !