Nicolas Dabene
Back to blog
02 October 2025 Nicolas Dabene 3 min

AI is Lazy: Its Hidden Strength

Optimize Your PrestaShop Modules with Symfony Service Lazy Loading Introduction

API PrestaShop development Performance
AI is Lazy: Its Hidden Strength

Optimize Your PrestaShop Modules with Symfony Service Lazy Loading Introduction

Optimize Your PrestaShop Modules with Symfony Service Lazy Loading

Introduction

Since PrestaShop relies on Symfony (from version 1.7.6 and reinforced in PrestaShop 8 and 9), we have access to the full power of the service container. Yet, many module developers continue to load their services “eagerly”… even when they’re almost never used.

The result? More slowness, more memory consumed for nothing. The solution: enable lazy loading of services. In this article, we’ll see how this simple technique can transform your PrestaShop module performance.

Understanding Service Lazy Loading

What is Lazy Loading?

The principle is simple:

  • Without lazy loading → Symfony instantiates your service immediately at startup
  • With lazy loading → Symfony places a proxy. The actual service is only created on first call

To understand well, let’s imagine a metaphor: your service is like a delivery truck 🚚.

  • Without lazy: the truck starts at every request, even if it’s empty
  • With lazy: it only starts when you actually have a package to deliver

Concrete Benefits

This approach brings several advantages:

  • Reduced memory consumption: only used services are instantiated
  • Faster response times: fewer objects to create at startup
  • Better scalability: your module adapts better to load

Why This is Particularly Useful in PrestaShop

PrestaShop modules often contain heavy services:

  • API clients (Stripe, Amazon S3, ChatGPT, etc.)
  • Excel/CSV parsers for import/export
  • PDF generators for invoices
  • Redis or Elasticsearch cache clients

The problem? Most pages in your store don’t need these services. Without lazy loading, you waste precious resources. With lazy loading, these services only load when actually necessary.

Practical Implementation in a Module

Example of a Heavy External Service

Let’s start by creating a service that simulates an external API client:

<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 of expensive initialization</span>
        <span class="c1">// (network connection, authentication, 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">// External API call to retrieve a catalog</span>
        <span class="c1">// Potentially slow operation</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">'Product 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">'Product 2'</span><span class="p">],</span>
        <span class="p">];</span>
    <span class="p">}</span>
<span class="p">}</span>

Service Configuration with Lazy Loading

Here’s how to configure this service in your services.yml file:

<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"># 💡 Proxy is generated only when needed</span>

Application Service Using the API Client

Let’s now create a service that uses our API client:

<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">// Synchronization logic with PrestaShop</span>
        <span class="c1">// (product creation/update)</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>

Symfony Controller to Trigger Synchronization

Finally, a controller that uses our 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">// Only here will ApiClient be actually instantiated</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"> products synchronized!"</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>

Best Practices and Use Cases

When to Enable Lazy Loading

Lazy loading is particularly beneficial for:

  • External API clients: Stripe, PayPal, delivery services
  • Heavy processing services: Excel manipulation, PDF generation, image processing
  • Export/import features: rarely used but expensive
  • Cache clients: Redis, Memcached when not always necessary

When to Avoid Lazy Loading

Conversely, avoid lazy loading for:

  • Simple helpers: lightweight services used everywhere
  • Critical services: used in every request
  • Logging services: must be immediately available

Pitfalls to Avoid and Best Practices

Beware of Final Classes

If your service is a final class, Symfony cannot create a proxy. Prefer an 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">// Implementation...</span>
<span class="p">}</span>

<span class="c1"># Configuration with 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>

Avoid Serializing Proxies

Lazy proxies should not be serialized. If you need to persist a service’s state, extract the necessary data first.

Test Performance

Use tools like Blackfire or the Symfony profiler to measure real impact:

<span class="c"># Debug services and their proxies</span>
bin/console debug:container <span class="nt">--show-private</span>

Advanced Technique: Service Subscriber

For even finer control, use the ServiceSubscriberInterface pattern:

<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">// Service is only retrieved when needed</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"> products synchronized!"</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>

Measuring Performance Impact

To evaluate lazy loading effectiveness in your context, here are some metrics to monitor:

Memory Consumed

<span class="c1">// Before and after lazy loading activation</span>
<span class="k">echo</span> <span class="s2">"Memory used: "</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>

Page Loading Times

Use the Symfony profiler or external tools to measure response time for pages that don’t use your heavy services.

Conclusion

Service lazy loading is a small configuration tweak that can have a considerable impact on your PrestaShop module performance:

  • Significant reduction in memory consumption
  • Faster response times for unaffected pages
  • More scalable and professional modules

Next time you develop a module with heavy services, think about adding that simple lazy: true line in your configuration. Your users and your server will thank you!

Don’t hesitate to test this technique on your existing projects and share your results with the PrestaShop community.


Article published on October 2, 2025 by Nicolas Dabène - PHP & PrestaShop Expert with 15+ years of experience

LinkedIn

Follow my AI and e-commerce analysis

I share practical notes on AI agents, PrestaShop architecture, MCP and automation for e-commerce teams.

Follow on LinkedIn