Skip to content
intermediate Phase 29 · DI Patterns

Generated Code for DI

Understanding factories, proxies, interceptors (plugins), and the generated classes directory.

45m
0 problems
Topic Progress 0%

Factories — Object Creation Without ObjectManager

Factories provide a clean way to create new object instances without directly depending on ObjectManager.

Why factories matter:

  • ObjectManager is discouraged for direct use
  • Factories encapsulate the creation pattern
  • Allow passing runtime data during creation
  • Follow Magento coding standards

How factories work:

// Without factory (bad practice)
use Magento\Framework\ObjectManager\ObjectManager;

class BadExample
{
    public function __construct(ObjectManager $objectManager)
    {
        $this->objectManager = $objectManager;
    }
    
    public function createProduct($sku)
    {
        return $this->objectManager->create(Product::class, ['sku' => $sku]);
    }
}

// With factory (correct)
class GoodExample
{
    public function __construct(
        private \Magento\Catalog\Model\ProductFactory $productFactory
    ) {}
    
    public function createProduct($sku): Product
    {
        return $this->productFactory->create(['sku' => $sku]);
    }
}

Generated factory:

namespace Magento\Catalog\Model\ResourceModel\Product;

class ProductFactory
{
    private $objectManager;
    
    public function __construct(\Magento\Framework\ObjectManager\ObjectManager $objectManager)
    {
        $this->objectManager = $objectManager;
    }
    
    public function create(array $data = [])
    {
        return $this->objectManager->create('Magento\Catalog\Model\Product', $data);
    }
}

Factory naming convention:

  • Append Factory to the class name: Product → ProductFactory
  • Place in the same namespace as the original class
  • Generated automatically when you type-hint the class

Proxies — Lazy Loading Dependencies

Proxies defer loading of heavy dependencies until they're actually needed, reducing memory usage and startup time.

Problem: Some classes have many dependencies, but not all are used in every request.

// This loads ALL dependencies immediately, even if save() is rarely called
class OrderProcessor
{
    public function __construct(
        private ProductRepository $productRepo,
        private CustomerRepository $customerRepo,
        private PaymentProcessor $paymentProcessor,
        private InventoryService $inventoryService,
        private TaxCalculator $taxCalculator,
        private EmailService $emailService
    ) {}
}

Solution — Use a proxy:

// Only the proxy is instantiated; the real object loads on first use
class OrderProcessor
{
    public function __construct(
        private ProductRepository $productRepo,
        private Customer\CustomerRepository\Proxy $customerRepoProxy
    ) {}
    
    public function process($orderId)
    {
        // customerRepoProxy only loads CustomerRepository when this is called
        $customer = $this->customerRepoProxy->getById($customerId);
    }
}

Configuring proxies in di.xml:

<type name="Vendor\Module\Service\OrderProcessor">
    <arguments>
        <argument name="customerRepoProxy" xsi:type="object">
            Magento\Customer\Api\CustomerRepositoryInterface\Proxy
        </argument>
    </arguments>
</type>

Proxy naming convention:

  • Append \Proxy to the full class path
  • Generated in the same namespace
  • Proxies extend the original class

Generated proxy:

namespace Magento\Customer\Api\CustomerRepositoryInterface;

class Proxy extends \Magento\Customer\Model\ResourceModel\Customer\Repository
{
    private $objectManager;
    private $realInstance;
    
    public function getById($customerId)
    {
        if ($this->realInstance === null) {
            $this->realInstance = $this->objectManager->get(
                \Magento\Customer\Model\ResourceModel\Customer\Repository::class
            );
        }
        return $this->realInstance->getById($customerId);
    }
}

Interceptors — Plugin Support

Interceptors (also called plugins) are generated wrapper classes that enable before/after/around method modification.

How interceptors work:
When a class has plugins declared in di.xml, Magento generates an interceptor (Proxy) class that wraps the original method calls.

Plugin declaration:

<type name="Magento\Catalog\Model\ProductRepository">
    <plugin name="vendor_product_plugin" type="Vendor\Module\Plugin\ProductPlugin" sortOrder="10"/>
</type>

