Skip to content
intermediate Phase 28 · DI Advanced

Magento Proxies - Lazy Loading

Understanding Magento 2 proxies: lazy loading, proxy generation, performance optimization, and when proxies help performance

45m
0 problems
Topic Progress 0%

Proxy Fundamentals

What is a Proxy?

A proxy is a placeholder object that delays creation of the actual object until it's first used. This is lazy loading.

// Without proxy - created immediately
$heavyService = new HeavyService();  // Expensive!

// With proxy - created on first use
$proxy = new HeavyServiceProxy();  // Cheap!
$proxy->doWork();  // Creates HeavyService here

Why Use Proxies?

Some dependencies are expensive to create:

  • Database connections
  • External API clients
  • Large object graphs
  • Objects with many dependencies

Proxies defer this cost until actually needed.

How Proxies Work

1. Proxy is created (lightweight)
2. Proxy stores the class name and arguments
3. On first method call, proxy creates the real object
4. Subsequent calls are forwarded to the real object

Proxy vs Factory

Aspect Factory Proxy
Creation Explicit create() call Automatic on first use
Use case Multiple instances Single instance, lazy
Control You control when Framework controls
DI Constructor injection Transparent wrapper

Configuring Proxies

di.xml Configuration

<type name="Amazon\Prep\Service\WarrantyService">
    <arguments>
        <argument name="heavyDependency" xsi:type="object">
            Amazon\Prep\Service\HeavyService\Proxy
        </argument>
    </arguments>
</type>

Generated Proxy Class

After bin/magento setup:di:compile, a proxy class is generated:

<?php
namespace Amazon\Prep\Service\HeavyService;

class Proxy
{
    private $instance;
    private $objectManager;
    private $instanceName = 'Amazon\Prep\Service\HeavyService';
    private $shared = true;

    public function __construct(
        \Magento\Framework\ObjectManager $objectManager,
        array $arguments = []
    ) {
        $this->objectManager = $objectManager;
    }

    public function __call($method, $arguments)
    {
        if ($this->instance === null) {
            $this->instance = $this->objectManager->create(
                $this->instanceName,
                ['arguments' => $arguments]
            );
        }
        return $this->instance->$method(...$arguments);
    }
}

Proxy with Arguments

<type name="Amazon\Prep\Service\WarrantyService">
    <arguments>
        <argument name="apiClient" xsi:type="object">
            Amazon\Prep\Service\ApiClient\Proxy
        </argument>
        <argument name="timeout" xsi:type="number">30</argument>
    </arguments>
</type>

Multiple Proxies

<type name="Amazon\Prep\Service\Orchestrator">
    <arguments>
        <argument name="serviceA" xsi:type="object">ServiceA\Proxy</argument>
        <argument name="serviceB" xsi:type="object">ServiceB\Proxy</argument>
        <argument name="serviceC" xsi:type="object">ServiceC\Proxy</argument>
    </arguments>
</type>

Proxy Performance Benefits

Before Proxy (Eager Loading)

class WarrantyService
{
    public function __construct(
        private HeavyApiClient $apiClient,        // Created always
        private DatabaseConnection $dbConnection,  // Created always
        private CacheManager $cacheManager         // Created always
    ) {}

    public function simpleOperation(): void
    {
        // Might not even use apiClient or dbConnection
        $this->cacheManager->clean();
    }
}

All three dependencies are created even if only cacheManager is needed.

After Proxy (Lazy Loading)

<type name="Amazon\Prep\Service\WarrantyService">
    <arguments>
        <argument name="apiClient" xsi:type="object">HeavyApiClient\Proxy</argument>
        <argument name="dbConnection" xsi:type="object">DatabaseConnection\Proxy</argument>
        <argument name="cacheManager" xsi:type="object">CacheManager</argument>  <!-- Not proxied -->
    </arguments>
</type>

Now apiClient and dbConnection are only created when their methods are actually called.

Performance Impact

