Sylius18 min

Developper un plugin Sylius from scratch : le guide du developpeur

Par Pierre-Arthur Demengel
SyliusPluginPHPSymfonyDeveloppement

L'ecosysteme de plugins est ce qui fait la puissance de Sylius. Savoir en developper un, c'est debloquer la capacite de personnaliser Sylius sans jamais toucher au core. Ce tutoriel vous guide de la creation du squelette a la publication sur Packagist.

Creer le squelette

Sylius fournit un squelette officiel :

composer create-project sylius/plugin-skeleton MonPlugin
cd MonPlugin

Structure generee :

MonPlugin/
  src/
    MonPluginPlugin.php          # Point d'entree du bundle
    DependencyInjection/
      Configuration.php
      MonPluginExtension.php
    Entity/                      # Vos entites Doctrine
    Repository/                  # Vos repositories
    Form/                        # Types de formulaires
    Menu/                        # Listeners de menu admin
    Resources/
      config/
        services.xml
        routes.yaml
        doctrine/                # Mappings Doctrine
      views/                     # Templates Twig
      translations/              # Traductions
  tests/
    Application/                 # App Sylius de test
    Unit/                        # Tests unitaires
    Functional/                  # Tests fonctionnels
  composer.json
  phpunit.xml.dist

Exemple concret : un plugin de wishlist

Construisons un plugin qui permet aux clients de sauvegarder des produits en favoris.

1. L'entite WishlistItem

<?php
// src/Entity/WishlistItem.php
namespace MonPlugin\Entity;

use Doctrine\ORM\Mapping as ORM;
use Sylius\Component\Core\Model\CustomerInterface;
use Sylius\Component\Core\Model\ProductInterface;

#[ORM\Entity]
#[ORM\Table(name: 'mon_plugin_wishlist_item')]
#[ORM\UniqueConstraint(columns: ['customer_id', 'product_id'])]
class WishlistItem
{
    #[ORM\Id]
    #[ORM\GeneratedValue]
    #[ORM\Column(type: 'integer')]
    private ?int $id = null;

    #[ORM\ManyToOne(targetEntity: CustomerInterface::class)]
    #[ORM\JoinColumn(nullable: false, onDelete: 'CASCADE')]
    private CustomerInterface $customer;

    #[ORM\ManyToOne(targetEntity: ProductInterface::class)]
    #[ORM\JoinColumn(nullable: false, onDelete: 'CASCADE')]
    private ProductInterface $product;

    #[ORM\Column(type: 'datetime_immutable')]
    private \DateTimeImmutable $createdAt;

    public function __construct()
    {
        $this->createdAt = new \DateTimeImmutable();
    }

    // Getters et setters...
}

2. Le controller

<?php
// src/Controller/WishlistController.php
namespace MonPlugin\Controller;

use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpFoundation\Request;
use Doctrine\ORM\EntityManagerInterface;

class WishlistController extends AbstractController
{
    public function toggle(
        int $productId,
        EntityManagerInterface $em,
        Request $request
    ): Response {
        $customer = $this->getUser()->getCustomer();
        $product = $em->getRepository(ProductInterface::class)->find($productId);

        $existing = $em->getRepository(WishlistItem::class)->findOneBy([
            'customer' => $customer,
            'product' => $product,
        ]);

        if ($existing) {
            $em->remove($existing);
            $this->addFlash('success', 'Produit retire des favoris');
        } else {
            $item = new WishlistItem();
            $item->setCustomer($customer);
            $item->setProduct($product);
            $em->persist($item);
            $this->addFlash('success', 'Produit ajoute aux favoris');
        }

        $em->flush();
        return $this->redirect($request->headers->get('referer', '/'));
    }
}

3. Les routes

# src/Resources/config/routes.yaml
mon_plugin_wishlist_toggle:
  path: /wishlist/toggle/{productId}
  controller: MonPlugin\Controller\WishlistController::toggle
  methods: [POST]
  requirements:
    productId: '\d+'

4. Enregistrer les services

<!-- src/Resources/config/services.xml -->
<?xml version="1.0" encoding="UTF-8" ?>
<container xmlns="http://symfony.com/schema/dic/services">
  <services>
    <defaults autowire="true" autoconfigure="true" public="false" />

    <prototype namespace="MonPlugin\" resource="../../*"
      exclude="../../{Entity,Migrations,Tests}" />
  </services>
</container>

5. Ajouter un bouton coeur via Twig Hook

# src/Resources/config/twig_hooks.yaml
sylius_twig_hooks:
  hooks:
    'sylius_shop.product.show.content':
      wishlist_button:
        template: '@MonPlugin/Shop/Product/_wishlist_button.html.twig'
        priority: 80
{# src/Resources/views/Shop/Product/_wishlist_button.html.twig #}
{% if app.user %}
  <form method="post" action="{{ path('mon_plugin_wishlist_toggle', {productId: product.id}) }}">
    <input type="hidden" name="_token" value="{{ csrf_token('wishlist') }}">
    <button type="submit" class="btn btn-outline-secondary">
      {% if is_in_wishlist(product) %}Retirer des favoris{% else %}Ajouter aux favoris{% endif %}
    </button>
  </form>
{% endif %}

Tester le plugin

# Installer l'application de test
cd tests/Application
composer install
bin/console doctrine:database:create
bin/console doctrine:schema:update --force
bin/console sylius:fixtures:load

# Lancer les tests
cd ../..
vendor/bin/phpunit

Publier sur Packagist

Votre composer.json doit inclure :

{
  "name": "monentreprise/sylius-wishlist-plugin",
  "type": "sylius-plugin",
  "description": "Wishlist/favorites plugin for Sylius",
  "require": {
    "sylius/sylius": "^2.0"
  },
  "autoload": {
    "psr-4": {
      "MonPlugin\\": "src/"
    }
  }
}

Poussez sur GitHub, enregistrez sur Packagist, et soumettez au Sylius Marketplace.

Besoin d'un plugin Sylius sur mesure pour votre projet ? Contactez-nous. Nous avons developpe des dizaines de plugins pour nos clients. Voir aussi notre guide complet Sylius.

Questions fréquentes

13 projets livrésGrand-Est & BelgiqueLighthouse >90Disponible immédiatement

Un projet en tête ?

Discutons de votre site web. Réponse garantie sous 24h.

Ou appelez directement :06 95 41 30 25

WhatsApp
Appeler