Modules multiboutiques PrestaShop 9 : prévenir les fuites de périmètre des données
Le bogue multiboutique le plus dangereux est souvent une requête valide à laquelle manque une condition. Elle renvoie un véritable enregistrement, passe les vérifications de type ordinaires et enregistre correctement — dans l’état de la mauvaise boutique. Une installation de développement à boutique unique révèle rarement l’erreur.
Les modules multiboutiques fiables commencent par la propriété des données. Décidez quelles valeurs sont globales, lesquelles appartiennent à un groupe de boutiques et lesquelles à une seule boutique. Reportez ensuite cette décision dans les clés, lectures, écritures, tâches en arrière-plan et caches.
Il s’agit d’un article général sur la conception de modules PrestaShop 9. Il ne décrit pas de nouvelles fonctions d’administration de la version 9.2, et les exemples SQL utilisent une table de module volontairement illustrative plutôt qu’un préfixe de production ou un schéma du cœur supposés.
Lorsque le travail délimité s’exécute en arrière-plan, appliquez une conception fiable des tâches cron tout en transmettant l’identité explicite de la boutique à chaque exécution.
Modélisez la propriété avant d’ajouter une colonne id_shop
Ajouter id_shop à une table ne rend pas le module sûr en multiboutique. Chaque opération doit l’utiliser de manière cohérente, et certains faits ne doivent pas du tout être dupliqués par boutique.
Prenez une correspondance de références externes. Si le système externe attribue une référence différente au même produit dans chaque boutique, le périmètre de boutique fait partie de l’identité de la correspondance. Si la référence est réellement globale, la dupliquer peut créer des copies contradictoires. Établissez d’abord la sémantique du système externe.
Distinguez l’association des entités de la propriété des valeurs. Un produit peut être associé à plusieurs boutiques alors qu’une valeur particulière du module diffère entre elles. Inversement, une entité associée à une boutique peut désigner un enregistrement de propriété globale.
Rédigez un tableau de propriété pour chaque jeu de données du module :
| Jeu de données | Propriétaire | Périmètre de lecture | Périmètre d’écriture |
|---|---|---|---|
| Identifiants d’accès à la destination externe | Périmètre de configuration choisi | Configuration effective explicite | Contexte autorisé |
| Correspondance produit | Définie par les règles d’identité externes | Produit et périmètre requis | Même frontière d’identité |
| Progression des tâches | Une partition de tâche logique | Clé explicite de boutique/tâche | Processus de travail propriétaire |
| Vocabulaire de référence partagé | Global s’il est réellement partagé | Global | Action d’administrateur contrôlée |
Ce tableau est un artefact de conception. Il doit expliquer pourquoi un périmètre existe, et non refléter simplement les colonnes les plus faciles à ajouter.
Délimitez les lectures et écritures avec la même identité
Un défaut courant est une lecture correctement délimitée suivie d’une mise à jour trop large. Un autre consiste à rechercher par produit et boutique tout en imposant l’unicité seulement par produit.
La requête suivante illustre une lecture de correspondance appartenant au module avec liaison de paramètres. Le nom littéral de table est un exemple ; un véritable installateur doit déduire son préfixe fiable de la configuration de l’application et de son propre schéma déclaré.
SELECT external_reference
FROM demo_product_mapping
WHERE id_shop = :shop_id
AND id_product = :product_id;
L’écriture correspondante doit utiliser la même frontière de propriété. Ne mettez pas à jour uniquement par id_product après une lecture avec les deux conditions. L’appelant doit transmettre un objet de périmètre validé ou un identifiant explicite de boutique plutôt que de dépendre d’un contexte global modifiable dans le dépôt.
Les identifiants dynamiques nécessitent un traitement distinct des valeurs de paramètres. Les paramètres liés protègent les valeurs, pas des noms arbitraires de tables. N’acceptez jamais un nom de table ou un préfixe provenant d’un paramètre HTTP pour faire fonctionner une requête multiboutique générique.
Pour une lecture par lot, incluez le périmètre dans les jointures comme dans le prédicat externe. Une jointure sur le seul produit peut rattacher la ligne du module d’une autre boutique même si la requête d’entité principale est correctement délimitée.
Testez explicitement le cas négatif : demandez la valeur de la boutique A quand seule B en possède une. Ne renvoyer aucune correspondance locale peut être correct ; emprunter silencieusement celle de B n’est pas un défaut sûr.
Faites exprimer la règle métier par l’unicité
Si le modèle prévoit une correspondance par produit et boutique, sa clé doit exprimer cette paire. Une clé unique sur le seul produit rejette des enregistrements valides d’une autre boutique. L’absence de clé unique permet les doublons lors d’insertions concurrentes.
Voici une définition illustrative de table de module :
CREATE TABLE demo_product_mapping (
id_shop INT UNSIGNED NOT NULL,
id_product INT UNSIGNED NOT NULL,
external_reference VARCHAR(191) NOT NULL,
PRIMARY KEY (id_shop, id_product)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
Ce n’est pas un import pour une base PrestaShop en production. Il démontre uniquement la clé de propriété. Un véritable schéma nécessite les conventions d’installation du module, des types d’identifiants vérifiés, une stratégie de migration et toute contrainte de relation supplémentaire.
N’ajoutez pas d’unicité sur external_reference sans décider si la valeur est unique globalement, localement à la boutique ou à la destination. Si plusieurs systèmes externes peuvent attribuer la même chaîne, l’identité de destination peut également faire partie de la clé.
Avant d’ajouter une contrainte aux données existantes, examinez et résolvez les doublons selon une politique définie. Supprimer la ligne qui se trouve être la deuxième n’est pas une stratégie de migration. Conservez la preuve de l’enregistrement retenu et de la raison du choix.
Une contrainte de base de données complète la validation applicative. Elle protège l’invariant lorsque deux requêtes apparemment valides entrent en concurrence.
Traitez la configuration héritée comme un état, pas seulement une valeur
Une boutique peut hériter d’une valeur ou enregistrer explicitement une valeur égale. Leurs comportements diffèrent lorsque le parent change. Un module lisant la valeur effective puis la réécrivant sans condition peut accidentellement transformer l’héritage en surcharge locale.
La documentation de configuration de PrestaShop décrit un accès tenant compte du périmètre via ShopConstraint, et ses conseils sur les formulaires distinguent la configuration locale des valeurs héritées. Respectez le contrat de service pertinent pour la version de plateforme prise en charge. Configuration des boutiques, formulaires de configuration.
Lors de l’implémentation d’un enregistrement, distinguez « définir une valeur locale », « supprimer la surcharge locale » et « laisser ce champ inchangé ». Ces opérations ne doivent pas se réduire à une seule affectation de valeur.
La même prudence s’applique aux identifiants secrets et destinations externes. Un processus doit savoir quelle configuration effective il a utilisée et quelle boutique il était autorisé à traiter. Journaliser l’identifiant de destination peut aider à diagnostiquer les problèmes de périmètre sans exposer l’identifiant secret lui-même.
Évitez d’inventer id_shop = 0 comme convention universelle de ligne globale pour chaque jeu de données. Les modèles de stockage du cœur et des modules diffèrent. Respectez le schéma précis et la sémantique des services plutôt que de répandre une sentinelle non documentée dans le code.
Transmettez le périmètre dans les tâches mises en file et planifiées
Une tâche en arrière-plan doit contenir assez d’identité pour s’exécuter sans session d’employé : boutique, opération, identifiants d’entités pertinents et clé métier stable. Validez la boutique avant de charger des données ou une configuration.
Ne mettez pas uniquement un identifiant de produit en file en laissant un processus ultérieur déduire la boutique du contexte global qu’il possède par hasard. Les processus persistants peuvent traiter plusieurs messages, rendant les fuites de contexte particulièrement difficiles à reproduire.
Lorsque l’ancien code exige un contexte ambiant, isolez soigneusement l’adaptation et rétablissez l’état précédent même en cas d’exception. Préférez un périmètre explicite aux frontières de vos propres services pour éviter que la dépendance héritée se propage dans tout le module.
Une tâche pour toutes les boutiques doit parcourir un ensemble délibéré et autorisé, en établissant puis en effaçant le périmètre approprié pour chaque partition. « Toutes les boutiques » doit être une opération explicite au résultat défini, et non la conséquence accidentelle d’un argument manquant.
Incluez le périmètre dans les clés de verrou et d’idempotence lorsque le travail est indépendant par boutique. Un verrou global peut sérialiser inutilement des tâches sans rapport, tandis qu’une clé sans identité de destination peut traiter deux exports différents comme des doublons.
Donnez aux données en cache les mêmes dimensions qu’à la requête
Une clé de cache fait partie du modèle de données. Si un résultat dépend de la boutique, de la langue, de la devise ou du groupe de clients, la clé et la stratégie d’invalidation doivent refléter les dimensions pertinentes.
Pour un libellé produit appartenant au module, une clé conceptuelle pourrait inclure l’espace de noms du module, l’identifiant du produit, de la boutique, de la langue et une version de représentation. C’est un modèle de conception, pas un format prescrit de clé de cache PrestaShop.
N’incluez que les dimensions affectant réellement la valeur, mais n’en omettez pas par commodité. Une requête correcte en base de données suivie d’une entrée de cache partagée peut quand même laisser fuiter le résultat de la première boutique dans la deuxième.
Testez en demandant la même entité dans des contextes différents dans les deux ordres : A puis B, et B puis A. Si le résultat change selon l’ordre des requêtes, suspectez un contexte mis en cache ou conservé. Des tests reposant uniquement sur un cache vidé peuvent masquer cette catégorie de défaut.
L’invalidation doit atteindre chaque représentation concernée. Un changement de valeur globale peut nécessiter l’invalidation de plusieurs sorties propres aux boutiques ; un changement local ne doit pas écraser les données d’autres boutiques pendant la reconstruction du cache.
Utilisez des jeux de données adverses pour les tests d’isolation
Créez deux boutiques avec des valeurs volontairement contradictoires pour une entité partagée. Donnez-leur des identifiants de destinations externes différents et des états de configuration héritée/locale différents. Exercez ensuite chaque lecture et écriture par les interfaces prises en charge par le module.
Les assertions utiles comprennent :
- Une écriture dans la boutique A laisse inchangées la ligne et la valeur effective de B.
- Une lecture dans A ne se rabat jamais sur B sans règle métier explicite.
- Une action sur toutes les boutiques affecte exactement l’ensemble autorisé sélectionné.
- Une tâche en arrière-plan traite la boutique nommée dans sa charge utile.
- Les résultats du cache restent corrects quel que soit l’ordre des requêtes.
- Les écritures concurrentes en double préservent l’identité unique déclarée.
- La suppression d’une surcharge locale rétablit l’héritage au lieu de copier une valeur parente.
Testez aussi les associations manquantes et entités supprimées. Le module doit renvoyer une erreur ou absence définie, et non sélectionner arbitrairement une ligne restante pour poursuivre le processus.
Pour un rapport de régression, conservez les deux identifiants de boutiques, les valeurs distinctes du jeu de données et l’opération ayant franchi la frontière. Un cas minimal à deux boutiques explique souvent le défaut plus clairement qu’un gros export de production.
Faites de l’isolation une habitude de revue
Lors de la revue de code, demandez où le périmètre entre dans le service, où il est validé et si chaque opération de persistance et de cache le préserve. Examinez ensemble les prédicats de requêtes et les contraintes d’unicité. Examinez les charges utiles des messages d’arrière-plan avec les gestionnaires qui les consomment.
Pour les modules existants, commencez par les écritures et intégrations externes aux effets les plus importants. Corriger une lecture délimitée ne suffit pas si la mise à jour ou l’export correspondant utilise encore des valeurs globales par défaut.
L’exactitude multiboutique n’est pas un mode spécial ajouté en fin de développement. C’est un accord entre le sens des données et chaque frontière qui les traite. Une fois cet accord explicite, les fuites entre boutiques deviennent des échecs testables plutôt que de mystérieux signalements clients.
Pour le contexte de la plateforme sous-jacente, consultez notre guide de développement de modules PrestaShop 9 et appliquez les vérifications propres à la version décrites ci-dessus à l’installation que vous évaluez.
Lors du choix d’une extension, parcourez les modules PrestaShop 9 de PrestaSuite et comparez la prise en charge des versions et les exigences d’intégration documentées à vos propres résultats de tests.