Nicolas Dabene
Retour au blog
02 October 2025 Nicolas Dabene 4 min

Optimiser PrestaShop avec le lazy loading

Optimiser vos modules PrestaShop avec le lazy loading des services Symfony Introduction

API PrestaShop développement Performance prestashop-ecommerce
Optimiser PrestaShop avec le lazy loading

Optimiser vos modules PrestaShop avec le lazy loading des services Symfony Introduction

Optimiser vos modules PrestaShop avec le lazy loading des services Symfony

Introduction

Depuis que PrestaShop repose sur Symfony (à partir de la version 1.7.6 et renforcé dans PrestaShop 8 et 9), nous avons accès à toute la puissance du conteneur de services. Pourtant, beaucoup de développeurs de modules continuent de charger leurs services « en eager »… même quand ceux-ci ne sont presque jamais utilisés.

Le résultat ? Plus de lenteur, plus de mémoire consommée pour rien. La solution : activer le lazy loading des services. Dans cet article, nous allons voir comment cette technique simple peut transformer les performances de vos modules PrestaShop.

Comprendre le lazy loading des services

Qu’est-ce que le lazy loading ?

Le principe est simple :

  • Sans lazy loading → Symfony instancie votre service immédiatement au démarrage
  • Avec lazy loading → Symfony place un proxy. Le service réel n’est créé qu’au premier appel

Pour bien comprendre, imaginons une métaphore : votre service est comme un camion de livraison 🚚.

  • Sans lazy : le camion démarre à chaque requête, même s’il est vide
  • Avec lazy : il ne démarre que quand vous avez effectivement un colis à livrer

Les bénéfices concrets

Cette approche apporte plusieurs avantages :

  • Réduction de la consommation mémoire : seuls les services utilisés sont instanciés
  • Temps de réponse plus rapides : moins d’objets à créer au démarrage
  • Meilleure scalabilité : votre module s’adapte mieux à la charge

Pourquoi c’est particulièrement utile dans PrestaShop

Les modules PrestaShop contiennent souvent des services lourds :

  • Clients API (Stripe, Amazon S3, ChatGPT, etc.)
  • Parsers Excel/CSV pour l’import/export
  • Générateurs PDF pour les factures
  • Clients de cache Redis ou Elasticsearch

Le problème ? La plupart des pages de votre boutique n’ont pas besoin de ces services. Sans lazy loading, vous gaspillez des ressources précieuses. Avec le lazy loading, ces services ne se chargent que quand ils sont réellement nécessaires.

Implémentation pratique dans un module

Exemple d’un service externe lourd

Commençons par créer un service qui simule un client API externe :

<span class="c1">// src/Infra/External/ApiClient.php</span>
<span class="kn">namespace</span> <span class="nn">MyVendor\MyModule\Infra\External</span><span class="p">;</span>

<span class="k">final</span> <span class="kd">class</span> <span class="nc">ApiClient</span> 
<span class="p">{</span>
    <span class="k">public</span> <span class="k">function</span> <span class="n">__construct</span><span class="p">(</span><span class="k">private</span> <span class="kt">string</span> <span class="nv">$apiKey</span><span class="p">)</span> 
    <span class="p">{</span>
        <span class="c1">// Simulation d'une initialisation coûteuse</span>
        <span class="c1">// (connexion réseau, authentification, etc.)</span>
    <span class="p">}</span>
    
    <span class="k">public</span> <span class="k">function</span> <span class="n">fetchCatalog</span><span class="p">():</span> <span class="kt">array</span> 
    <span class="p">{</span>
        <span class="c1">// Appel API externe pour récupérer un catalogue</span>
        <span class="c1">// Opération potentiellement lente</span>
        <span class="k">return</span> <span class="p">[</span>
            <span class="p">[</span><span class="s1">'id'</span> <span class="o">=></span> <span class="mi">1</span><span class="p">,</span> <span class="s1">'name'</span> <span class="o">=></span> <span class="s1">'Produit 1'</span><span class="p">],</span>
            <span class="p">[</span><span class="s1">'id'</span> <span class="o">=></span> <span class="mi">2</span><span class="p">,</span> <span class="s1">'name'</span> <span class="o">=></span> <span class="s1">'Produit 2'</span><span class="p">],</span>
        <span class="p">];</span>
    <span class="p">}</span>
<span class="p">}</span>

