Table des matières :
- Cadrage : “module PrestaShop sans override” comme contrainte d’architecture
- Choisir les bons points d’extension : hooks, routes BO, décorateurs de services
- Modèle de données sans override : extra-champs, tables dédiées, API et synchronisation
- Tests : verrouiller le “sans override” par la preuve (unit, intégration, E2E, perf)
- Livraison : packaging, CI/CD, migrations, sécurité (et ce que le core ne fait pas pour vous)
- Remplacer un existant basé sur overrides : méthode de refactor, critères de stop, plan de migration
Cadrage : “module PrestaShop sans override” comme contrainte d’architecture
Un module PrestaShop sans override n’est pas un vœu pieux : c’est un livrable technique qui engage la maintenabilité (mises à jour core, compatibilité modules tiers) et la capacité à diagnostiquer en production. L’override (classes override/, surcharge de contrôleurs, etc.) reste une mécanique legacy : il court-circuite la résolution des conflits (ordre de chargement), rend les upgrades plus risqués et complique l’observabilité. Pour cadrer proprement, le premier document à produire n’est pas une maquette mais une liste d’impacts : quelles classes/méthodes seraient “naturellement” surchargées, quels hooks existent, quelles routes BO sont touchées, quelles contraintes de perf et de sécurité s’appliquent.
Concrètement, une liste d’impacts “exploitable” ressemble à ceci (format que vous pouvez mettre dans votre ticket de cadrage ou votre spec) :
- Impacts techniques
- méthodes/objets concernés (ex.
Order,Cart,Customer, services Symfony BO) - points d’extension candidats (hooks, routes module, commande Symfony, décorateur de service)
- données à créer (tables, colonnes, indexes), contraintes multiboutique (
id_shop) et multilingue (id_lang) - Impacts métier
- quelles règles changent réellement (ex. calcul de frais, enrichissement d’un export, logique de validation)
- qui porte la responsabilité fonctionnelle (côté marchand / AMOA / équipe technique)
- Impacts run / exploitation (important en prod)
- logs attendus (quoi, où, niveau, rotation), métriques (durées, erreurs), et stratégie d’alerting
- comportement en erreur : fail-open vs fail-closed (ex. “si l’API partenaire est en panne, on bloque le checkout ?”)
- Impacts conformité (GEO/UE)
- si vous stockez de la donnée client additionnelle (même un simple “tag” marketing), vérifiez la minimisation et la durée de conservation (RGPD). Le “sans override” n’est pas une garantie de conformité, mais il rend plus simple l’audit : tables dédiées, flux documentés, migrations versionnées.
Contexte versions (à figer dès le kick-off) : en 2026, si vous visez un périmètre “raisonnable”, cadrez au minimum PrestaShop 8.1/8.2 et 9.1+, avec une matrice PHP 8.1 → 8.3 (selon la politique du marchand et l’hébergeur). Si votre module touche au Back Office Symfony, assumez la cohabitation legacy + Symfony (services, controllers, forms). Pour anticiper les régressions, ajoutez une exigence d’environnement de staging reproductible (Docker/compose ou VM) et une stratégie cache claire (OPcache/Redis/Varnish le cas échéant), faute de quoi les tests “semblent passer” mais la prod diverge (différences de cache, d’extensions PHP, de limites mémoire, etc.). En pratique, c’est un sujet très “terrain” : beaucoup de marchands français/UE délèguent l’hébergement (infogérance, cloud managé), et vous devez cadrer qui a la main sur OPcache/Redis et quels redémarrages sont possibles en fenêtre de maintenance.
Le cadrage doit se terminer par une définition de fini (DoD) vérifiable. Exemple de critères d’acceptation spécifiques au “sans override” :
- aucune écriture dans
override/et aucun patch du core ; - toutes les extensions passent par hooks, routes module, décorateurs de services, ou tables dédiées ;
- tests automatisés exécutés sur au moins 2 versions PrestaShop (et 2 versions PHP si le module est sensible au runtime) ;
- plan de rollback et scripts d’upgrade/cleanup fournis.
Vous pouvez transformer ces principes en checklist “go/no-go” (utile pour valider une livraison sans débat) :
- [ ] Le module s’installe / se désinstalle proprement (y compris suppression de tables optionnelle si c’est votre politique)
- [ ] Aucune dépendance cachée à un override historique (audit
override/+ recherche des classes surchargées) - [ ] Compatibilité déclarée réaliste (
ps_versions_compliancy), et testée - [ ] Le comportement en échec est documenté (ex. API down, timeout, quota)
- [ ] Les migrations sont idempotentes (re-jouables sans casser)
- [ ] Les logs n’exposent pas de données sensibles (tokens, PII, payload complet)
Cette rigueur n’est pas bureaucratique : l’objectif est de produire un module lisible, traçable et upgrade-friendly. Sur l’aspect lisibilité, la formulation souvent citée de Martin Fowler est un bon rappel (attribuée à Refactoring, 1999) : « Any fool can write code that a computer can understand. Good programmers write code that humans can understand. » (Martin Fowler, 1999).
Choisir les bons points d’extension : hooks, routes BO, décorateurs de services
Le cœur de PrestaShop expose plusieurs points d’extension “officiels”. Côté front, les hooks (hookDisplayHeader, hookDisplayFooter, hookActionProductSave, etc.) restent la voie standard pour injecter assets, enrichir le rendu ou réagir à des événements métier. Côté Back Office, depuis l’architecture Symfony, vous avez des routes de modules, des controllers Symfony dans src/Controller, et des services déclarés dans config/services.yml. Le choix du point d’extension doit être guidé par une règle simple : modifier le comportement au plus près du besoin, au plus tard dans la chaîne, afin de limiter les effets de bord.
Une manière simple de décider consiste à poser “où se situe la responsabilité” (UI, événement métier, service BO), puis à sélectionner le mécanisme :
| Besoin | Point d’extension recommandé | Pourquoi “sans override” |
|---|---|---|
| Injecter JS/CSS, enrichir un template | Hook d’affichage (display*) |
Isolation, peu intrusif, facile à désactiver |
| Réagir à une création/modif d’objet | Hook d’action (actionObject*) |
Déclenchement métier, meilleure perf que “polling” |
| Ajouter un écran BO / formulaire | Route module + contrôleur Symfony | Stable, testable, respecte l’architecture moderne |
| Modifier une décision interne BO (quand hook insuffisant) | Décoration de service Symfony | Cible un service identifié, évite la surcharge de classe legacy |
Exemple minimal (PrestaShop 8.2/9.1, PHP 8.2+) : un module qui injecte un script et journalise une action sans override.
// modules/acme_notoverride/acme_notoverride.php
class Acme_NotOverride extends Module
{
public function __construct()
{
$this->name = 'acme_notoverride';
$this->version = '1.0.0';
$this->author = 'ACME';
$this->bootstrap = true;
parent::__construct();
}
public function install()
{
return parent::install()
&& $this->registerHook('displayHeader')
&& $this->registerHook('actionObjectOrderAddAfter');
}
public function hookDisplayHeader(array $params)
{
$this->context->controller->registerJavascript(
'acme-notoverride',
'modules/'.$this->name.'/views/js/front.js',
['position' => 'bottom', 'priority' => 150]
);
}
public function hookActionObjectOrderAddAfter(array $params)
{
/** @var Order $order */
$order = $params['object'];
PrestaShopLogger::addLog(
sprintf('[acme_notoverride] order created: %d', (int) $order->id),
1
);
}
}
Deux précautions “prod” souvent oubliées :
- Limiter la verbosité des hooks : si vous loggez une action à chaque événement volumineux (création commande, update panier), vous risquez d’inonder les logs et de masquer les vraies erreurs. Préférez des logs structurés (message court + identifiant) et un niveau adapté.
- Éviter la logique lourde en hook d’affichage : un
displayHeaderne doit pas faire de requêtes métier coûteuses. Si vous avez besoin de données, chargez-les sur un hook métier lors du save, puis lisez un résultat agrégé/caché.
Quand les hooks sont insuffisants (cas fréquent : besoin de changer une décision métier interne), la meilleure alternative à l’override est souvent le décorateur de service Symfony (service decoration). Dans PrestaShop 9, une partie significative de la logique BO est déjà servifiée ; vous pouvez donc envelopper un service du core, appeler le service “inner”, et ajouter votre logique. C’est plus stable qu’un override de classe legacy parce que vous ciblez une API de conteneur (identifiant de service, signature) et vous testez plus facilement en isolation.
Mini-schéma (très simplifié) de ce que vous visez :
- vous “décorez” un service core ;
- vous ajoutez une petite règle ;
- vous laissez tout le reste au core.
Enfin, pour le Back Office, évitez de “forcer” une URL via override de contrôleur admin legacy. Préférez le remapping route/config quand c’est pertinent, afin de garder l’URL stable tout en changeant le contrôleur exécuté. Si vous avez ce besoin, la méthode détaillée dans « Contrôleur Back Office PrestaShop : remapper une route YAML sans changer l’URL » est directement exploitable.
Modèle de données sans override : extra-champs, tables dédiées, API et synchronisation
La tentation classique de l’override est d’ajouter un champ à un ObjectModel (ex. Product, Customer) en surchargeant la définition statique et les méthodes de validation. En pratique, c’est fragile : au moindre changement de core, votre override diverge, et vous vous retrouvez avec des comportements non déterministes (validation, multiboutique, caches). Sans override, vous avez trois patterns robustes : (1) données dans une table dédiée, (2) méta-données portées par une fonctionnalité native, (3) stockage externe + synchronisation.
Sur les versions récentes, PrestaShop propose (selon version/roadmap) des mécanismes de champs additionnels exposés via API d’administration. Si votre besoin est de stocker des attributs additionnels structurés (ex. “code fournisseur”, “niveau de fragilité”, “éligible click&collect”) sans exploser la dette, c’est l’option la plus “core-friendly” quand elle est disponible et suffisante pour votre modèle. L’article « PrestaShop 9.2 : Extra Properties, champs personnalisés natifs via Admin API » donne le cadre fonctionnel et technique.
Si vous devez supporter aussi PrestaShop 8.x (ou si les champs natifs ne couvrent pas votre modèle), la table dédiée reste la voie la plus sûre : ps_acme_entity, avec clés étrangères vers l’ID du modèle (id_product, id_customer) et une clé de contexte multiboutique si nécessaire (id_shop). L’important n’est pas la table : c’est la discipline autour des lectures/écritures.
- Indexez selon vos requêtes réelles, pas selon l’intuition. Cas classique : vous filtrez par
(id_shop, id_product)mais vous n’avez qu’un index surid_product⇒ performances erratiques dès que le catalogue grossit. - Distinguez “source of truth” et “cache” : si vous stockez une donnée calculée (ex. scoring, statut de synchronisation), documentez qui la calcule, quand, et comment la reconstruire.
- Pensez RGPD dès la structure : si vous ajoutez une table liée à
id_customer, prévoyez une stratégie d’effacement/anonymisation quand le client exerce ses droits. Le “sans override” aide ici : c’est une migration SQL et un service applicatif, pas un patch dispersé dans des surcharges.
Évitez les lectures dans des hooks trop bavards (ex. displayHeader) ; privilégiez des hooks métier (save/update) pour maintenir un cache applicatif (ou Redis si l’architecture le justifie). Pour les gros volumes, appuyez-vous sur des patterns de synchronisation et d’intégration API : l’article « Intégration API : synchronisation bidirectionnelle et automatisation pour forte volumétrie » couvre précisément les points de friction (retries, idempotence, pagination).
Mini-scenario (fréquent en e-commerce) : vous synchronisez un ERP et devez stocker un identifiant externe par produit. Sans override, vous créez une table ps_acme_erp_product_map (id_shop, id_product, erp_id, updated_at) avec index (id_shop, erp_id) et (id_shop, id_product). Vous alimentez cette table sur import (ou webhook) et vous lisez uniquement ce mapping au moment nécessaire (export, rapprochement), sans toucher au Product core.
Tests : verrouiller le “sans override” par la preuve (unit, intégration, E2E, perf)
Sans batterie de tests, “sans override” devient une promesse non vérifiable. La base pragmatique : pyramide de tests (unitaires rapides, intégration réaliste, E2E ciblés) + une matrice PrestaShop/PHP. En module, commencez par tester votre logique pure (services, mappers, validateurs) via PHPUnit, puis ajoutez des tests d’intégration qui bootstrappent le kernel/containeur (pour PS 9) ou un contexte minimal (pour PS 8). Les E2E (Playwright/Cypress) ne doivent couvrir que les flux où la régression coûte cher : création de commande, remboursement, génération facture, actions BO.
Un modèle de “minimum testable” (souvent réaliste pour un module sur mesure) :
- Unit tests : règles métier pures, mappers, parseurs, normalisation de données API, calculs
- Intégration : installation/désinstallation, exécution d’un hook clé, accès à une route BO module, migrations d’upgrade
- E2E (2 à 5 scénarios max) : un flux critique côté BO (ex. configuration) + un flux critique front (ex. impact panier/commande)
- Non-régression “sans override” : un test simple qui vérifie l’absence de dossier
override/dans l’artefact et l’absence d’instructions de patch core dans la doc de release
Pour industrialiser la compatibilité multi-environnements, vous avez besoin d’exécuter la suite sur plusieurs couples (PS, PHP). L’article « ChaosLab : tester modules PrestaShop sur 23 environnements PHP » est utile pour cadrer une stratégie de “test matrix” sans passer des semaines à maintenir des images : ajoutez un garde-fou : si votre module cible PrestaShop 9 (Symfony 6.4, PHP 8.3 selon projet), documentez-le explicitement — « PrestaShop 9 : guide de migration Symfony 6.4 et PHP 8.3 » sert de référence interne.
Un détail qui fait gagner du temps lors des retours QA : séparez “tests fonctionnels” et “tests de compatibilité”. Exemple :
- Compatibilité : “le module s’installe et passe sa suite sur PS 8.1 + PHP 8.1”
- Fonctionnel : “la règle X se déclenche quand Y”
Cela évite les discussions interminables du type “ça marche chez moi”.
Le dernier étage, souvent ignoré, c’est la performance et la preuve par la mesure. Un module “sans override” peut quand même ruiner le TTFB s’il déclenche des requêtes N+1 ou s’il surcharge les hooks. Outillage concret : profiler (Xdebug, Blackfire si dispo), et logs SQL. Pour la partie profiling PHP/Symfony, vous pouvez vous appuyer sur la méthode décrite ici : optimisation & profilage PHP/Symfony. Pour objectiver les requêtes, utilisez EXPLAIN et vérifiez les index (cas typique : table custom sans index composite id_shop,id_product). Référence : index MySQL & EXPLAIN.
Astuce de cadrage perf (utile côté client) : définissez 2 ou 3 budgets simples, compréhensibles et mesurables, par exemple :
- “pas plus de X requêtes SQL supplémentaires sur la page panier”
- “pas plus de Y ms ajoutés sur la route BO ciblée”
- “aucun appel réseau bloquant en front (checkout) sans timeout + fallback”
Même sans chiffres “parfaits”, le fait de poser des budgets rend les arbitrages explicites.
Livraison : packaging, CI/CD, migrations, sécurité (et ce que le core ne fait pas pour vous)
La livraison d’un module sans override ne se limite pas à “zipper un dossier”. Vous devez figer une structure de repo (code, tests, docs, scripts) et une stratégie de dépendances (Composer dans vendor/, ou dépendances embarquées). Sur PrestaShop 8/9, composer est quasi incontournable dès que vous touchez à des SDKs (API, queues) ou à des composants Symfony additionnels. Dans ce cas, décidez explicitement si vous livrez vendor/ dans l’archive (simple mais lourd) ou si vous imposez une étape de build CI qui produit un artefact final prêt à installer.
Une checklist de livraison “sans surprise” (souvent plus utile qu’un long document) :
- Artefact
- [ ] archive reproducible (même contenu pour un tag donné)
- [ ]
config.xml/composer.jsoncohérents avec la compatibilité annoncée - [ ] absence d’éléments de dev (fixtures, dumps,
.env, scripts locaux) - Migrations
- [ ] scripts
upgrade/upgrade-X.Y.Z.phpprésents et testés - [ ] SQL idempotent (ex.
CREATE TABLE IF NOT EXISTS,ALTER TABLEconditionnés) - [ ] stratégie de rollback documentée (au minimum : comment désactiver et revenir à la version N-1)
- Exploitation
- [ ] logs utiles (et pas de logs de debug permanents)
- [ ] options de configuration documentées (BO + fichier éventuel)
- Sécurité
- [ ] sorties BO échappées (HTML/JS) et permissions vérifiées
- [ ] pas de
unserialize()sur entrée non fiable - [ ] endpoints module protégés (token/CSRF/ACL selon contexte)
Le minimum CI pour éviter les “modules qui passent en local” : (1) lint PHP + standards, (2) exécution tests, (3) packaging artefact, (4) validation PrestaShop. Pour la validation, intégrez ps-validator en pipeline : c’est le moyen le plus direct de détecter les problèmes de structure, de licences, de compatibilité déclarée. Référence interne : ps-validator : integration CI. Ajoutez un contrôle d’authenticité/chaîne de confiance côté Git (tags signés, provenance) si vous opérez en équipe étendue ; GitHub PrestaShop : dépôts officiels, sécurité et checklist d’authenticité donne une checklist transposable.
La gestion des versions et des migrations doit être contractuelle. Le “sans override” n’empêche pas les breaking changes, il les rend juste explicites. La référence la plus utile reste SemVer :
“Given a version number MAJOR.MINOR.PATCH, increment the: MAJOR version when you make incompatible API changes, MINOR version when you add functionality in a backwards compatible manner, and PATCH version when you make backwards compatible bug fixes.”
(Semantic Versioning 2.0.0, semver.org)
Côté PrestaShop, traduisez ça en scripts upgrade/upgrade-X.Y.Z.php, en SQL idempotent, et en rollback documenté.
Sur la sécurité, ne répétez pas les erreurs classiques (données non échappées BO, injection HTML, unserialize() sur entrée non fiable) : les incidents côté écosystème montrent que ce n’est pas théorique. Pour contextualiser (analyse et correctifs), voir :
- Analyse vulnérabilité ps_facetedsearch — RCE via unserialize
- CVE-2026-44212 — correctifs Stored XSS Back Office
Remplacer un existant basé sur overrides : méthode de refactor, critères de stop, plan de migration
Le scénario réel : boutique en prod avec overrides historiques (ex. override/classes/Cart.php, override/controllers/admin/AdminOrdersController.php) et un besoin de mise à jour vers 8.2/9.x. La première étape n’est pas de coder : c’est d’auditer et de classifier les overrides (bugfix, ajout fonctionnel, hack de compat, perf). Utilisez une checklist d’audit orientée migration, parce que le risque se joue souvent dans les détails (hooks déjà utilisés, thèmes, modules concurrents). Référence interne : migration PrestaShop — checklist d’audit
- Override “correctif” : corrige un bug core d’une version donnée (peut disparaître après upgrade)
- Override “fonctionnel” : ajoute une règle métier (souvent remplaçable par hooks/services)
- Override “compat” : contourne une incompatibilité avec un module/thème (à traiter en coordination)
- Override “perf” : modifie une requête/chargement (à re-mesurer après upgrade, car le core change)
Ensuite, appliquez une stratégie “strangler” : vous remplacez l’override par un module en isolant la fonctionnalité derrière un point d’extension stable. Exemple concret : un override modifie le calcul de total commande. Sans override, vous cherchez d’abord un hook de calcul (si dispo) ; sinon, en contexte Symfony/BO, vous tentez une décoration de service liée au calcul/au panier (selon services exposés par la version).
Si ni hook ni service ne permettent proprement, c’est un critère de stop (à formaliser, pas à improviser en fin de sprint). Documentez alors une des décisions suivantes :
- (a) contribution au core (ou attente d’une évolution core),
- (b) montée de version (pour accéder à un point d’extension),
- (c) override assumé et encapsulé (tests de compatibilité, monitoring, et plan de retrait).
L’objectif n’est pas de supprimer les overrides “à tout prix”, mais d’éviter les overrides “par défaut”. Comme l’écrit Robert C. Martin : « The only way to go fast, is to go well. » (Clean Code, 2008) — sur PrestaShop, “go well” veut souvent dire “découpler du core”.
Enfin, le plan de migration doit inclure la logistique : staging, sauvegardes, et fenêtre de bascule. Si vous êtes dans une démarche de mise à jour majeure (1.6/1.7 vers 8.x/9.x), ne mélangez pas refonte module et upgrade core dans le même sprint sans filet : vous perdez la capacité d’attribuer une régression à une cause.
Une séquence de migration réaliste (et défendable en comité de pilotage) :
- Staging iso-prod (mêmes versions PHP/MySQL, mêmes caches si possible)
- Gel fonctionnel court (éviter les changements BO majeurs pendant la bascule)
- Migration core (sans refactor métier, juste compat)
- Remplacement progressif des overrides par modules “sans override”, lot par lot
- Validation multiboutique / multi-langue si concerné (souvent là que les surprises sortent)
- Fenêtre de mise en prod + rollback prêt (artefact N-1, DB backup, procédure)
Appuyez-vous sur une procédure de migration manuelle détaillée pour structurer les étapes et limiter le downtime : procédure de migration manuelle. Et si votre refactor touche aux caches (très fréquent quand on retire un override qui “court-circuite” une logique), prévoyez des validations explicites en multiboutique : cache PrestaShop — bonnes pratiques multiboutique




