Product Grid Pro PrestaShop : colonnes avancées pour gérer le catalogue

Optimisez la grille produits PrestaShop : colonnes triables/filtrables, agrégats pré‑calculés, sécurité, compatibilité 8/9 et bonnes pratiques performance.

Interface logicielle de Product Grid Pro sur des écrans d'ordinateur avec des illustrations de flux de travail.

Table des matières :

  1. Grille produits PrestaShop (Symfony Grid) : ce que le cœur fait… et ce qu’il ne fait pas
  2. Product Grid Pro PrestaShop : cahier des charges réaliste pour des colonnes avancées
  3. Implémentation : colonnes, filtres et tris via hooks (sans ruiner les perfs)
  4. Industrialisation : compatibilité PrestaShop 8/9, tests, et observabilité BO
  5. Sécurité et gouvernance : permissions, injection SQL, et exposition de données
  6. Décider entre module “Product Grid Pro” et approche data : checklist pragmatique

Avant de chercher un « Product Grid Pro PrestaShop », il faut se rappeler pourquoi la grille des produits du back‑office est (souvent) le meilleur levier de productivité côté catalogue : c’est là que vos équipes passent du temps pour trier, filtrer, corriger et décider (arrêts, réassorts, SEO, pricing). Ajouter des colonnes est donc utile… à condition de le faire dans la chaîne Symfony Grid, pas « en surcouche » coûteuse.

Grille produits PrestaShop (Symfony Grid) : ce que le cœur fait… et ce qu’il ne fait pas

Dans PrestaShop 8.x et 9.x, la liste des produits du back-office (le « product grid ») s’appuie sur le composant Symfony Grid intégré par l’équipe PrestaShop. En pratique, ce grid est un pipeline : Definition (colonnes, filtres, actions) → QueryBuilder (requête paginée + tri + filtres) → DataFactory (formatage des lignes) → rendu Twig. Ce choix est sain côté architecture, mais le cœur reste conservateur : les colonnes utiles à l’exploitation (marge, couverture de stock, statut d’indexation, date de dernière vente, score qualité de fiche…) ne sont pas exposées nativement, et les besoins « gros catalogue » ne sont pas traités par défaut.

Pour vous repérer dans la logique officielle (objets Grid, SearchCriteria, modificateurs), la doc développeur PrestaShop sur le Grid component est un bon point de départ : Documentation officielle (Grid component).

Le point d’extension le plus connu côté données est le hook actionProductGridDataModifier, documenté et disséqué ici : Hook actionProductGridDataModifier PrestaShop : emplacement, appel et usage développeur. Mais pour des colonnes avancées « propres » (triables, filtrables, exportables), on doit généralement intervenir plus tôt dans la chaîne, via des hooks de définition et de requête (selon versions : actionProductGridDefinitionModifier, actionProductGridQueryBuilderModifier, etc.). Si votre « Product Grid Pro PrestaShop » n’expose que actionProductGridDataModifier sans toucher au QueryBuilder, vous aurez une colonne affichée… mais souvent non triable et non filtrable, et surtout coûteuse (calcul ligne par ligne).

Un exemple très concret (souvent observé sur des catalogues B2C en France, notamment quand la gestion est faite “au quotidien” par une petite équipe) : vous ajoutez « Marge » via un calcul PHP après requête. Le lundi matin, l’équipe veut trier les produits à marge négative. Résultat : impossible (ou tri “visuel” côté PHP sur une seule page), donc export CSV, manipulation Excel, puis ré-import ou corrections manuelles. C’est typiquement le signal qu’il faut rendre la donnée queryable (tri/filtre au niveau SQL), ou pré‑calculée si l’agrégat est trop lourd.

Enfin, n’oubliez pas le facteur exploitation : la page Catalog > Products est un point chaud en BO. Sur un catalogue de plus de 50 000 produits, une colonne « simple » peut déclencher des JOINs supplémentaires, des ORDER BY sur colonnes non indexées, et vous retrouvez des requêtes à plusieurs centaines de millisecondes voire secondes. La doctrine « fonctionnalité d’abord » est tentante, mais Donald Knuth rappelait : “Premature optimization is the root of all evil.” (Knuth, 1974). Traduction opérationnelle : commencez par instrumenter (temps SQL, nombre de JOIN, plan d’exécution), puis optimisez les 20% de colonnes qui créent 80% de latence.

