Nicolas Dabene
Retour au blog
24 February 2026 Nicolas Dabene 13 min

Vibe Coding en e-commerce : pourquoi 80% des modules generés par IA ne passeront jamais en production

Le rêve vendu vs. la réalité du terrain “Décris ce que tu veux, l’IA code pour toi.”

PrestaShop Vibe Coding IA e-commerce sécurité performance multi-shop dette technique hooks modules développement Intelligence Artificielle Sécurité prestashop-ecommerce
Vibe Coding en e-commerce : pourquoi 80% des modules generés par IA ne passeront jamais en production

Le rêve vendu vs. la réalité du terrain “Décris ce que tu veux, l’IA code pour toi.”

Le rêve vendu vs. la réalité du terrain

“Décris ce que tu veux, l’IA code pour toi.”

Depuis qu’Andrej Karpathy a popularisé le concept de Vibe Coding dans un tweet viral de février 2025 — et que des outils comme Cursor, Claude Code ou GitHub Copilot ont explosé — un narratif séduisant s’est installé : tout le monde peut désormais créer des logiciels. Plus besoin de comprendre le code, il suffit de “donner le vibe”.

Et franchement ? Pour un prototype, une démo, un side-project… ça marche. C’est même bluffant.

Mais moi, je développe des modules PrestaShop depuis plus de 10 ans. Des modules qui tournent sur des boutiques qui font 50 000 commandes par mois. Des modules installés sur des architectures multi-shop avec 12 boutiques, 4 langues, 3 devises, des règles métier spécifiques par groupe client et un ERP branché derrière.

Et ce que je vois arriver depuis 6 mois dans mes audits, mes reprises de code et mes demandes de support, c’est une vague de modules “vibe-codés” qui partagent tous le même destin :

Ils fonctionnent en démo. Ils cassent en production.

Cet article n’est pas un pamphlet anti-IA. J’utilise l’IA tous les jours dans mon workflow. Mais je vais vous montrer, avec des exemples concrets et du vrai code, pourquoi le Vibe Coding appliqué à l’e-commerce — et spécifiquement à PrestaShop — est un champ de mines que seule l’expertise métier permet de traverser.


1. Les hooks : le piège n°1 que l’IA ne comprend pas

Ce que l’IA génère

Demandez à un LLM de créer un module qui affiche un bloc de réassurance sur la page produit. Vous obtiendrez quelque chose comme :

<span class="k">public</span> <span class="k">function</span> <span class="n">hookDisplayProductAdditionalInfo</span><span class="p">(</span><span class="nv">$params</span><span class="p">)</span>
<span class="p">{</span>
    <span class="nv">$product</span> <span class="o">=</span> <span class="nv">$params</span><span class="p">[</span><span class="s1">'product'</span><span class="p">];</span>

    <span class="nv">$this</span><span class="o">-></span><span class="n">context</span><span class="o">-></span><span class="n">smarty</span><span class="o">-></span><span class="nf">assign</span><span class="p">([</span>
        <span class="s1">'product_name'</span> <span class="o">=></span> <span class="nv">$product</span><span class="o">-></span><span class="n">name</span><span class="p">,</span>
        <span class="s1">'reassurance_text'</span> <span class="o">=></span> <span class="s1">'Livraison gratuite dès 49€'</span><span class="p">,</span>
    <span class="p">]);</span>

    <span class="k">return</span> <span class="nv">$this</span><span class="o">-></span><span class="nf">display</span><span class="p">(</span><span class="k">__FILE__</span><span class="p">,</span> <span class="s1">'views/templates/hook/reassurance.tpl'</span><span class="p">);</span>
<span class="p">}</span>

Ça a l’air propre. Ça fonctionne sur une installation fraîche de PrestaShop. Le client est content pendant 48 heures.

Ce qui explose en production

Problème 1 : $params['product'] n’est pas ce que vous croyez.

Selon la version de PrestaShop (1.7.6 vs 1.7.8 vs 8.1), selon que vous êtes sur la page produit standard ou dans un module de quick-view, selon le thème utilisé… $params['product'] peut être :

  • Un objet Product
  • Un tableau associatif (retour de ProductPresenter)
  • null (si le hook est appelé dans un contexte inattendu)
  • Un tableau avec des clés différentes selon la version

Le code robuste ressemble à ça :

<span class="k">public</span> <span class="k">function</span> <span class="n">hookDisplayProductAdditionalInfo</span><span class="p">(</span><span class="nv">$params</span><span class="p">)</span>
<span class="p">{</span>
    <span class="c1">// Gestion défensive du paramètre product</span>
    <span class="nv">$product</span> <span class="o">=</span> <span class="kc">null</span><span class="p">;</span>

    <span class="k">if</span> <span class="p">(</span><span class="k">isset</span><span class="p">(</span><span class="nv">$params</span><span class="p">[</span><span class="s1">'product'</span><span class="p">]))</span> <span class="p">{</span>
        <span class="k">if</span> <span class="p">(</span><span class="nb">is_array</span><span class="p">(</span><span class="nv">$params</span><span class="p">[</span><span class="s1">'product'</span><span class="p">]))</span> <span class="p">{</span>
            <span class="c1">// PrestaShop 1.7.7+ avec ProductPresenter</span>
            <span class="nv">$productId</span> <span class="o">=</span> <span class="p">(</span><span class="n">int</span><span class="p">)</span> <span class="p">(</span><span class="nv">$params</span><span class="p">[</span><span class="s1">'product'</span><span class="p">][</span><span class="s1">'id_product'</span><span class="p">]</span> <span class="o">??</span> <span class="nv">$params</span><span class="p">[</span><span class="s1">'product'</span><span class="p">][</span><span class="s1">'id'</span><span class="p">]</span> <span class="o">??</span> <span class="mi">0</span><span class="p">);</span>
            <span class="k">if</span> <span class="p">(</span><span class="nv">$productId</span> <span class="o">></span> <span class="mi">0</span><span class="p">)</span> <span class="p">{</span>
                <span class="nv">$product</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">Product</span><span class="p">(</span><span class="nv">$productId</span><span class="p">,</span> <span class="kc">false</span><span class="p">,</span> <span class="nv">$this</span><span class="o">-></span><span class="n">context</span><span class="o">-></span><span class="n">language</span><span class="o">-></span><span class="n">id</span><span class="p">,</span> <span class="nv">$this</span><span class="o">-></span><span class="n">context</span><span class="o">-></span><span class="n">shop</span><span class="o">-></span><span class="n">id</span><span class="p">);</span>
            <span class="p">}</span>
        <span class="p">}</span> <span class="k">elseif</span> <span class="p">(</span><span class="nv">$params</span><span class="p">[</span><span class="s1">'product'</span><span class="p">]</span> <span class="k">instanceof</span> <span class="nc">Product</span><span class="p">)</span> <span class="p">{</span>
            <span class="nv">$product</span> <span class="o">=</span> <span class="nv">$params</span><span class="p">[</span><span class="s1">'product'</span><span class="p">];</span>
        <span class="p">}</span>
    <span class="p">}</span>

    <span class="c1">// Fallback sur le contexte si le hook ne fournit rien d'exploitable</span>
    <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="nc">Validate</span><span class="o">::</span><span class="nf">isLoadedObject</span><span class="p">(</span><span class="nv">$product</span><span class="p">))</span> <span class="p">{</span>
        <span class="nv">$productId</span> <span class="o">=</span> <span class="p">(</span><span class="n">int</span><span class="p">)</span> <span class="nc">Tools</span><span class="o">::</span><span class="nf">getValue</span><span class="p">(</span><span class="s1">'id_product'</span><span class="p">);</span>
        <span class="k">if</span> <span class="p">(</span><span class="nv">$productId</span> <span class="o">></span> <span class="mi">0</span><span class="p">)</span> <span class="p">{</span>
            <span class="nv">$product</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">Product</span><span class="p">(</span><span class="nv">$productId</span><span class="p">,</span> <span class="kc">false</span><span class="p">,</span> <span class="nv">$this</span><span class="o">-></span><span class="n">context</span><span class="o">-></span><span class="n">language</span><span class="o">-></span><span class="n">id</span><span class="p">,</span> <span class="nv">$this</span><span class="o">-></span><span class="n">context</span><span class="o">-></span><span class="n">shop</span><span class="o">-></span><span class="n">id</span><span class="p">);</span>
        <span class="p">}</span>
    <span class="p">}</span>

    <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="nc">Validate</span><span class="o">::</span><span class="nf">isLoadedObject</span><span class="p">(</span><span class="nv">$product</span><span class="p">)</span> <span class="o">||</span> <span class="o">!</span><span class="nv">$product</span><span class="o">-></span><span class="n">active</span><span class="p">)</span> <span class="p">{</span>
        <span class="k">return</span> <span class="s1">''</span><span class="p">;</span>
    <span class="p">}</span>

    <span class="c1">// ... suite du traitement</span>
<span class="p">}</span>

Sexy ? Non. Nécessaire ? Absolument.

Problème 2 : le hook n’existe peut-être même pas.

L’IA va vous proposer hookDisplayProductAdditionalInfo parce que c’est dans la doc officielle. Mais sur une boutique réelle avec un thème custom (flavor flavor, Warehouse, flavor flavor…), ce hook :

  • peut avoir été supprimé du template
  • peut être appelé à un endroit différent dans le DOM
  • peut être en concurrence avec 4 autres modules qui s’enregistrent dessus

Un développeur senior sait qu’il faut vérifier le thème cible, proposer un widget comme alternative, et implémenter un fallback avec hookDisplayFooterProduct ou un hookDisplayOverrideTemplate si nécessaire.

Problème 3 : les hooks d’action oubliés.

