💻 DéveloppementIntermédiaire PS 1.7 PS 8.x

Créer un module dashboard PrestaShop avec requêtes SQL personnalisées

Apprenez à développer un module PrestaShop qui affiche des données SQL personnalisées dans le tableau de bord : hook dashboardZoneTwo, requêtes optimisées et templates Smarty.

En bref : Créez un module PrestaShop qui affiche des données SQL personnalisées dans le tableau de bord via le hook dashboardZoneTwo, en utilisant DbQuery pour des requêtes sécurisées et un template Smarty pour le rendu.

Publié le 21 mars 2026 6 min de lecture Alexandre Carette

Pourquoi créer un widget dashboard personnalisé

Le tableau de bord PrestaShop affiche par défaut des indicateurs standards : chiffre d'affaires, commandes, visiteurs. Mais en tant que marchand ou développeur, vous avez souvent besoin d'y intégrer vos propres données métier — par exemple, le nombre de clients en attente de réapprovisionnement pour un produit, les produits les plus demandés en rupture de stock, ou tout autre indicateur issu d'une requête SQL spécifique.

Dans cet article, nous allons construire un module complet qui s'intègre au tableau de bord via le hook dashboardZoneTwo et affiche les résultats d'une requête SQL dans un tableau propre et fonctionnel.

Architecture du module

Notre module suivra la structure standard PrestaShop :


my_dashboardwidget/
├── my_dashboardwidget.php
├── views/
│   └── templates/
│       └── hook/
│           └── dashboard_zone_two.tpl

Étape 1 : Le squelette du module

Commençons par la classe principale du module avec toutes les bonnes pratiques PrestaShop 8.x :


<?php

if (!defined('_PS_VERSION_')) {
    exit;
}

class My_DashboardWidget extends Module
{
    public function __construct()
    {
        $this->name = 'my_dashboardwidget';
        $this->tab = 'dashboard';
        $this->version = '1.0.0';
        $this->author = 'Votre nom';
        $this->need_instance = 0;
        $this->bootstrap = true;

        parent::__construct();

        $this->displayName = $this->l('Widget Dashboard Personnalisé');
        $this->description = $this->l('Affiche des données SQL personnalisées dans le tableau de bord.');
        $this->ps_versions_compliancy = [
            'min' => '1.7.0.0',
            'max' => '8.99.99',
        ];
    }

    public function install()
    {
        return parent::install()
            && $this->registerHook('dashboardZoneTwo');
    }

    public function uninstall()
    {
        return parent::uninstall();
    }
}

Note PrestaShop 8.x : La structure reste identique à la 1.7, mais veillez à utiliser la syntaxe array courte [] et à déclarer la compatibilité jusqu'à 8.99.99.

Étape 2 : Le hook dashboardZoneTwo

PrestaShop expose plusieurs hooks pour le tableau de bord :

HookPosition `dashboardZoneOne`Colonne principale (haut) `dashboardZoneTwo`Colonne principale (milieu) `dashboardTop`Bandeau supérieur `dashboardData`Données dynamiques (AJAX)

Le hook dashboardZoneTwo est idéal pour un widget de type tableau de données.

Étape 3 : Écrire des requêtes SQL propres

Voici le point crucial. Prenons un cas concret : afficher les produits en rupture de stock les plus demandés par les clients (table ps_mailalert_customer_oos).

L'erreur classique : tout dans une seule requête

Beaucoup de développeurs tentent de tout récupérer en une seule requête complexe avec des sous-requêtes imbriquées. C'est souvent source de bugs et de problèmes de performance.

L'approche recommandée : décomposer

Décomposez en deux requêtes simples et lisibles :


public function getOutOfStockAlerts()
{
    // 1. Récupérer les produits distincts avec le nombre de demandes
    $sql = new DbQuery();
    $sql->select('ma.id_product, COUNT(ma.id_customer) AS nb_demandes');
    $sql->from('mailalert_customer_oos', 'ma');
    $sql->groupBy('ma.id_product');
    $sql->orderBy('nb_demandes DESC');
    $sql->limit(20);

    $results = Db::getInstance(_PS_USE_SQL_SLAVE_)->executeS($sql);

    if (empty($results)) {
        return [];
    }

    // 2. Enrichir chaque ligne avec les infos produit
    $data = [];
    $id_lang = (int) Context::getContext()->language->id;

    foreach ($results as $row) {
        $id_product = (int) $row['id_product'];

        $sqlProduct = new DbQuery();
        $sqlProduct->select('p.id_product, p.reference, pl.name');
        $sqlProduct->from('product', 'p');
        $sqlProduct->leftJoin(
            'product_lang',
            'pl',
            'p.id_product = pl.id_product AND pl.id_lang = ' . $id_lang
        );
        $sqlProduct->where('p.id_product = ' . $id_product);

        $product = Db::getInstance(_PS_USE_SQL_SLAVE_)->getRow($sqlProduct);

        if ($product) {
            $data[] = [
                'id_product'  => $product['id_product'],
                'reference'   => $product['reference'],
                'name'        => $product['name'],
                'nb_demandes' => (int) $row['nb_demandes'],
            ];
        }
    }

    return $data;
}

