Table des matières :
- PrestaShop 9 : un module dans une base hybride (Legacy + Symfony 6.4)
- Arborescence recommandée : séparer bootstrap PrestaShop et code applicatif
- Services Symfony : définition, autowiring et consommation depuis un hook
- Back-office Symfony : routes, contrôleurs, sécurité et Twig
- Accès aux données : Legacy Db, Doctrine, DBAL… choisir sans dogme
- Étendre PrestaShop sans overrides : hooks, événements et décoration de services
- Qualité logicielle et packaging : Composer, statique, tests, builds reproductibles
- Performance, cache et sécurité : les “vrais” problèmes en production
- Checklist de module PrestaShop 9 “livrable” : ce que vous devez pouvoir justifier
PrestaShop 9 : un module dans une base hybride (Legacy + Symfony 6.4)
PrestaShop 9 (et 9.1 à date du 2026-06-29) est une plateforme hybride : le front-office et une partie du back-office restent fortement couplés au cœur historique (classes ObjectModel, Context, hooks), tandis que le back-office « moderne » repose sur Symfony 6.4 (contrôleurs, routing, Twig, DI). Cette cohabitation est un avantage (migration progressive), mais c’est aussi la source principale de modules bancals : on voit encore trop de modules qui injectent du Symfony dans du Legacy sans frontière claire, ou à l’inverse, qui ignorent le conteneur et recâblent tout “à la main”.
Sur PrestaShop 9, un module “propre” doit assumer ce modèle : conserver une classe principale Module pour l’install/uninstall, l’inscription aux hooks, et la compatibilité Marketplace, tout en déportant la logique métier dans des services Symfony instanciés par le container. Si vous ne le faites pas, vous retombez dans les patterns PS 1.6/1.7 (singletons, statiques, dépendances cachées) et vous payez la dette technique sur chaque évolution.
Un bon repère pour garder une frontière saine :
- Legacy (classe
Module, hooks) : orchestration, lecture du contexte (shop, langue, devise), validation des préconditions, délégation. - Symfony (
src/Service,src/Repository,src/Controller) : règles métier, accès aux données, appels externes, logs, gestion d’erreur, transactions. - Templates : Twig côté BO moderne, Smarty uniquement si vous n’avez pas le choix (FO ou écrans legacy existants).
Côté runtime, gardez en tête les contraintes de version : PrestaShop 9.1 annonce une compatibilité PHP 8.1 à 8.5 (cf. article interne : PrestaShop 9.1 : compatibilité PHP 8.1–8.5, CLI et nouveautés développeurs). Ça a des impacts concrets : typage strict, attributs PHP 8, et dépendances Composer qui doivent suivre. Ne livrez pas un module avec une contrainte PHP trop stricte sans raison (ex. ^8.3), sinon vous créez des incompatibilités artificielles chez des marchands.
Enfin, gardez en tête le contexte “terrain” : en France, l’e-commerce se joue souvent sur des volumes et des pics saisonniers (soldes, Noël). Un module qui ajoute 200 ms par requête ou 3 requêtes SQL par produit “pour une petite feature” finit par coûter cher en conversion et en exploitation. Sans surcharger l’article de chiffres macro, on peut rappeler que les enjeux sont réels : la FEVAD publie chaque année des bilans chiffrés sur l’e-commerce en France (source : FEVAD).
« Symfony is a PHP framework for web and console applications and a set of reusable PHP components. » — Symfony (symfony.com, consulté en 2026)
Arborescence recommandée : séparer bootstrap PrestaShop et code applicatif
Le squelette d’un module PrestaShop 9 reste centré sur le fichier principal mymodule.php (héritant de Module). Mais ce fichier ne doit plus contenir la logique : il sert de bootstrap (installation, déclaration des hooks, redirection éventuelle vers une page Symfony, configuration basique). Le reste vit dans src/ (PSR-4) et est chargé via Composer. Sans composer.json, vous finissez par recoder un autoloader, et surtout vous perdez l’accès à l’écosystème (PHPStan, PHPUnit, Rector, etc.).
Arborescence minimale réaliste (pas un “Hello World”) :
modules/mymodule/
mymodule.php
composer.json
config/
services.yml
routes.yml
src/
Controller/Admin/ConfigController.php
Form/Type/ConfigType.php
Service/StockAllocator.php
Repository/OrderRepository.php
Install/Installer.php
views/
templates/admin/config.html.twig
translations/
fr-FR.xlf
upgrade/
upgrade-1.1.0.php
sql/
install.sql
uninstall.sql
Trois points pratiques, souvent mal gérés : (1) config/services.yml est le point d’entrée DI du module, (2) config/routes.yml expose vos routes Symfony (souvent back-office), et (3) views/templates doit accueillir du Twig si vous faites du BO moderne (et pas des templates Smarty bricolés). Pour Twig, si vous avez besoin d’un rappel sur les patterns propres (héritage, includes, macros), l’article interne Twig PHP : syntaxe essentielle, héritage de templates et includes est un bon socle.
Pour éviter le “module spaghetti”, clarifiez aussi ce qui a le droit d’être dans mymodule.php. Une règle simple : pas de SQL, pas d’appel HTTP, pas de boucle métier. Exemple de répartition utile :
| Besoin | Où le mettre | Pourquoi |
|---|---|---|
install(), uninstall(), enable(), disable() |
mymodule.php + src/Install/Installer.php |
Le module reste la façade PrestaShop, mais la logique d’install est testable |
Hook hookActionValidateOrder |
mymodule.php (méthode hook) + service src/Service/... |
Hook fin = faible risque de régression et meilleure observabilité |
| Page de configuration | config/routes.yml + src/Controller/... + Twig |
Aligné avec le back-office Symfony moderne, CSRF et permissions gérées proprement |
Dernier détail qui évite des heures de debug : versionnez votre module comme un produit. Une convention simple est SemVer + un CHANGELOG.md. Et ne faites pas l’impasse sur les scripts upgrade/ (PrestaShop les appelle en fonction des versions). Livrer un install() qui “migre” tout à chaque activation est une antipattern.
Services Symfony : définition, autowiring et consommation depuis un hook
La règle : toute logique doit être dans un service. Le conteneur Symfony vous donne l’injection de dépendances, l’autowiring et le décorateur de service (utile pour étendre sans override). Dans config/services.yml, partez d’un défaut autowire: true / autoconfigure: true, puis exposez explicitement ce qui doit l’être.
Exemple minimal (adapté à PrestaShop 9 / Symfony 6.4) :
# modules/mymodule/config/services.yml
services:
_defaults:
autowire: true
autoconfigure: true
public: false
MyVendor\MyModule\:
resource: '../src/'
exclude:
- '../src/Entity/'
- '../src/Kernel.php'
mymodule.stock_allocator:
class: MyVendor\MyModule\Service\StockAllocator
public: true
Pourquoi un service public: true ? Parce que le pont avec le Legacy (hooks) passe encore souvent par SymfonyContainer::getInstance()->get() ou \PrestaShop\PrestaShop\Adapter\SymfonyContainer. Ce n’est pas “beau” du point de vue Symfony, mais c’est pragmatique dans PrestaShop : les hooks sont appelés par la classe Module, hors contrôleur Symfony, donc hors injection automatique. On limite la casse en ne rendant publics que les points d’entrée nécessaires.
- Exposez des façades, pas des détails : rendez public un service “application” (ex.
mymodule.order_workflow) plutôt quemymodule.order_repository. Ça limite le nombre de services publics et vous laisse refactorer. - Typage et invariants au niveau service : vos hooks reçoivent des structures parfois instables (
$paramsincomplets selon le contexte). Faites porter la validation au service d’entrée, pas au hook.
Exemple de consommation depuis un hook (en gardant la méthode du hook fine) :
public function hookActionValidateOrder($params)
{
$container = \PrestaShop\PrestaShop\Adapter\SymfonyContainer::getInstance();
/** @var \MyVendor\MyModule\Service\StockAllocator $allocator */
$allocator = $container->get('mymodule.stock_allocator');
$orderId = (int) $params['order']->id;
$allocator->allocateForOrder($orderId);
}
Mini-scénario réaliste : votre module réserve du stock “entrepôt” lors de la validation de commande, puis appelle un WMS. Si vous faites l’appel HTTP dans le hook, vous transformez un aléa réseau en indisponibilité du checkout. La variante robuste consiste à (1) enregistrer une intention en base (table mymodule_reservation), (2) répondre vite au hook, (3) traiter l’envoi WMS via cron/worker. Même sans queue sophistiquée, vous gagnez en résilience.
Si vous devez générer des URLs vers vos routes Symfony, ne reconstruisez pas à la main : utilisez le router Symfony ou le helper de liens PrestaShop. Pour comprendre les implications _legacy_link et la génération d’URL propre en back-office, référence interne : Génération d’URL PrestaShop : Link, routes Symfony et legacylink.
Back-office Symfony : routes, contrôleurs, sécurité et Twig
Un module PrestaShop 9 “moderne” expose généralement une page de configuration en Symfony (au lieu d’un getContent() monolithique). Le routing se déclare dans modules/mymodule/config/routes.yml. Vous mappez une URL dans le back-office vers un contrôleur Symfony, et vous pouvez rattacher un _legacy_link pour l’intégration au système de droits et pour conserver des patterns PrestaShop existants.
Exemple de route :
mymodule_admin_config:
path: /mymodule/config
methods: [GET, POST]
defaults:
_controller: 'MyVendor\\MyModule\\Controller\\Admin\\ConfigController::index'
_legacy_controller: 'AdminMyModuleConfig'
_legacy_link: 'AdminMyModuleConfig'
Dans le contrôleur, vous utilisez Twig et les Form Types Symfony (les composants sont déjà présents dans l’écosystème PrestaShop). Le point critique est la sécurité : sur le back-office, vous devez gérer permissions + CSRF.
- Permissions : ne partez pas du principe que “si l’URL est dans le BO, c’est OK”. Attachez correctement
_legacy_controller/_legacy_linket vérifiez vos droits (lecture/écriture) côté contrôleur, surtout si vous avez des actions destructrices (purge, resync). - CSRF : activez les protections natives des Form Types, et évitez les endpoints POST “artisanaux” sans token.
« Cross-Site Request Forgery (CSRF) is an attack that forces an end user to execute unwanted actions on a web application in which they’re currently authenticated. » — OWASP (CSRF, consulté en 2026)
Enfin, ne mélangez pas Twig et Smarty dans le même écran, à moins d’avoir une bonne raison (et du temps). Twig est plus cohérent avec le BO moderne, et l’outillage (tests, lint, héritage) est plus propre. Si vous cherchez le contexte architectural global de PrestaShop 9 côté Symfony (versions, modernisation, impacts sur modules), l’article interne PrestaShop 9 : nouveautés techniques Symfony 6.4, API et performances cadre bien les changements.
Références externes utiles (à lire, pas à survoler) :
- Symfony Routing : https://symfony.com/doc/6.4/routing.html
- Symfony Service Container : https://symfony.com/doc/6.4/service_container.html
- OWASP CSRF : https://owasp.org/www-community/attacks/csrf
Accès aux données : Legacy Db, Doctrine, DBAL… choisir sans dogme
PrestaShop fournit plusieurs chemins d’accès à la donnée, et c’est précisément là que beaucoup de modules se dégradent. Le Legacy propose Db::getInstance() et des ObjectModel (pratique, mais couplé aux conventions PS, et souvent peu testable). Symfony/Doctrine propose EntityManager, DBAL, repositories, événements… mais tout n’est pas “première classe” partout dans PrestaShop, et vous pouvez vous retrouver à lutter contre le schéma existant.
Pour un module, le choix pragmatique est souvent : DBAL pour lire/écrire sur vos propres tables, Legacy ObjectModel ou Adapter pour interagir avec les entités cœur (produit, commande, client) si vous ne voulez pas ré-implémenter des invariants métier.
Une matrice simple (et utile en revue de code) :
| Besoin | Option conseillée | À surveiller |
|---|---|---|
| CRUD sur tables du module | Doctrine DBAL / QueryBuilder | Transactions, index, hydration |
| Lecture simple (stats, listes) | DBAL ou Db si très ponctuel |
Risque SQL non paramétré si mal fait |
| Modifier produit/commande/client | APIs/Adapters PrestaShop, parfois ObjectModel |
Invariants métier, hooks natifs, multi-boutique |
| “Tout faire en ORM” sur schéma core | À éviter en module | Complexité, mapping fragile, temps de debug |
En clair : utilisez Doctrine quand il apporte de la structure (transactions, query builder, typage), mais ne réécrivez pas la logique de stock de PrestaShop uniquement “pour faire Symfony”. Si vous voulez un exemple orienté Doctrine dans l’écosystème PrestaShop, référence interne : Symfony PrestaShop : développer des modules robustes avec Doctrine.
Côté schéma, évitez les migrations “maison” dispersées dans le code. Standardisez : sql/install.sql + scripts d’upgrade versionnés + indexation dès le départ. Un module qui ajoute une table sans index sur les colonnes de jointure est un module qui se comporte “bien” en dev et qui explose en prod. Pour les bonnes pratiques d’index et l’usage d’EXPLAIN, référence interne : Index MySQL : optimiser WHERE, JOIN et ORDER BY avec EXPLAIN.
Enfin, si votre module pousse la base (catalogue massif, synchronisations), vous ne pourrez pas esquiver la question de l’engine et de la maintenance : routines de purge, analyse, et choix MySQL/PostgreSQL si votre stack le permet. Deux lectures internes complémentaires selon le besoin : Base de données PrestaShop : routine de maintenance et nettoyage automatisé et PostgreSQL vs MySQL 2026 : performances, sécurité et cas d’usage.
Étendre PrestaShop sans overrides : hooks, événements et décoration de services
Les overrides PHP existent encore, mais c’est une technologie de dette : collisions entre modules, difficultés de merge, comportements non déterministes selon l’ordre d’installation, et upgrades à risque. Sur PrestaShop 9, l’extension “propre” passe par : (1) hooks Legacy, (2) événements Symfony lorsqu’ils existent, (3) décoration de services lorsque vous devez remplacer/augmenter un service du cœur.
Sur les hooks, ne faites pas “tout” dans hookXxx() : validez les préconditions (types, IDs, contexte), déléguez à un service, et loggez de manière structurée. Exemple de garde-fous simples (qui évitent des bugs coûteux) :
$paramscontient bien ce que vous attendez (ex.orderobjet,id_orderentier).- Vous êtes dans le bon shop (multi-boutique) et la bonne langue si nécessaire.
- Vous évitez les doubles traitements (un hook peut être appelé plusieurs fois selon le flux).
Le hook actionProductGridDataModifier est un bon exemple de point d’extension back-office “catalogue” : il permet de modifier colonnes et données sans patcher le core. Référence interne : Hook actionProductGridDataModifier PrestaShop : emplacement, appel et usage développeur.
Pour la décoration, vous gardez l’interface publique du service décoré, et vous encapsulez la logique additionnelle. C’est une alternative nettement plus sûre à l’override, mais elle exige de connaître les IDs de service du core et les contrats (interfaces). Exemple conceptuel :
services:
MyVendor\MyModule\Decorator\CoreServiceDecorator:
decorates: prestashop.core.some_service
arguments:
- '@MyVendor\\MyModule\\Decorator\\CoreServiceDecorator.inner'
Conseil pratique : si vous décorez un service sensible (prix, stock, panier), prévoyez un “mode safe” via configuration (désactivation partielle) pour permettre un rollback fonctionnel sans désinstaller le module. Ça aide énormément en exploitation.
La limite : si le core renomme l’ID de service ou modifie la signature, votre module casse. D’où l’importance de tester sur plusieurs versions (9.0, 9.1) en CI et de suivre les changelogs. Côté gestion des modifications en prod, une checklist post-déploiement évite de livrer à l’aveugle (ex. tunnel de commande, règles panier). Référence interne : Contrôle PrestaShop post-modification : checklist tunnel de commande et règles panier.
Qualité logicielle et packaging : Composer, statique, tests, builds reproductibles
Le niveau minimal en 2026 : Composer + PSR-4 + analyse statique. Déclarez clairement vos contraintes (php, prestashop/prestashop, dépendances), et évitez d’embarquer des vendor énormes si vous n’en avez pas besoin. Un module qui embarque 20 dépendances pour faire un call HTTP (au lieu d’utiliser Symfony HttpClient, souvent déjà présent) est un module qui augmente la surface de vulnérabilité et les conflits de versions.
Un composer.json “sain” (schéma) doit au moins clarifier :
require.php(ex.>=8.1)autoload.psr-4(namespace du module)- scripts/outillage en dev uniquement (
phpstan,phpunit…), pas forcément dans l’artefact livré
Pour l’analyse statique, PHPStan est devenu un standard de fait côté PHP. Couplé à Rector, ça permet de maintenir un codebase aligné avec PHP 8.x et de remonter des erreurs avant production (types, nullability, appels incohérents). Référence interne directement actionnable : PHPStan et Rector : industrialiser la qualité du code PHP. En complément, assurez-vous que votre module n’émet pas d’erreurs/notice en production : config PHP, logs, et différenciation dev/prod doivent être maîtrisées (voir Gestion d’erreur PHP : bonnes pratiques et configuration développement/production).
Côté tests, même si vous ne visez pas 80% de couverture, vous devez au moins pouvoir valider automatiquement :
- install/upgrade (schéma + config)
- un scénario métier critique (ex. réservation stock, export commande)
- une route BO (permissions + CSRF + rendu minimal)
Pour la livraison, privilégiez des builds reproductibles : conteneurs, cache Composer, artefacts zip signés en interne si besoin. PrestaShop n’impose pas votre pipeline, mais vos clients paieront les conséquences si vous livrez “depuis votre laptop”. Un exemple concret de chaîne CI adaptée à PrestaShop (BuildKit, GitHub Actions, build déterministe) est détaillé ici : Intégration continue PrestaShop : BuildKit, GitHub Actions et build reproductible.
Performance, cache et sécurité : les “vrais” problèmes en production
Le module le plus élégant peut tuer un shop si vous le déployez sans garde-fous : requêtes N+1 en BO, hooks front-office lourds, appels API synchrones sur le thread HTTP, ou invalidation de cache mal pensée. Côté perf pure PHP, vous devez connaître vos fondamentaux serveur (PHP-FPM, OPcache, MySQL). Référence interne orientée mesures : Performance PrestaShop : benchmarks et optimisation PHP-FPM, OPCache, MySQL. Sur la latence perçue, le TTFB reste un indicateur simple et brutal : TTFB PrestaShop : réduire le Time To First Byte sous 200 ms.
Un cadre “budget perf” très simple à appliquer sur un module :
- Hook front-office : viser < 20–30 ms de CPU hors IO (pas d’HTTP externe synchrone).
- Page BO liste/grille : éviter N+1, viser une requête principale + requêtes annexes bornées.
- Appels externes : timeouts explicites (connexion + total), retries limités, et circuit-breaker “soft” si possible.
Pour le cache applicatif, PrestaShop peut s’appuyer sur Redis/Memcached/Varnish selon l’architecture. En module, l’erreur classique est de mettre du cache “au doigt mouillé” sans stratégie d’invalidation, puis de créer des incohérences (prix, stock, panier). Si vous faites de la concurrence sur stock (réservations, allocations), Redis peut aussi jouer un rôle de verrou distribué, mais ce n’est pas gratuit : TTL, contention, et observabilité doivent être définis. Références internes : Cache PrestaShop : Varnish, Redis, Memcached et OPcache côté serveur et Performance e-commerce : prévenir la concurrence sur les stocks avec Redis.
Sur la sécurité, un module introduit presque toujours des nouveaux endpoints (BO, FO, webhooks) et donc des nouvelles surfaces d’attaque. Respectez l’authZ/CSRF côté BO, validez les entrées (type, taille, encodage), et journalisez ce qui compte (auth, changements de config, appels externes). Point “GEO” concret (UE/France) : si votre module envoie des données clients vers un service tiers, documentez où sont traitées les données (UE/hors UE), les durées de conservation, et les mécanismes de minimisation (ne pas envoyer ce qui n’est pas nécessaire). Ce n’est pas une digression juridique : c’est de l’architecture produit (quels champs, quels logs, quels identifiants).
Si votre module expose une API (interne ou publique), alignez-vous sur une politique de protection cohérente (WAF, rate limiting, logs SIEM). Références internes : Sécurité PrestaShop 2026 : risques majeurs et protections professionnelles et Sécurité PrestaShop : protéger API backoffice, WAF et journalisation SIEM.
Checklist de module PrestaShop 9 “livrable” : ce que vous devez pouvoir justifier
Vous devez pouvoir répondre factuellement à trois questions avant livraison : (1) comment le module s’installe/upgrade/désinstalle sans laisser de déchets (tables, tabs, config), (2) où sont les frontières entre Legacy et Symfony (et pourquoi), (3) quelles sont les garanties de non-régression (tests, CI, monitoring). Si l’une des réponses est “on verra chez le client”, vous êtes déjà en incident.
Checklist courte (pragmatique) à utiliser en pré-release :
- Installation / désinstallation
install.sqletuninstall.sqlidempotents autant que possible- suppression des
Configurationet desTabcréés - prise en charge multi-boutique si le module stocke des paramètres
- Upgrades
- scripts
upgrade-x.y.z.phptestés sur une base existante (pas seulement “from scratch”) - pas de migration “silencieuse” trop longue sur une page BO (préférez un batch/cron si nécessaire)
- Frontières Legacy/Symfony
- hooks = orchestration, services = métier
- pas de dépendances cachées via
Contextau cœur de la logique (injectez ce qui est utile) - Observabilité
- logs structurés sur les opérations critiques (sync, erreurs API, changements config)
- messages d’erreur exploitables (pas juste “Error 500”)
- Compatibilité
- versions PrestaShop 9.0/9.1 testées (au moins en smoke tests)
- contrainte PHP cohérente avec la cible (8.1–8.5 si annoncé)
- Sécurité
- CSRF sur actions BO, contrôle des permissions, validation serveur des entrées
- secrets (API keys) stockés proprement et non loggés
Sur l’exploitabilité, documentez les prérequis (versions PrestaShop 9.0/9.1 supportées, PHP 8.1–8.5, extensions PHP, droits FS), et les risques (migrations SQL, compatibilités multi-boutiques, impacts performance). Un module qui modifie le tunnel de commande doit être déployé avec une procédure de validation systématique (voir la checklist post-modif citée plus haut), sinon vous jouez à la roulette russe sur le chiffre d’affaires.
Enfin, prévoyez l’intégration avec le reste de l’écosystème : webservices, automatisation, et runbooks. Si votre module dialogue avec un système tiers (ERP, WMS, PIM), soyez explicite sur les retries, timeouts, idempotence et journalisation. Pour des approches “opérationnelles” autour des webservices et de l’automatisation (utile si votre module expose une API ou consomme des endpoints), vous pouvez croiser avec PrestaShop MCP Server : installation, webservices et gestion produits et Automatisation PrestaShop : orchestrer commandes, stocks et prix via MCP.