L’IA génère très bien les hooks d’affichage (display). Elle oublie systématiquement les hooks d’action critiques. Un module de gestion de stock vibe-codé que j’ai audité le mois dernier :

  • Gérait hookActionProductUpdate pour recalculer le stock
  • Oubliait hookActionObjectProductDeleteAfter → produits fantômes en base
  • Oubliait hookActionProductAttributeUpdate → déclinaisons jamais synchronisées
  • Oubliait hookActionObjectCombinationDeleteAfter → crash de l’ERP
  • Ne gérait pas hookActionObjectStockAvailableUpdateAfter → conflit avec le stock natif

Un seul hook oublié = des données incohérentes sur des centaines de produits.

Note technique : Les hooks actionObject[ClassName][Add/Update/Delete]Before/After sont des hooks dynamiques générés par ObjectModel. Ils ne peuvent pas être enregistrés via $this->registerHook() dans install(). Pour les utiliser, votre module doit implémenter la méthode correspondante (ex : hookActionObjectCombinationDeleteAfter) — PrestaShop l’appellera automatiquement si elle existe. Pas besoin de registerHook, contrairement aux hooks display.


2. La sécurité : le trou béant que l’IA laisse grand ouvert

L’AJAX sans protection

C’est le pattern que je retrouve dans 90% des modules vibe-codés avec une interface d’administration :

<span class="c1">// front/ajax.php — généré par IA</span>
<span class="k">include</span><span class="p">(</span><span class="s1">'../../config/config.inc.php'</span><span class="p">);</span>

<span class="nv">$action</span> <span class="o">=</span> <span class="nc">Tools</span><span class="o">::</span><span class="nf">getValue</span><span class="p">(</span><span class="s1">'action'</span><span class="p">);</span>

<span class="k">if</span> <span class="p">(</span><span class="nv">$action</span> <span class="o">===</span> <span class="s1">'updatePrice'</span><span class="p">)</span> <span class="p">{</span>
    <span class="nv">$id_product</span> <span class="o">=</span> <span class="nc">Tools</span><span class="o">::</span><span class="nf">getValue</span><span class="p">(</span><span class="s1">'id_product'</span><span class="p">);</span>
    <span class="nv">$new_price</span> <span class="o">=</span> <span class="nc">Tools</span><span class="o">::</span><span class="nf">getValue</span><span class="p">(</span><span class="s1">'price'</span><span class="p">);</span>

    <span class="nv">$product</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">Product</span><span class="p">(</span><span class="nv">$id_product</span><span class="p">);</span>
    <span class="nv">$product</span><span class="o">-></span><span class="n">price</span> <span class="o">=</span> <span class="nv">$new_price</span><span class="p">;</span>
    <span class="nv">$product</span><span class="o">-></span><span class="nf">save</span><span class="p">();</span>

    <span class="k">die</span><span class="p">(</span><span class="nb">json_encode</span><span class="p">([</span><span class="s1">'success'</span> <span class="o">=></span> <span class="kc">true</span><span class="p">]));</span>
<span class="p">}</span>

Ce code est une porte ouverte totale. N’importe qui peut modifier le prix de n’importe quel produit de la boutique avec un simple appel curl :

curl <span class="s2">"https://votre-boutique.com/modules/monmodule/front/ajax.php?action=updatePrice&id_product=1&price=0.01"</span>

Félicitations : tous vos produits sont à 1 centime.

Ce que le code sécurisé impose

<span class="c1">// controllers/front/ajax.php — version sécurisée</span>
<span class="kd">class</span> <span class="nc">MonModuleAjaxModuleFrontController</span> <span class="kd">extends</span> <span class="nc">ModuleFrontController</span>
<span class="p">{</span>
    <span class="k">public</span> <span class="k">function</span> <span class="n">initContent</span><span class="p">()</span>
    <span class="p">{</span>
        <span class="c1">// Vérification que c'est bien une requête AJAX</span>
        <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="nv">$this</span><span class="o">-></span><span class="n">ajax</span><span class="p">)</span> <span class="p">{</span>
            <span class="nv">$this</span><span class="o">-></span><span class="nf">ajaxRender</span><span class="p">(</span><span class="nb">json_encode</span><span class="p">([</span><span class="s1">'error'</span> <span class="o">=></span> <span class="s1">'Invalid request'</span><span class="p">]));</span>
            <span class="k">return</span><span class="p">;</span>
        <span class="p">}</span>

        <span class="k">parent</span><span class="o">::</span><span class="nf">initContent</span><span class="p">();</span>
    <span class="p">}</span>

    <span class="k">public</span> <span class="k">function</span> <span class="n">displayAjaxUpdatePrice</span><span class="p">()</span>
    <span class="p">{</span>
        <span class="c1">// 1. Vérification du token CSRF</span>
        <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="nv">$this</span><span class="o">-></span><span class="nf">isTokenValid</span><span class="p">())</span> <span class="p">{</span>
            <span class="nb">header</span><span class="p">(</span><span class="s1">'HTTP/1.1 403 Forbidden'</span><span class="p">);</span>
            <span class="nv">$this</span><span class="o">-></span><span class="nf">ajaxRender</span><span class="p">(</span><span class="nb">json_encode</span><span class="p">([</span><span class="s1">'error'</span> <span class="o">=></span> <span class="s1">'Invalid token'</span><span class="p">]));</span>
            <span class="k">return</span><span class="p">;</span>
        <span class="p">}</span>

        <span class="c1">// 2. Vérification des permissions (employé admin connecté)</span>
        <span class="nv">$cookie</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">Cookie</span><span class="p">(</span><span class="s1">'psAdmin'</span><span class="p">);</span>
        <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="nv">$cookie</span><span class="o">-></span><span class="n">id_employee</span><span class="p">)</span> <span class="p">{</span>
            <span class="nb">header</span><span class="p">(</span><span class="s1">'HTTP/1.1 401 Unauthorized'</span><span class="p">);</span>
            <span class="nv">$this</span><span class="o">-></span><span class="nf">ajaxRender</span><span class="p">(</span><span class="nb">json_encode</span><span class="p">([</span><span class="s1">'error'</span> <span class="o">=></span> <span class="s1">'Unauthorized'</span><span class="p">]));</span>
            <span class="k">return</span><span class="p">;</span>
        <span class="p">}</span>

        <span class="nv">$employee</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">Employee</span><span class="p">((</span><span class="n">int</span><span class="p">)</span> <span class="nv">$cookie</span><span class="o">-></span><span class="n">id_employee</span><span class="p">);</span>
        <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="nc">Validate</span><span class="o">::</span><span class="nf">isLoadedObject</span><span class="p">(</span><span class="nv">$employee</span><span class="p">)</span>
            <span class="o">||</span> <span class="o">!</span><span class="nv">$employee</span><span class="o">-></span><span class="nf">hasAuthOnShop</span><span class="p">(</span><span class="nv">$this</span><span class="o">-></span><span class="n">context</span><span class="o">-></span><span class="n">shop</span><span class="o">-></span><span class="n">id</span><span class="p">))</span> <span class="p">{</span>
            <span class="nb">header</span><span class="p">(</span><span class="s1">'HTTP/1.1 403 Forbidden'</span><span class="p">);</span>
            <span class="nv">$this</span><span class="o">-></span><span class="nf">ajaxRender</span><span class="p">(</span><span class="nb">json_encode</span><span class="p">([</span><span class="s1">'error'</span> <span class="o">=></span> <span class="s1">'Insufficient permissions'</span><span class="p">]));</span>
            <span class="k">return</span><span class="p">;</span>
        <span class="p">}</span>

        <span class="c1">// 3. Validation stricte des entrées</span>
        <span class="nv">$idProduct</span> <span class="o">=</span> <span class="p">(</span><span class="n">int</span><span class="p">)</span> <span class="nc">Tools</span><span class="o">::</span><span class="nf">getValue</span><span class="p">(</span><span class="s1">'id_product'</span><span class="p">);</span>
        <span class="nv">$newPrice</span> <span class="o">=</span> <span class="p">(</span><span class="n">float</span><span class="p">)</span> <span class="nc">Tools</span><span class="o">::</span><span class="nf">getValue</span><span class="p">(</span><span class="s1">'price'</span><span class="p">);</span>

        <span class="k">if</span> <span class="p">(</span><span class="nv">$idProduct</span> <span class="o"><=</span> <span class="mi">0</span><span class="p">)</span> <span class="p">{</span>
            <span class="nv">$this</span><span class="o">-></span><span class="nf">ajaxRender</span><span class="p">(</span><span class="nb">json_encode</span><span class="p">([</span><span class="s1">'error'</span> <span class="o">=></span> <span class="s1">'Invalid product ID'</span><span class="p">]));</span>
            <span class="k">return</span><span class="p">;</span>
        <span class="p">}</span>

        <span class="k">if</span> <span class="p">(</span><span class="nv">$newPrice</span> <span class="o"><</span> <span class="mi">0</span> <span class="o">||</span> <span class="nv">$newPrice</span> <span class="o">></span> <span class="mf">999999.99</span><span class="p">)</span> <span class="p">{</span>
            <span class="nv">$this</span><span class="o">-></span><span class="nf">ajaxRender</span><span class="p">(</span><span class="nb">json_encode</span><span class="p">([</span><span class="s1">'error'</span> <span class="o">=></span> <span class="s1">'Invalid price range'</span><span class="p">]));</span>
            <span class="k">return</span><span class="p">;</span>
        <span class="p">}</span>

        <span class="c1">// 4. Vérification que le produit appartient au shop context</span>
        <span class="nv">$product</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">Product</span><span class="p">(</span><span class="nv">$idProduct</span><span class="p">,</span> <span class="kc">false</span><span class="p">,</span> <span class="kc">null</span><span class="p">,</span> <span class="nv">$this</span><span class="o">-></span><span class="n">context</span><span class="o">-></span><span class="n">shop</span><span class="o">-></span><span class="n">id</span><span class="p">);</span>
        <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="nc">Validate</span><span class="o">::</span><span class="nf">isLoadedObject</span><span class="p">(</span><span class="nv">$product</span><span class="p">))</span> <span class="p">{</span>
            <span class="nv">$this</span><span class="o">-></span><span class="nf">ajaxRender</span><span class="p">(</span><span class="nb">json_encode</span><span class="p">([</span><span class="s1">'error'</span> <span class="o">=></span> <span class="s1">'Product not found in current shop'</span><span class="p">]));</span>
            <span class="k">return</span><span class="p">;</span>
        <span class="p">}</span>

        <span class="c1">// 5. Mise à jour sécurisée avec gestion multi-shop</span>
        <span class="nv">$product</span><span class="o">-></span><span class="n">price</span> <span class="o">=</span> <span class="nv">$newPrice</span><span class="p">;</span>

        <span class="k">if</span> <span class="p">(</span><span class="nc">Shop</span><span class="o">::</span><span class="nf">getContext</span><span class="p">()</span> <span class="o">===</span> <span class="nc">Shop</span><span class="o">::</span><span class="no">CONTEXT_SHOP</span><span class="p">)</span> <span class="p">{</span>
            <span class="nv">$product</span><span class="o">-></span><span class="nf">save</span><span class="p">();</span>
        <span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
            <span class="c1">// En contexte multi-shop, on force le shop courant</span>
            <span class="nv">$product</span><span class="o">-></span><span class="n">id_shop_default</span> <span class="o">=</span> <span class="nv">$this</span><span class="o">-></span><span class="n">context</span><span class="o">-></span><span class="n">shop</span><span class="o">-></span><span class="n">id</span><span class="p">;</span>
            <span class="nv">$product</span><span class="o">-></span><span class="nf">save</span><span class="p">();</span>
        <span class="p">}</span>

        <span class="c1">// 6. Log de l'action pour audit trail</span>
        <span class="nc">PrestaShopLogger</span><span class="o">::</span><span class="nf">addLog</span><span class="p">(</span>
            <span class="nb">sprintf</span><span class="p">(</span><span class="s1">'Product #%d price updated to %f by employee #%d via module MonModule'</span><span class="p">,</span>
                <span class="nv">$idProduct</span><span class="p">,</span> <span class="nv">$newPrice</span><span class="p">,</span> <span class="p">(</span><span class="n">int</span><span class="p">)</span> <span class="nv">$cookie</span><span class="o">-></span><span class="n">id_employee</span><span class="p">),</span>
            <span class="mi">1</span><span class="p">,</span>
            <span class="kc">null</span><span class="p">,</span>
            <span class="s1">'Product'</span><span class="p">,</span>
            <span class="nv">$idProduct</span><span class="p">,</span>
            <span class="kc">true</span><span class="p">,</span>
            <span class="p">(</span><span class="n">int</span><span class="p">)</span> <span class="nv">$cookie</span><span class="o">-></span><span class="n">id_employee</span>
        <span class="p">);</span>

        <span class="c1">// 7. Purge du cache produit</span>
        <span class="nc">Product</span><span class="o">::</span><span class="nf">flushPriceCache</span><span class="p">();</span>

        <span class="nv">$this</span><span class="o">-></span><span class="nf">ajaxRender</span><span class="p">(</span><span class="nb">json_encode</span><span class="p">([</span>
            <span class="s1">'success'</span> <span class="o">=></span> <span class="kc">true</span><span class="p">,</span>
            <span class="s1">'product_id'</span> <span class="o">=></span> <span class="nv">$idProduct</span><span class="p">,</span>
            <span class="s1">'new_price'</span> <span class="o">=></span> <span class="nv">$newPrice</span><span class="p">,</span>
        <span class="p">]));</span>
    <span class="p">}</span>
