Optimisation code PHP : réduire la dette technique sans refonte

Réduire la dette technique PHP sur PrestaShop sans refonte : audit mesurable, stratégie incrémentale, quick wins (typage, OPcache), optimisation SQL, sécurité et plan 30 jours.

Écran d'ordinateur avec du code et des icônes techniques pour l'optimisation en PHP.

Table des matières :

  1. Mesurer la dette technique PHP avant de la « réduire »
  2. Stratégie incrémentale : réduire la dette sans refonte (et sans « big bang »)
  3. Quick wins sur le code PHP : typage, erreurs, autoload, OPcache
  4. Points noirs PrestaShop : overrides, hooks, dette SQL et effets N+1
  5. La dette technique est aussi de la dette sécurité (et elle coûte cher)
  6. Industrialiser : CI, analyse statique, tests, et observabilité en production
  7. Plan d’action 30 jours (réaliste) pour optimiser le code PHP sans refonte

Mesurer la dette technique PHP avant de la « réduire »

Sur PrestaShop, la dette technique n’est pas un concept abstrait : elle se matérialise dans des overrides qui cassent les upgrades, des modules qui mélangent I/O, SQL et rendu, des contrôleurs qui font de la validation métier « à la volée », et des bouts de legacy qui ne sont plus couverts par aucun test. Sur un socle PrestaShop 8.1/8.2 (PHP 8.1–8.3) ou PrestaShop 9 (PHP selon matrice supportée), la première erreur consiste à optimiser « au feeling » : vous allez toucher du code froid, et laisser intactes les 20 % qui produisent 80 % des incidents.

La dette technique mesurable, c’est un ensemble d’indicateurs. Côté code : complexité cyclomatique (seuils pratiques : >10 à surveiller, >20 à traiter), duplication, couplage (classes qui connaissent trop de détails PrestaShop), profondeur d’héritage, et surtout surface de changement (fichiers modifiés à chaque nouvelle feature). Côté runtime : taux d’erreurs PHP, temps CPU, latence P95/P99, nombre de requêtes SQL par page, et volume de cache misses (Redis/OPcache). Sur PrestaShop, activez le profilage et isolez les pages critiques (checkout, recherche, listing) : l’article sur le debug profiling vous donne une méthode reproductible (PrestaShop debug profiling : activer et analyser performances SQL).

Ne vous limitez pas à des métriques « statiques ». Le meilleur levier est de corréler : un fichier souvent modifié + des erreurs récurrentes + un endpoint à forte volumétrie = cible prioritaire. Pour la partie profiling applicatif, une approche déjà détaillée côté Symfony/Blackfire reste valable même si votre code n’est pas 100% Symfony : isolez un parcours, capturez des traces avant/après, puis refactorez avec un budget de performance explicite (Dette technique Symfony : profiling Blackfire et refactoring mesurable).

Pour rendre l’audit exploitable (et éviter « un PDF de plus »), fixez dès le départ un format de sortie qui se transforme en backlog. Par exemple, pour chaque hotspot identifié :

  • Où : route/page + contexte (front/BO, guest/loggé, multi-boutique oui/non).
  • Symptôme mesuré : latence P95, erreurs, requêtes SQL, mémoire.
  • Impact business : checkout, SEO, campagne SEA, imports, BO.
  • Hypothèse : N+1, index manquant, override fragile, cache absent, validation incohérente.
  • Action minimale : une modification isolable (feature flag, service extrait, suppression d’override).
  • Critères d’acceptation : métrique avant/après, et comment elle est collectée.

Un tableau simple suffit souvent à prioriser sans débat « esthétique » :

Signal de dette (observé) Risque typique Exemple PrestaShop Action la plus rentable
Fichier modifié à chaque sprint Régressions + conflits Product::getPriceStatic override Extraire un service + tests ciblés
P95 en hausse sur listing Perte conversion/SEO filtres + prix spécifiques Corriger N+1 + index + cache
Erreurs PHP intermittentes Pages blanches / 500 null inattendu, accès array Types + exceptions + logs
SQL « lent » sans index CPU DB + timeouts jointures sur *_lang EXPLAIN + index + bornage