Product Grid Pro PrestaShop : cahier des charges réaliste pour des colonnes avancées

Un module de type Product Grid Pro PrestaShop (ou une implémentation interne équivalente) doit d’abord clarifier ce qu’on entend par « colonnes avancées » : ce ne sont pas des champs supplémentaires collés à la volée, ce sont des attributs de pilotage du catalogue, exploitables par les équipes produit/ops. Exemples concrets :

  • Marge (prix HT – prix d’achat) et/ou taux de marge ;
  • Couverture de stock (stock / ventes 30 jours) ;
  • Dernière date de commande (pour repérer des produits morts) ;
  • État SEO (meta title vide, canonical incohérent, noindex) ;
  • Statut marketplace/ERP (sync OK/KO, horodatage de dernière synchro).

Dans la pratique, ces colonnes ne se valent pas : certaines sont “locales” (dépendent uniquement de ps_product / ps_product_shop), d’autres sont agrégées (ventes, rotations), d’autres encore sont contextuelles (multi‑shop / devise / règles de prix spécifiques). Une manière efficace de cadrer un backlog “grid pro” est de classifier les colonnes par coût et par dépendances :

Type de colonne Exemples Source principale Risque perf typique Bonne approche
Champ simple EAN, référence, fournisseur ps_product, ps_product_shop faible SELECT direct + index si tri fréquent
Calcul simple marge brute “base” ps_product_shop moyen (tri sur expression) calcul SQL + tri optionnel, ou matérialisation
Jointure 1:1 stock dispo (sans ventes) ps_stock_available moyen JOIN + index (id_product, id_shop)
Agrégat temporel ventes 30j, dernière vente ps_orders + ps_order_detail élevé table d’agrégats pré‑calculée
Qualité / SEO champs manquants, statut noindex ps_product_lang, tables SEO moyen (multi-lang) JOIN limitée + filtre lang, ou score pré‑calculé
Sync externe ERP / marketplace table custom variable table normalisée + index sur statut/date

Ensuite, techniquement, le module doit être capable de gérer trois niveaux de maturité : (1) affichage (colonne calculée en PHP), (2) tri/filtre (colonne « queryable »), (3) scalabilité (pré-calcul, index, agrégats). C’est là que beaucoup d’extensions échouent : elles livrent l’étape (1) et s’arrêtent. Or, côté expérience BO, une colonne non triable/non filtrable a peu de valeur : elle oblige à exporter ou à scroller, et elle dégrade la page.

Enfin, un « Pro » crédible doit traiter les réalités PrestaShop : multi-boutique, multi-langue, produits à déclinaisons, gestion de stock avancée (table ps_stock_available), et permissions BO (rôles Symfony). Autrement dit, une colonne « stock » doit préciser quel shop et quel attribut, et une colonne « libellé » ne doit pas déclencher un N+1 sur ps_product_lang. Si vous cherchez une règle simple : tout ce qui dépend du contexte (shop/lang/currency) doit être calculé dans la requête ou via un dataset pré-calculé, pas dans une boucle PHP.

Deux précisions utiles (souvent oubliées) quand on parle “marge” et “prix” dans une grille PrestaShop :

  • p.price n’est pas toujours le prix réellement vendu (les prix spécifiques, règles de catalogue, promotions, groupes, etc. peuvent modifier le prix final). Pour du pilotage, définissez clairement si vous voulez une marge “catalogue” (prix de base) ou une marge “réalisée” (nécessite des ventes).
  • sur des catalogues à déclinaisons, la marge pertinente peut être au niveau combinaison (prix/achat par attribut), ce qui change complètement le modèle (JOIN sur ps_product_attribute_shop et éventuellement agrégats par id_product_attribute).

Implémentation : colonnes, filtres et tris via hooks (sans ruiner les perfs)

Pré-requis explicites pour ce qui suit : PrestaShop 8.1+ ou 9.0/9.1, PHP conforme à la matrice officielle de votre version PrestaShop (et à votre environnement MySQL/MariaDB), accès à un environnement de staging, et capacité à revenir en arrière (backup DB + rollback du module). Si vous touchez au grid, vous touchez à une page critique d’exploitation : testez avec un dump réaliste.

