Afficher le nombre de favoris (likes) sur une fiche produit PrestaShop
Découvrez comment afficher le compteur de favoris sur vos fiches produits PrestaShop avec une requête SQL optimisée et sécurisée. Guide complet avec code.
En bref : Pour afficher le nombre de favoris d'un produit PrestaShop, utilisez Db::getInstance()->getValue() avec un COUNT(*) sécurisé par un cast (int), jamais ExecuteS() qui retourne un tableau inutilisable directement dans Smarty.
Pourquoi afficher un compteur de favoris sur vos fiches produits
Le social proof (preuve sociale) est l'un des leviers de conversion les plus puissants en e-commerce. Afficher le nombre de fois qu'un produit a été ajouté aux favoris par d'autres clients crée un signal de confiance immédiat : « Si 47 personnes ont mis ce produit en favori, c'est qu'il vaut le coup. »
Dans PrestaShop, le module Produits Favoris (blockwishlist en 1.7+ ou favoriteproducts en 1.6) stocke ces données en base. Il suffit d'une requête SQL bien construite pour récupérer ce compteur et l'afficher sur la fiche produit.
Cependant, cette opération apparemment simple cache plusieurs pièges techniques que nous allons détailler.
Le piège classique : `ExecuteS()` vs `getValue()`
L'erreur fréquente
Beaucoup de développeurs PrestaShop écrivent instinctivement ce type de code pour compter des enregistrements :
// ❌ Code problématique — ne faites pas ça
$id_product = $_GET['id_product'];
$sql = 'SELECT COUNT(*)
FROM `' . _DB_PREFIX_ . 'favorite_product`
WHERE `id_product` = ' . $id_product;
$results = Db::getInstance()->ExecuteS($sql);
$smarty->assign('likeproductCount', $results);
Ce code comporte trois problèmes majeurs :
- **`ExecuteS()` retourne un tableau**, même pour un scalaire. Le résultat sera quelque chose comme `[['COUNT(*)' => '12']]` au lieu du simple entier `12`. Côté template Smarty, vous obtiendrez un objet `Smarty_Variable` contenant un tableau, impossible à afficher directement avec `{$likeproductCount}`.
- **Injection SQL** : `$_GET['id_product']` est injecté directement dans la requête sans aucune validation. Un attaquant pourrait manipuler l'URL pour exécuter des requêtes arbitraires sur votre base de données.
- **Variable superglobale brute** : utiliser `$_GET` directement contourne le système de filtrage de PrestaShop.
- **`getValue()`** au lieu de `ExecuteS()` : cette méthode retourne directement la première colonne de la première ligne, soit un scalaire (ici, le nombre entier).
- **`(int)` cast** sur `Tools::getValue()` : sécurise l'entrée contre les injections SQL en forçant la conversion en entier.
- **`Tools::getValue()`** au lieu de `$_GET` : utilise le helper PrestaShop qui gère proprement la récupération des paramètres GET/POST.
- **`$this->context->smarty`** au lieu de `global $smarty` : en PrestaShop moderne, l'accès au contexte via `$this->context` est la pratique recommandée. L'utilisation de `global` est dépréciée.
La solution propre
// ✅ Code corrigé et sécurisé
$id_product = (int) Tools::getValue('id_product');
$likeCount = (int) Db::getInstance()->getValue(
'SELECT COUNT(*)
FROM `' . _DB_PREFIX_ . 'favorite_product`
WHERE `id_product` = ' . $id_product
);
$this->context->smarty->assign('likeproductCount', $likeCount);
Les corrections apportées :
Implémentation complète dans un module
Voici comment implémenter proprement cette fonctionnalité dans un module PrestaShop :
Structure du module
monmodule/
├── monmodule.php
└── views/
└── templates/
└── hook/
└── product-likes.tpl
Code PHP du module
/**
* @author Alexandre Carette <contact@alexandrecarette.fr>
* @copyright 2026 Alexandre Carette
* @license Propriétaire et Confidentiel
*/
class MonModule extends Module
{
public function __construct()
{
$this->name = 'monmodule';
$this->version = '1.0.0';
$this->author = 'Alexandre Carette';
parent::__construct();
}
public function install()
{
return parent::install()
&& $this->registerHook('displayProductAdditionalInfo');
}
/**
* Récupère le nombre de favoris pour un produit donné.
*/
public static function getFavoriteCount(int $idProduct): int
{
// Adapter le nom de la table selon le module de favoris utilisé
// PS 1.6 : favorite_product (module favoriteproducts)
// PS 1.7+ : wishlist_product (module blockwishlist)
$table = version_compare(_PS_VERSION_, '1.7', '>=')
? 'wishlist_product'
: 'favorite_product';
return (int) Db::getInstance(_PS_USE_SQL_SLAVE_)->getValue(
'SELECT COUNT(*)
FROM `' . _DB_PREFIX_ . pSQL($table) . '`
WHERE `id_product` = ' . (int) $idProduct
);
}
public function hookDisplayProductAdditionalInfo(array $params): string
{
$idProduct = (int) $params['product']['id_product'];
$count = self::getFavoriteCount($idProduct);
$this->context->smarty->assign([
'like_count' => $count,
'like_label' => $count > 1 ? 'personnes' : 'personne',
]);
return $this->display(__FILE__, 'views/templates/hook/product-likes.tpl');
}
}
Template Smarty (PS 1.6 / 1.7)
{* views/templates/hook/product-likes.tpl *}
{if $like_count > 0}
<div class="product-likes">
<i class="material-icons">favorite</i>
<span>{$like_count} {$like_label} {if $like_count > 1}ont{else}a{/if} ajouté ce produit en favori</span>
</div>
{/if}
Variante PrestaShop 8.x avec Symfony
Sur PrestaShop 8.x, si vous travaillez côté back-office ou dans un controller Symfony, privilégiez Doctrine DBAL :
use Doctrine\DBAL\Connection;
class ProductLikesService
{
private Connection $connection;
private string $dbPrefix;
public function __construct(Connection $connection, string $dbPrefix)
{
$this->connection = $connection;
$this->dbPrefix = $dbPrefix;
}
public function getFavoriteCount(int $productId): int
{
$qb = $this->connection->createQueryBuilder();
$qb->select('COUNT(*)')
->from($this->dbPrefix . 'wishlist_product')
->where('id_product = :id')
->setParameter('id', $productId);
return (int) $qb->executeQuery()->fetchOne();
}
}
Cette approche utilise les prepared statements nativement, éliminant tout risque d'injection SQL.
Optimisation des performances
Utiliser l'instance slave
Notez l'utilisation de Db::getInstance(_PS_USE_SQL_SLAVE_) dans notre code. Si votre hébergement dispose d'une réplication MySQL master/slave, les requêtes en lecture seront dirigées vers le slave, soulageant le serveur principal.
Mettre en cache le compteur
Sur un catalogue important (> 5 000 produits), exécuter un COUNT(*) à chaque affichage de fiche produit peut peser. Deux stratégies :
1. Cache objet PrestaShop :
public static function getFavoriteCount(int $idProduct): int
{
$cacheKey = 'MonModule::getFavoriteCount_' . $idProduct;
if (!Cache::isStored($cacheKey)) {
$count = (int) Db::getInstance(_PS_USE_SQL_SLAVE_)->getValue(
'SELECT COUNT(*)
FROM `' . _DB_PREFIX_ . 'wishlist_product`
WHERE `id_product` = ' . (int) $idProduct
);
Cache::store($cacheKey, $count);
}
return (int) Cache::retrieve($cacheKey);
}
2. Colonne dénormalisée :
Pour les très gros volumes, ajoutez une colonne favorite_count directement dans ps_product et mettez-la à jour via un hook actionObjectWishlistProductAddAfter / actionObjectWishlistProductDeleteAfter. Le compteur est alors lu sans aucune jointure.
Différences entre les versions de PrestaShop
Aller plus loin : chargement asynchrone en AJAX
Pour éviter tout impact sur le temps de chargement initial de la page, vous pouvez charger le compteur en AJAX après le rendu de la page :
// front.js
document.addEventListener('DOMContentLoaded', function () {
const container = document.querySelector('.product-likes-count');
if (!container) return;
const productId = container.dataset.productId;
fetch(`/module/monmodule/ajax?action=getLikeCount&id_product=${productId}`)
.then(response => response.json())
.then(data => {
if (data.count > 0) {
container.textContent = `${data.count} favori${data.count > 1 ? 's' : ''}`;
container.style.display = 'block';
}
});
});
Cette technique est particulièrement pertinente si vous utilisez un cache full-page (Varnish, CloudFlare) car le compteur dynamique ne bloque pas la mise en cache du reste de la page.
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.