Enfin, pensez géolocalisation dès la mesure. Si votre trafic est majoritairement en France/Belgique/Suisse, mesurez (synthetic ou RUM) depuis une sonde proche (Paris/Frankfurt/Amsterdam) pour éviter de conclure à tort à un « problème PHP » alors que c’est une latence réseau ou un CDN mal réglé. Et si vous comparez staging/prod, assurez-vous que le staging n’est pas « au fond d’un mutualisé » : sinon, vous comparez des machines, pas votre code.

Stratégie incrémentale : réduire la dette sans refonte (et sans « big bang »)

Si vous ne pouvez pas refondre, vous devez encadrer le changement. Les patterns qui marchent en e-commerce sont rarement glamour : strangler / incremental replacement, branch by abstraction, flags de fonctionnalités, et extraction progressive de services. L’objectif est double : 1) améliorer le code au fil des livraisons, 2) conserver une capacité de rollback rapide. Sur des boutiques à fort trafic, c’est la différence entre « refactoring » et « incident de prod ».

Concrètement, imposez un prérequis non négociable : staging proche de prod (mêmes versions PHP, même configuration OPcache, mêmes extensions), et un mécanisme de déploiement qui sait revenir en arrière. Si votre process est artisanal, commencez par le rendre incrémental et réversible : le schéma blue/green, même simplifié, réduit drastiquement le risque (Migration PrestaShop : audit technique et plan incrémental blue/green). Ajoutez une checklist de rollback et des tests minimaux avant switch (Migration PrestaShop 9 : sécurité, tests et plan de rollback).

Une règle pratique : ne refactorez que ce que vous pouvez observer. Sans logs exploitables, vous n’avez pas de boucle de feedback. Un code « plus propre » qui augmente la latence ou crée des erreurs silencieuses vous fera perdre du temps. Mettez à plat vos sources : logs PHP-FPM, logs applicatifs PrestaShop, logs MySQL, et alertes. Si ce socle n’est pas en place, traitez d’abord l’observabilité (PrestaShop monitoring d’erreurs : logs PHP, MySQL, JavaScript et alertes e-mail).

Une approche incrémentale qui fonctionne bien sur PrestaShop (et qui limite la dette future) est de définir une zone “safe” :

  • Le legacy continue de vivre, mais on évite d’y ajouter des branches.
  • Toute nouvelle logique (prix, règles panier, exports, intégrations) passe par un service testable, typé, loggé.
  • On remplace les appels « directs » progressivement (contrats d’interface, adaptateurs).

Mini-scenario réaliste : vous avez une boutique FR avec pics pendant les soldes. Un module custom calcule une remise et fait 6 requêtes par produit dans le panier. Plutôt que réécrire le panier, vous :

1) mettez un flag (config) pour activer le nouveau calcul,
2) extrayez le calcul dans un service pur (sans SQL),
3) batchez les données (stocks/prix spécifiques) en amont,
4) comparez en production sur un faible pourcentage (ou sur un segment : employés / IPs internes) avant généralisation.

Le gain ici, ce n’est pas « un beau code », c’est un refactoring réversible, avec mesure avant/après.

Quick wins sur le code PHP : typage, erreurs, autoload, OPcache

Sur PHP 8.x, le gain le plus fiable (et le moins risqué) n’est pas une micro-optimisation, mais la réduction de l’ambiguïté : activer le typage strict quand c’est possible, ajouter des types de retour, et remplacer les « faux succès » (retour false/null) par des exceptions explicites. Cela réduit la dette parce que vous supprimez des chemins d’exécution implicites, souvent responsables d’effets de bord en production. Si vous partez d’un code module/override legacy, ciblez d’abord les classes « de service » (celles qui ne font ni HTML ni accès direct au $_POST).