Avant même d’écrire une ligne de code, posez ces deux garde-fous :

  • limitez le nombre de colonnes “lourdes” sur la vue par défaut (vous pouvez les rendre activables via paramètres, ou les réserver à un rôle BO) ;
  • privilégiez des colonnes normalisées (ex. sync_status = OK/KO + sync_updated_at) plutôt que des colonnes “verbeuses” (12 champs techniques d’intégration).

Exemple : ajouter une colonne “marge” (triable + filtrable)

L’approche robuste consiste à : (a) ajouter la colonne dans la définition, (b) étendre la requête SQL pour exposer un alias margin, (c) déclarer le filtre associé. Exemple simplifié (les signatures exactes varient selon versions, adaptez au hook disponible) :

<?php
use PrestaShop\PrestaShop\Core\Grid\Column\DataColumn;
use PrestaShop\PrestaShop\Core\Grid\Filter\Filter;
use Symfony\Component\Form\Extension\Core\Type\NumberType;

public function hookActionProductGridDefinitionModifier(array $params): void
{
    $definition = $params['definition'];

    $definition->getColumns()->addAfter(
        'price',
        (new DataColumn('margin'))
            ->setName($this->l('Marge'))
            ->setOptions([
                'field' => 'margin',
            ])
    );

    $definition->getFilters()->add(
        (new Filter('margin', NumberType::class))
            ->setAssociatedColumn('margin')
    );
}

public function hookActionProductGridQueryBuilderModifier(array $params): void
{
    $qb = $params['search_query_builder'];

    // ATTENTION : selon la requête existante, wholesale_price peut venir de p.wholesale_price
    // et price est parfois déjà une expression.
    $qb->addSelect('(p.price - p.wholesale_price) AS margin');

    // Filtre (ex. marge >= X)
    $filters = $params['search_criteria']->getFilters();
    if (isset($filters['margin']) && $filters['margin'] !== '') {
        $qb->andWhere('(p.price - p.wholesale_price) >= :margin');
        $qb->setParameter('margin', (float) $filters['margin']);
    }

    // Tri
    if ($params['search_criteria']->getOrderBy() === 'margin') {
        $qb->addOrderBy('margin', $params['search_criteria']->getOrderWay());
    }
}

Quelques points “terrain” à intégrer dès la V1 pour éviter les mauvaises surprises :

  • Dans les requêtes du grid, le prix et le prix d’achat proviennent souvent de ps_product_shop (alias selon builder). Sur un multi‑shop, utiliser ps_product peut donner des incohérences si les champs sont surchargés par boutique.
  • Si vous affichez la marge en BO, précisez l’unité : montant (ex. 12,34) vs % (taux). Les équipes ne prennent pas les mêmes décisions selon l’affichage.
  • Gérez le cas “prix d’achat non renseigné” (NULL ou 0) : sinon vous allez classer à tort des produits comme “marge élevée” (si achat = 0).

Le piège classique : vous venez d’introduire un ORDER BY margin sur une expression calculée. Sur gros volume, ce tri peut forcer un tri coûteux (et donc une dégradation notable de la grille). Deux stratégies : (1) accepter que cette colonne soit filtrable mais non triable (et l’assumer UX), (2) matérialiser la marge dans une table dédiée (ou une colonne custom) mise à jour via cron/worker, avec index.

À noter : selon votre moteur et version (MySQL 8+ vs MariaDB), il existe des options comme les colonnes générées ou les index fonctionnels (index sur expression). C’est puissant, mais à manier avec prudence dans PrestaShop : vous devez maintenir la compatibilité de votre stack, documenter la migration, et prévoir la réversibilité.

Perfs : SQL, EXPLAIN et indexation

Quand vous ajoutez des colonnes qui impliquent des JOIN (ps_stock_available, ps_order_detail, ps_product_attribute…), vous devez relire votre plan d’exécution. Le bon réflexe est d’exécuter la requête finale en SQL et de passer un EXPLAIN/EXPLAIN ANALYZE (MySQL 8). Pour cadrer la méthode, vous avez un guide interne sur les index MySQL et les patterns WHERE/JOIN/ORDER BY : Index MySQL : optimiser WHERE, JOIN et ORDER BY avec EXPLAIN.