Configuration des services avec lazy loading

Voici comment configurer ce service dans votre fichier services.yml :

<span class="c1"># modules/mymodule/config/services.yml</span>
<span class="na">services</span><span class="pi">:</span>
  <span class="na">_defaults</span><span class="pi">:</span>
    <span class="na">autowire</span><span class="pi">:</span> <span class="no">true</span>
    <span class="na">autoconfigure</span><span class="pi">:</span> <span class="no">true</span>
    <span class="na">public</span><span class="pi">:</span> <span class="no">false</span>

  <span class="na">MyVendor\MyModule\Infra\External\ApiClient</span><span class="pi">:</span>
    <span class="na">arguments</span><span class="pi">:</span>
      <span class="na">$apiKey</span><span class="pi">:</span> <span class="s1">'</span><span class="s">%env(MYMODULE_API_KEY)%'</span>
    <span class="na">lazy</span><span class="pi">:</span> <span class="no">true</span>   <span class="c1"># 💡 Le proxy est généré uniquement au besoin</span>

Service applicatif utilisant le client API

Créons maintenant un service qui utilise notre client API :

<span class="c1">// src/App/Catalog/SyncCatalog.php</span>
<span class="kn">namespace</span> <span class="nn">MyVendor\MyModule\App\Catalog</span><span class="p">;</span>

<span class="kn">use</span> <span class="nc">MyVendor\MyModule\Infra\External\ApiClient</span><span class="p">;</span>

<span class="kd">class</span> <span class="nc">SyncCatalog</span> 
<span class="p">{</span>
    <span class="k">public</span> <span class="k">function</span> <span class="n">__construct</span><span class="p">(</span><span class="k">private</span> <span class="kt">ApiClient</span> <span class="nv">$client</span><span class="p">)</span> <span class="p">{}</span>
    
    <span class="k">public</span> <span class="k">function</span> <span class="n">__invoke</span><span class="p">():</span> <span class="kt">int</span> 
    <span class="p">{</span>
        <span class="nv">$rows</span> <span class="o">=</span> <span class="nv">$this</span><span class="o">-></span><span class="n">client</span><span class="o">-></span><span class="nf">fetchCatalog</span><span class="p">();</span>
        
        <span class="c1">// Logique de synchronisation avec PrestaShop</span>
        <span class="c1">// (création/mise à jour des produits)</span>
        
        <span class="k">return</span> <span class="err">\</span><span class="nb">count</span><span class="p">(</span><span class="nv">$rows</span><span class="p">);</span>
    <span class="p">}</span>
<span class="p">}</span>

Controller Symfony pour déclencher la synchronisation

Enfin, un contrôleur qui utilise notre service :

<span class="c1">// src/Ui/Controller/Admin/CatalogController.php</span>
<span class="kn">namespace</span> <span class="nn">MyVendor\MyModule\Ui\Controller\Admin</span><span class="p">;</span>

<span class="kn">use</span> <span class="nc">MyVendor\MyModule\App\Catalog\SyncCatalog</span><span class="p">;</span>
<span class="kn">use</span> <span class="nc">Symfony\Bundle\FrameworkBundle\Controller\AbstractController</span><span class="p">;</span>
<span class="kn">use</span> <span class="nc">Symfony\Component\HttpFoundation\Response</span><span class="p">;</span>

