PrestaShop 9.2 : bonnes pratiques d’override et tests de compatibilité

Gérez vos overrides en PrestaShop 9.2 sans casser la production : alternatives stables, bonnes pratiques, checklist de déploiement et matrice de tests pour éviter les régressions.

Interface de développement numérique avec des écrans affichant du code et des diagrammes pour PrestaShop.

Table des matières :

  1. Comprendre le chargement des overrides en 9.2 (et pourquoi ça casse)
  2. Remplacer l’override par des points d’extension plus stables (quand c’est possible)
  3. Bonnes pratiques d’override en PrestaShop 9.2 : réduire la surface de casse
  4. Tests de compatibilité 9.2 : définir une matrice et automatiser (sinon ça ne sert à rien)
  5. Scénarios de régression fréquents lors d’une montée vers 9.2 (et comment les attraper)
  6. Déployer un override 9.2 sans casser la prod : checklist opérable
  7. Compatibilité contractuelle : versionner vos changements comme une API

Un override PrestaShop 9.2, c’est une substitution de classe « legacy » via le dossier /override/ (ex. override/classes/Cart.php) qui prend le pas sur la classe cœur suffixée Core (ex. classes/Cart.php contenant CartCore). Tant que vous êtes dans la partie historique de PrestaShop (ObjectModel, Controllers legacy, Helpers…), le mécanisme reste identique à ce que vous avez connu en 1.7/8 : résolution via l’index de classes généré et chargement prioritaire des fichiers d’override. Le point qui surprend en 9.x, c’est que le cœur a continué à se « symfoniser » : une part croissante des comportements vit dans des services Symfony, pas dans des classes legacy. Résultat : l’override n’est plus un outil universel, c’est un outil de dernier recours.

En environnement 9.2, partez du principe suivant : si le code que vous voulez modifier passe par le container Symfony (Command Bus, services prestashop.*, contrôleurs Symfony, etc.), un override de classe legacy ne suffira pas. C’est exactement le genre de piège qui déclenche des « corrections » sales (copier/coller de classes cœur, hacks dans vendor/, etc.) qui explosent au prochain patch de sécurité. Sur ce point, le meilleur signal faible, c’est la présence de namespaces PrestaShopBundle / PrestaShop\PrestaShop\Core et la dépendance à Symfony 6.4 (LTS) telle que décrite dans les contenus officiels PrestaShop 9.

Dernier rappel pratique : un override est aussi un point de rupture en mise à jour. En production, il crée un couplage fort entre votre boutique et la signature exacte des méthodes du cœur. Quand PrestaShop 9.2 ajoute un type hint ou un : void quelque part, votre override devient un candidat naturel au fatal error « Declaration must be compatible… ». C’est un incident typique de page blanche ; gardez sous la main une procédure de diagnostic rapide (voir PrestaShop page blanche : diagnostiquer l’erreur fatale PHP rapidement).

Dans un contexte e-commerce « réel » (pics de charge, campagnes Ads, soldes), l’enjeu est moins théorique qu’il n’y paraît : une incompatibilité d’override se transforme en indisponibilité totale au pire moment (checkout). Pour situer l’enjeu côté marché (et rester factuel), la Fédération du e-commerce et de la vente à distance publie régulièrement des bilans sur le poids du canal en France :

En France, le e-commerce a atteint 159,9 milliards d’euros de chiffre d’affaires en 2023. (FEVAD, Bilan e-commerce 2023, publication 2024 — FEVAD)

Ce chiffre ne dit pas « quoi faire techniquement », mais il rappelle pourquoi on traite un override comme du code critique : un incident de compatibilité en 9.2 n’est pas un bug isolé, c’est une rupture de vente.

Comprendre le chargement des overrides en 9.2 (et pourquoi ça casse)

Le mécanisme d’override repose sur un index de classes (fichier class_index.php dans le cache de l’application, typiquement sous var/cache/{env}/) qui mappe un nom de classe vers un chemin de fichier. Lorsqu’un override existe, PrestaShop enregistre le chemin de l’override à la place du chemin cœur. La génération/régénération de cet index dépend du contexte (installation d’un module, suppression d’un override, vidage de cache) et c’est précisément là que les « ça marche en local mais pas en prod » apparaissent.

Le premier point qui casse en 9.2 n’est pas « l’override ne marche plus », mais « l’override n’est pas chargé ». Causes fréquentes : cache non vidé, OPcache qui conserve une ancienne version, permissions qui empêchent l’écriture du cache, ou divergence entre plusieurs nœuds (infra multi-serveurs) si vous ne propagez pas le cache de manière cohérente. Si vous êtes sur une stack Nginx/PHP-FPM, la gestion d’OPcache et du redéploiement est un sujet à traiter explicitement (voir Symfony 7 : déploiement PHP-FPM Nginx, cache et opcache, transposable côté principes).

Quelques symptômes typiques (et ce qu’ils signifient souvent) :

  • Votre méthode overridée n’est jamais appelée : l’override n’est pas pris en compte (cache/class index non régénéré, override désactivé, fichier non déployé sur un nœud).
  • Ça marche sur un serveur, pas sur l’autre : déploiement non atomique + cache local par nœud (class index et OPcache divergent).
  • Ça marche après un “vider le cache” back-office : vous avez un problème d’invalidation côté CI/CD (post-deploy incomplet) ou de permissions sur var/cache.

Deux réglages « bêtes mais mortels » à vérifier avant d’aller plus loin :

  • Dans le back-office : Paramètres avancés → Performance → Désactiver tous les overrides (selon version/libellé). Si c’est activé, aucun override ne s’applique, même s’il est correctement déployé.
  • Sur le FS : le répertoire var/cache/ doit être écrivable par l’utilisateur PHP (sinon index non régénéré, comportements incohérents).

Côté gestes opérables (sans inventer une procédure magique), un déploiement qui embarque un override devrait au minimum inclure une étape de nettoyage/warmup du cache, par exemple :