Une mini-checklist pragmatique avant de livrer une colonne “lourde” :

  • Le WHERE utilise-t-il des colonnes indexées (ou au moins sélectives) ?
  • Le JOIN se fait-il sur des clés attendues (id_product, id_shop, id_lang) et non sur des champs texte ?
  • Le ORDER BY porte-t-il sur un champ indexable (et pas une expression) ?
  • La cardinalité attendue est-elle correcte (pas de duplication de lignes due à des jointures mal contraintes) ?
  • Le coût est-il stable quand on passe de page 1 à page 50 (effet OFFSET) ?

Dans un cas réel (catalogue ~120k produits, multi-boutique), une colonne « dernières ventes 30 jours » joinée sur ps_orders + ps_order_detail a fait passer le chargement de la grille de ~450 ms à ~3,8 s tant que l’agrégat n’était pas pré-calculé. La solution propre n’a pas été d’« optimiser PHP », mais de créer une table d’agrégats (id_product, id_shop, qty_30d, last_order_at) alimentée par job (cron ou worker), indexée sur (id_shop, qty_30d) pour le tri/filtre.

Ce pattern “table d’agrégats” est souvent le meilleur compromis en e‑commerce : vous payez un coût de calcul hors back‑office (planifié), et vous gardez une expérience BO fluide, y compris quand l’équipe filtre/tri en pleine journée.

Industrialisation : compatibilité PrestaShop 8/9, tests, et observabilité BO

Sur PrestaShop 9, le cœur continue sa trajectoire Symfony 6.4 (cf. PrestaShop 9 : nouveautés techniques Symfony 6.4, API et performances) et la matrice PHP évolue vite (cf. PrestaShop 9.1 : compatibilité PHP 8.1–8.5, CLI et nouveautés développeurs). Un module « grid pro » doit donc minimiser la surface de fragilité : évitez les overrides et préférez les hooks + services Symfony. Les overrides de controllers Admin sont la dette technique la plus fréquente en maintenance, surtout lors des upgrades mineures.

Côté qualité, traitez ce module comme une brique critique : tests unitaires sur vos classes de calcul (marges, scores), tests d’intégration sur le QueryBuilder (au minimum, assertions sur la présence des SELECT/JOIN/WHERE), et linting statique. Si votre équipe fait déjà du PHPStan/Rector, réutilisez les patterns (cf. PHPStan et Rector : industrialiser la qualité du code PHP).

Un socle de tests “réaliste” pour une colonne avancée ressemble souvent à ça :

  • unit test : la formule de marge (arrondi, NULL, seuils) ;
  • integration test : la requête grid inclut bien l’alias SQL (ex. margin) et le filtre ne casse pas la pagination ;
  • non‑regression : multi‑shop (même produit, prix différents), multi‑lang (pas de duplication), et produits avec/sans déclinaisons.

La citation de Martin Fowler reste pertinente en extension PrestaShop : “Any fool can write code that a computer can understand. Good programmers write code that humans can understand.” (Fowler, 1999). Un grid modifié sans tests devient vite incompréhensible à la troisième itération.

Enfin, instrumentez. Le back-office n’a pas de Core Web Vitals, mais il a de la latence qui tue la productivité et augmente les erreurs humaines. Centralisez : temps de réponse, slow queries, erreurs PHP, saturation CPU lors de filtrages. Les sources “faciles” (et souvent suffisantes) : slow query log MySQL, métriques PHP-FPM, logs Symfony/Monolog, et, si possible, un APM.

Vous pouvez reprendre une approche « runbook » et monitoring déjà posée sur le front : PrestaShop performance : monitoring, tests de charge et runbooks soldes. Et si vous corrélez des lenteurs DB avec un TTFB dégradé côté front, la méthodologie décrite ici reste applicable : TTFB PrestaShop : réduire le Time To First Byte sous 200 ms.

Sécurité et gouvernance : permissions, injection SQL, et exposition de données

Ajouter des colonnes au product grid, c’est potentiellement exposer des données sensibles (prix d’achat, marge, statut fournisseur, logs de synchro ERP). Votre module doit respecter les permissions BO : sur PrestaShop 8/9, l’ACL est hybride (legacy + Symfony). Ne partez pas du principe que « si l’utilisateur voit la page, il voit tout ». Implémentez une logique : colonne visible uniquement si l’employé a le droit (ex. permission sur AdminProducts, rôle spécifique, ou configuration interne). Et si vous ajoutez un export CSV enrichi, appliquez la même contrainte.

