Introduction to code generation in Magento 2

Magento 2 is on the way, and with it, much excitement. Today I watched a Meet Magento DE presentation about code generation from Sergii Shymko, which  helped me in a great way to understand some of the key new concepts in Magento 2. Ever wondered when and why code gets created in /var/generation folder or needed a bigger picture behind the whole process? This talk is for you.

The following is a quick recap with the most important notes from the presentation.

Overview of code generation

Code generation can be triggered in two ways:

  1. On the fly. You declare a class with a meaningful name, that follows a certain pattern. When the system tries to autoload the class, if it doesn’t find it, it’s going to be generated. Very useful in development mode since the system does everything on its own, you don’t run generate command over and over from the command line. However, this slows down the system.
  2. Command-line. It will go through the system, inspect the code and generate all necessary classes that it might need. Useful for production, since it speeds up the system.

For the second option, you would run

magento setup:di:compile

in order to generate necessary files. Both options ends up with generating classes in MAGENTO_ROOT/var/generation directory.

What classes are generated?

There are a few class types that are being generated by the system. We’ll focus on these 3, which are the most important:

  • Factories
  • Proxies
  • Plugins

Let’s start one by one.

Factories

Factories are used to instantiate objects that can not be injected automatically.¬† For example, a product object has to be loaded from the database, but dependency injection container doesn’t have enough information to create this object. That’s why we use factories.

Let’s take a look at the existing class in Magento 2 code base app/code/Magento/Catalog/Model/Product/Copier.php

/**
 * @param CopyConstructorInterface $copyConstructor
 * @param \Magento\Catalog\Model\ProductFactory $productFactory
 */
public function __construct(
    CopyConstructorInterface $copyConstructor,
    \Magento\Catalog\Model\ProductFactory $productFactory
) {
    $this->productFactory = $productFactory;
    $this->copyConstructor = $copyConstructor;
}

Expected workflow:

  1. Developer declares a dependency on factory in constructor ($productFactory)
  2. Object manager injects this dependency
  3. Developer can access create() method (the only method in factory object) to create as many Product instances as he wants.

That’s how you, as a developer, can use factories. In the background, the system will generate a file located under /var/generation/Magento/Catalog/Model/ProductFactory.php that looks like this (simplified):

<?php
namespace Magento\Catalog\Model;

class ProductFactory
{
    public function __construct(
        \Magento\Framework\ObjectManagerInterface $objectManager, $instanceName = '\\Magento\\Catalog\\Model\\Product'
    )
    {
        $this->_objectManager = $objectManager;
        $this->_instanceName = $instanceName;
    }
    
    public function create(array $data = array())
    {
        return $this->_objectManager->create($this->_instanceName, $data);
    }
}

As we can see, the only purpose of factories is to delegate object instantiation to object managers.

Proxies

Magento 2 uses constructor injection in which all dependencies are required. You cannot instantiate an object without passing all dependencies. What if you’d like to have optional dependencies? That’s why proxies exist.

<config>
	<type name="Magento\Catalog\Model\Resource\Product\Collection">
	    <arguments>
	        <argument name="customerSession" xsi:type="object">
	        	Magento\Customer\Model\Session\Proxy
	        </argument>
	    </arguments>
	</type>
</config>

Expected workflow:

  1. Developer never touches PHP files to use proxy, only di.xml
  2. Notice a Proxy keyword appended to Magento\Customer\Model\Session\. Dependencies marked this way are optional dependencies.

In the background, the system will generate a file located under /var/generation/Magento/Customer/Model/Session/Proxy.php that looks like this (simplified):

<?php
namespace Magento\Customer\Model\Session;

class Proxy extends \Magento\Customer\Model\Session
{  
    protected function _getSubject()
    {
        if (!$this->_subject) {
            $this->_subject = true === $this->_isShared
                ? $this->_objectManager->get($this->_instanceName)
                : $this->_objectManager->create($this->_instanceName);
        }
        return $this->_subject;
    }
    ...
}

As we can see, it extends the original class, it overrides every single method which would delegate the call to the original instance. That means – whenever any proxy method is called for the first time, the original instance gets created with all its dependencies . So the only purpose of a proxy is to delay creation of an instance (and its dependencies) until the very first usage.

Plugins (Interceptors)

Simply put, plugins are the primary customization mechanisms for Magento 2. No more class rewrites. It allows you to hook in and do something before, after or around any public method of the application.

Let’s take a look at core plugin (Magento uses many plugins accross the code and encourages us to use them too):

<?php

namespace Magento\Store\App\Action\Plugin;

class StoreCheck
{
    public function aroundDispatch(
        \Magento\Framework\App\Action\Action $subject,
        \Closure $proceed,
        \Magento\Framework\App\RequestInterface $request
    ) {
        if (!$this->_storeManager->getStore()->getIsActive()) {
            throw new \Magento\Framework\Exception\State\InitException(
                __('Current store is not active.')
            );
        }
        return $proceed($request);
    }
}

Expected workflow:

  1. Developer writes a plugin class depending on his requirements (hint: see a great and easy to read, official documentation)
  2. Developer registers a plugin in di.xml like this
<config>
    <type name="Magento\Framework\App\Action\Action">
        <plugin name="storeCheck" 
                type="Magento\Store\App\Action\Plugin\StoreCheck" 
                sortOrder="10"/>
    </type>
</config>

As a result, the system will generate Interceptor class under /var/generation/Magento/Framework/App/Action/Interceptor.php

<?php 
namespace Magento\Framework\App\Action\Action;

class Interceptor extends \Magento\Framework\App\Action\Action
{
    public function dispatch(\Magento\Framework\App\RequestInterface $request) 
    {
        $pluginInfo = $this->pluginList->getNext('\\Magento\\Framework\\App\\Action\\Action', 'dispatch');
        if(!$pluginInfo) {
            return parent::dispatch($request);
        } else {
            return $this->__callPlugins('dispatch', func_get_args(), $pluginInfo);
        }
    }
}

Unlike with proxies, this time the generation tool will generate only the methods that you wanted to rewrite.

That’s about it. You can find slides from the presentation here.

Milan Stojanov is a certified Magento developer and Tuts+ author on Magento Fundamentals premium course. He likes to read and write about web development.