# À adapter à votre politique de déploiement et à l'environnement (prod/preprod)
rm -rf var/cache/prod/*
php bin/console cache:clear --env=prod
php bin/console cache:warmup --env=prod

Le deuxième point qui casse, plus violent : incompatibilité de signature. PHP 8.x est nettement plus strict dès que des types et des valeurs de retour sont ajoutés. Un override qui « shadow » une méthode sans respecter exactement la signature (paramètres optionnels, types, visibilité, static, final) fait tomber l’application au moment du chargement de classe — donc avant toute gestion d’erreur applicative. C’est aussi pour ça que traiter l’override comme un composant testable (et pas comme un patch) est non négociable.

Enfin, notez un piège fréquent en 9.x : certaines personnalisations « historiques » visaient une classe legacy… alors que le flux réel est désormais piloté par un service Symfony. On se retrouve avec un override chargé mais inutile (il n’est plus dans le chemin d’exécution). D’où l’intérêt, avant d’écrire, de localiser le point d’entrée réel (profiling, recherche d’appels, logs) au lieu de supposer que « Cart fait tout ».

Remplacer l’override par des points d’extension plus stables (quand c’est possible)

Avant d’écrire un override en 9.2, vérifiez systématiquement si un hook, un event, ou une extension Symfony fait le job. Le pattern général : plus vous êtes « proche » du cœur (override), plus le coût de maintenance et de compatibilité est élevé. À l’inverse, les hooks (y compris dynamiques) sont une API d’extension prévue pour évoluer sans casser les boutiques. Pour un rappel utile sur la résolution des hooks dynamiques et leurs regex, voir Hooks PrestaShop : comprendre les hooks dynamiques et les regex.

Une manière simple de décider (sans sur-analyser) consiste à raisonner en « stabilité contractuelle » :

Besoin Mécanisme à privilégier Pourquoi (en 9.2)
Ajouter/altérer un contenu d’affichage Override de template / hook d’affichage Impact limité, évolutions du cœur moins cassantes
Ajuster un comportement métier exposé par hooks Hook action/filtre existant API d’extension prévue pour ça
Modifier une règle métier portée par un service Décoration de service Symfony Contrat container, testable, rollbackable
Corriger un comportement legacy sans hook Override (dernier recours) Couplage fort aux signatures du cœur

Sur la partie Symfony, privilégiez la décoration de services plutôt que la substitution brutale. La décoration vous permet d’« envelopper » un service existant en interceptant des appels, tout en conservant le service original disponible sous un alias interne. C’est nettement plus robuste qu’un override, parce que vous restez dans le contrat du container. La documentation Symfony décrit ce mécanisme et fournit des exemples concrets : Décoration de services — Symfony.

Enfin, ne sous-estimez pas les overrides front (templates) comme alternative à certains overrides back. Beaucoup de customisations « métier » sont en réalité des ajustements d’affichage ou de mapping de variables Smarty/Twig. Côté performance, c’est souvent moins risqué qu’un override de Product::getPriceStatic() qui sera appelé des centaines de fois. Pour les problématiques d’optimisation perçues comme « fonctionnelles », il y a souvent un vrai enjeu perf sous-jacent (voir Optimisation PrestaShop : accélérer pages produit et panier).

Mini-scenario (très courant en France/UE) : vous voulez afficher une ligne d’info « éco-participation » ou une mention légale dans le panier. Réflexe historique : override Cart::getSummaryDetails(). Alternative plus stable : injecter cette information via un hook d’affichage du récapitulatif (ou un décorateur de service si la donnée doit aussi exister côté API/commande). Vous évitez ainsi de figer une méthode du cœur… pour un besoin principalement de présentation.

Bonnes pratiques d’override en PrestaShop 9.2 : réduire la surface de casse

La règle de base : un override doit être minimal, isolé et diffable. Si vous copiez-collez une classe cœur entière pour changer 10 lignes, vous vous garantissez une divergence fonctionnelle au prochain correctif. Dans un monde idéal, l’override contient soit (1) une surcharge de 1–3 méthodes, soit (2) un « trampoline » qui appelle parent::… et insère un comportement autour. Ce n’est pas du purisme : c’est la seule manière de survivre à des mises à jour fréquentes.

Bon réflexe éditorial (qui change réellement la maintenance) : au-dessus de chaque méthode overridée, notez la version PrestaShop et le lien vers le fichier cœur (ou au minimum le chemin + signature) au moment de l’écriture. Lors d’une montée de version, vous savez quoi comparer.

Exemple typique (simplifié) : surcharge de Cart::getOrderTotal() pour intégrer un calcul spécifique, tout en respectant signature et types. Pré-requis : PrestaShop 9.2, PHP 8.3, et vous devez vérifier la signature exacte dans le cœur 9.2 avant de committer.

<?php
// override/classes/Cart.php

class Cart extends CartCore
{
    /**
     * IMPORTANT : la signature doit matcher EXACTEMENT celle de CartCore en 9.2.
     */
    public function getOrderTotal(
        $withTaxes = true,
        $type = self::BOTH,
        $products = null,
        $idCarrier = null,
        $useCache = true
    ) {
        $total = parent::getOrderTotal($withTaxes, $type, $products, $idCarrier, $useCache);

        // Exemple : surcharge contrôlée via une feature flag en config
        if (defined('_PS_ADMIN_DIR_') && (int) Configuration::get('MY_FEATURE_ENABLE') === 1) {
            $total += (float) Configuration::get('MY_FIXED_FEE');
        }

        return $total;
    }
}

Le point à surveiller en 9.2 : les évolutions de type hints, l’ajout de declare(strict_types=1) dans certaines zones, et le fait que des morceaux du calcul total ont été progressivement déplacés vers des services. Si votre override remplace des règles de taxe/remise, vous risquez aussi de court-circuiter des correctifs du cœur. D’où une discipline stricte : versionner, commenter la raison, et lister explicitement les méthodes impactées.

