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

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.

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

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 :

TableRôle `ps_customization`Lie une personnalisation à un panier, un produit et une déclinaison `ps_customization_field`Définit les champs disponibles (texte ou fichier) pour un produit `ps_customization_field_lang`Labels traduits de chaque champ `ps_customized_data`Stocke les **valeurs saisies** par le client (texte ou chemin fichier)

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.

#ObjectModel #personnalisation produit #DbQuery #module PrestaShop #customization

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.