Version optimisée avec une seule requête JOIN

Si la performance est critique (catalogue volumineux), une requête unique avec JOIN et GROUP BY sera plus efficace :


public function getOutOfStockAlertsOptimized()
{
    $id_lang = (int) Context::getContext()->language->id;
    $id_shop = (int) Context::getContext()->shop->id;

    $sql = new DbQuery();
    $sql->select('p.id_product, p.reference, pl.name, COUNT(ma.id_customer) AS nb_demandes');
    $sql->from('mailalert_customer_oos', 'ma');
    $sql->innerJoin('product', 'p', 'ma.id_product = p.id_product');
    $sql->innerJoin(
        'product_lang',
        'pl',
        'p.id_product = pl.id_product AND pl.id_lang = ' . $id_lang . ' AND pl.id_shop = ' . $id_shop
    );
    $sql->groupBy('p.id_product, p.reference, pl.name');
    $sql->orderBy('nb_demandes DESC');
    $sql->limit(20);

    return Db::getInstance(_PS_USE_SQL_SLAVE_)->executeS($sql);
}

Bonne pratique : Utilisez toujours DbQuery plutôt que des requêtes SQL brutes. La classe gère automatiquement le préfixe des tables et offre une meilleure lisibilité. Utilisez _PS_USE_SQL_SLAVE_ pour les requêtes en lecture afin de ne pas surcharger le serveur principal.

Étape 4 : Le hook complet avec template Smarty

Intégrons maintenant la récupération de données dans le hook :


public function hookDashboardZoneTwo()
{
    $data = $this->getOutOfStockAlertsOptimized();

    $this->context->smarty->assign([
        'oos_alerts'   => $data,
        'module_name'  => $this->displayName,
        'admin_link'   => $this->context->link->getAdminLink('AdminProducts'),
    ]);

    return $this->display(__FILE__, 'views/templates/hook/dashboard_zone_two.tpl');
}

Étape 5 : Le template Smarty

Créez le fichier views/templates/hook/dashboard_zone_two.tpl :


<section id="dashboardwidget_oos" class="panel widget">
    <header class="panel-heading">
        <i class="icon-bell"></i> {$module_name} — Produits les plus demandés en rupture
    </header>
    <div class="panel-body">
        {if $oos_alerts|count > 0}
            <table class="table table-striped table-hover">
                <thead>
                    <tr>
                        <th>ID</th>
                        <th>Référence</th>
                        <th>Nom du produit</th>
                        <th class="text-center">Demandes</th>
                        <th>Action</th>
                    </tr>
                </thead>
                <tbody>
                    {foreach $oos_alerts as $alert}
                        <tr>
                            <td>{$alert.id_product}</td>
                            <td><code>{$alert.reference|escape:'html'}</code></td>
                            <td>{$alert.name|escape:'html'}</td>
                            <td class="text-center">
                                <span class="badge badge-warning">{$alert.nb_demandes}</span>
                            </td>
                            <td>
                                <a href="{$admin_link}&id_product={$alert.id_product}&updateproduct"
                                   class="btn btn-default btn-xs">
                                    <i class="icon-pencil"></i> Éditer
                                </a>
                            </td>
                        </tr>
                    {/foreach}
                </tbody>
            </table>
        {else}
            <div class="alert alert-info">
                Aucune alerte de rupture de stock en cours.
            </div>
        {/if}
    </div>
</section>

Étape 6 : Ajouter le rafraîchissement AJAX (optionnel)

Pour que votre widget se mette à jour dynamiquement quand on change la plage de dates du dashboard, implémentez le hook dashboardData :


public function install()
{
    return parent::install()
        && $this->registerHook('dashboardZoneTwo')
        && $this->registerHook('dashboardData');
}