Exemple concret (approche incrémentale) : typage + validation d’entrée sans toucher au reste de la stack. Vous pouvez introduire une nouvelle classe typée, et adapter l’appelant sans refondre tout le contrôleur.

<?php
declare(strict_types=1);

final class PriceCalculator
{
    public function computeTaxedPrice(float $net, float $vatRate): float
    {
        if ($net < 0.0) {
            throw new InvalidArgumentException('Net price must be >= 0');
        }
        if ($vatRate < 0.0 || $vatRate > 1.0) {
            throw new InvalidArgumentException('VAT rate must be between 0 and 1');
        }
        return $net * (1.0 + $vatRate);
    }
}

Deux quick wins souvent sous-estimés dans du code PrestaShop legacy :

  • Normaliser les erreurs : remplacez les return false; dispersés par une exception métier (ou un objet résultat) là où c’est critique. Ensuite, au point d’entrée (controller/hook), vous loggez l’erreur avec un identifiant de corrélation (request id) et un contexte (idcart, idcustomer) — attention à ne pas logguer de données sensibles.
  • Réduire la magie : là où vous avez des tableaux “fourre-tout” (ex. array $params), commencez par typer 1 ou 2 champs via DTO minimal. Cela a un effet immédiat sur l’analyse statique et diminue les régressions.

Sur la perf pure, évitez de bricoler sans regarder OPcache et l’autoloader Composer. Le runtime PHP en prod dépend fortement de ces deux couches. Le manuel PHP est explicite : « The OPcache extension improves PHP performance by storing precompiled script bytecode in shared memory » (php.net – OPcache). Donc : vérifiez l’extension, dimensionnez la mémoire (opcache.memory_consumption), évitez la validation agressive des timestamps en prod (opcache.validate_timestamps=0 si votre déploiement est atomique), et monitoriez les stats (hits/misses, num_cached_scripts). Pour un guide orienté paramétrage, vous avez deux points d’entrée utiles selon votre contexte (PHP OPcache : paramètres recommandés pour optimiser les performances et OPcache PHP : activer et vérifier l’extension dans cPanel).

Petit garde-fou : si vous désactivez validate_timestamps, assurez-vous que votre déploiement invalide correctement le cache (redémarrage PHP-FPM, rotation de release avec chemin différent, ou opcache_reset() maîtrisé). Sinon, vous créez des « bugs fantômes » où la prod exécute du vieux bytecode.

Côté Composer, l’objectif est de réduire le coût d’autoload et les surprises de dépendances. Le site officiel résume le périmètre sans détour : « Composer is a dependency manager for PHP » (getcomposer.org). En production, privilégiez composer install --no-dev --classmap-authoritative (si votre projet le supporte), et activez --optimize-autoloader. Ce n’est pas magique, mais sur des shops où le front déclenche beaucoup de classes (modules, adapters, overrides), vous récupérez facilement quelques millisecondes par requête et vous stabilisez le comportement.

Checklist express “perf safe” (sans refonte) :

  • [ ] OPcache actif + dimensionné (pas de saturation sur les cached_scripts)
  • [ ] realpath_cache_size cohérent avec le volume de fichiers
  • [ ] composer install --no-dev en prod (pas de vendor “dev” chargé)
  • [ ] autoload optimisé (--optimize-autoloader)
  • [ ] pas de require dynamiques répétés dans les hooks/listeners

Points noirs PrestaShop : overrides, hooks, dette SQL et effets N+1

