Table des matières :
- Ce que signifie “développement PHP critique” sur un projet e-commerce PrestaShop
- Architecture DDD pragmatique dans un module PrestaShop (sans réécrire le core)
- Stratégie de tests : pyramide réaliste (PHPUnit, intégration DB, API, E2E)
- Qualité continue : analyse statique, conventions, sécurité et gestion d’erreurs
- CI/CD : pipeline reproductible, packaging module, déploiement staging/prod et rollbacks
Ce que signifie “développement PHP critique” sur un projet e-commerce PrestaShop
Sur un e-commerce, “développement PHP critique” ne veut pas dire “code compliqué”. Ça veut dire : code qui casse le checkout, la facturation, le stock, l’indexation, ou la perf en prod. Dans PrestaShop, la zone à risque est souvent la même : modules qui s’accrochent à 10 hooks, manipulent l’ObjectModel legacy, écrivent en base sans transaction, et déclenchent des effets de bord invisibles (ex. recalcul de prix, cache, index). Résultat : régressions difficiles à reproduire, et déploiements qui finissent “à la main” via FTP parce que personne ne fait confiance au pipeline.
Le point dur, ce n’est pas “faire du Symfony” (même si le back-office en dépend de plus en plus), c’est de tenir un niveau d’invariants métier dans un environnement où le core n’est pas strictement orienté DDD. PrestaShop expose une couche moderne (container Symfony, Command Bus partiel selon versions, Doctrine utilisable côté modules) mais conserve une forte inertie legacy. L’approche réaliste consiste donc à isoler le métier (Domain) du “comment PrestaShop le fait” (Infrastructure) plutôt que d’essayer de “DDD-ifier” le core.
Dans la pratique, on peut qualifier “critique” tout développement qui touche à au moins un de ces points (exemples typiques en contexte e-commerce France/UE) :
- Encaissement : redirections PSP (Stripe/PayPal/Adyen…), webhooks, 3‑D Secure (cadre DSP2), capture/annulation, multi-devises.
- Prix & taxes : TVA, règles de taxes, arrondis, prix spécifiques, promotions (soldes, Black Friday), export compta.
- Stock : décrémentation, réservations panier, retours, ruptures, backorders, synchro ERP/WMS.
- Expédition : choix transporteur, règles par zones, points relais, génération d’étiquettes, cut-off horaire.
- Données & conformité : RGPD (droit d’accès/effacement), export client, logs qui contiennent des données personnelles.
- Front : hooks FO et templates qui peuvent impacter le SEO, le rendu et les Core Web Vitals (LCP/INP/CLS).
Deux métriques doivent guider les décisions, parce qu’elles forcent l’honnêteté :
- Change Failure Rate (taux d’échec des changements) : combien de déploiements finissent en rollback/hotfix.
- MTTR (Mean Time To Recovery) : combien de temps pour revenir à un état stable.
Sans tests + CI/CD, ces métriques explosent mécaniquement. Et sans architecture qui limite les effets de bord, les tests deviennent trop coûteux à maintenir.
Pour rendre ces métriques actionnables sur un projet PrestaShop, le plus simple est d’associer à chaque release un “journal d’exploitation” minimal :
- heure de déploiement, hash Git, version module, liste des hooks touchés ;
- indicateurs business (taux de conversion, taux d’abandon checkout, erreurs paiement) sur une fenêtre courte ;
- erreurs techniques (exceptions, timeouts, erreurs SQL) corrélées par un
request-id.
« Any fool can write code that a computer can understand. Good programmers write code that humans can understand. » — Martin Fowler, Refactoring: Improving the Design of Existing Code (1999)
Architecture DDD pragmatique dans un module PrestaShop (sans réécrire le core)
DDD (Domain-Driven Design) est utile ici pour une raison très concrète : forcer une séparation stable entre règles métier et intégration (PrestaShop, API externes, DB). Le principe clé, c’est le Bounded Context : un périmètre où le langage métier est cohérent et où les invariants sont contrôlés. Dans un module (ex. transporteurs, paiement, PIM, ERP), ce contexte est généralement plus petit que le SI global, ce qui rend DDD applicable sans usine à gaz.
Dans PrestaShop 8.2/9.1 (PHP 8.2+ recommandé ; cf. compatibilité et nouveautés côté CLI dans PrestaShop 9.1 : compatibilité PHP 8.1–8.5, CLI et nouveautés développeurs), une organisation “DDD pragmatique” pour un module ressemble à ceci :
modules/monmodule/
src/
Domain/
Shipment/
Model/
ValueObject/
Repository/
Service/
Event/
Application/
Command/
Handler/
Query/
DTO/
Infrastructure/
PrestaShop/
Adapter/
Hook/
Controller/
Persistence/
Doctrine/
DbAdapter/
config/services.yml
tests/
- Domain : PHP pur, sans dépendances PrestaShop. Entités, Value Objects, interfaces de repositories, services métier. Objectif : testable sans DB.
- Application : orchestration (use-cases). Command/Handler (type CQRS light), DTO, mapping. Objectif : règles d’enchaînement, pas de SQL.
- Infrastructure : connecteurs : hooks PrestaShop,
Db, Doctrine, HTTP clients, filesystem, queue. Objectif : “comment ça tourne”.
Le piège classique dans PrestaShop est l’adhérence au legacy : appeler Context::getContext(), instancier Product/Order partout, et mélanger “récupérer des données” avec “appliquer une règle”. Pour casser ce couplage sans tout réécrire, on met une couche d’adaptation (Anti-Corruption Layer) : des Adapters qui traduisent des objets PrestaShop (Order, Cart, Customer…) vers des DTO/Value Objects du domaine. Ça coûte un peu au début, mais ça élimine les régressions “magiques” liées à l’état global de Context.
Mini-scénario (transport) : vous avez une règle métier “les colis > 20 kg ne peuvent pas partir en point relais”.
Sans séparation, vous finissez par disperser cette règle dans un hook d’affichage, un hook de validation, et un script cron de réconciliation. Avec DDD pragmatique, la règle vit une seule fois dans un service de domaine (test unitaire), et l’infrastructure se contente de convertir le panier PrestaShop en un objet métier (poids total, destination, options), puis d’afficher/filtrer les transporteurs en conséquence.
Exemple minimal de Value Object (immuable) pour verrouiller un invariant :
final class TrackingNumber
{
public function __construct(private string $value)
{
$value = trim($value);
if ($value === '' || strlen($value) > 64) {
throw new InvalidArgumentException('Invalid tracking number');
}
$this->value = $value;
}
public function value(): string
{
return $this->value;
}
}
Ici, la valeur n’est jamais “à moitié valide” dans le reste du code. C’est trivial, mais c’est exactement ce qui rend un module durable.
Deux détails concrets qui évitent beaucoup de dette technique dans un module PrestaShop :
- Dépendances inversées : le Domain dépend d’interfaces (ex.
ShipmentRepository), l’Infrastructure fournit l’implémentation (Doctrine/Db). - Effets de bord explicités : ce qui envoie un e-mail, appelle une API, écrit en DB, purge un cache… reste côté Infrastructure, déclenché par l’Application (use-case) et jamais “caché” dans une entité.
Si vous utilisez Doctrine côté module, faites-le dans Infrastructure (mapping, EntityManager, migrations) et exposez côté Domain uniquement des interfaces. Pour une mise en place propre dans l’écosystème PrestaShop, voir l’article interne Symfony PrestaShop : développer des modules robustes avec Doctrine.
Stratégie de tests : pyramide réaliste (PHPUnit, intégration DB, API, E2E)
Le test “critique” en PHP, ce n’est pas 100% de coverage. C’est de couvrir les chemins qui coûtent cher quand ils cassent : pricing, taxes, stock, paiement, expédition, conformité (RGPD), génération de documents, webhooks. La pyramide de tests reste valide, mais il faut l’adapter à PrestaShop :
- Unit tests (Domain) : rapides, sans I/O, ciblent les invariants.
- Integration tests (Infrastructure) : DB, hooks, container Symfony, compat legacy.
- E2E (staging) : parcours utilisateur critique, idéalement automatisé.
Le point décisif : les tests unitaires ne doivent pas dépendre de PrestaShop. Un module bien structuré permet de tester 60–80% du métier sans lancer une boutique. Ensuite, on complète avec des tests d’intégration qui valident l’adaptation (ex. “le hook X déclenche bien le use-case Y”). Pour les tests d’intégration, vous aurez besoin d’une base jetable et d’un dataset maîtrisé (fixtures). Côté DB, anticipez la maintenance et le nettoyage (index, tables volumineuses, historiques) ; l’article Base de données PrestaShop : routine de maintenance et nettoyage automatisé est utile pour éviter que l’environnement de test devienne une poubelle.
Une façon simple de décider “quoi tester en priorité” consiste à croiser impact business et probabilité de régression. Exemple rapide (à adapter à votre boutique) :
| Zone | Impact si cassée | Régression fréquente | Type de test recommandé |
|---|---|---|---|
| Webhook paiement (capture/annulation) | Très élevé | Élevée (contrats API, signatures) | Intégration + E2E |
| Règles de frais de port | Élevé | Moyenne (hooks, zones) | Unitaire + intégration |
| Génération facture/PDF | Élevé | Moyenne (formats, TVA) | Intégration |
| SEO (balises, canonical) | Moyen/élevé | Élevée (thème, modules FO) | E2E + audit staging |
| Back-office (formulaires, permissions) | Moyen | Élevée (tokens/CSRF) | Intégration |
Exemple de test unitaire (PHPUnit) sur une règle de domaine :
public function test_it_rejects_empty_tracking_number(): void
{
$this->expectException(InvalidArgumentException::class);
new TrackingNumber(' ');
}
Exemple de test d’intégration (schéma) : on boot le container Symfony du module et on vérifie le câblage. Sur PrestaShop 8.2/9.1, selon l’architecture du module, vous pouvez tester via KernelTestCase (si vous exposez un Kernel) ou via une commande Symfony (plus simple à stabiliser). L’objectif n’est pas de reproduire tout PrestaShop, mais de valider les points d’intégration : services déclarés, paramètres, accès DB, client HTTP.
Deux techniques pragmatiques pour éviter les tests d’intégration “flaky” (intermittents) :
- Données déterministes : des fixtures minimales (1 client, 1 produit, 1 taxe, 1 transporteur) plutôt qu’une copie de prod.
- Isolation : reset DB entre tests (transaction + rollback quand possible, ou recréation de schéma), et cache désactivé/maîtrisé.
Pour l’E2E, arrêtez de bricoler Selenium à la main sur des environnements instables. Utilisez un runner moderne (Playwright/Cypress) contre un staging immutable (build dockerisé + DB provisionnée). Un E2E doit viser les 3–5 parcours qui font le CA : ajout panier, création compte, paiement, commande, email. Si un déploiement ne passe pas ces checks, il ne doit pas partir en prod.
« Continuous delivery is the ability to get changes of all types—including new features, configuration changes, bug fixes and experiments—into production, or into the hands of users, safely and quickly in a sustainable way. » — Jez Humble & David Farley, Continuous Delivery (2010)
Qualité continue : analyse statique, conventions, sécurité et gestion d’erreurs
Sur un codebase PHP “critique”, l’analyse statique n’est pas un bonus, c’est un filet de sécurité à faible coût. Concrètement : PHPStan ou Psalm en niveau strict progressif, avec baseline initiale et réduction itérative (ne faites pas “level max” day one si le projet a 5 ans de dettes). Ajoutez un formatteur (PHP-CS-Fixer) et, si vous avez des refactors répétitifs, Rector. La séquence qui marche en CI : lint → static analysis → unit tests → integration tests.
La particularité PrestaShop : le core et beaucoup de modules historiques sont dynamiques (magic methods, ObjectModel, champs définis à runtime). Vous ne gagnerez pas partout. Donc le bon compromis est de borner le domaine (PHP pur, typé, analysable) et d’accepter une zone “infra/legacy” moins stricte, mais couverte par des tests d’intégration. Cette frontière est exactement l’effet recherché par DDD : une partie du code où les garanties sont fortes.
Pour progresser sans bloquer l’équipe, une démarche qui fonctionne bien :
- 1) Typage progressif dans
src/Domain(types stricts, retours, propriétés), sans toucher au legacy. - 2) Baseline PHPStan/Psalm pour “figer” l’existant.
- 3) Politique “boy scout” : chaque ticket réduit un peu la baseline (ou empêche qu’elle grossisse).
- 4) Gates CI : au début, analyse statique en warning ; ensuite, en required sur les dossiers maîtrisés.
Ensuite, la robustesse en prod dépend beaucoup de la gestion d’erreurs, du logging et de la configuration environnement. Dans PrestaShop, la tentation est de masquer les erreurs en prod et de “corriger quand ça remonte”. Mauvaise idée : vous devez instrumenter. Mettez en place :
- un handler d’exceptions cohérent (conversion en réponses contrôlées côté API/back-office),
- un logging structuré (JSON) corrélé par request-id,
- des niveaux de logs non verbaux (pas de
var_dump), - et des environnements réellement séparés.
Sur ce point, l’article interne Gestion d’erreur PHP : bonnes pratiques et configuration développement/production est un prérequis. Il couvre aussi le risque numéro 1 : croire que l’environnement de dev est “assez proche” de la prod, alors que PHP, extensions, OPcache, limites mémoire, et config MySQL diffèrent.
Enfin, sécurité : si votre module touche à l’auth, aux tokens, au back-office ou à des endpoints exposés, vous devez traiter la sécurité comme une contrainte d’architecture (pas un audit final). Contrôlez les entrées, verrouillez les permissions, signez les webhooks, rotatez les secrets, et utilisez des scanners (composer audit, Trivy si images Docker, SAST minimal). Quelques pièges courants côté modules PrestaShop :
- XSS via champs configurables affichés en FO/BO (templates Smarty/Twig, champs WYSIWYG).
- IDOR (Insecure Direct Object Reference) sur endpoints type “télécharger une facture/étiquette” sans contrôle d’accès strict.
- CSRF sur actions BO si la vérification de token est contournée ou réimplémentée.
- Secrets en clair dans la configuration (clé API) et loggés par erreur lors d’un debug.
- Webhooks acceptés sans signature (ou avec horodatage non vérifié), ce qui ouvre la porte au rejeu.
Pour une cartographie des risques 2026, voir Sécurité PrestaShop 2026 : risques majeurs et protections professionnelles.
CI/CD : pipeline reproductible, packaging module, déploiement staging/prod et rollbacks
Une CI/CD “critique” n’a qu’un but : rendre le déploiement ennuyeux. Pour un module PrestaShop, ça signifie : build reproductible, artefact versionné, et déploiement idempotent. Le point le plus négligé : la reproductibilité. Si “composer install” télécharge des versions différentes selon le jour, ou si votre pipeline dépend de ressources externes non pinées, vous n’avez pas de CI/CD, vous avez un script.
Les métriques DORA (Change Failure Rate, MTTR, etc.) servent justement à piloter ce sujet dans la durée : définitions et approche sont centralisées par le programme DORA (lien de référence) : https://dora.dev/.
Pipeline type (GitHub Actions ou GitLab CI) :
- Checkout + cache Composer
composer validate+composer install --no-interaction --prefer-dist- Lint + code style
- Analyse statique (PHPStan/Psalm)
- Tests unitaires (PHPUnit)
- Tests d’intégration (docker-compose : DB + PrestaShop + module)
- Packaging : zip du module (sans
vendorsi vous externalisez, ou avecvendorsi vous livrez en bundle ; choisissez une stratégie et tenez-la) - Publication d’artefact (Release, registry, S3)
Exemple minimal (GitHub Actions) :
name: ci
on: [push, pull_request]
jobs:
test:
runs-on: ubuntu-latest
services:
mysql:
image: mysql:8.0
env:
MYSQL_ROOT_PASSWORD: root
MYSQL_DATABASE: prestashop
ports: ['3306:3306']
options: >-
--health-cmd="mysqladmin ping -proot" --health-interval=5s --health-timeout=5s --health-retries=20
steps:
- uses: actions/checkout@v4
- uses: shivammathur/setup-php@v2
with:
php-version: '8.3'
tools: composer
- run: composer install --no-interaction --prefer-dist
- run: vendor/bin/phpstan analyse
- run: vendor/bin/phpunit
Deux points “packaging” souvent sous-estimés, mais déterminants pour la stabilité :
- Verrouiller vos dépendances :
composer.lockdoit être versionné et respecté en CI (sinon vous testez une version et vous déployez une autre). - Définir une politique de livraison :
- Module livré avec
vendor/: plus simple côté client, mais zip plus gros et risque de conflits si plusieurs modules embarquent des versions différentes. - Module sans
vendor/: plus “propre” mais implique un runtime qui sait installer les dépendances (rare chez les marchands).
Dans la majorité des projets “critiques”, l’artefact immuable (avec dépendances) reste le plus fiable, à condition d’isoler correctement (autoload, namespaces, collisions).
Le déploiement, lui, doit être pensé avec rollback. Deux patterns qui marchent :
- Blue/Green (ou “release directory”) : déployer dans un nouveau répertoire, switcher un symlink, purger cache, garder l’ancien pour rollback rapide.
- Artefact immuable : le zip versionné est le même entre staging et prod, seuls les secrets changent.
Attention aux migrations : si votre module introduit des tables ou colonnes, vous devez définir une stratégie (migrations Doctrine, scripts SQL versionnés, ou upgrade module PrestaShop). La règle d’or : migrations backward-compatible si vous voulez du vrai rollback (ajouter colonne nullable, double-write, puis cleanup plus tard). Sinon, rollback = restauration DB, donc indisponibilité.
Checklist “release” (courte, mais qui évite des nuits blanches) :
- version taguée + changelog (même minimal) ;
- zip reproductible (mêmes sources → même artefact) ;
- migrations testées sur une DB vierge et une DB “ancienne” ;
- smoke tests staging (back-office, commande test, webhook test si possible) ;
- plan de rollback documenté (répertoire précédent + stratégie DB).
Dernier point : la CI/CD n’est pas que du delivery, c’est aussi du contrôle qualité SEO/perf quand les modules touchent au front (templates, assets, hooks FO). Si vos changements peuvent impacter le rendu (LCP/INP/CLS), intégrez un budget de performance en staging (Lighthouse CI par exemple) et corrélez les régressions. Pour les impacts WebPerf, l’article Core Web Vitals : actions concrètes pour améliorer LCP, INP et CLS donne une checklist exploitable.
En pratique, une équipe qui applique DDD pragmatique + tests ciblés + CI/CD reproductible obtient un effet immédiat : moins de hotfix, moins de “comportements fantômes” liés au contexte global PrestaShop, et des releases plus petites. Ce n’est pas “propre” pour le plaisir : c’est le seul moyen de maintenir des modules critiques quand le core évolue (PrestaShop 8 → 9), que PHP avance (8.2 → 8.4+), et que les intégrations externes (paiement, transport, ERP) changent leurs contrats d’API.