Une règle opérationnelle utile : évitez les overrides sur les “hot paths” (prix, stock, panier, checkout) si vous n’avez pas (a) une matrice de tests, et (b) un plan de rollback. Dans PrestaShop, un changement dans ces chemins peut produire des effets de bord non triviaux : arrondis, règles de taxe selon pays, prix spécifiques, cart rules, transporteurs… Un override « qui marche » sur un cas simple peut diverger en B2B (TVA intracom, exemptions) ou en multi-boutique.

Côté sécurité, un override est aussi un endroit où l’on réintroduit des anti-patterns (ex. unserialize() sur des données non maîtrisées, ou manipulation de requêtes SQL à la main). Rappel OWASP très direct : “The best way to prevent insecure deserialization attacks is to never deserialize untrusted data.” (OWASP, Deserialization Cheat Sheet : OWASP Deserialization Cheat Sheet). Si votre override touche à des payloads, relisez vos pratiques de désérialisation (voir Désérialisation PHP : bonnes pratiques pour prévenir l’injection d’objets) et gardez en tête que des modules cœur peuvent déjà avoir eu des CVE sur ce sujet (voir Vulnérabilité PrestaShop ps_facetedsearch : analyse RCE via unserialize()).

Tests de compatibilité 9.2 : définir une matrice et automatiser (sinon ça ne sert à rien)

Un test de compatibilité utile, ce n’est pas « ça passe en navigation manuelle », c’est une matrice versionnée : PrestaShop (9.0/9.1/9.2), PHP (8.2/8.3 selon support), base de données (MariaDB vs MySQL si votre parc varie), et modules critiques. Sur le papier, ça ressemble à de la QA « lourde ». En pratique, c’est moins coûteux que 2 nuits à déboguer un fatal en prod à cause d’une signature modifiée.

Pour éviter une matrice “théorique”, listez vos invariants business (ce qui ne doit jamais casser) et associez-les à des tests. Exemple d’invariants e-commerce classiques :

  • Un client peut se connecter et récupérer son historique.
  • Un client peut ajouter au panier (produit simple + déclinaison).
  • Les frais de port se recalculent (France métropolitaine vs une zone UE si vous vendez à l’international).
  • Le checkout crée une commande payée (au moins un moyen de paiement réel en sandbox).
  • Les emails transactionnels se déclenchent (ou, a minima, pas d’erreur bloquante côté logs).

La phrase de Michael Feathers reste le meilleur garde-fou quand on touche au legacy : “Legacy code is code without tests.” (Working Effectively with Legacy Code, 2004). Un override est exactement du legacy : vous modifiez un comportement du cœur sans contrôler l’évolution du cœur. Donc vous devez ajouter des tests autour de l’effet attendu. Sur PrestaShop, ça se traduit souvent par : (1) tests unitaires PHP pour vos services/modules, (2) tests d’intégration sur une base éphémère, (3) tests end-to-end (Playwright/Cypress) sur le tunnel critique (login, panier, paiement).

Concrètement, mon minimum acceptable sur un repo boutique/module qui embarque des overrides :

  • Lint + static analysis : php -l, PHPStan/Psalm avec un baseline contrôlé.
  • PHPUnit : tests centrés sur vos règles métier, pas sur celles de PrestaShop.
  • E2E : un parcours « add-to-cart → checkout → order » + une variante B2B si activée.

Pour industrialiser, appuyez-vous sur une préprod Docker fidèle à la prod (versions PHP/extensions, Redis, Elasticsearch/OpenSearch si présent) (voir Staging PrestaShop : créer un environnement Docker fidèle à la production) et sur un pipeline qui peut générer des environnements jetables (voir PrestaShop CI/CD : générer des démos à la demande en minutes).