Without proxies:
  Request → Create Service → Create API Client → Create DB → ... → Response
  Time: 500ms

With proxies:
  Request → Create Service (lightweight) → Response
  Time: 100ms (if API client not used)

When to Proxy

✅ Proxy when:

  • Dependency is expensive to create
  • Dependency might not be used in all code paths
  • Multiple heavy dependencies exist
  • Performance is critical

❌ Don't proxy when:

  • Dependency is lightweight
  • Dependency is always used
  • You need direct object access
  • Adds unnecessary complexity

Memory Considerations

// Proxy uses minimal memory until instantiated
$proxy = new ServiceProxy();  // ~1KB

// Real object uses full memory
$real = new HeavyService();   // ~100KB

// After first call, proxy holds reference to real object
$proxy->doWork();  // Now uses ~101KB total

Proxy Best Practices

Naming Convention

<!-- Append \Proxy to class name -->
<argument xsi:type="object">ClassName\Proxy</argument>

<!-- Correct -->
<argument xsi:type="object">Amazon\Prep\Service\ApiClient\Proxy</argument>

<!-- Wrong -->
<argument xsi:type="object">Amazon\Prep\Service\Proxy\ApiClient</argument>

Proxy Chain

<!-- Avoid chaining proxies -->
<argument xsi:type="object">ServiceA\Proxy\Proxy</argument>  <!-- Don't do this -->

<!-- Better: proxy only the expensive dependency -->
<argument xsi:type="object">ServiceA\Proxy</argument>

Testing Proxies

// In tests, use the real object (not proxy)
class WarrantyServiceTest extends TestCase
{
    public function testWithRealDependencies(): void
    {
        $apiClient = $this->createMock(ApiClient::class);
        $service = new WarrantyService($apiClient);
        // Test with real mock, not proxy
    }
}

Debug Proxies

# Generate proxy classes
bin/magento setup:di:compile

# Check generated proxies
cat generated/Amazon/Prep/Service/HeavyService/Proxy.php

# Verify proxy usage
grep -r "Proxy" var/di/

Common Proxy Patterns

<!-- API clients -->
<argument xsi:type="object">Api\Client\Proxy</argument>

<!-- Database-heavy services -->
<argument xsi:type="object">Service\HeavyService\Proxy</argument>

<!-- External service integrations -->
<argument xsi:type="object">Integration\ExternalService\Proxy</argument>

<!-- File system operations -->
<argument xsi:type="object">File\Handler\Proxy</argument>

Quiz

1. What does a proxy do in Magento DI?

Question 1 options

2. How do you configure a proxy in di.xml?

Question 2 options

3. When should you use a proxy?

Question 3 options

Flashcards

Question

What is a proxy?

Answer

A placeholder that delays object creation until first use (lazy loading)

Question

How do you configure a proxy?

Answer

Append \Proxy to class name in di.xml arguments

Question

When are proxies useful?

Answer

For expensive dependencies that might not be used in all code paths

Question

Proxy vs Factory: which is for lazy loading?

Answer

Proxies for lazy loading; factories for explicit creation

Question

Where are proxy classes generated?

Answer

In the generated/ directory after setup:di:compile

Revision Notes

Key Takeaways

  • 1. Proxies delay object creation until first method call
  • 2. Configure by appending \Proxy to class name in di.xml
  • 3. Proxies improve performance by avoiding unnecessary object creation
  • 4. Generated proxy classes are in the generated/ directory
  • 5. Use proxies for expensive dependencies that might not always be used

Interview Tips

  • Explain how proxies enable lazy loading
  • Know when to use proxies vs direct injection
  • Be ready to configure proxies in di.xml
  • Discuss performance implications of proxy usage

Cheat Sheet

Proxy:
  Delays creation until first use
  Configure: ClassName\Proxy

  <argument xsi:type="object">Service\Proxy</argument>

Generated: generated/Vendor/Module/Service/Proxy.php

Use for: expensive deps, optional deps, performance
Avoid for: lightweight, always-used, simple objects