<span class="p">}</span>

On est passé de 8 lignes à 65 lignes. Et chaque ligne supplémentaire bloque un vecteur d’attaque réel.

Point d’architecture : Cet exemple illustre la sécurisation d’un endpoint front. Mais modifier le prix d’un produit est une action d’administration — elle devrait idéalement passer par un ModuleAdminController ou un controller Symfony avec vérification de permissions native. Sur PrestaShop 8+, le cookie psAdmin est en cours de dépréciation au profit du système d’auth Symfony. Pour une action admin réelle, préférez un endpoint déclaré dans config/routes.yml avec @IsGranted('ROLE_MOD_TAB_MONMODULE_UPDATE').

Les injections SQL : toujours là en 2026

L’IA adore générer des requêtes SQL “lisibles” :

<span class="c1">// Généré par IA — injection SQL</span>
<span class="nv">$sql</span> <span class="o">=</span> <span class="s2">"SELECT * FROM "</span> <span class="mf">.</span> <span class="n">_DB_PREFIX_</span> <span class="mf">.</span> <span class="s2">"product
        WHERE reference = '"</span> <span class="mf">.</span> <span class="nv">$reference</span> <span class="mf">.</span> <span class="s2">"'"</span><span class="p">;</span>
<span class="nv">$results</span> <span class="o">=</span> <span class="nc">Db</span><span class="o">::</span><span class="nf">getInstance</span><span class="p">()</span><span class="o">-></span><span class="nf">executeS</span><span class="p">(</span><span class="nv">$sql</span><span class="p">);</span>

<span class="c1">// Version sécurisée</span>
<span class="nv">$sql</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">DbQuery</span><span class="p">();</span>
<span class="nv">$sql</span><span class="o">-></span><span class="nf">select</span><span class="p">(</span><span class="s1">'p.id_product, p.reference, p.price, pl.name'</span><span class="p">);</span>
<span class="nv">$sql</span><span class="o">-></span><span class="nf">from</span><span class="p">(</span><span class="s1">'product'</span><span class="p">,</span> <span class="s1">'p'</span><span class="p">);</span>
<span class="nv">$sql</span><span class="o">-></span><span class="nf">innerJoin</span><span class="p">(</span><span class="s1">'product_lang'</span><span class="p">,</span> <span class="s1">'pl'</span><span class="p">,</span> <span class="s1">'p.id_product = pl.id_product AND pl.id_lang = '</span> <span class="mf">.</span> <span class="p">(</span><span class="n">int</span><span class="p">)</span> <span class="nv">$this</span><span class="o">-></span><span class="n">context</span><span class="o">-></span><span class="n">language</span><span class="o">-></span><span class="n">id</span><span class="p">);</span>
<span class="nv">$sql</span><span class="o">-></span><span class="nf">innerJoin</span><span class="p">(</span><span class="s1">'product_shop'</span><span class="p">,</span> <span class="s1">'ps'</span><span class="p">,</span> <span class="s1">'p.id_product = ps.id_product AND ps.id_shop = '</span> <span class="mf">.</span> <span class="p">(</span><span class="n">int</span><span class="p">)</span> <span class="nv">$this</span><span class="o">-></span><span class="n">context</span><span class="o">-></span><span class="n">shop</span><span class="o">-></span><span class="n">id</span><span class="p">);</span>
<span class="nv">$sql</span><span class="o">-></span><span class="nf">where</span><span class="p">(</span><span class="s1">'p.reference = \''</span> <span class="mf">.</span> <span class="nf">pSQL</span><span class="p">(</span><span class="nv">$reference</span><span class="p">)</span> <span class="mf">.</span> <span class="s1">'\''</span><span class="p">);</span>
<span class="nv">$sql</span><span class="o">-></span><span class="nf">where</span><span class="p">(</span><span class="s1">'ps.active = 1'</span><span class="p">);</span>
<span class="nv">$results</span> <span class="o">=</span> <span class="nc">Db</span><span class="o">::</span><span class="nf">getInstance</span><span class="p">(</span><span class="n">_PS_USE_SQL_SLAVE_</span><span class="p">)</span><span class="o">-></span><span class="nf">executeS</span><span class="p">(</span><span class="nv">$sql</span><span class="p">);</span>

Notez les détails que l’IA oublie systématiquement :

  • pSQL() pour échapper la valeur
  • La jointure product_shop pour le contexte multi-shop
  • La jointure product_lang pour la langue courante
  • L’utilisation de _PS_USE_SQL_SLAVE_ pour les requêtes en lecture (performance)
  • Le filtrage sur active = 1

3. Les performances : le serial killer silencieux

Le problème N+1 : le classique que l’IA reproduit à l’infini

Un module de cross-selling vibe-codé que j’ai audité récemment :