public function hookDashboardData($params)
{
    // Récupérer les dates du filtre dashboard
    $date_from = $params['date_from'] ?? date('Y-m-d', strtotime('-30 days'));
    $date_to = $params['date_to'] ?? date('Y-m-d');

    $data = $this->getAlertsByDateRange($date_from, $date_to);

    return [
        'data_value' => [
            'oos_total' => count($data),
        ],
        'data_chart' => [],
    ];
}

Bonnes pratiques et pièges à éviter

Sécurité

  • **Ne jamais injecter de variables utilisateur** directement dans vos requêtes. Utilisez `(int)` pour les identifiants et `pSQL()` pour les chaînes.
  • Le hook dashboard est exécuté dans le contexte back-office, mais un module mal sécurisé pourrait exposer des données sensibles via AJAX.

Performance

  • Limitez toujours vos résultats avec `$sql->limit()`. Un tableau de 10 000 lignes dans le dashboard va tuer l'expérience utilisateur.
  • Utilisez `_PS_USE_SQL_SLAVE_` pour les lectures.
  • Si vos données changent peu, envisagez un cache avec `Cache::getInstance()->set()`.

Compatibilité PrestaShop 8.x

  • Les hooks dashboard fonctionnent de manière identique en 1.7 et 8.x.
  • En PrestaShop 8.x, Symfony est plus intégré. Pour un dashboard plus avancé, vous pouvez créer un **AdminController Symfony** avec des composants Vue.js ou React via le nouveau système de composants du back-office.
  • La classe `DbQuery` reste pleinement supportée en 8.x et constitue la méthode recommandée.

Code complet du module

Voici le fichier principal complet, prêt à installer :


<?php

if (!defined('_PS_VERSION_')) {
    exit;
}

class My_DashboardWidget extends Module
{
    public function __construct()
    {
        $this->name = 'my_dashboardwidget';
        $this->tab = 'dashboard';
        $this->version = '1.0.0';
        $this->author = 'Votre nom';
        $this->need_instance = 0;
        $this->bootstrap = true;

        parent::__construct();

        $this->displayName = $this->l('Widget Dashboard Personnalisé');
        $this->description = $this->l('Affiche des données SQL personnalisées dans le tableau de bord.');
        $this->ps_versions_compliancy = [
            'min' => '1.7.0.0',
            'max' => '8.99.99',
        ];
    }

    public function install()
    {
        return parent::install()
            && $this->registerHook('dashboardZoneTwo');
    }

    public function uninstall()
    {
        return parent::uninstall();
    }

    public function getOutOfStockAlerts()
    {
        $id_lang = (int) Context::getContext()->language->id;
        $id_shop = (int) Context::getContext()->shop->id;

        $sql = new DbQuery();
        $sql->select('p.id_product, p.reference, pl.name, COUNT(ma.id_customer) AS nb_demandes');
        $sql->from('mailalert_customer_oos', 'ma');
        $sql->innerJoin('product', 'p', 'ma.id_product = p.id_product');
        $sql->innerJoin(
            'product_lang',
            'pl',
            'p.id_product = pl.id_product AND pl.id_lang = ' . $id_lang . ' AND pl.id_shop = ' . $id_shop
        );
        $sql->groupBy('p.id_product, p.reference, pl.name');
        $sql->orderBy('nb_demandes DESC');
        $sql->limit(20);

        return Db::getInstance(_PS_USE_SQL_SLAVE_)->executeS($sql);
    }

    public function hookDashboardZoneTwo()
    {
        $data = $this->getOutOfStockAlerts();

        $this->context->smarty->assign([
            'oos_alerts'  => $data,
            'module_name' => $this->displayName,
            'admin_link'  => $this->context->link->getAdminLink('AdminProducts'),
        ]);

        return $this->display(__FILE__, 'views/templates/hook/dashboard_zone_two.tpl');
    }
}
#module prestashop #dashboard #requête SQL #hook dashboardZoneTwo #back-office

Questions fréquentes

Tout ce que vous devez savoir sur ce sujet.

Un projet PrestaShop ?

Discutons-en directement.

★★★★★

193 projets livrés

Gratuit & sans engagement — réponse sous 24h

Alexandre Carette

Alexandre Carette

Expert PrestaShop & Architecture E-commerce

Développeur PrestaShop depuis 2014, 193 projets livrés. Je conçois des architectures headless Nuxt + PrestaShop et des outils d'automatisation IA pour les e-commerçants.