Overriding classes in Magento 2

Overriding classes in Magento 2

Compared to its previous version, Magento 2 came out with a new concept of dependency injection where classes inject dependencies (different objects) for an object instead of that object manually creating them internally. That way overriding and manipulating with classes is much easier and allows us more ways of extending the native 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:

<moduleDir>/etc/di.xml
<moduleDir>/etc/<area>/di.xml

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. We’ll go through three different ways of extending native 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="Inchoo\Catalog\Model\Product" />
</config>
<?php
namespace Inchoo\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 defined module sequence for Magento_Catalog in our example.

 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="inchoo_catalog_product" type="Inchoo\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 Inchoo\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. Next to a class object the method accepts one more argument and that’s the result that also must return. Method that’s being extended has to have the same name with prefix “after”.

<?php
namespace Inchoo\Catalog\Plugin\Model;
 
class Product
{
    public function afterGetName(\Magento\Catalog\Model\Product $subject, $result)
    {
        $result .= ' (Inchoo)';
        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 Inchoo\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 configures which dependencies will be injected into a class what means that they can be controlled and changed to something that will be useful for us. If a change does not need to be global, but for a specific 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">Inchoo\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 we go back to a helper example, instead of injecting a new helper, there are occasions when changing just one argument in the original helper will do the job and creating a new file is redundant. In our example that would mean creating a virtual type from Magento\Catalog\Helper\Product, changing its arguments and using that virtual helper as an argument of a catalog product class.

<config>
    <virtualType name="virtualHelper" type="Magento\Catalog\Helper\Product">
        <arguments>
            <argument name="catalogSession" xsi:type="object">Inchoo\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.

There’s more than one way of overriding classes and methods and choosing which one to use will depend on a situation you run into. While using a class preference as a way of overriding may look like the easiest way which will work in most situations, it’s the cause of many conflicts when different modules try to override the same classes and the same methods, which is why all the ways should be taken into consideration.

Related Inchoo Services

You made it all the way down here so you must have enjoyed this post! You may also like:

3 best open-source eCommerce platforms in 2021 Zrinka Antolovic
Zrinka Antolovic, | 8

3 best open-source eCommerce platforms in 2021

Introducing BrexitCart by Inchoo – a new cart abandonment solution Aron Stanic
Aron Stanic, | 1

Introducing BrexitCart by Inchoo – a new cart abandonment solution

Ready for 2nd Meet Magento Croatia with agenda focused on PWA? Book the dates! Maja Kardum
Maja Kardum, | 0

Ready for 2nd Meet Magento Croatia with agenda focused on PWA? Book the dates!

13 comments

  1. I want to replace a public function using the around plugin, but inside my public function, many private functions have been called, how to handle it.

    Core (parent) class


    class a{
    private getClient(){
    return $this->anotherPrivateFunction();
    }
    private anotherPrivateFunction(){
    return 'someData';
    }

    /**
    * "customMethod" function that needs to customize
    **/
    public function customMethod(){
    $data = ['a', 'b', 'c',]; // here i need to add some additional element in $data array using around plugin
    $client = $this->getClient();
    return $client->process($client, $data);
    }
    }

    How I can achieve it, Please help..

  2. I want to extend below abstract class’s protected function.

    namespace Magento\ImportExport\Model\Import\Entity;
    abstract class AbstractEntity {
    protected function _prepareRowForDb(array $rowData)
    }
    Is there any way to override above function please let me know, any ideas would be appreciated. !
    Thanks in advance.!!

  3. i want override abstractpdf class but its now working when i override it
    plz tell me how override abstract class and modify method of this class

  4. I got the problem
    Area code not set: Area code must be set before starting a session.
    Its resolution is
    In construct use
    public function __construct(
    \Magento\Framework\App\State $state,
    array $commands = []
    )
    {
    $this->state = $state;
    if (!$this->state->validateAreaCode()) {
    $this->state->setAreaCode(\Magento\Framework\App\Area::AREA_ADMINHTML);
    }
    $this->commands = $commands;
    parent::__construct(‘webjump:createinvoice’);
    }

  5. Is it valid to override block in the extension which will be submitted on Magento Market Place?

    1. I don’t see why not. The extension has to pass their extension quality check (MEQP2) in order to submit it. More about their coding standard can be read in our blog post Magento Coding Standards.

  6. If I’ve understood this correctly, in all these cases you still have to modify a core file – that being di.xml – right?

    1. Hi,
      There is no core di.xml file in Magento2. As its name suggests, (di = Dependency Injection) this xml file in /app/etc/di.xml is created during compliation process when you run command “php bin/magento setup:di:complie”. During the process Magento 2 reads every di.xml file present in every Magento2 module in /vendor/magento/module- directory and combines them all into a single xml tree and puts them in di.xml file. So there can be n number of contributed modules in your installation and Magento2 can chuck this information out of every contributed module and put it in di.xml. If you delete di.xml and re-run the compile command, the di.xml will be created again. So there is no core di.xml. You have to declare your own di.xml which will let Magento2 know that what core classes your custom classes are being override or intercept in your module. When during runtime the core or third party class will be needed to instantiate, Magento2 will read /app/etc/di.xml and provide your class instead of that core / third party module class.

  7. Please fix this

    <type name="virtualHelper" type="Magento\Catalog\Helper\Product">
            <arguments>
                <argument name="catalogSession" xsi:type="object">Inchoo\Catalog\Model\Session\Proxy</argument>
            </arguments>
        </type>

    To

    <virtualType name="virtualHelper" type="Magento\Catalog\Helper\Product">
            <arguments>
                <argument name="catalogSession" xsi:type="object">Inchoo\Catalog\Model\Session\Proxy</argument>
            </arguments>
        </virtualType>

    .

  8. Quite informative. Conflicts in modules are common occurrence when it comes to large website which have dependent modules. One trick that i have used myself and it works for me nicely is to make modules dependent on each other by using , by doing this i force the order in which modules are rewritten.

Leave a Reply

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes: <a href="" title=""> <blockquote cite=""> <code> <del datetime=""> <em> <s> <strike> <strong>. You may use following syntax for source code: <pre><code>$current = "Inchoo";</code></pre>.

Tell us about your project

Drop us a line. We'd love to know more about your project.