<span class="c1">// Code généré par IA — N+1 queries</span>
<span class="k">public</span> <span class="k">function</span> <span class="n">hookDisplayShoppingCartFooter</span><span class="p">(</span><span class="nv">$params</span><span class="p">)</span>
<span class="p">{</span>
    <span class="nv">$cart</span> <span class="o">=</span> <span class="nv">$this</span><span class="o">-></span><span class="n">context</span><span class="o">-></span><span class="n">cart</span><span class="p">;</span>
    <span class="nv">$products</span> <span class="o">=</span> <span class="nv">$cart</span><span class="o">-></span><span class="nf">getProducts</span><span class="p">();</span>
    <span class="nv">$recommendations</span> <span class="o">=</span> <span class="p">[];</span>

    <span class="k">foreach</span> <span class="p">(</span><span class="nv">$products</span> <span class="k">as</span> <span class="nv">$product</span><span class="p">)</span> <span class="p">{</span>
        <span class="c1">// Requête 1 par produit : récupérer la catégorie</span>
        <span class="nv">$category</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">Category</span><span class="p">(</span><span class="nv">$product</span><span class="p">[</span><span class="s1">'id_category_default'</span><span class="p">],</span> <span class="nv">$this</span><span class="o">-></span><span class="n">context</span><span class="o">-></span><span class="n">language</span><span class="o">-></span><span class="n">id</span><span class="p">);</span>

        <span class="c1">// Requête 2 par produit : récupérer les produits de la même catégorie</span>
        <span class="nv">$categoryProducts</span> <span class="o">=</span> <span class="nv">$category</span><span class="o">-></span><span class="nf">getProducts</span><span class="p">(</span><span class="nv">$this</span><span class="o">-></span><span class="n">context</span><span class="o">-></span><span class="n">language</span><span class="o">-></span><span class="n">id</span><span class="p">,</span> <span class="mi">1</span><span class="p">,</span> <span class="mi">10</span><span class="p">);</span>

        <span class="k">foreach</span> <span class="p">(</span><span class="nv">$categoryProducts</span> <span class="k">as</span> <span class="nv">$catProduct</span><span class="p">)</span> <span class="p">{</span>
            <span class="c1">// Requête 3 par produit recommandé : vérifier le stock</span>
            <span class="nv">$stockAvailable</span> <span class="o">=</span> <span class="nc">StockAvailable</span><span class="o">::</span><span class="nf">getQuantityAvailableByProduct</span><span class="p">(</span><span class="nv">$catProduct</span><span class="p">[</span><span class="s1">'id_product'</span><span class="p">]);</span>

            <span class="k">if</span> <span class="p">(</span><span class="nv">$stockAvailable</span> <span class="o">></span> <span class="mi">0</span><span class="p">)</span> <span class="p">{</span>
                <span class="c1">// Requête 4 par produit recommandé : récupérer l'image</span>
                <span class="nv">$image</span> <span class="o">=</span> <span class="nc">Image</span><span class="o">::</span><span class="nf">getCover</span><span class="p">(</span><span class="nv">$catProduct</span><span class="p">[</span><span class="s1">'id_product'</span><span class="p">]);</span>
                <span class="nv">$catProduct</span><span class="p">[</span><span class="s1">'image_url'</span><span class="p">]</span> <span class="o">=</span> <span class="nv">$this</span><span class="o">-></span><span class="n">context</span><span class="o">-></span><span class="n">link</span><span class="o">-></span><span class="nf">getImageLink</span><span class="p">(</span>
                    <span class="nv">$catProduct</span><span class="p">[</span><span class="s1">'link_rewrite'</span><span class="p">],</span>
                    <span class="nv">$image</span><span class="p">[</span><span class="s1">'id_image'</span><span class="p">],</span>
                    <span class="s1">'home_default'</span>
                <span class="p">);</span>
                <span class="nv">$recommendations</span><span class="p">[]</span> <span class="o">=</span> <span class="nv">$catProduct</span><span class="p">;</span>
            <span class="p">}</span>
        <span class="p">}</span>
    <span class="p">}</span>

    <span class="nv">$this</span><span class="o">-></span><span class="n">context</span><span class="o">-></span><span class="n">smarty</span><span class="o">-></span><span class="nf">assign</span><span class="p">(</span><span class="s1">'recommendations'</span><span class="p">,</span> <span class="nb">array_slice</span><span class="p">(</span><span class="nv">$recommendations</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">4</span><span class="p">));</span>
    <span class="k">return</span> <span class="nv">$this</span><span class="o">-></span><span class="nf">display</span><span class="p">(</span><span class="k">__FILE__</span><span class="p">,</span> <span class="s1">'views/templates/hook/recommendations.tpl'</span><span class="p">);</span>
<span class="p">}</span>

Un panier avec 5 produits, dans des catégories de 50 produits chacune = potentiellement 1 000+ requêtes SQL sur chaque affichage du panier.

Sur une boutique avec du trafic, ce module met le serveur à genoux en 24 heures.

La version optimisée

<span class="k">public</span> <span class="k">function</span> <span class="n">hookDisplayShoppingCartFooter</span><span class="p">(</span><span class="nv">$params</span><span class="p">)</span>
<span class="p">{</span>
    <span class="nv">$cart</span> <span class="o">=</span> <span class="nv">$this</span><span class="o">-></span><span class="n">context</span><span class="o">-></span><span class="n">cart</span><span class="p">;</span>
    <span class="nv">$products</span> <span class="o">=</span> <span class="nv">$cart</span><span class="o">-></span><span class="nf">getProducts</span><span class="p">();</span>

    <span class="k">if</span> <span class="p">(</span><span class="k">empty</span><span class="p">(</span><span class="nv">$products</span><span class="p">))</span> <span class="p">{</span>
        <span class="k">return</span> <span class="s1">''</span><span class="p">;</span>
    <span class="p">}</span>

    <span class="c1">// Cache : on ne recalcule pas à chaque affichage</span>
    <span class="nv">$cacheKey</span> <span class="o">=</span> <span class="s1">'monmodule_reco_'</span> <span class="mf">.</span> <span class="nv">$cart</span><span class="o">-></span><span class="n">id</span> <span class="mf">.</span> <span class="s1">'_'</span> <span class="mf">.</span> <span class="nb">md5</span><span class="p">(</span><span class="nb">serialize</span><span class="p">(</span><span class="nb">array_column</span><span class="p">(</span><span class="nv">$products</span><span class="p">,</span> <span class="s1">'id_product'</span><span class="p">)));</span>

    <span class="c1">// Mise en cache via le système natif PrestaShop</span>
    <span class="nv">$cacheId</span> <span class="o">=</span> <span class="s1">'MonModule_reco_'</span> <span class="mf">.</span> <span class="nv">$cacheKey</span><span class="p">;</span>

    <span class="k">if</span> <span class="p">(</span><span class="nc">Cache</span><span class="o">::</span><span class="nf">isStored</span><span class="p">(</span><span class="nv">$cacheId</span><span class="p">))</span> <span class="p">{</span>
        <span class="k">return</span> <span class="nc">Cache</span><span class="o">::</span><span class="nf">retrieve</span><span class="p">(</span><span class="nv">$cacheId</span><span class="p">);</span>
    <span class="p">}</span>

    <span class="nv">$productIds</span> <span class="o">=</span> <span class="nb">array_column</span><span class="p">(</span><span class="nv">$products</span><span class="p">,</span> <span class="s1">'id_product'</span><span class="p">);</span>
    <span class="nv">$categoryIds</span> <span class="o">=</span> <span class="nb">array_unique</span><span class="p">(</span><span class="nb">array_column</span><span class="p">(</span><span class="nv">$products</span><span class="p">,</span> <span class="s1">'id_category_default'</span><span class="p">));</span>
    <span class="nv">$idLang</span> <span class="o">=</span> <span class="p">(</span><span class="n">int</span><span class="p">)</span> <span class="nv">$this</span><span class="o">-></span><span class="n">context</span><span class="o">-></span><span class="n">language</span><span class="o">-></span><span class="n">id</span><span class="p">;</span>
    <span class="nv">$idShop</span> <span class="o">=</span> <span class="p">(</span><span class="n">int</span><span class="p">)</span> <span class="nv">$this</span><span class="o">-></span><span class="n">context</span><span class="o">-></span><span class="n">shop</span><span class="o">-></span><span class="n">id</span><span class="p">;</span>

    <span class="c1">// UNE SEULE requête pour tout récupérer</span>
    <span class="nv">$sql</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">DbQuery</span><span class="p">();</span>
    <span class="nv">$sql</span><span class="o">-></span><span class="nf">select</span><span class="p">(</span><span class="s1">'DISTINCT p.id_product, pl.name, pl.link_rewrite, p.price,
                   p.id_category_default, sa.quantity as stock,
                   IFNULL(img.id_image, 0) as id_image'</span><span class="p">);</span>
    <span class="nv">$sql</span><span class="o">-></span><span class="nf">from</span><span class="p">(</span><span class="s1">'product'</span><span class="p">,</span> <span class="s1">'p'</span><span class="p">);</span>
    <span class="nv">$sql</span><span class="o">-></span><span class="nf">innerJoin</span><span class="p">(</span><span class="s1">'product_lang'</span><span class="p">,</span> <span class="s1">'pl'</span><span class="p">,</span>
        <span class="s1">'p.id_product = pl.id_product AND pl.id_lang = '</span> <span class="mf">.</span> <span class="nv">$idLang</span> <span class="mf">.</span> <span class="s1">' AND pl.id_shop = '</span> <span class="mf">.</span> <span class="nv">$idShop</span><span class="p">);</span>
    <span class="nv">$sql</span><span class="o">-></span><span class="nf">innerJoin</span><span class="p">(</span><span class="s1">'product_shop'</span><span class="p">,</span> <span class="s1">'ps'</span><span class="p">,</span>
        <span class="s1">'p.id_product = ps.id_product AND ps.id_shop = '</span> <span class="mf">.</span> <span class="nv">$idShop</span><span class="p">);</span>
    <span class="nv">$sql</span><span class="o">-></span><span class="nf">innerJoin</span><span class="p">(</span><span class="s1">'stock_available'</span><span class="p">,</span> <span class="s1">'sa'</span><span class="p">,</span>
        <span class="s1">'p.id_product = sa.id_product AND sa.id_product_attribute = 0 AND sa.id_shop = '</span> <span class="mf">.</span> <span class="nv">$idShop</span><span class="p">);</span>
    <span class="nv">$sql</span><span class="o">-></span><span class="nf">leftJoin</span><span class="p">(</span><span class="s1">'image_shop'</span><span class="p">,</span> <span class="s1">'img'</span><span class="p">,</span>
        <span class="s1">'p.id_product = img.id_product AND img.id_shop = '</span> <span class="mf">.</span> <span class="nv">$idShop</span> <span class="mf">.</span> <span class="s1">' AND img.cover = 1'</span><span class="p">);</span>
    <span class="nv">$sql</span><span class="o">-></span><span class="nf">where</span><span class="p">(</span><span class="s1">'p.id_category_default IN ('</span> <span class="mf">.</span> <span class="nb">implode</span><span class="p">(</span><span class="s1">','</span><span class="p">,</span> <span class="nb">array_map</span><span class="p">(</span><span class="s1">'intval'</span><span class="p">,</span> <span class="nv">$categoryIds</span><span class="p">))</span> <span class="mf">.</span> <span class="s1">')'</span><span class="p">);</span>
    <span class="nv">$sql</span><span class="o">-></span><span class="nf">where</span><span class="p">(</span><span class="s1">'p.id_product NOT IN ('</span> <span class="mf">.</span> <span class="nb">implode</span><span class="p">(</span><span class="s1">','</span><span class="p">,</span> <span class="nb">array_map</span><span class="p">(</span><span class="s1">'intval'</span><span class="p">,</span> <span class="nv">$productIds</span><span class="p">))</span> <span class="mf">.</span> <span class="s1">')'</span><span class="p">);</span>
    <span class="nv">$sql</span><span class="o">-></span><span class="nf">where</span><span class="p">(</span><span class="s1">'ps.active = 1'</span><span class="p">);</span>
    <span class="nv">$sql</span><span class="o">-></span><span class="nf">where</span><span class="p">(</span><span class="s1">'sa.quantity > 0'</span><span class="p">);</span>
    <span class="nv">$sql</span><span class="o">-></span><span class="nf">orderBy</span><span class="p">(</span><span class="s1">'RAND()'</span><span class="p">);</span>
    <span class="nv">$sql</span><span class="o">-></span><span class="nf">limit</span><span class="p">(</span><span class="mi">4</span><span class="p">);</span>

    <span class="nv">$recommendations</span> <span class="o">=</span> <span class="nc">Db</span><span class="o">::</span><span class="nf">getInstance</span><span class="p">(</span><span class="n">_PS_USE_SQL_SLAVE_</span><span class="p">)</span><span class="o">-></span><span class="nf">executeS</span><span class="p">(</span><span class="nv">$sql</span><span class="p">);</span>

    <span class="k">if</span> <span class="p">(</span><span class="k">empty</span><span class="p">(</span><span class="nv">$recommendations</span><span class="p">))</span> <span class="p">{</span>
        <span class="k">return</span> <span class="s1">''</span><span class="p">;</span>
    <span class="p">}</span>

    <span class="c1">// Construction des URLs d'images en batch (pas de requête supplémentaire)</span>
    <span class="k">foreach</span> <span class="p">(</span><span class="nv">$recommendations</span> <span class="k">as</span> <span class="o">&</span><span class="nv">$reco</span><span class="p">)</span> <span class="p">{</span>
        <span class="k">if</span> <span class="p">(</span><span class="nv">$reco</span><span class="p">[</span><span class="s1">'id_image'</span><span class="p">]</span> <span class="o">></span> <span class="mi">0</span><span class="p">)</span> <span class="p">{</span>
            <span class="nv">$reco</span><span class="p">[</span><span class="s1">'image_url'</span><span class="p">]</span> <span class="o">=</span> <span class="nv">$this</span><span class="o">-></span><span class="n">context</span><span class="o">-></span><span class="n">link</span><span class="o">-></span><span class="nf">getImageLink</span><span class="p">(</span>
                <span class="nv">$reco</span><span class="p">[</span><span class="s1">'link_rewrite'</span><span class="p">],</span>
                <span class="nv">$reco</span><span class="p">[</span><span class="s1">'id_product'</span><span class="p">]</span> <span class="mf">.</span> <span class="s1">'-'</span> <span class="mf">.</span> <span class="nv">$reco</span><span class="p">[</span><span class="s1">'id_image'</span><span class="p">],</span>
                <span class="nc">ImageType</span><span class="o">::</span><span class="nf">getFormattedName</span><span class="p">(</span><span class="s1">'home'</span><span class="p">)</span>
            <span class="p">);</span>
        <span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
            <span class="nv">$reco</span><span class="p">[</span><span class="s1">'image_url'</span><span class="p">]</span> <span class="o">=</span> <span class="nv">$this</span><span class="o">-></span><span class="n">context</span><span class="o">-></span><span class="n">link</span><span class="o">-></span><span class="nf">getImageLink</span><span class="p">(</span>
                <span class="nv">$reco</span><span class="p">[</span><span class="s1">'link_rewrite'</span><span class="p">],</span>
                <span class="nv">$this</span><span class="o">-></span><span class="n">context</span><span class="o">-></span><span class="n">language</span><span class="o">-></span><span class="n">iso_code</span> <span class="mf">.</span> <span class="s1">'-default'</span><span class="p">,</span>
                <span class="nc">ImageType</span><span class="o">::</span><span class="nf">getFormattedName</span><span class="p">(</span><span class="s1">'home'</span><span class="p">)</span>
            <span class="p">);</span>
        <span class="p">}</span>
    <span class="p">}</span>

    <span class="nv">$this</span><span class="o">-></span><span class="n">context</span><span class="o">-></span><span class="n">smarty</span><span class="o">-></span><span class="nf">assign</span><span class="p">(</span><span class="s1">'recommendations'</span><span class="p">,</span> <span class="nv">$recommendations</span><span class="p">);</span>
    <span class="nv">$output</span> <span class="o">=</span> <span class="nv">$this</span><span class="o">-></span><span class="nf">display</span><span class="p">(</span><span class="k">__FILE__</span><span class="p">,</span> <span class="s1">'views/templates/hook/recommendations.tpl'</span><span class="p">);</span>

    <span class="c1">// Mise en cache via le système natif PrestaShop</span>
    <span class="nc">Cache</span><span class="o">::</span><span class="nf">store</span><span class="p">(</span><span class="nv">$cacheId</span><span class="p">,</span> <span class="nv">$output</span><span class="p">);</span>
    <span class="c1">// Note : Cache::getInstance() utilise CacheFs par défaut (filesystem).</span>
    <span class="c1">// Pour du Memcached ou Redis, configurer dans Paramètres Avancés > Performances.</span>

    <span class="k">return</span> <span class="nv">$output</span><span class="p">;</span>
<span class="p">}</span>

Résultat : 1 requête au lieu de 1 000. Cache en prime. Prêt pour le trafic.

Note : Sur des installations haute performance, préférer une clé d’invalidation liée au date_upd du panier pour éviter de servir un cache obsolète après modification du panier.


4. Le multi-shop : l’angle mort total de l’IA

C’est ici que l’immense majorité des modules vibe-codés s’effondrent, parce que l’IA n’a tout simplement aucune idée de la complexité du multi-shop PrestaShop.

Ce que l’IA ne sait pas

Le multi-shop PrestaShop, c’est 3 contextes possibles :

Shop::CONTEXT_SHOP    → une seule boutique
Shop::CONTEXT_GROUP   → un groupe de boutiques
Shop::CONTEXT_ALL     → toutes les boutiques

Et chaque table de la base a potentiellement une table _shop associée. Quand un module vibe-codé fait :

<span class="c1">// Ne fonctionne qu'en mono-shop</span>
<span class="nc">Configuration</span><span class="o">::</span><span class="nf">updateValue</span><span class="p">(</span><span class="s1">'MON_MODULE_SETTING'</span><span class="p">,</span> <span class="nv">$value</span><span class="p">);</span>

En contexte multi-shop, cette ligne peut :

  • Écraser la configuration de TOUTES les boutiques (si contexte ALL)
  • Ne sauvegarder que pour le groupe (si contexte GROUP)
  • Fonctionner correctement (si contexte SHOP, et encore…)

La gestion correcte

<span class="c1">// Gestion multi-shop explicite</span>
<span class="k">public</span> <span class="k">function</span> <span class="n">saveConfiguration</span><span class="p">()</span>
<span class="p">{</span>
    <span class="nv">$shops</span> <span class="o">=</span> <span class="nc">Shop</span><span class="o">::</span><span class="nf">getShops</span><span class="p">(</span><span class="kc">true</span><span class="p">,</span> <span class="kc">null</span><span class="p">,</span> <span class="kc">true</span><span class="p">);</span>

    <span class="k">if</span> <span class="p">(</span><span class="nc">Shop</span><span class="o">::</span><span class="nf">getContext</span><span class="p">()</span> <span class="o">===</span> <span class="nc">Shop</span><span class="o">::</span><span class="no">CONTEXT_SHOP</span><span class="p">)</span> <span class="p">{</span>
        <span class="c1">// Sauvegarde pour la boutique courante uniquement</span>
        <span class="nc">Configuration</span><span class="o">::</span><span class="nf">updateValue</span><span class="p">(</span>
            <span class="s1">'MON_MODULE_SETTING'</span><span class="p">,</span>
            <span class="nc">Tools</span><span class="o">::</span><span class="nf">getValue</span><span class="p">(</span><span class="s1">'MON_MODULE_SETTING'</span><span class="p">),</span>
            <span class="kc">false</span><span class="p">,</span>
            <span class="kc">null</span><span class="p">,</span>
            <span class="p">(</span><span class="n">int</span><span class="p">)</span> <span class="nv">$this</span><span class="o">-></span><span class="n">context</span><span class="o">-></span><span class="n">shop</span><span class="o">-></span><span class="n">id</span>
        <span class="p">);</span>
    <span class="p">}</span> <span class="k">elseif</span> <span class="p">(</span><span class="nc">Shop</span><span class="o">::</span><span class="nf">getContext</span><span class="p">()</span> <span class="o">===</span> <span class="nc">Shop</span><span class="o">::</span><span class="no">CONTEXT_ALL</span><span class="p">)</span> <span class="p">{</span>
        <span class="c1">// L'utilisateur veut appliquer à toutes les boutiques</span>
        <span class="k">foreach</span> <span class="p">(</span><span class="nv">$shops</span> <span class="k">as</span> <span class="nv">$idShop</span><span class="p">)</span> <span class="p">{</span>
            <span class="nc">Configuration</span><span class="o">::</span><span class="nf">updateValue</span><span class="p">(</span>
                <span class="s1">'MON_MODULE_SETTING'</span><span class="p">,</span>
                <span class="nc">Tools</span><span class="o">::</span><span class="nf">getValue</span><span class="p">(</span><span class="s1">'MON_MODULE_SETTING'</span><span class="p">),</span>
                <span class="kc">false</span><span class="p">,</span>
                <span class="kc">null</span><span class="p">,</span>
                <span class="p">(</span><span class="n">int</span><span class="p">)</span> <span class="nv">$idShop</span>
            <span class="p">);</span>
        <span class="p">}</span>
    <span class="p">}</span>
<span class="p">}</span>

L’installation multi-shop : le vrai cauchemar

Le install() d’un module vibe-codé :

<span class="c1">// Installation naïve</span>
<span class="k">public</span> <span class="k">function</span> <span class="n">install</span><span class="p">()</span>
<span class="p">{</span>
    <span class="k">return</span> <span class="k">parent</span><span class="o">::</span><span class="nf">install</span><span class="p">()</span>
        <span class="o">&&</span> <span class="nv">$this</span><span class="o">-></span><span class="nf">registerHook</span><span class="p">(</span><span class="s1">'displayProductAdditionalInfo'</span><span class="p">)</span>
        <span class="o">&&</span> <span class="nv">$this</span><span class="o">-></span><span class="nf">installDb</span><span class="p">();</span>
<span class="p">}</span>

<span class="k">private</span> <span class="k">function</span> <span class="n">installDb</span><span class="p">()</span>
<span class="p">{</span>
    <span class="nv">$sql</span> <span class="o">=</span> <span class="s1">'CREATE TABLE IF NOT EXISTS `'</span> <span class="mf">.</span> <span class="n">_DB_PREFIX_</span> <span class="mf">.</span> <span class="s1">'monmodule_data` (
        `id` INT AUTO_INCREMENT PRIMARY KEY,
        `id_product` INT NOT NULL,
        `custom_field` VARCHAR(255) NOT NULL
    )'</span><span class="p">;</span>

    <span class="k">return</span> <span class="nc">Db</span><span class="o">::</span><span class="nf">getInstance</span><span class="p">()</span><span class="o">-></span><span class="nf">execute</span><span class="p">(</span><span class="nv">$sql</span><span class="p">);</span>
<span class="p">}</span>

Le install() robuste :

<span class="c1">// Installation multi-shop aware</span>
<span class="k">public</span> <span class="k">function</span> <span class="n">install</span><span class="p">()</span>
<span class="p">{</span>
    <span class="k">if</span> <span class="p">(</span><span class="nc">Shop</span><span class="o">::</span><span class="nf">isFeatureActive</span><span class="p">())</span> <span class="p">{</span>
        <span class="nc">Shop</span><span class="o">::</span><span class="nf">setContext</span><span class="p">(</span><span class="nc">Shop</span><span class="o">::</span><span class="no">CONTEXT_ALL</span><span class="p">);</span>
    <span class="p">}</span>

    <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="k">parent</span><span class="o">::</span><span class="nf">install</span><span class="p">())</span> <span class="p">{</span>
        <span class="nv">$this</span><span class="o">-></span><span class="n">_errors</span><span class="p">[]</span> <span class="o">=</span> <span class="nv">$this</span><span class="o">-></span><span class="nf">l</span><span class="p">(</span><span class="s1">'Could not install module base'</span><span class="p">);</span>
        <span class="k">return</span> <span class="kc">false</span><span class="p">;</span>
    <span class="p">}</span>

    <span class="nv">$hooks</span> <span class="o">=</span> <span class="p">[</span>
        <span class="s1">'displayProductAdditionalInfo'</span><span class="p">,</span>
        <span class="s1">'displayBackOfficeHeader'</span><span class="p">,</span>
        <span class="s1">'actionProductUpdate'</span><span class="p">,</span>
        <span class="s1">'actionProductDelete'</span><span class="p">,</span>
        <span class="s1">'actionShopDataDuplication'</span><span class="p">,</span> <span class="c1">// ← Crucial pour le multi-shop !</span>
        <span class="s1">'actionObjectShopDeleteAfter'</span><span class="p">,</span>
    <span class="p">];</span>

    <span class="k">foreach</span> <span class="p">(</span><span class="nv">$hooks</span> <span class="k">as</span> <span class="nv">$hook</span><span class="p">)</span> <span class="p">{</span>
        <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="nv">$this</span><span class="o">-></span><span class="nf">registerHook</span><span class="p">(</span><span class="nv">$hook</span><span class="p">))</span> <span class="p">{</span>
            <span class="nv">$this</span><span class="o">-></span><span class="n">_errors</span><span class="p">[]</span> <span class="o">=</span> <span class="nb">sprintf</span><span class="p">(</span><span class="nv">$this</span><span class="o">-></span><span class="nf">l</span><span class="p">(</span><span class="s1">'Could not register hook: %s'</span><span class="p">),</span> <span class="nv">$hook</span><span class="p">);</span>
            <span class="k">return</span> <span class="kc">false</span><span class="p">;</span>
        <span class="p">}</span>
    <span class="p">}</span>

    <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="nv">$this</span><span class="o">-></span><span class="nf">installDb</span><span class="p">())</span> <span class="p">{</span>
        <span class="k">return</span> <span class="kc">false</span><span class="p">;</span>
    <span class="p">}</span>

    <span class="c1">// Configuration par défaut pour toutes les boutiques</span>
    <span class="nv">$this</span><span class="o">-></span><span class="nf">initializeDefaultConfig</span><span class="p">();</span>

    <span class="k">return</span> <span class="kc">true</span><span class="p">;</span>
<span class="p">}</span>

<span class="k">private</span> <span class="k">function</span> <span class="n">installDb</span><span class="p">()</span>
<span class="p">{</span>
    <span class="nv">$sql</span> <span class="o">=</span> <span class="p">[];</span>

    <span class="nv">$sql</span><span class="p">[]</span> <span class="o">=</span> <span class="s1">'CREATE TABLE IF NOT EXISTS `'</span> <span class="mf">.</span> <span class="n">_DB_PREFIX_</span> <span class="mf">.</span> <span class="s1">'monmodule_data` (
        `id_monmodule_data` INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
        `id_product` INT UNSIGNED NOT NULL,
        `custom_field` VARCHAR(255) NOT NULL,
        `date_add` DATETIME NOT NULL,
        `date_upd` DATETIME NOT NULL,
        INDEX (`id_product`)
    ) ENGINE='</span> <span class="mf">.</span> <span class="n">_MYSQL_ENGINE_</span> <span class="mf">.</span> <span class="s1">' DEFAULT CHARSET=utf8mb4'</span><span class="p">;</span>

    <span class="c1">// Table _shop pour le multi-shop</span>
    <span class="nv">$sql</span><span class="p">[]</span> <span class="o">=</span> <span class="s1">'CREATE TABLE IF NOT EXISTS `'</span> <span class="mf">.</span> <span class="n">_DB_PREFIX_</span> <span class="mf">.</span> <span class="s1">'monmodule_data_shop` (
        `id_monmodule_data` INT UNSIGNED NOT NULL,
        `id_shop` INT UNSIGNED NOT NULL,
        `active` TINYINT(1) UNSIGNED NOT NULL DEFAULT 1,
        PRIMARY KEY (`id_monmodule_data`, `id_shop`),
        INDEX (`id_shop`)
    ) ENGINE='</span> <span class="mf">.</span> <span class="n">_MYSQL_ENGINE_</span> <span class="mf">.</span> <span class="s1">' DEFAULT CHARSET=utf8mb4'</span><span class="p">;</span>

    <span class="c1">// Table _lang pour le multilingue</span>
    <span class="nv">$sql</span><span class="p">[]</span> <span class="o">=</span> <span class="s1">'CREATE TABLE IF NOT EXISTS `'</span> <span class="mf">.</span> <span class="n">_DB_PREFIX_</span> <span class="mf">.</span> <span class="s1">'monmodule_data_lang` (
        `id_monmodule_data` INT UNSIGNED NOT NULL,
        `id_lang` INT UNSIGNED NOT NULL,
        `id_shop` INT UNSIGNED NOT NULL DEFAULT 1,
        `custom_label` VARCHAR(255) NOT NULL,
        PRIMARY KEY (`id_monmodule_data`, `id_lang`, `id_shop`),
        INDEX (`id_lang`),
        INDEX (`id_shop`)
    ) ENGINE='</span> <span class="mf">.</span> <span class="n">_MYSQL_ENGINE_</span> <span class="mf">.</span> <span class="s1">' DEFAULT CHARSET=utf8mb4'</span><span class="p">;</span>

    <span class="k">foreach</span> <span class="p">(</span><span class="nv">$sql</span> <span class="k">as</span> <span class="nv">$query</span><span class="p">)</span> <span class="p">{</span>
        <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="nc">Db</span><span class="o">::</span><span class="nf">getInstance</span><span class="p">()</span><span class="o">-></span><span class="nf">execute</span><span class="p">(</span><span class="nv">$query</span><span class="p">))</span> <span class="p">{</span>
            <span class="nv">$this</span><span class="o">-></span><span class="n">_errors</span><span class="p">[]</span> <span class="o">=</span> <span class="nv">$this</span><span class="o">-></span><span class="nf">l</span><span class="p">(</span><span class="s1">'Database installation error'</span><span class="p">);</span>
            <span class="k">return</span> <span class="kc">false</span><span class="p">;</span>
        <span class="p">}</span>
    <span class="p">}</span>

    <span class="k">return</span> <span class="kc">true</span><span class="p">;</span>
<span class="p">}</span>

<span class="c1">// Hook crucial : quand une boutique est dupliquée, les données doivent suivre</span>
<span class="k">public</span> <span class="k">function</span> <span class="n">hookActionShopDataDuplication</span><span class="p">(</span><span class="nv">$params</span><span class="p">)</span>
<span class="p">{</span>
    <span class="nv">$oldShopId</span> <span class="o">=</span> <span class="p">(</span><span class="n">int</span><span class="p">)</span> <span class="nv">$params</span><span class="p">[</span><span class="s1">'old_id_shop'</span><span class="p">];</span>
    <span class="nv">$newShopId</span> <span class="o">=</span> <span class="p">(</span><span class="n">int</span><span class="p">)</span> <span class="nv">$params</span><span class="p">[</span><span class="s1">'new_id_shop'</span><span class="p">];</span>

    <span class="nc">Db</span><span class="o">::</span><span class="nf">getInstance</span><span class="p">()</span><span class="o">-></span><span class="nf">execute</span><span class="p">(</span><span class="s1">'
        INSERT INTO `'</span> <span class="mf">.</span> <span class="n">_DB_PREFIX_</span> <span class="mf">.</span> <span class="s1">'monmodule_data_shop` (`id_monmodule_data`, `id_shop`, `active`)
        SELECT `id_monmodule_data`, '</span> <span class="mf">.</span> <span class="nv">$newShopId</span> <span class="mf">.</span> <span class="s1">', `active`
        FROM `'</span> <span class="mf">.</span> <span class="n">_DB_PREFIX_</span> <span class="mf">.</span> <span class="s1">'monmodule_data_shop`
        WHERE `id_shop` = '</span> <span class="mf">.</span> <span class="nv">$oldShopId</span>
    <span class="p">);</span>
<span class="p">}</span>