<span class="k">final</span> <span class="kd">class</span> <span class="nc">CatalogController</span> <span class="kd">extends</span> <span class="nc">AbstractController</span> 
<span class="p">{</span>
    <span class="k">public</span> <span class="k">function</span> <span class="n">sync</span><span class="p">(</span><span class="kt">SyncCatalog</span> <span class="nv">$useCase</span><span class="p">):</span> <span class="kt">Response</span> 
    <span class="p">{</span>
        <span class="c1">// C'est seulement ici que ApiClient sera réellement instancié</span>
        <span class="nv">$count</span> <span class="o">=</span> <span class="nv">$useCase</span><span class="p">();</span>
        
        <span class="nv">$this</span><span class="o">-></span><span class="nf">addFlash</span><span class="p">(</span><span class="s1">'success'</span><span class="p">,</span> <span class="s2">"</span><span class="nv">$count</span><span class="s2"> produits synchronisés !"</span><span class="p">);</span>
        <span class="k">return</span> <span class="nv">$this</span><span class="o">-></span><span class="nf">redirectToRoute</span><span class="p">(</span><span class="s1">'mymodule_catalog_index'</span><span class="p">);</span>
    <span class="p">}</span>
<span class="p">}</span>

Bonnes pratiques et cas d’usage

Quand activer le lazy loading

Le lazy loading est particulièrement bénéfique pour :

  • Clients API externes : Stripe, PayPal, services de livraison
  • Services de traitement lourd : manipulation Excel, génération PDF, traitement d’images
  • Fonctionnalités d’export/import : utilisées rarement mais coûteuses
  • Clients de cache : Redis, Memcached quand ils ne sont pas toujours nécessaires

Quand éviter le lazy loading

À l’inverse, évitez le lazy loading pour :

  • Helpers simples : services légers utilisés partout
  • Services critiques : utilisés dans chaque requête
  • Services de logging : doivent être disponibles immédiatement

Pièges à éviter et bonnes pratiques

Attention aux classes finales

Si votre service est une classe final, Symfony ne peut pas créer de proxy. Préférez une interface :

<span class="kd">interface</span> <span class="nc">ApiClientInterface</span> 
<span class="p">{</span>
    <span class="k">public</span> <span class="k">function</span> <span class="n">fetchCatalog</span><span class="p">():</span> <span class="kt">array</span><span class="p">;</span>
<span class="p">}</span>

<span class="k">final</span> <span class="kd">class</span> <span class="nc">ApiClient</span> <span class="kd">implements</span> <span class="nc">ApiClientInterface</span> 
<span class="p">{</span>
    <span class="c1">// Implémentation...</span>
<span class="p">}</span>

<span class="c1"># Configuration avec interface</span>
<span class="na">services</span><span class="pi">:</span>
  <span class="na">MyVendor\MyModule\Infra\External\ApiClientInterface</span><span class="pi">:</span>
    <span class="na">class</span><span class="pi">:</span> <span class="s">MyVendor\MyModule\Infra\External\ApiClient</span>
    <span class="na">arguments</span><span class="pi">:</span>
      <span class="na">$apiKey</span><span class="pi">:</span> <span class="s1">'</span><span class="s">%env(MYMODULE_API_KEY)%'</span>
    <span class="na">lazy</span><span class="pi">:</span> <span class="no">true</span>

Évitez la sérialisation des proxies

Les proxies lazy ne doivent pas être sérialisés. Si vous devez persister l’état d’un service, extrayez d’abord les données nécessaires.

Testez les performances

Utilisez des outils comme Blackfire ou le profiler Symfony pour mesurer l’impact réel :

<span class="c"># Débugger les services et leurs proxies</span>
bin/console debug:container <span class="nt">--show-private</span>

Technique avancée : Service Subscriber

Pour un contrôle encore plus fin, utilisez le pattern ServiceSubscriberInterface :

<span class="kn">use</span> <span class="nc">Symfony\Contracts\Service\ServiceSubscriberInterface</span><span class="p">;</span>
<span class="kn">use</span> <span class="nc">Psr\Container\ContainerInterface</span><span class="p">;</span>