La dette technique spécifique à PrestaShop vient souvent d’un mauvais usage des overrides. C’est tentant parce que ça « marche tout de suite », mais ça dégrade l’évolutivité : conflits entre modules, comportement dépendant de l’ordre de chargement, et upgrades core plus coûteux. Si vous êtes en train de migrer vers PrestaShop 9 (contrôleurs services, Twig, etc.), la stratégie raisonnable est de réduire les overrides au profit d’extensions via hooks et services, ou au minimum de les isoler et de documenter leur raison d’être (PrestaShop 9 : adapter modules et thèmes aux contrôleurs services et Twig). Pour retrouver rapidement où vous pouvez intervenir proprement, l’inventaire des hooks (y compris dynamiques) est un vrai accélérateur (Hooks PrestaShop : rechercher et identifier les hooks dynamiques).

Une méthode pragmatique pour “débunker” un override (et décider s’il faut le garder) :

  • Pourquoi existe-t-il ? (bug contourné ? ajout métier ? patch de sécurité ?)
  • Est-il encore nécessaire sur votre version (8.1/8.2/9) ?
  • Peut-on le remplacer par :
  • un hook natif,
  • un service décoré (quand l’architecture le permet),
  • ou un module qui étend le comportement sans toucher au core ?
  • Quel est son coût d’upgrade ? (à chaque mise à jour, combien de temps passez-vous à le rebase ?)

Deuxième point noir : la dette SQL, en particulier le N+1 déguisé. PrestaShop n’impose pas un ORM strict, ce qui est à la fois une force (vous pouvez écrire du SQL efficace) et un piège (vous pouvez écrire du SQL catastrophique). Le pattern classique : boucle sur des produits + requête pour chaque produit (stock, attributs, prix spécifiques), puis cache absent → latence qui explose avec la taille du panier ou du listing. Si vous devez arbitrer « ORM vs SQL brut », faites-le sur des critères d’observabilité (EXPLAIN, index, cardinalité) et non sur une préférence de style (ORM : limites, requêtes N+1 et quand préférer le SQL brut).

Le remède n’est pas une refonte : c’est une batchification et un contrat de données. Exemple : récupérer en une requête tous les stocks pour une liste d’IDs, puis hydrater en mémoire.

SELECT id_product, quantity
FROM ps_stock_available
WHERE id_product IN (12, 34, 56)
  AND id_shop = 1;

À ce stade, la “vraie” optimisation est souvent un triptyque :

1) Réduire le nombre de requêtes (batch / IN / jointures maîtrisées),
2) Réduire le volume (sélectionner uniquement les colonnes utiles, pagination réelle, bornes),
3) S’assurer que MySQL peut exécuter vite (index, cardinalité, EXPLAIN).

Ensuite, assurez-vous que MySQL/MariaDB vous donne les preuves. Activez le slow query log sur une fenêtre maîtrisée et tracez les requêtes par route fonctionnelle (Requêtes MySQL lentes PrestaShop : activer slow query log). Pour les gros catalogues, la performance n’est pas un « tweak », c’est un design : index adaptés, requêtes bornées, caches multi-niveaux, et invalidation contrôlée (PrestaShop performance : optimiser gros catalogues et Core Web Vitals).

Pour relier dette technique et SEO (sans sur-promettre), gardez en tête que les optimisations back peuvent impacter directement des indicateurs front (TTFB, stabilité, erreurs). Les seuils Core Web Vitals sont documentés par Google ; par exemple, la recommandation courante pour le LCP est d’être ≤ 2,5 s. Documentation : Web.dev – LCP

La dette technique est aussi de la dette sécurité (et elle coûte cher)

Réduire la dette technique « sans refonte » ne veut pas dire uniquement améliorer la lisibilité : sur PrestaShop, les dettes qui finissent en incident sont souvent des dettes de contrôle d’accès, de validation d’entrée et de gestion des secrets. Un code legacy avec des paramètres non filtrés, des contrôleurs qui font confiance à des IDs passés en URL, ou des webservices exposés sans limitation de débit, ce sont des tickets SOC en attente.