L’IA ne génère JAMAIS hookActionShopDataDuplication. Jamais. Et sans ça, la duplication de boutique casse les données du module.


5. Les tests et la validation : ce qui n’existe tout simplement pas

Un module vibe-codé n’a aucun test. Zéro. L’IA génère le code fonctionnel, pas l’infrastructure de qualité.

Sur un module professionnel, voici ce qui existe en plus du code :

monmodule/
├── monmodule.php
├── config/
│   └── services.yml          ← Injection de dépendances
├── src/
│   ├── Controller/
│   ├── Repository/            ← Couche d'abstraction BDD
│   ├── Service/
│   └── Exception/             ← Exceptions métier typées
├── tests/
│   ├── Unit/
│   │   ├── Service/
│   │   └── Repository/
│   └── Integration/
│       ├── HookTest.php
│       ├── MultiShopTest.php
│       └── InstallTest.php
├── upgrade/
│   ├── upgrade-1.1.0.php      ← Migration de BDD
│   ├── upgrade-1.2.0.php
│   └── upgrade-2.0.0.php
├── views/
├── translations/
└── .github/
    └── workflows/
        └── ci.yml             ← CI/CD automatisé

Les fichiers d’upgrade : le grand oublié

Quand votre module évolue, la base de données doit migrer. L’IA ne pense jamais à créer les fichiers d’upgrade :

<span class="c1">// upgrade/upgrade-1.2.0.php</span>
<span class="k">function</span> <span class="n">upgrade_module_1_2_0</span><span class="p">(</span><span class="nv">$module</span><span class="p">)</span>
<span class="p">{</span>
    <span class="nv">$sql</span> <span class="o">=</span> <span class="p">[];</span>

    <span class="c1">// Ajout d'une colonne sans casser les données existantes</span>
    <span class="nv">$sql</span><span class="p">[]</span> <span class="o">=</span> <span class="s1">'ALTER TABLE `'</span> <span class="mf">.</span> <span class="n">_DB_PREFIX_</span> <span class="mf">.</span> <span class="s1">'monmodule_data`
              ADD COLUMN `priority` INT UNSIGNED NOT NULL DEFAULT 0 AFTER `custom_field`'</span><span class="p">;</span>

    <span class="c1">// Migration des données existantes</span>
    <span class="nv">$sql</span><span class="p">[]</span> <span class="o">=</span> <span class="s1">'UPDATE `'</span> <span class="mf">.</span> <span class="n">_DB_PREFIX_</span> <span class="mf">.</span> <span class="s1">'monmodule_data` SET `priority` = 1 WHERE `active` = 1'</span><span class="p">;</span>

    <span class="c1">// Nouveau hook nécessaire</span>
    <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="nv">$module</span><span class="o">-></span><span class="nf">registerHook</span><span class="p">(</span><span class="s1">'displayAfterBodyOpeningTag'</span><span class="p">))</span> <span class="p">{</span>
        <span class="k">return</span> <span class="kc">false</span><span class="p">;</span>
    <span class="p">}</span>

    <span class="k">foreach</span> <span class="p">(</span><span class="nv">$sql</span> <span class="k">as</span> <span class="nv">$query</span><span class="p">)</span> <span class="p">{</span>
        <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="nc">Db</span><span class="o">::</span><span class="nf">getInstance</span><span class="p">()</span><span class="o">-></span><span class="nf">execute</span><span class="p">(</span><span class="nv">$query</span><span class="p">))</span> <span class="p">{</span>
            <span class="k">return</span> <span class="kc">false</span><span class="p">;</span>
        <span class="p">}</span>
    <span class="p">}</span>

    <span class="c1">// Nettoyage du cache</span>
    <span class="k">if</span> <span class="p">(</span><span class="nb">method_exists</span><span class="p">(</span><span class="s1">'Cache'</span><span class="p">,</span> <span class="s1">'clean'</span><span class="p">))</span> <span class="p">{</span>
        <span class="nc">Cache</span><span class="o">::</span><span class="nf">clean</span><span class="p">(</span><span class="s1">'monmodule_*'</span><span class="p">);</span>
    <span class="p">}</span>

    <span class="k">return</span> <span class="kc">true</span><span class="p">;</span>
<span class="p">}</span>

Sans fichier d’upgrade, la mise à jour du module casse toutes les installations existantes. C’est le genre de chose qu’on apprend après avoir reçu 200 tickets de support en une journée.

Important PS 8+ : Depuis PrestaShop 8.0, les scripts d’upgrade du Core ont été entièrement déplacés dans le module autoupgrade. Pour vos propres modules, les fichiers upgrade/upgrade-X.Y.Z.php restent la bonne approche et sont toujours pris en charge. Mais assurez-vous que votre module déclare correctement sa version dans monmodule.php ($this->version) et dans config.xml — c’est ce que le système d’upgrade utilise pour déclencher les bons scripts de migration.


6. La compatibilité : le vrai métier

Compatibilité avec les autres modules

Un module ne vit jamais seul. Sur une boutique PrestaShop typique, il y a 30 à 80 modules installés. Un module vibe-codé :

  • Écrase les overrides sans vérifier s’ils existent déjà → crash d’autres modules
  • Charge jQuery en double ou charge une version incompatible → JavaScript cassé partout
  • Modifie des ObjectModel sans utiliser les hooks → les modules qui observent ces objets ne sont jamais notifiés
  • Ajoute du CSS/JS partout au lieu de cibler les pages concernées → ralentissement global
<span class="c1">// Ce que l'IA génère</span>
<span class="k">public</span> <span class="k">function</span> <span class="n">hookDisplayHeader</span><span class="p">()</span>
<span class="p">{</span>
    <span class="c1">// Chargé sur TOUTES les pages front</span>
    <span class="nv">$this</span><span class="o">-></span><span class="n">context</span><span class="o">-></span><span class="n">controller</span><span class="o">-></span><span class="nf">addCSS</span><span class="p">(</span><span class="nv">$this</span><span class="o">-></span><span class="n">_path</span> <span class="mf">.</span> <span class="s1">'views/css/style.css'</span><span class="p">);</span>
    <span class="nv">$this</span><span class="o">-></span><span class="n">context</span><span class="o">-></span><span class="n">controller</span><span class="o">-></span><span class="nf">addJS</span><span class="p">(</span><span class="nv">$this</span><span class="o">-></span><span class="n">_path</span> <span class="mf">.</span> <span class="s1">'views/js/script.js'</span><span class="p">);</span>
<span class="p">}</span>

<span class="c1">// Ce qu'il faut faire</span>
<span class="k">public</span> <span class="k">function</span> <span class="n">hookDisplayHeader</span><span class="p">()</span>
<span class="p">{</span>
    <span class="c1">// Méthode recommandée par PrestaShop devdocs</span>
    <span class="k">if</span> <span class="p">(</span><span class="nv">$this</span><span class="o">-></span><span class="n">context</span><span class="o">-></span><span class="n">controller</span><span class="o">-></span><span class="n">php_self</span> <span class="o">===</span> <span class="s1">'product'</span><span class="p">)</span> <span class="p">{</span>
        <span class="nv">$this</span><span class="o">-></span><span class="n">context</span><span class="o">-></span><span class="n">controller</span><span class="o">-></span><span class="nf">registerStylesheet</span><span class="p">(</span>
            <span class="s1">'monmodule-product'</span><span class="p">,</span>
            <span class="s1">'modules/'</span> <span class="mf">.</span> <span class="nv">$this</span><span class="o">-></span><span class="n">name</span> <span class="mf">.</span> <span class="s1">'/views/css/product.css'</span><span class="p">,</span>
            <span class="p">[</span><span class="s1">'media'</span> <span class="o">=></span> <span class="s1">'all'</span><span class="p">,</span> <span class="s1">'priority'</span> <span class="o">=></span> <span class="mi">150</span><span class="p">]</span>
        <span class="p">);</span>
        <span class="nv">$this</span><span class="o">-></span><span class="n">context</span><span class="o">-></span><span class="n">controller</span><span class="o">-></span><span class="nf">registerJavascript</span><span class="p">(</span>
            <span class="s1">'monmodule-product'</span><span class="p">,</span>
            <span class="s1">'modules/'</span> <span class="mf">.</span> <span class="nv">$this</span><span class="o">-></span><span class="n">name</span> <span class="mf">.</span> <span class="s1">'/views/js/product.js'</span><span class="p">,</span>
            <span class="p">[</span><span class="s1">'position'</span> <span class="o">=></span> <span class="s1">'bottom'</span><span class="p">,</span> <span class="s1">'priority'</span> <span class="o">=></span> <span class="mi">150</span><span class="p">,</span> <span class="s1">'attribute'</span> <span class="o">=></span> <span class="s1">'defer'</span><span class="p">]</span>
        <span class="p">);</span>
    <span class="p">}</span>
<span class="p">}</span>

Pourquoi php_self plutôt que instanceof ? La propriété $php_self est une string définie sur chaque controller PrestaShop ('product', 'category', 'cart', etc.) et reste cohérente même avec des thèmes custom ou des surcharges de controller. L’instanceof dépend de la chaîne d’héritage et peut être brisé par des overrides.

Compatibilité de version PrestaShop

Un module professionnel doit gérer ça :

