Récupérer les personnalisations produit PrestaShop via ObjectModel
Créez une classe ObjectModel pour gérer les personnalisations produit PrestaShop. Guide complet avec requêtes DbQuery, bonnes pratiques et code compatible 8.x.
En bref : Guide complet pour créer une classe ObjectModel dédiée à la récupération des personnalisations produit PrestaShop, avec requêtes DbQuery par produit et par commande, bonnes pratiques de sécurité SQL et compatibilité 1.7/8.x.
Introduction
La personnalisation produit est une fonctionnalité native de PrestaShop qui permet aux clients de soumettre du texte ou des fichiers (gravure, impression, upload d'image) avant d'ajouter un article au panier. Ces données sont stockées dans les tables ps_customization, ps_customized_data et ps_customization_field.
Lorsque vous développez un module qui doit exploiter ces données — tableau de bord des personnalisations, export, traitement en production — vous avez besoin d'une classe dédiée pour interroger proprement la base. Voici comment structurer cette classe en suivant les conventions PrestaShop.
Architecture des tables de personnalisation
Avant de coder, il est essentiel de comprendre le schéma relationnel :
La colonne type dans ps_customized_data distingue les fichiers (0) des textes (1).
Créer la classe dans votre module
Créez le fichier classes/CustomizationData.php dans votre module :
<?php
/**
* @author Alexandre Carette <contact@alexandrecarette.fr>
* @copyright 2026 Alexandre Carette
* @license Propriétaire et Confidentiel
*/
if (!defined('_PS_VERSION_')) {
exit;
}
class CustomizationData extends ObjectModel
{
/** @var int */
public $id_customization;
/** @var int Type : 0 = fichier, 1 = texte */
public $type;
/** @var int Index du champ */
public $index;
/** @var string Valeur saisie par le client */
public $value;
/** @var int Identifiant du module (si applicable) */
public $id_module;
/**
* @see ObjectModel::$definition
*/
public static $definition = [
'table' => 'customized_data',
'primary' => 'id_customization',
'fields' => [
'id_customization' => ['type' => self::TYPE_INT, 'validate' => 'isUnsignedId', 'required' => true],
'type' => ['type' => self::TYPE_INT, 'validate' => 'isUnsignedInt', 'required' => true],
'index' => ['type' => self::TYPE_INT, 'validate' => 'isUnsignedInt', 'required' => true],
'value' => ['type' => self::TYPE_STRING, 'validate' => 'isCleanHtml', 'required' => true],
'id_module' => ['type' => self::TYPE_INT, 'validate' => 'isUnsignedId'],
],
];
/**
* Récupère toutes les valeurs de personnalisation.
*
* @return array
*/
public static function getAllCustomizations()
{
$query = new DbQuery();
$query->select('cd.value, cd.type, cd.`index`, cd.id_customization');
$query->from('customized_data', 'cd');
return Db::getInstance(_PS_USE_SQL_SLAVE_)->executeS($query);
}
/**
* Récupère les personnalisations d'un produit spécifique.
*
* @param int $idProduct
* @param int $idProductAttribute Déclinaison (0 = produit simple)
* @return array
*/
public static function getByProduct($idProduct, $idProductAttribute = 0)
{
$query = new DbQuery();
$query->select('cd.value, cd.type, cd.`index`, c.id_cart, c.id_customization');
$query->from('customized_data', 'cd');
$query->innerJoin('customization', 'c', 'c.id_customization = cd.id_customization');
$query->where('c.id_product = ' . (int) $idProduct);
$query->where('c.id_product_attribute = ' . (int) $idProductAttribute);
$query->where('c.in_cart = 1');
return Db::getInstance(_PS_USE_SQL_SLAVE_)->executeS($query);
}
/**
* Récupère les personnalisations d'une commande avec les labels traduits.
*
* @param int $idOrder
* @param int $idLang
* @return array
*/
public static function getByOrder($idOrder, $idLang = null)
{
if (!$idLang) {
$idLang = (int) Configuration::get('PS_LANG_DEFAULT');
}
$query = new DbQuery();
$query->select('cd.value, cd.type, cfl.name AS field_label, od.product_name');
$query->from('customized_data', 'cd');
$query->innerJoin('customization', 'c', 'c.id_customization = cd.id_customization');
$query->innerJoin('order_detail', 'od', 'od.id_order = ' . (int) $idOrder
. ' AND od.product_id = c.id_product'
. ' AND od.product_attribute_id = c.id_product_attribute');
$query->leftJoin('customization_field_lang', 'cfl',
'cfl.id_customization_field = cd.`index` AND cfl.id_lang = ' . (int) $idLang
. ' AND cfl.id_shop = c.id_shop');
$query->orderBy('c.id_customization ASC, cd.`index` ASC');
return Db::getInstance(_PS_USE_SQL_SLAVE_)->executeS($query);
}
}
Charger la classe dans votre module
Dans le fichier principal de votre module, déclarez le chargement automatique :
public function __construct()
{
// ... configuration du module
parent::__construct();
require_once _PS_MODULE_DIR_ . $this->name . '/classes/CustomizationData.php';
}
Sur PrestaShop 8.x, vous pouvez aussi utiliser l'autoload Composer du module en ajoutant dans composer.json :
{
"autoload": {
"classmap": ["classes/"]
}
}
Puis lancez composer dump-autoload dans le répertoire du module.
Utilisation concrète dans un controller ou un hook
Récupérer toutes les personnalisations
$allCustomizations = CustomizationData::getAllCustomizations();
foreach ($allCustomizations as $row) {
if ((int) $row['type'] === 1) {
// Texte saisi par le client
echo 'Texte : ' . htmlspecialchars($row['value']);
} else {
// Fichier uploadé (chemin relatif)
echo 'Fichier : ' . _PS_UPLOAD_DIR_ . $row['value'];
}
}
Afficher les personnalisations d'une commande
// Dans un hook displayAdminOrderMain (PS 8.x) ou displayAdminOrder (PS 1.7)
public function hookDisplayAdminOrderMain(array $params)
{
$idOrder = (int) $params['id_order'];
$customizations = CustomizationData::getByOrder($idOrder, $this->context->language->id);
$this->context->smarty->assign('customizations', $customizations);
return $this->display(__FILE__, 'views/templates/hook/admin-order-customizations.tpl');
}
Bonnes pratiques et pièges à éviter
Toujours utiliser DbQuery plutôt que du SQL brut
DbQuery offre plusieurs avantages :
- **Préfixe automatique** des tables (`ps_` est ajouté par `from()`)
- **Lisibilité** avec le chaînage des méthodes
- **Maintenabilité** : chaque clause est isolée et modifiable
Cast systématique des identifiants
Chaque variable injectée dans une requête doit être castée avec (int) pour prévenir les injections SQL :
// ✅ Correct
$query->where('c.id_product = ' . (int) $idProduct);
// ❌ Dangereux
$query->where('c.id_product = ' . $idProduct);
Utiliser le replica SQL en lecture
_PS_USE_SQL_SLAVE_ redirige les lectures vers le serveur esclave si configuré. Utilisez-le systématiquement pour les requêtes SELECT :
Db::getInstance(_PS_USE_SQL_SLAVE_)->executeS($query);
Attention à la colonne `index`
Le mot index est un mot réservé SQL. Encadrez-le toujours avec des backticks dans vos requêtes :
$query->select('cd.`index`');
Différence entre `in_cart` et personnalisation orpheline
La table ps_customization possède un champ in_cart. Une personnalisation avec in_cart = 0 est un brouillon (le client a saisi du texte mais n'a pas encore ajouté au panier). Filtrez selon votre besoin.
Évolution PrestaShop 8.x
Sur PrestaShop 8.x, la gestion des personnalisations a été modernisée :
- Les **CQRS Commands** (`AddCustomizationFieldCommand`, `UpdateCustomizationFieldCommand`) sont désormais disponibles pour les opérations back-office
- Le service `ProductCustomizationProvider` encapsule les requêtes de lecture
- Les hooks d'administration utilisent Symfony (`displayAdminOrderMain` remplace `displayAdminOrder`)
Pour un module rétrocompatible 1.7 + 8.x, la classe ObjectModel avec DbQuery reste la solution la plus portable. Migrez vers les services Symfony uniquement si vous ciblez exclusivement PrestaShop 8.x+.
Conclusion
Créer une classe dédiée dans votre module pour accéder aux données de personnalisation est la bonne approche : vous isolez la logique SQL, vous respectez les conventions PrestaShop, et vous obtenez un code testable et réutilisable. Pensez à toujours filtrer par produit, commande ou panier selon le contexte, et à ne jamais exposer les valeurs sans les échapper côté template.
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.