Le problème typique : IDOR (Insecure Direct Object Reference) dans un endpoint front ou back-office custom. L’OWASP résume le principe de manière opérationnelle : « Access control is the process of granting or denying specific requests from a user, program, or process » (OWASP – Access Control). En pratique, dans PrestaShop, cela veut dire : ne jamais charger une ressource (commande, adresse, facture) sur la base d’un id_* sans vérifier l’appartenance au client connecté ou le droit BO via isGranted/permissions.

Vous avez intérêt à traiter ces dettes en même temps que les dettes qualité, parce que les remèdes se recoupent : typage, exceptions, validation centralisée, tests, et suppression des « bypass ». Pour cadrer ces aspects côté back-office et modules, vous pouvez vous appuyer sur les méthodos et anti-patterns décrits ici : Broken access control : prévenir IDOR et élévation de privilèges et, au niveau de la menace actuelle sur e-commerce, Sécurité PrestaShop 2026 : webskimmers et infostealers côté serveur. La dette technique non contrôlée élargit mécaniquement la surface d’attaque (dépendances non mises à jour, overrides invisibles, endpoints non documentés).

Point “terrain” souvent oublié : les logs. L’observabilité est indispensable, mais en e-commerce vous traitez des données personnelles (identifiants, emails, adresses, IDs de commandes). Réduire la dette sécurité, c’est aussi :

  • minimiser ce qui est loggué (pas de PAN, pas de secrets, pas de tokens),
  • fixer des durées de rétention,
  • contrôler l’accès aux dashboards et aux exports.

Même sans rentrer dans un chantier RGPD, ces pratiques limitent les dégâts en cas d’incident.

Industrialiser : CI, analyse statique, tests, et observabilité en production

Sans CI, vous ne réduisez pas la dette : vous la déplacez. L’objectif n’est pas d’atteindre 100% de couverture, mais de créer des garde-fous : linter + format (PSR-12), analyse statique (PHPStan/Psalm) sur un niveau réaliste, et tests ciblés sur les flux qui cassent souvent (checkout, création client, paiement, import). C’est particulièrement vrai en écosystème PrestaShop où chaque module peut embarquer sa propre dette. Si vous voulez un angle « supply chain » (provenance, SBOM, validation), vous avez un cadre actionnable côté CI modules (CI PrestaShop : provenance, SBOM et validation automatique des modules).

Pour que ce soit maintenable, vous devez séparer règles et exceptions. Exemple : commencez par un phpstan.neon tolérant, puis montez le niveau sur un sous-ensemble de namespaces (nouveau code, ou code en cours de refactoring). Même logique pour PHP-CS-Fixer : adoptez une base, puis interdisez progressivement les patterns qui créent des bugs (comparaisons lâches, empty() utilisé comme validation, retours mixtes). Cette approche incrémentale est compatible avec « pas de refonte » : vous améliorez la qualité des PR futures, et vous évitez que la dette revienne.

Un bon compromis (et très “ROI”) consiste à cibler quelques règles qui attrapent des bugs coûteux :

  • Types de retour manquants sur services (réduit les surprises en prod).
  • Comparaisons lâches sur des IDs (ex. == vs ===) quand 0, '' et null se croisent.
  • Accès array non vérifié sur des payloads externes (webhooks, API transporteur, paiement).
  • Exceptions avalées (catch vide) qui créent des erreurs silencieuses.

Enfin, l’observabilité ferme la boucle. Sur e-commerce, une dette technique qui génère des pages vides/erreurs 5xx finit en dette SEO (crawl gaspillé, soft 404, perte d’indexation) et en dette business (abandons). Si vous voyez des pages blanches ou des réponses incomplètes, traitez-les comme des incidents de prod, pas comme des « bugs front ». Vous avez deux ressources internes utiles pour relier symptôme et impact : Page vide : causes fréquentes et impact sur l’indexation Google et Erreur HTTP 503 : diagnostic serveur, logs et ressources.