<span class="c1">// Adapter le code selon la version</span>
<span class="k">if</span> <span class="p">(</span><span class="nb">version_compare</span><span class="p">(</span><span class="n">_PS_VERSION_</span><span class="p">,</span> <span class="s1">'8.0.0'</span><span class="p">,</span> <span class="s1">'>='</span><span class="p">))</span> <span class="p">{</span>
    <span class="c1">// PrestaShop 8 : utilise Symfony et Doctrine</span>
    <span class="c1">// Les AdminController legacy sont dépréciés</span>
    <span class="c1">// Le système de thème a évolué</span>
<span class="p">}</span> <span class="k">elseif</span> <span class="p">(</span><span class="nb">version_compare</span><span class="p">(</span><span class="n">_PS_VERSION_</span><span class="p">,</span> <span class="s1">'1.7.7'</span><span class="p">,</span> <span class="s1">'>='</span><span class="p">))</span> <span class="p">{</span>
    <span class="c1">// PrestaShop 1.7.7+ : nouveaux hooks, nouveau ProductPresenter</span>
    <span class="c1">// Symfony partiellement intégré</span>
<span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
    <span class="c1">// PrestaShop 1.7.x legacy</span>
    <span class="c1">// Encore du code pre-Symfony partout</span>
<span class="p">}</span>


7. PrestaShop 9 : les ruptures que l’IA ignore complètement

Si les sections précédentes s’appliquaient à PrestaShop 1.7.x et 8.x, PrestaShop 9.0 (sorti en juin 2025) introduit des ruptures supplémentaires qui rendent les modules vibe-codés encore plus fragiles.

La page produit legacy est supprimée.

Dans PrestaShop 8, deux pages produit coexistaient (legacy et Symfony). Dans PrestaShop 9, seule la page Symfony existe. Tout module qui ciblait hookActionProductSave ou les hooks de la legacy page produit sans adaptation Symfony est cassé sans migration.

<span class="c1">// Ce pattern fonctionnait en PS 8 legacy — cassé en PS 9</span>
<span class="k">public</span> <span class="k">function</span> <span class="n">hookActionProductSave</span><span class="p">(</span><span class="nv">$params</span><span class="p">)</span> <span class="p">{</span> <span class="mf">...</span> <span class="p">}</span>

<span class="c1">// PS 9 : utiliser les nouveaux Form Handlers</span>
<span class="k">public</span> <span class="k">function</span> <span class="n">hookActionAfterUpdateCreateProductFormHandler</span><span class="p">(</span><span class="nv">$params</span><span class="p">)</span> <span class="p">{</span> <span class="mf">...</span> <span class="p">}</span>
<span class="k">public</span> <span class="k">function</span> <span class="n">hookActionAfterCreateCreateProductFormHandler</span><span class="p">(</span><span class="nv">$params</span><span class="p">)</span> <span class="p">{</span> <span class="mf">...</span> <span class="p">}</span>

Les hooks d’alias déclenchent des deprecation notices.

PrestaShop 8.0 avait introduit une alerte en mode développeur quand un alias de hook est utilisé. En PS 9, Using a hook alias will trigger a deprecation notice est documenté officiellement. Si votre module utilise encore displayProductButtons (alias de displayProductAdditionalInfo), préparez-vous à devoir le corriger.

L’authentification admin back-office est désormais 100% Symfony.

Fini Context::$cookie pour l’auth admin. Le système est maintenant basé sur Symfony Security avec des sessions côté serveur. Tout module qui lisait $cookie->id_employee pour vérifier une permission admin dans un contexte non-standard doit être revu.

Le bon réflexe avant de commencer un module en 2026 :

1. Vérifier la version cible : PS 1.7 / PS 8 / PS 9
2. Consulter "Changes in PrestaShop X.0.x" sur devdocs.prestashop-project.org
3. Vérifier la liste des hooks supprimés pour cette version
4. Adapter l'architecture en conséquence (legacy vs Symfony)

Un module vibe-codé en 2024 ciblant PS 1.7 peut être incompatible avec PS 9 sur 40% de ses fonctionnalités. L’IA qui génère du code en 2026 ne sait pas forcément quelle version vous ciblez — et ne le demandera jamais.


8. Le bilan : ce que le Vibe Coding fait bien, et où il s’arrête

Soyons honnête. Voici mon bilan après 6 mois d’utilisation quotidienne de l’IA dans mon développement PrestaShop :

Ce que l’IA fait très bien

Tâche Gain de temps Scaffolding initial d’un module ~60% Génération de templates Smarty basiques ~50% Écriture de requêtes SQL simples ~40% Génération de formulaires HelperForm ~70% Documentation du code ~80% Refactoring de code existant ~40% ### Ce que l’IA fait mal (ou pas du tout)

Problème Conséquence en production Sécurité (tokens, permissions, SQL injection) Boutique piratée, données volées Multi-shop Données corrompues entre boutiques Performance (N+1, cache) Serveur down en période de charge Hooks d’action complets Données incohérentes, ERP/CRM désynchronisés Fichiers d’upgrade Module impossible à mettre à jour Compatibilité cross-version Module qui crashe sur d’autres versions Compatibilité PS 9 (page produit legacy) Module cassé sur toutes les boutiques PS 9.0+ Hooks dynamiques actionObject* mal utilisés registerHook() silencieux, hook jamais déclenché Gestion d’erreurs robuste Écrans blancs, 500 errors Conformité RGPD Risque juridique Accessibilité (a11y) Non conformité légale ---

9. Ma méthode : l’IA comme accélérateur, pas comme remplaçant

Je ne suis pas contre le Vibe Coding. Je suis contre le Vibe Coding sans filet.

Voici comment j’intègre l’IA dans mon workflow quotidien :

1. L’IA génère, je spécifie

Je ne demande jamais “crée-moi un module de wishlist”. Je demande :

“Génère la classe WishlistRepository avec les méthodes add, remove, getByCustomer. Utilise DbQuery, gère le multi-shop avec jointure sur shop, échappe les valeurs avec pSQL et (int). La méthode getByCustomer doit utiliser _PS_USE_SQL_SLAVE.”

2. L’IA code, je review

Chaque ligne générée passe par ma checklist mentale :

  • Sécurité : token, permissions, échappement
  • Multi-shop : contexte, jointures _shop
  • Multi-langue : jointures _lang
  • Performance : nombre de requêtes, cache
  • Hooks : complets (action + display)
  • Compatibilité : version PS, thèmes
  • Erreurs : try/catch, Validate, fallbacks

3. L’IA itère, je valide

L’IA est excellente pour itérer rapidement sur des variantes. Mais la validation finale est humaine, avec des tests sur une vraie boutique, avec de vraies données, dans un vrai contexte multi-shop.


🔑#### Points Clés à Retenir — Vibe Coding & Modules PrestaShop

Ce que chaque développeur (et chaque marchand qui commande un module) doit comprendre sur le Vibe Coding appliqué à PrestaShop :

  1. L’IA génère du code fonctionnel — pas du code de production. Les modules vibe-codés compilent et fonctionnent en démo. Ils cassent sur les cas limites, la charge réelle et les interactions entre modules. C’est précisément ce qui les rend dangereux.
  2. Trois vecteurs d’échec inévitables : sécurité (AJAX sans token CSRF, injections SQL), multi-shop (jointures _shop et _lang manquantes, hookActionShopDataDuplication jamais généré), et performance (requêtes N+1 qui mettent un serveur à genoux en 24h de trafic).
  3. Les hooks d’action critiques sont systématiquement oubliés. L’IA génère bien les hooks display, jamais les hookActionObject*Delete/Update/Add nécessaires à la cohérence des données avec les ERP et CRM.
  4. PrestaShop 9 introduit des ruptures que l’IA ignore. Page produit legacy supprimée, authentification admin 100% Symfony, déprecation des hooks aliases — un module vibe-codé sans audit peut être incompatible sur 40% de ses fonctionnalités.
  5. La bonne méthode : IA pour accélérer, expert pour valider. L’IA gagne 40–80% sur le scaffolding. Mais chaque ligne générée doit passer par une checklist mentale : sécurité, multi-shop, multi-langue, performance, hooks complets, compatibilité de version.

Conclusion : le Vibe Coding ne remplace pas 10 ans de production cassée

Le Vibe Coding est un outil formidable entre les mains d’un développeur qui sait ce qu’il fait. Il accélère le travail de 30 à 50%.

Mais entre les mains de quelqu’un qui ne connaît pas les pièges de PrestaShop — et ils sont innombrables — c’est un générateur de dette technique, de failles de sécurité et de boutiques instables.

Les 80% de modules vibe-codés qui ne passeront jamais en production, ce ne sont pas des modules mal codés au sens syntaxique. L’IA écrit du code qui compile, qui s’exécute, qui a l’air de fonctionner. C’est précisément ce qui les rend dangereux.

Ils échouent sur les cas limites, les contextes spécifiques, les interactions entre modules, les montées en charge, les mises à jour, et les contraintes métier que seule l’expérience terrain permet de connaître.

L’expertise d’un développeur senior PrestaShop, ce n’est pas de savoir écrire du PHP. C’est de savoir tout ce qui peut mal tourner en production, et de l’empêcher avant que ça arrive.

Le Vibe Coding génère du code. L’expérience génère de la confiance.

Et en e-commerce, quand chaque minute de downtime coûte des milliers d’euros, c’est la confiance qui a de la valeur.


Vous avez un module généré par IA et vous vous demandez s’il est prêt pour la production ? Je propose un audit technique complet avec rapport détaillé, corrections prioritaires et estimation de la dette technique. Parce que le meilleur moment pour trouver les problèmes, c’est avant que vos clients ne les trouvent.

Et vous, quelle est votre expérience avec le Vibe Coding sur PrestaShop ? Des modules IA qui ont tenu en production ? Des catastrophes évitées de justesse ?

LinkedIn

Suivez mes analyses IA et e-commerce

Je partage des retours terrain sur les agents IA, PrestaShop, MCP et l automatisation pour les equipes e-commerce.

Me suivre sur LinkedIn