Extrait d’idée de matrice GitHub Actions (schéma, à adapter) :

strategy:
  fail-fast: false
  matrix:
    prestashop: ["9.1", "9.2"]
    php: ["8.3"]
steps:
  - run: docker compose up -d --wait
  - run: docker compose exec -T php composer install --no-interaction
  - run: docker compose exec -T php vendor/bin/phpstan analyse
  - run: docker compose exec -T php vendor/bin/phpunit
  - run: docker compose exec -T e2e npx playwright test

Astuce “peu coûteuse, très rentable” quand vous avez 1–5 overrides : ajoutez un test (ou un job CI) qui bootstrap au moins une fois l’app (front ou CLI). Beaucoup de fatals de signature sont attrapés dès le chargement de classes, donc très tôt dans la pipeline.

Scénarios de régression fréquents lors d’une montée vers 9.2 (et comment les attraper)

Le scénario n°1 est le plus banal : changement de signature. Le cœur ajoute un type, un paramètre optionnel, ou une valeur de retour. Votre override ne matche plus et PHP stoppe net. Pour l’attraper tôt : un job CI qui charge le kernel et exécute au moins un bootstrap complet (CLI ou front) suffit souvent à reproduire le fatal. En complément, vous pouvez écrire un script de réflexion qui compare signatures ReflectionMethod entre FooCore et Foo pour les classes overridées ; ça évite de découvrir le problème par un client.

Voici un mini-exemple de contrôle (à exécuter en CI) : il ne remplace pas des tests fonctionnels, mais il détecte vite une dérive de signature.

<?php
// tools/check-override-signatures.php (exemple)
$overrides = [
    ['CartCore', 'Cart', 'getOrderTotal'],
    // Ajoutez vos couples Core/Override ici
];

foreach ($overrides as [$core, $override, $method]) {
    $a = new ReflectionMethod($core, $method);
    $b = new ReflectionMethod($override, $method);

    if ((string) $a !== (string) $b) {
        fwrite(STDERR, "Signature mismatch: {$override}::{$method} vs {$core}::{$method}\n");
        fwrite(STDERR, "Core:     {$a}\n");
        fwrite(STDERR, "Override: {$b}\n");
        exit(1);
    }
}

Le scénario n°2 : modification du flux fonctionnel. Exemple courant : vous overridez une méthode parce qu’il n’y avait pas de hook, puis PrestaShop 9.2 refactorise ce flux vers un service Symfony, et votre override continue de s’exécuter… mais n’est plus appelé à l’endroit métier critique (ou l’est deux fois). C’est perfide : pas de fatal, mais des écarts de stock, de prix, de frais de port. Le remède est structurel : réduire les overrides qui touchent aux chemins critiques (pricing, stock, commande) et préférer une extension via services/hook. Pour une lecture globale sur l’évolution 9.x, voir PrestaShop 9.1 : fonctionnalités clés et évolutions techniques 2026 et PrestaShop 9 : guide de migration Symfony 6.4 et PHP 8.3.

Le scénario n°3 : conflit d’override entre modules. Deux modules essaient d’override la même classe : le dernier installé « gagne », l’autre perd silencieusement, et votre boutique a un comportement dépendant de l’ordre d’installation. En 9.2, ce risque augmente mécaniquement si vous avez un parc modules large. Méthode efficace : interdire les overrides dans certains modules (lint CI), et centraliser l’override dans un seul module « plateforme » quand c’est inévitable, avec une gouvernance claire.

Une variante fréquente (notamment en agence / multi-prestataires) : un override “historique” est encore là, mais un nouveau module arrive avec une autre surcharge. Sans inventaire, on perd des heures à diagnostiquer un comportement qui change “au hasard” après une réinstallation. D’où l’intérêt d’un audit régulier : liste des fichiers sous /override/, qui les a introduits, et si un hook/service ne permet pas de supprimer l’override.

Déployer un override 9.2 sans casser la prod : checklist opérable

