Un bundle pour Symfony 6.1 (et plus)
Pour la petite histoire...
En bonne feignasse, quand j'ai besoin d'un nouvel outil, je cherche d'abord si quelque chose existe.
Puis, en bonne développeuse exigeante, si rien n'existe ou si l'existant ne correspond pas à mes attentes, je développe mon propre outil.
C'est ainsi qu'est né phpBlocNote il y a des années.
Bien sûr, au fil du temps, il a (beaucoup) évolué. Mon instance perso porte même un autre nom (non, je ne vous le dirai pas, bande de petits curieux ^^).
Cet outil m'a aussi beaucoup servi de "base d'essai" pour tester des frameworks (oui, plusieurs en plus de 15 ans)
puis pour éprouver les montées de versions du framework gagnant (Symfony, en version 6.1 à l'heure où j'écris).
Puis, d'autres outils se sont ajoutés... j'ai aujourd'hui 7 web apps sur la même instance de Symfony !
Et depuis un certain temps, je voulais en faire des bundles, histoire de séparer, organiser proprement tout mon petit bazar (car aucune app n'a vraiment de lien avec ses copines).
Et je me suis cassé les dents plusieurs fois
Depuis que j'ai migré en Symfony 5, la volonté de "bundliser" mes apps (qui n'étaient que 3 à l'époque) revient régulièrement...
Mais faute de temps (ou de motivation), je ne m'y suis réellement collée que ces derniers jours. Et comme j'en ai ch*é, je me suis dit que, tant qu'à faire,
j'allais partager le résultat. Qui sait, ça peut servir à d'autres ;)
Créer un bundle Symfony 6.2 complet
Dans cet exemple, le bundle est relativement simple mais comprend notamment des templates, quelques assets, des traductions et forcément, le routing.
Documentation Symfony
J'ai consulté la documentation officielle Symfony concernant les bundles.
Mais cette documentation ne m'a pas permis, à elle seule, de créer et rendre mon bundle fonctionnel.
Arborescence et organisation des fichiers
Après avoir pas mal tâtonné pour trouver une structure de fichiers fonctionnelle, voici à quoi je suis arrivée :
Vous reconnaîtrez sans doute l'arborescence standard d'un projet Symfony.
Comme j'aurai, à terme, plusieurs bundles, j'ai choisi d'ajouter un niveau "organisation" ou "vendor" (DelPlop), mais je pense qu'on peut s'en passer.
Ce répertoire principal se place à la racine du projet.
Le code du bundle se place dans un sous-répertoire du nom du bundle avec le suffixe (WishListBundle).
Enfin, la structure est celle d'un projet Symfony classique, telle que mentionnée dans
la documentation officielle.
Chargement des services
Pour cette partie, je n'ai pas eu vraiment de souci (à part avoir voulu faire un truc bien compliqué en premier lieu ><), la documentation officielle présente une solution simple et efficace.
<?php declare(strict_types=1); namespace DelPlop\WishListBundle; use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\DependencyInjection\Loader\Configurator\ContainerConfigurator; use Symfony\Component\HttpKernel\Bundle\AbstractBundle; class DelPlopWishListBundle extends AbstractBundle { public function prependExtension(ContainerConfigurator $container, ContainerBuilder $builder): void { $container->import('../config/services.yaml'); } }Et le contenu de config/services.yaml :
services: _defaults: autowire: true autoconfigure: true DelPlop\WishListBundle\: resource: '../src/' exclude: - '../src/Entity/' DelPlop\WishListBundle\Controller\: resource: '../src/Controller' tags: [ 'controller.service_arguments' ]
Activer mon bundle
C'est probablement la partie qui m'a le plus bloquée et pourtant, ça semble si logique une fois fait ><
Composer sera votre ami. Il faut éditer le fichier composer.json à la racine du projet puis en créer un dans le bundle (à la racine également).
[...] "repositories": [ { "type": "path", "url": "DelPlop/WishListBundle" } ], "require": { "delplop/wishlist-bundle": "dev-master" }Le fichier composer.json du bundle :
{ "name": "delplop/wishlist-bundle", "description": "My Wish-list Symfony bundle", "type": "symfony-bundle", "require": { "php": ">=8.1", "symfony/framework-bundle": "6.1.*" }, "autoload": { "psr-4": { "DelPlop\\WishListBundle\\": "src/" } }, "license": "proprietary", "authors": [ { "name": "Del Plop", "email": "<votre email>" } ] }Bien sûr, ajustez selon vos goûts (licence, nom du ou des auteurs, description, etc) ; veillez simplement à ce que le nom corresponde à ce que vous ajoutez dans le fichier composer.json du projet.
composer updateEt on est presque prêt ;)
Exemple de contrôleur et template
<?php declare(strict_types=1); namespace DelPlop\WishListBundle\Controller; use DelPlop\WishListBundle\Repository\WishListUserRepository; use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; use Symfony\Component\HttpFoundation\Response; class UserController extends AbstractController { public function index(WishListUserRepository $wishListUserRepository): Response { return $this->render('@DelPlopWishList/user/list.html.twig', [ 'users' => $wishListUserRepository->findAll() ]); } }Le template associé :
{% extends '@DelPlopWishList/wishlist.html.twig' %} {% trans_default_domain 'wishlist' %} {% block title %}{{ ('users.list_long'|trans) }}{% endblock %} {% block body %} <h1 class="w3-card-4 w3-padding-16">{{ ('users.list_long'|trans) }}</h1> <table class="w3-table w3-striped w3-bordered"> <tr class="w3-theme"> <th>{{ ('users.name'|trans) }}</th> </tr> {% for user in users %} <tr> <td> <a href="{{ path('wishlist_user_articles', {id: user.id}) }}" title="{{ ('articles.see_user_articles'|trans({username: user.user.username})) }}">{{ user.user.username }}</a> </td> </tr> {% endfor %} </table> {% endblock %}Rien d'extraordinaire là-dedans.
Le trans_default_domain (wishlist) correspond au nom des fichiers de trad (wishlist.fr.yaml / wishlist.en.yaml).
Routing
wishlist_routes: name_prefix: 'wishlist_' resource: '@DelPlopWishListBundle/config/routes.yaml'
Assets
Après avoir lancé bin/console assets:install, les assets du bundle se trouvent dans public/bundles/delplopwishlist/.