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.
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 :
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
DbQueryplutô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');
}
}
Questions fréquentes
Tout ce que vous devez savoir sur ce sujet.
Un projet PrestaShop ?
Discutons-en directement.
193 projets livrés
Lire sur le blog

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.