Déployer un override n’est pas « copier un fichier ». C’est un changement de comportement du cœur, donc vous devez traiter ça comme un changement de code applicatif avec rollback. À minima : (1) livraison via Git/CI, (2) invalidation de cache applicatif, (3) gestion d’OPcache, (4) vérification de santé post-déploiement (HTTP 200 + endpoints critiques). Si vous avez des symptômes de lenteur après déploiement, ne partez pas au hasard : mesurez TTFB, bots, saturation PHP-FPM (voir PrestaShop lent : diagnostic TTFB, bots et optimisation serveur).

Checklist courte (celle qui évite les incidents “classiques”) :

  • Avant : valider sur préprod (même PHP/extensions), et tagger un point de rollback.
  • Déploiement :
  • livrer le fichier d’override sur tous les nœuds,
  • purger var/cache/{env} (ou cache:clear) sur chaque nœud,
  • gérer OPcache (redémarrage PHP-FPM ou mécanisme prévu chez vous).
  • Après :
  • vérifier les pages critiques (home, produit, panier, checkout),
  • vérifier les logs php-fpm / prod.log (absence de Declaration must be compatible),
  • exécuter un parcours E2E minimal si vous en avez un.

Côté mise à jour PrestaShop 9.2, ne négociez pas l’étape « pré-prod + tests + backup ». Les overrides sont un motif majeur d’échec de mise à jour, parce que le cœur ne peut pas anticiper votre divergence. Utilisez une checklist structurée (voir Mise à jour PrestaShop : checklist sauvegarde, pré-production et tests) et gardez une procédure de réparation si une montée de version a déjà mal tourné (voir Mise à jour PrestaShop ratée : diagnostiquer et réparer rapidement).

Enfin, instrumentez. Un override qui casse se voit dans les logs PHP-FPM, mais en e-commerce, il faut aussi détecter les erreurs fonctionnelles (commande bloquée, paiement KO, panier vide). Un système d’alerting applicatif (Sentry/Prometheus + alertes) est une assurance faible coût, surtout si votre override touche le tunnel. Pour une implémentation simple côté PrestaShop (alertes Slack/Discord/email), voir PrestaShop : module d’alerting temps réel Slack, Discord et email.

Compatibilité contractuelle : versionner vos changements comme une API

Si vous maintenez un module (ou une « plateforme » interne) qui contient des overrides, considérez que vous exposez une API interne à la boutique : signature, side effects, contraintes de données. C’est exactement le cas d’usage pour appliquer une discipline de versionnage sémantique. La spec SemVer résume la règle sans ambiguïté : “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 backward compatible manner, and PATCH version when you make backward compatible bug fixes.” (Semantic Versioning 2.0.0 : semver.org).

Traduction opérationnelle en PrestaShop 9.2 : si un override change un calcul de prix, une règle de stock, un mapping de statut commande, vous êtes souvent sur un changement MAJOR (incompatible fonctionnellement) même si vous n’avez touché qu’une ligne. Inversement, si vous ajoutez un log ou un guard clause sans modifier l’output, ça ressemble à du PATCH. Ce langage commun (SemVer + changelog) évite les discussions stériles au moment d’une montée en 9.2.

Dernier point : documentez explicitement pourquoi l’override existe (absence de hook, bug cœur, contrainte légale, etc.), où il s’exécute, et comment le tester. Un format simple, qui tient dans un README de module “plateforme”, suffit :

  • Classe/méthode overridée + signature attendue (copiée du cœur à la date X)
  • Effet métier (entrée → sortie)
  • Risques connus (perf, sécurité, compat modules)
  • Test(s) de non-régression (unit/integration/E2E) + commande CI

C’est la différence entre un override « survivable » et un override qui finira supprimé dans l’urgence après un incident. Et si vous devez en supprimer, faites-le comme un refactoring : tests d’abord, puis suppression, puis validation sur la matrice de compatibilité.


À lire aussi