Generated interceptor structure:

namespace Magento\Catalog\Model\ResourceModel\Product;

class ProductRepository\Interceptor extends ProductRepository
{
    private $pluginManager;
    
    public function save(\Magento\Catalog\Api\Data\ProductInterface $product, $options = [])
    {
        $pluginManager = $this->pluginManager;
        
        // Before plugins
        $args = [$product, $options];
        $result = $pluginManager->callPlugin('beforeSave', $args);
        
        // Around plugins (if any)
        if ($result !== false) {
            $result = parent::save(...$args);
        }
        
        // After plugins
        $result = $pluginManager->callPlugin('afterSave', [$result]);
        
        return $result;
    }
}

Plugin execution order:

around(10) → before(10) → before(20) → Method → after(20) → after(10)

Plugin types:

  • beforeMethod — Executes before the original method, can modify arguments
  • afterMethod — Executes after, can modify the return value
  • aroundMethod — Wraps the method, can skip execution entirely

Generated Code Management

Managing generated code effectively is crucial for development and deployment.

Key generated directories:

generated/
├── code/
│   └── Vendor/Module/    # Your module's generated classes
├── di/
│   └── etc/              # Compiled DI configuration
└── metadata/             # Derived database schema

Cleaning generated code:

# Clean everything
rm -rf generated/code/*
rm -rf generated/di/*
rm -rf generated/metadata/*

# Or use Magento command
php bin/magento setup:di:compile --cleanup-generated

When generated code is stale:

  • After changing di.xml without recompiling
  • After modifying constructor signatures
  • After adding/removing plugins
  • After changing preferences

Debugging generated code:

# Check if a class is generated
grep -r "class ProductRepository" generated/code/

# Compare generated vs expected
php bin/magento setup:di:compile --dry-run

# Verify plugin registration
cat generated/di/etc/module.xml

Generated code in version control:

  • Never commit generated/ to git
  • Add to .gitignore
  • Regenerate during deployment
  • Clean before compilation

Common issues:

# Stale generated code causing errors
rm -rf generated/*
php bin/magento cache:flush
php bin/magento setup:di:compile

# Plugin not working
# 1. Check plugin is declared in di.xml
# 2. Recompile: setup:di:compile
# 3. Clear generated code if needed

Quiz

1. What naming convention do generated factories follow?

Question 1 options

2. What is the primary benefit of using proxies?

Question 2 options

3. Which generated class enables before/after/around plugin functionality?

Question 3 options

4. Should generated/ be committed to version control?

Question 4 options

Flashcards

Question

What does a factory do?

Answer

Creates new object instances without direct ObjectManager usage

Question

When is a proxy's real object actually loaded?

Answer

On the first method call to the proxy

Question

What is the naming convention for interceptors?

Answer

ClassName\Interceptor (extends original class)

Question

Where are compiled DI files stored?

Answer

generated/di/etc/

Question

How do you clean generated code?

Answer

rm -rf generated/* or setup:di:compile --cleanup-generated

Revision Notes

Key Takeaways

  • 1. Factories create objects without using ObjectManager directly
  • 2. Proxies defer loading heavy dependencies until first use
  • 3. Interceptors enable plugin (before/after/around) functionality
  • 4. All generated code lives in the generated/ directory
  • 5. Generated code must be regenerated after any DI changes
  • 6. Never commit generated/ to version control

Interview Tips

  • Explain why factories are preferred over direct ObjectManager usage
  • Describe a use case for proxies with a heavy dependency graph
  • Explain how interceptors enable the plugin system
  • Discuss generated code management in CI/CD pipelines

Cheat Sheet

Generated Code Cheat Sheet

Factories:

  • Naming: ClassNameFactory
  • Purpose: Create objects without ObjectManager
  • Usage: Type-hint the factory, call create()

Proxies:

  • Naming: ClassName\Proxy
  • Purpose: Lazy-load expensive dependencies
  • Usage: Type-hint the proxy class

Interceptors:

  • Naming: ClassName\Interceptor
  • Purpose: Enable plugin before/after/around
  • Auto-generated when plugins are declared

Directory: generated/
Never commit to git