Sur la partie requêtes, ne concaténez jamais des filtres directement dans le SQL. Le risque n’est pas théorique : un module de grid est un endroit typique où un tri non whiteliste ouvre la porte à un ORDER BY arbitraire, ou où un filtre texte finit “injecté” dans une clause. Bonnes pratiques minimales :

  • QueryBuilder/DBAL + paramètres (setParameter) ;
  • cast strict ((int), (float)) sur les filtres numériques (marge, stock, quantités) ;
  • whitelist explicite des champs triables (ex. name, price, margin, mais pas n’importe quelle valeur reçue) ;
  • si vous exposez des IDs (fournisseur, catégorie), validez l’existence/portée (shop) avant de filtrer.

Enfin, mettez à jour et surveillez votre supply chain. Même si l’exemple n’est pas un module de grid, l’existence d’alertes comme CVE-2025-61922 ps_checkout : mise à jour 5.0.5 et mesures rappelle une règle simple : un module qui touche au back-office doit être versionné, audité, et déployé avec un processus (staging → prod, changelog, rollback). Pour une vue d’ensemble des contrôles défensifs, voir Sécurité PrestaShop : protéger API backoffice, WAF et journalisation SIEM.

Décider entre module “Product Grid Pro” et approche data : checklist pragmatique

Avant d’installer/écrire un Product Grid Pro PrestaShop, posez le diagnostic : voulez-vous mieux afficher ou mieux gérer le catalogue ? Si le vrai besoin est “masquer” des produits saisonniers, vous gagnerez plus avec une logique d’archivage métier qu’avec une colonne de plus. Référence utile : Module PrestaShop Archive Products : archiver produits sans suppression. Le grid est un outil ; ce n’est pas un modèle de données.

Si le besoin est bien « pilotage », utilisez cette checklist technique (oui/non) :

  • La colonne est-elle triable et filtrable sans expression non indexée ?
  • Le coût SQL est-il acceptable sur votre volumétrie (EXPLAIN + temps réel) ?
  • Multi-shop/lang gérés sans approximations ?
  • Le calcul est-il déterministe et testable (pas de dépendance cachée au contexte) ?
  • Y a-t-il un plan de pré-calcul (cron/worker) si la colonne est agrégée (ventes 30j, couverture) ?

Ajoutez aussi une checklist “exploitation” (souvent la différence entre une bonne idée et un irritant quotidien) :

  • La colonne est-elle compréhensible par un non‑tech (libellé, format, arrondi) ?
  • L’équipe sait-elle quoi faire quand la valeur est “anormale” (ex. marge négative, stock incohérent) ?
  • Avez-vous une action rapide associée (édition, désactivation, redirection vers la fiche) ?
  • Le besoin est-il récurrent (quotidien/hebdo) ou ponctuel (audit trimestriel) ? Si ponctuel, un export dédié peut suffire.

Enfin, n’oubliez pas le cycle de vie : vous ajoutez des colonnes, donc vous ajoutez des dépendances sur vos tables. Si vous créez une table d’agrégats, documentez la routine de purge/rebuild et incluez-la dans votre maintenance DB (cf. Base de données PrestaShop : routine de maintenance et nettoyage automatisé). Si vous synchronisez des statuts ERP (ex. Dolibarr/Odoo), préférez afficher un statut normalisé (OK/KO + timestamp) plutôt que 12 colonnes brutes ; et traitez la colonne comme un reflet de votre intégration, pas comme une source de vérité (voir par exemple Synchronisation Prestashop Dolibarr : installation, paramètres et webservices SOAP).

Pour aller plus loin côté docs officielles : Symfony est un framework PHP basé sur des composants réutilisables (voir la documentation Symfony), et PrestaShop expose son code du composant Grid dans son dépôt GitHub (à lire pour comprendre les objets GridDefinition, SearchCriteria, et les builders). Si votre module « Product Grid Pro PrestaShop » ne s’aligne pas sur ces abstractions, il fonctionnera peut‑être aujourd’hui… et cassera silencieusement au prochain cycle 8.x → 9.x.


À lire aussi