Pour éviter l’“observabilité gadget”, définissez 3 signaux minimaux (et actionnables) :

  • Saturation : CPU, mémoire, pool PHP-FPM (file d’attente).
  • Erreur : taux de 5xx + top exceptions (avec contexte).
  • Performance : latence P95 par route critique + nombre de requêtes SQL.

Avec ça, vous pouvez refactorer sans refonte… et sans naviguer à vue.

Plan d’action 30 jours (réaliste) pour optimiser le code PHP sans refonte

Semaine 1 : audit court, mais instrumenté. Listez les routes/pages critiques, capturez un baseline (latence P95, CPU, nombre de requêtes SQL, taux d’erreurs), et identifiez 5–10 hotspots (fichiers ou classes) via git + logs + profiling. À ce stade, le livrable attendu n’est pas « une roadmap vague », mais un backlog avec critères d’acceptation mesurables : “réduire de 25 % le nombre de requêtes SQL sur category.php pour 48 produits”, “supprimer 2 overrides en les remplaçant par hook X/Y”, etc.

Astuce de cadrage (très utile en contexte multi-équipes) : associez à chaque item un risque de déploiement (faible/moyen/fort) et une stratégie de rollback (feature flag / revert / blue-green). Vous évitez ainsi de traiter un refactoring comme une simple “tâche dev”, alors que c’est une opération de prod.

Semaine 2 : quick wins sûrs. Activez/validez OPcache et ajustez les paramètres en cohérence avec votre mode de déploiement (sinon, validate_timestamps=0 est un piège). Optimisez l’autoload Composer en prod, supprimez les require manuels, et introduisez du typage sur les nouvelles classes extraites. Parallèlement, créez une règle simple : tout nouveau code doit avoir des types de retour et ne pas retourner false pour signaler une erreur.

Un indicateur simple pour valider que vous ne “cassez” pas : sur 2–3 parcours clés (listing, fiche produit, panier), comparez avant/après :

  • latence P95,
  • nombre de requêtes SQL,
  • taux d’erreurs PHP,
  • et, si possible, TTFB côté front.

Semaine 3 : dette PrestaShop structurante. Priorité aux N+1, aux requêtes non indexées, et aux overrides à haut churn. Activez le slow query log sur une courte fenêtre, corrigez 3–5 requêtes « top offenders », et ajoutez des tests de non-régression sur les parcours associés. Si vous préparez PrestaShop 9, commencez à déplacer la logique vers des services (même si vos contrôleurs restent legacy) : c’est le chemin le plus court vers une base maintenable.

Objectif “réaliste” semaine 3 : pas de “gros refactoring”, mais des corrections nettes qui se mesurent. Typiquement :

  • 1 override supprimé (ou neutralisé) avec justification documentée,
  • 1 N+1 supprimé sur une page à fort trafic,
  • 1 index ajouté après EXPLAIN (avec vérification des écritures).

Semaine 4 : industrialisation minimale. Branchez CI avec analyse statique progressive, ajoutez un job qui exécute au moins les tests unitaires/integ critiques, et imposer un seuil d’échec (même faible) sur les erreurs bloquantes (type errors, violations de règles de sécurité, secrets committés). Côté prod, mettez des alertes simples : pic d’erreurs 5xx, augmentation du temps de réponse, et régression sur le nombre de requêtes SQL. À la fin du mois, vous n’avez pas « refondu » : vous avez réduit la dette technique de manière quantifiable et, surtout, vous avez mis en place des garde-fous pour éviter qu’elle regonfle au sprint suivant.

Pour ancrer le tout dans la durée, terminez par un rituel léger (mais efficace) : une revue mensuelle “dette technique PHP” de 30 minutes, basée uniquement sur des métriques (erreurs, P95, top requêtes lentes, fichiers à fort churn). Ce n’est pas une réunion de plus : c’est la condition pour que l’optimisation du code PHP reste un processus continu, même sans refonte.


À lire aussi