Chapitre 15.8 — Créer & publier un bundle
Partie 15 : Expert — Chapitre 8/9 Version de référence : Symfony 7.4 / Packagist.
⏱️ TL;DR
- Un bundle empaquette du code réutilisable entre projets (rappel Partie 5.4 :
AbstractBundle). On le publie pour le partager (ou le vendre).- Cycle : extraire le code dans un dépôt dédié →
composer.json(type: symfony-bundle, autoload PSR-4) → tests + CI → tag SemVer → publier sur Packagist.- Une recipe (optionnelle) automatise la configuration à l’installation (Flex, Partie 1.3).
- Un bon bundle : documenté, testé, versionné en SemVer, avec des options de config validées.
- Angle produit/monétisation : bundle open source pour la visibilité, version premium ou open-core pour le revenu (Partie 17).
- Pour un dev Next.js : c’est publier un package npm réutilisable — mais qui, en prime, se configure tout seul dans l’app hôte (recipe).
🎯 Objectifs
- Extraire du code dans un bundle autonome.
- Configurer
composer.jsonet l’autoload. - Versionner en SemVer et publier sur Packagist.
- Connaître l’angle produit d’un bundle.
1. Quand extraire un bundle
Vous avez écrit, dans BookLab, une fonctionnalité générique et réutilisable : un système de webhooks sortants (Partie 13.2), un connecteur Moodle (13.4), un pont LTI (13.5)… Si vous vous surprenez à copier-coller ce code d’un projet à l’autre, c’est le signal : extrayez un bundle.
Rappel (Partie 5.4) : on ne crée pas un bundle pour organiser une app, seulement pour du code partagé/publié.
2. La structure du dépôt
Un bundle est un dépôt Git séparé, publiable sur Packagist :
acme/moodle-connector-bundle/
├── src/
│ ├── AcmeMoodleConnectorBundle.php ← extends AbstractBundle (Partie 5.4)
│ ├── Client/MoodleClient.php
│ └── DependencyInjection/ (si config avancée)
├── config/
│ └── services.php
├── tests/
├── composer.json
├── README.md
└── .github/workflows/ci.yml3. Le composer.json
{
"name": "acme/moodle-connector-bundle",
"description": "Connecteur Moodle Web Services pour Symfony.",
"type": "symfony-bundle",
"license": "MIT",
"require": {
"php": ">=8.2",
"symfony/framework-bundle": "^7.0",
"symfony/http-client": "^7.0"
},
"require-dev": {
"symfony/phpunit-bridge": "^7.0",
"phpstan/phpstan": "^2.0"
},
"autoload": {
"psr-4": { "Acme\\MoodleConnectorBundle\\": "src/" }
},
"autoload-dev": {
"psr-4": { "Acme\\MoodleConnectorBundle\\Tests\\": "tests/" }
}
}Points clés : type: symfony-bundle (Flex/Composer le reconnaissent), une licence claire (MIT pour un usage large — Partie 1.2), des contraintes de version raisonnables sur Symfony (^7.0, pas figées à un patch), et l’autoload PSR-4.
⚠️ Piège des contraintes trop strictes : exiger
symfony/framework-bundle: 7.4.3(un patch précis) rend votre bundle inutilisable dans un projet sur 7.4.5 ou 7.3. Utilisez des contraintes larges mais sûres (^7.0ou^7.2) pour être compatible avec le plus de projets possible. Un bundle trop contraint ne s’installe nulle part. Testez la compatibilité en CI sur plusieurs versions (matrice, Partie 14.7).
4. Tests, CI et documentation
Un bundle public doit inspirer confiance :
- Tests (Partie 14) : unitaires + fonctionnels (montez un mini-kernel de test).
- CI (Partie 14.7) : tests + PHPStan + CS Fixer, sur une matrice de versions PHP/Symfony.
- README : installation, configuration, exemples d’usage, options.
- CHANGELOG : ce qui change entre versions.
Un bundle testé, documenté et intégré en CI se distingue immédiatement d’un dépôt bâclé — c’est ce qui fait qu’on l’adopte.
5. Versionner en SemVer et publier
- SemVer (Partie 2.6) :
MAJOR.MINOR.PATCH. Un changement cassant → MAJOR ; un ajout compatible → MINOR ; un correctif → PATCH. On tague dans git :git tag v1.0.0 && git push --tags - Packagist : on soumet l’URL du dépôt GitHub sur packagist.org , et un webhook met à jour Packagist à chaque tag. Dès lors,
composer require acme/moodle-connector-bundlefonctionne pour tout le monde.
6. La recipe (configuration automatique)
Pour que votre bundle se configure tout seul à l’installation (créer un fichier de config, ajouter des variables .env, s’enregistrer), on fournit une recipe (Partie 1.3) — soumise au dépôt symfony/recipes-contrib. C’est ce qui transforme composer require votre-bundle en une expérience « installer = configurer », comme les bundles officiels.
7. L’angle produit / monétisation
Un bundle n’est pas qu’un cadeau à la communauté — c’est un actif :
- Visibilité : un bundle open source utile vous fait connaître (portfolio, réputation — Partie 17).
- Open-core : le cœur open source (gratuit), des fonctionnalités premium payantes (un bundle « pro » ou un service SaaS autour).
- Support/sponsoring : GitHub Sponsors, support commercial.
La licence MIT de Symfony (Partie 1.2) vous laisse toute liberté de bâtir du propriétaire par-dessus — contrairement au GPL de WordPress/Moodle. On détaille ces modèles en Partie 17.
💡 Pour un dev Next.js : publier un bundle Composer ≈ publier un package npm, avec les mêmes bonnes pratiques (SemVer, README, CI, tests). La recipe n’a pas d’équivalent npm : c’est un plus (le package se câble automatiquement). Côté carrière, un bundle Symfony + un package npm reconnus (par ex. autour de votre pont API Symfony↔Next.js) forment un portfolio rare qui attire des missions — exactement l’effet levier de la Partie 17.
✏️ Exercices
Exercice 1. Vous copiez pour la 3ᵉ fois votre connecteur Moodle d’un projet à l’autre. Que faites-vous, et quelle est la première étape ?
✅ Solution
C’est le signal qu’il faut extraire un bundle (code réutilisable dupliqué). Première étape : créer un dépôt Git séparé avec la structure d’un bundle (classe extends AbstractBundle, src/, config/services.php), un composer.json en type: symfony-bundle avec l’autoload PSR-4 et des contraintes Symfony larges. Puis : tests + CI + README, tag SemVer, publication sur Packagist. Les projets consommateurs font alors composer require au lieu de copier-coller.
Exercice 2. Pourquoi des contraintes de version trop strictes dans le composer.json d’un bundle sont-elles un problème ?
✅ Solution
Des contraintes strictes (ex. symfony/framework-bundle: 7.4.3) rendent le bundle incompatible avec la plupart des projets (qui sont sur un autre patch/minor), donc ininstallable. Un bundle doit viser une large compatibilité avec des contraintes souples mais sûres (^7.0/^7.2), pour s’installer dans un maximum de projets. On vérifie cette compatibilité en CI via une matrice de versions.
Exercice 3. Comment un bundle open source peut-il générer un revenu tout en restant MIT ?
✅ Solution
Via un modèle open-core : le cœur reste open source (MIT, gratuit, pour la visibilité et l’adoption), et on vend des fonctionnalités premium (un bundle « pro » sous licence commerciale, un SaaS/service autour, du support). La licence MIT de Symfony autorise de bâtir du propriétaire par-dessus (contrairement au GPL). La visibilité du bundle gratuit alimente aussi les missions freelance. (Détails en Partie 17.)
🧠 Quiz de révision
1. Quand crée-t-on un bundle ?
Pour du code réutilisable/partagé entre projets (dupliqué), pas pour organiser une app.
2. Quel type dans le composer.json d’un bundle ?
type dans le composer.json d’un bundle ?type: "symfony-bundle", avec autoload PSR-4 et des contraintes Symfony larges (^7.0).
3. Comment publie-t-on un bundle ?
Tag SemVer dans git (v1.0.0) + soumission du dépôt sur Packagist (un webhook met à jour à chaque tag).
4. À quoi sert une recipe pour un bundle ?
À le configurer automatiquement à l’installation (fichiers de config, .env, enregistrement) — « installer = configurer », via symfony/recipes-contrib.
5. Pourquoi la licence MIT est-elle un atout pour monétiser ?
Elle permet de bâtir du propriétaire (premium, SaaS) par-dessus le bundle open source — modèle open-core — sans les contraintes du GPL.
Prochain chapitre : 15.9 — Multi-tenant — un code, plusieurs clients.