Créer un select dynamique dans un template Smarty PrestaShop
Apprenez à créer des listes déroulantes dynamiques dans vos templates Smarty PrestaShop : fournisseurs, catégories ou objets personnalisés avec requêtes SQL.
En bref : Pour créer un select dynamique dans un template Smarty PrestaShop, utilisez soit les méthodes statiques natives (comme Supplier::getSuppliers()), soit un ObjectModel personnalisé avec DbQuery, en assignant les données via le controller plutôt que par appel direct dans le .tpl.
Introduction
Lorsque vous développez un module ou personnalisez un thème PrestaShop, vous aurez souvent besoin d'afficher une liste déroulante () alimentée dynamiquement depuis la base de données. Que ce soit pour sélectionner un fournisseur, une catégorie personnalisée ou tout autre objet métier, le principe reste le même : récupérer les données via une méthode PHP, puis les injecter dans votre template Smarty.
Cet article couvre les deux approches possibles — l'utilisation des classes natives PrestaShop et la création d'un ObjectModel personnalisé — avec des exemples concrets et les bonnes pratiques pour PrestaShop 8.x.
Approche 1 : Utiliser une classe native PrestaShop
PrestaShop fournit déjà des méthodes statiques pour récupérer les entités courantes. Pour les fournisseurs par exemple, la classe Supplier expose une méthode getSuppliers() prête à l'emploi.
Appel direct dans le template Smarty
La méthode la plus rapide consiste à appeler la fonction statique directement depuis le fichier .tpl :
{assign var=suppliers value=Supplier::getSuppliers()}
<select name="id_supplier" class="form-select">
<option value="">-- Sélectionnez un fournisseur --</option>
{foreach from=$suppliers item=supplier}
<option value="{$supplier.id_supplier|intval}">{$supplier.name|escape:'html':'UTF-8'}</option>
{/foreach}
</select>
Points d'attention :
- Utilisez `|intval` sur les valeurs numériques et `|escape:'html':'UTF-8'` sur les chaînes pour prévenir les injections XSS.
- Ajoutez toujours une option vide par défaut pour guider l'utilisateur.
- La classe `form-select` est celle de Bootstrap 5, utilisé nativement dans PrestaShop 8.x. Pour les versions 1.7, utilisez `form-control`.
Passage via le controller (approche recommandée)
L'appel direct dans Smarty fonctionne, mais il mélange logique métier et présentation. La bonne pratique est d'assigner les données depuis votre controller :
// Dans votre ModuleFrontController ou AdminController
public function initContent()
{
parent::initContent();
$suppliers = Supplier::getSuppliers(
false, // $get_nb_products
$this->context->language->id,
true // $active uniquement
);
$this->context->smarty->assign([
'suppliers' => $suppliers,
]);
$this->setTemplate('module:monmodule/views/templates/front/formulaire.tpl');
}
Le template devient alors plus propre :
<select name="id_supplier" class="form-select">
<option value="">-- Sélectionnez un fournisseur --</option>
{foreach from=$suppliers item=supplier}
<option value="{$supplier.id_supplier|intval}">{$supplier.name|escape:'html':'UTF-8'}</option>
{/foreach}
</select>
Cette séparation des responsabilités facilite la maintenance, les tests et le débogage.
Approche 2 : ObjectModel personnalisé avec select dynamique
Si vous travaillez avec une table personnalisée (par exemple des fournisseurs internes, des types de produits ou toute autre entité métier), vous devez créer votre propre méthode de récupération.
Créer la classe ObjectModel
Supposons une table ps_custom_entity avec les colonnes id_custom_entity, name et active :
// modules/monmodule/classes/CustomEntity.php
class CustomEntity extends ObjectModel
{
public $id_custom_entity;
public $name;
public $active;
public static $definition = [
'table' => 'custom_entity',
'primary' => 'id_custom_entity',
'fields' => [
'name' => [
'type' => self::TYPE_STRING,
'validate' => 'isGenericName',
'required' => true,
'size' => 128,
],
'active' => [
'type' => self::TYPE_BOOL,
'validate' => 'isBool',
],
],
];
/**
* Récupère toutes les entités actives
*
* @param bool $activeOnly Ne retourner que les entités actives
* @return array Liste des entités
*/
public static function getAll(bool $activeOnly = true): array
{
$sql = new DbQuery();
$sql->select('id_custom_entity, name, active');
$sql->from('custom_entity');
if ($activeOnly) {
$sql->where('active = 1');
}
$sql->orderBy('name ASC');
return Db::getInstance(_PS_USE_SQL_SLAVE_)->executeS($sql) ?: [];
}
}
Bonnes pratiques pour la requête SQL
Plusieurs points importants par rapport à une requête brute :
- **Utilisez `DbQuery`** plutôt qu'une concaténation de chaînes. Cette classe fournie par PrestaShop construit la requête de manière sécurisée et lisible.
- **Utilisez `_PS_USE_SQL_SLAVE_`** pour les lectures. Cela permet d'exploiter un éventuel serveur de réplication en lecture.
- **Sélectionnez uniquement les colonnes nécessaires** au lieu de `SELECT *`, pour de meilleures performances.
- **Retournez un tableau vide** en fallback (`?: []`) pour éviter les erreurs dans le template si la table est vide.
- **Triez les résultats** (`ORDER BY name ASC`) pour une meilleure expérience utilisateur dans le select.
Intégration dans le controller
// N'oubliez pas l'autoload
require_once _PS_MODULE_DIR_ . 'monmodule/classes/CustomEntity.php';
// Dans votre controller
$entities = CustomEntity::getAll(true);
$this->context->smarty->assign([
'custom_entities' => $entities,
]);
Template Smarty correspondant
<div class="form-group">
<label for="custom_entity" class="form-label">
{l s='Choisir une entité' mod='monmodule'}
</label>
<select name="id_custom_entity" id="custom_entity" class="form-select" required>
<option value="">{l s='-- Sélectionnez --' mod='monmodule'}</option>
{foreach from=$custom_entities item=entity}
<option value="{$entity.id_custom_entity|intval}"
{if isset($selected_entity) && $selected_entity == $entity.id_custom_entity}
selected="selected"
{/if}
>
{$entity.name|escape:'html':'UTF-8'}
</option>
{/foreach}
</select>
</div>
Aller plus loin : select dépendant en cascade (AJAX)
Dans certains cas, vous avez besoin d'un deuxième select qui se met à jour en fonction du premier (par exemple : catégorie → sous-catégorie). Cela nécessite une requête AJAX.
Endpoint dans le controller du module
// modules/monmodule/controllers/front/ajax.php
class MonModuleAjaxModuleFrontController extends ModuleFrontController
{
public function initContent()
{
$parentId = (int) Tools::getValue('parent_id');
if (!$parentId) {
$this->ajaxResponse(['error' => 'ID parent manquant'], 400);
return;
}
$children = CustomEntity::getByParent($parentId);
$this->ajaxResponse(['items' => $children]);
}
private function ajaxResponse(array $data, int $code = 200): void
{
header('Content-Type: application/json');
http_response_code($code);
echo json_encode($data);
exit;
}
}
JavaScript côté front
document.getElementById('parent_select').addEventListener('change', function () {
const parentId = this.value;
const childSelect = document.getElementById('child_select');
if (!parentId) {
childSelect.innerHTML = '<option value="">--</option>';
return;
}
fetch(`${prestashop.urls.base_url}module/monmodule/ajax?parent_id=${encodeURIComponent(parentId)}`, {
headers: { 'X-Requested-With': 'XMLHttpRequest' }
})
.then(response => response.json())
.then(data => {
childSelect.innerHTML = '<option value="">-- Sélectionnez --</option>';
data.items.forEach(item => {
const option = document.createElement('option');
option.value = item.id;
option.textContent = item.name;
childSelect.appendChild(option);
});
})
.catch(error => console.error('Erreur AJAX:', error));
});
Récapitulatif des approches
Quelle que soit l'approche choisie, pensez toujours à échapper les données dans vos templates et à valider les entrées côté serveur lors du traitement du formulaire.
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.