<span class="k">final</span> <span class="kd">class</span> <span class="nc">MyController</span> <span class="kd">extends</span> <span class="nc">AbstractController</span> <span class="kd">implements</span> <span class="nc">ServiceSubscriberInterface</span> 
<span class="p">{</span>
    <span class="k">public</span> <span class="k">static</span> <span class="k">function</span> <span class="n">getSubscribedServices</span><span class="p">():</span> <span class="kt">array</span> 
    <span class="p">{</span>
        <span class="k">return</span> <span class="p">[</span>
            <span class="s1">'syncCatalog'</span> <span class="o">=></span> <span class="nc">SyncCatalog</span><span class="o">::</span><span class="n">class</span><span class="p">,</span>
            <span class="s1">'apiClient'</span> <span class="o">=></span> <span class="nc">ApiClientInterface</span><span class="o">::</span><span class="n">class</span><span class="p">,</span>
        <span class="p">];</span>
    <span class="p">}</span>
    
    <span class="k">public</span> <span class="k">function</span> <span class="n">__construct</span><span class="p">(</span><span class="k">private</span> <span class="kt">ContainerInterface</span> <span class="nv">$locator</span><span class="p">)</span> <span class="p">{}</span>
    
    <span class="k">public</span> <span class="k">function</span> <span class="n">sync</span><span class="p">():</span> <span class="kt">Response</span> 
    <span class="p">{</span>
        <span class="c1">// Le service n'est récupéré que quand on en a besoin</span>
        <span class="nv">$useCase</span> <span class="o">=</span> <span class="nv">$this</span><span class="o">-></span><span class="n">locator</span><span class="o">-></span><span class="nf">get</span><span class="p">(</span><span class="s1">'syncCatalog'</span><span class="p">);</span>
        <span class="nv">$count</span> <span class="o">=</span> <span class="nv">$useCase</span><span class="p">();</span>
        
        <span class="nv">$this</span><span class="o">-></span><span class="nf">addFlash</span><span class="p">(</span><span class="s1">'success'</span><span class="p">,</span> <span class="s2">"</span><span class="nv">$count</span><span class="s2"> produits synchronisés !"</span><span class="p">);</span>
        <span class="k">return</span> <span class="nv">$this</span><span class="o">-></span><span class="nf">redirectToRoute</span><span class="p">(</span><span class="s1">'mymodule_catalog_index'</span><span class="p">);</span>
    <span class="p">}</span>
<span class="p">}</span>

Mesurer l’impact sur les performances

Pour évaluer l’efficacité du lazy loading dans votre contexte, voici quelques métriques à surveiller :

Mémoire consommée

<span class="c1">// Avant et après activation du lazy loading</span>
<span class="k">echo</span> <span class="s2">"Mémoire utilisée : "</span> <span class="mf">.</span> <span class="nb">memory_get_peak_usage</span><span class="p">(</span><span class="kc">true</span><span class="p">)</span> <span class="o">/</span> <span class="mi">1024</span> <span class="o">/</span> <span class="mi">1024</span> <span class="mf">.</span> <span class="s2">" MB</span><span class="se">\n</span><span class="s2">"</span><span class="p">;</span>

Temps de chargement des pages

Utilisez le profiler Symfony ou des outils externes pour mesurer le temps de réponse des pages qui n’utilisent pas vos services lourds.

Conclusion

Le lazy loading des services est un petit réglage de configuration qui peut avoir un impact considérable sur les performances de vos modules PrestaShop :

  • Réduction significative de la consommation mémoire
  • Temps de réponse plus rapides pour les pages non concernées
  • Modules plus scalables et professionnels

La prochaine fois que vous développez un module avec des services lourds, pensez à ajouter cette simple ligne lazy: true dans votre configuration. Vos utilisateurs et votre serveur vous remercieront !

N’hésitez pas à tester cette technique sur vos projets existants et à partager vos résultats avec la communauté PrestaShop.


Article publié le 2 octobre 2025 par Nicolas Dabène - Expert PHP & PrestaShop avec 15+ ans d’expérience

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