Magento 2 shipped with a new concept of dependency injection where dependent objects, classes are passed as arguments in a class constructor method instead of those objects, classes are manually created inside of a class. This way overriding and manipulating with classes is much easier and allows us more ways of extending the core functionalities.

Which dependencies have to be injected in classes are controlled by the di.xml file. Each module can have a global and area-specific di.xml file that can be used depending on scope. Paths for module di.xml files:

Vendor/ModuleName/etc/di.xml
Vendor/ModuleName/etc/<area>/di.xml (<area> is called 'scope' in Magento 2 this can be either adminhtml or frontend).

It’s important to note that there are no more differences between overriding block, model, helper, controller or something else. They are all classes that can be overridden. There are three different ways of extending core Magento classes and methods.

Class preference

Let’s call this the old-fashioned way of overriding classes that we got used to, but slightly different. All the classes are defined by their interfaces and configured by di.xml files. There’s an abstraction-implementation mapping implemented when the constructor signature of a class requests an object by its interface. That means that interfaces should be used, where available, and the mapping will tell which class should be initiated.

Let’s take a look at how catalog product class is defined in the di.xml file of the Catalog module:

<config>
    <preference for="Magento\Catalog\Api\Data\ProductInterface" type="Magento\Catalog\Model\Product" />
</config>

To override Magento\Catalog\Model\Product class all we need is to define our preference and to create a file that will extend the original class:

<config>
    <preference for="Magento\Catalog\Api\Data\ProductInterface" type="Test\Catalog\Model\Product" />
</config>
<?php
namespace Test\Catalog\Model;
 
class Product extends \Magento\Catalog\Model\Product
{
// code
}

To make sure we have the right order of module dependencies, etc/module.xml should have module sequence for Magento_Catalog like below

<sequence>
    <module name="Magento_Catalog" />
</sequence>

 Plugins

Rewriting by class preference can cause conflicts if multiple classes extend the same original class. To help solve this problem a new concept of plugins is introduced. Plugins extend methods and do not change the class itself as rewriting by class preference does, but intercept a method call before, after or around its call.

Plugins are configured in the di.xml files and they are called before, after or around methods that are being overridden. The first argument is always an object of the observed method’s name followed by arguments of the original method.

As an example we’re going to extend a few methods from the catalog product module. This is how the di.xml file would look like:

<config>
    <type name="Magento\Catalog\Api\Data\ProductInterface">
        <plugin name="test_catalog_product" type="Test\Catalog\Plugin\Model\Product" />
    </type>
</config>
Before method

Before plugin is run prior to an observed method and has to return the same number of arguments  in array that the method accepts or null – if the method should not be modified. Method that’s being extended has to have the same name with prefix “before”.

<?php
namespace Test\Catalog\Plugin\Model;
 
class Product
{
    public function beforeSetPrice(\Magento\Catalog\Model\Product $subject, $price)
    {
        $price += 10;
        return [$price];
    }
}
After method

After methods are executed after the original method is called. For arguments, it takes the original class as $subject and also must return $result. Method that’s being extended has to have the same name with prefix “after”.

<?php
namespace Test\Catalog\Plugin\Model;
 
class Product
{
    public function afterGetName(\Magento\Catalog\Model\Product $subject, $result)
    {
        $result .= ' (Test)';
        return $result;
    }
}
Around method

Around methods wrap the original method and allow code execution before and after the original method call. Next to a class object the method accepts another argument receives is callable that allows other plugins call in the chain. Method that’s being extended has to have the same name with prefix “around”.

<?php
namespace Test\Catalog\Plugin\Model;
 
class Product
{
    public function aroundSave(\Magento\Catalog\Model\Product $subject, \callable $proceed)
    {
        // before save
        $result = $proceed();
        // after save
 
        return $result;
    }
}

Using plugins looks like an ideal solution for overriding methods, but it comes with limitations. Plugins cannot be used for all types of methods and other solutions will have to be looked for when trying to extends the following methods:

  • Objects that are instantiated before Magento\Framework\Interception is bootstrapped
  • Final methods
  • Final classes
  • Any class that contains at least one final public method
  • Non-public methods
  • Class methods (such as static methods)
  • __construct
  • Virtual types

Constructor arguments

di.xml defines which dependencies will be injected into a class. This means, they can be controlled and changed to something that will be useful for us. If a change does not need to be global, but specific for a class, instead of overriding the whole class or creating plugins for different methods we’re able to configure arguments that the class receives.

Type configuration

One of the arguments that catalog product module receives is a helper \Magento\Catalog\Helper\Product $catalogProduct. With di.xml we can configure to use our helper instead:

<config>
    <type name="Magento\Catalog\Api\Data\ProductInterface">
        <arguments>
            <argument name="catalogProduct" xsi:type="object">Test\Catalog\Helper\Product</argument>
        </arguments>
    </type>
</config>

Different argument types are allowed, depending of what is being changed. Allowed types are

object, string, boolean, number, const, null, array and init_parameter.

Virtual type configuration

In the documentation virtual type is defined as a type that allows you to change the arguments of a specific injectable dependency and change the behavior of a particular class.

If take the previous example, instead of injecting a new helper, there are occasions when changing just one argument in the constructor method of the helper will do the job and creating a new file is not really necessary.

So, that would mean creating a virtual type from Magento\Catalog\Helper\Product, changing its arguments and using that virtual helper as an argument to another class.

<config>
    <type name="virtualHelper" type="Magento\Catalog\Helper\Product">
        <arguments>
            <argument name="catalogSession" xsi:type="object">Test\Catalog\Model\Session\Proxy</argument>
        </arguments>
    </type>
    <type name="Magento\Catalog\Api\Data\ProductInterface">
        <arguments>
            <argument name="catalogProduct" xsi:type="object">virtualHelper</argument>
        </arguments>
    </type>
</config>

Virtual types come in handy when only construct arguments of dependencies have to be changed without creating any additional files and by just configuring it in xml files.

Categorized in: