Magento 2: Override/Rewrite Block, Model, Controller, Helper

Magento 2: Override/Rewrite Block, Model, Controller, Helper

In most cases, when work with Magento, you will have to change the core functionality of block, model, controller and helper. It is not good if you modify the core files, that may have certain influence on another program. Therefore, today, I highly recommend the great way to rewrite all files in a convenient way.

This article shows how you can override / rewrite Block, Controller, Model and Helper using plugin and preference in Magento 2.

There are two ways to override / rewrite Block, Controller, Model, and Helper in Magento.

  1. Using Plugin : A plugin allows you to execute code before, around, or after methods from the class you’re hooking onto. Your plugin class does not replace the target class, and it is not an instance of it. You just have methods before{method}, around{method}, after{method} which get executed at the appropriate time in respect to {method} on the target class.

We don’t replace the core/target class’s code/function. We just add some code before/after the core code. It’s somehow similar to event observer. We just observe the core/target class’s function and execute our code in-between the core/target class’s function.

Since plugins do not replace the target class, any number of plugins can be active on a class simultaneously. Magento just executes them one after another based on the sortOrder parameter in your XML.

Because of that, Plugin appears as the clever choice to rewrite block, model, controller, helper in Magento 2.

  1. Using Preference : A preference is equivalent to class rewriting from Magento 1. It’s equivalent to saying, “Whenever code asks for ClassA, give them MyClassB instead.” MyClassB is expected to be a complete implementation of ClassA, plus whatever behavior you add or modify on top.

As in Magento 1, only one preference (rewrite) can be active for a class at one time unless you chain them manually (such that MyClassB extends OtherClassB, and OtherClassB extends ClassA).

Preference is similar to class rewrite in Magento 1. There is always possibility of conflict when two or more custom modules try to rewrite/override same core class.

Let’s see, how we can override / rewrite Block, Controller, Model and Helper using plugin and preference in Magento 2.

To do this, I am going to create two modules in Magento.

  1. DemoOverridePlugin – This module is to test the Plugin method.
  2. DemoOverridePreference – This module is to test the Preference method.

If you want to learn more about creating custom module in Magento2, Please follow this link – https://iyngaran.com/creating-running-cron-in-magento-2-custom-module

Method 1: Using Plugin

Plugin observes the core/target class’s function and executes some code before and after the observed function. Just like Preferences, plugins are also declared in etc/di.xml file.

Here are the examples of rewriting Block, Model, Controller, and Helper class using Plugin. I suppose the module name as Iyngaran_DemoOverridePlugin.

Block Override

app/code/Iyngaran/DemoOverridePlugin/etc/di.xml

    ```xml
    <?xml version="1.0"?>

    <config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
        xsi:noNamespaceSchemaLocation="urn:magento:framework:ObjectManager/etc/config.xsd">
        <type name="Magento\Catalog\Block\Product\View">
        <plugin name="iyngaran-demo-override-plugin-block" 
            type="Iyngaran\DemoOverridePlugin\Plugin\ProductPluginView" 
            sortOrder="5" />
        </type>
    </config>
    ```

Here, we use all the methods: before, after, and around methods. With around method, we can add directly add code both before and after the observed function. As you can see above, that we have observed core class Magento\Catalog\Block\Product\View. In the below plugin code, we observe the function getProduct() which is present in the class Magento\Catalog\Block\Product\View. At first the beforeGetProduct method will be executed. After that, aroundGetProduct method will be executed. Finally, afterGetProduct method will be executed. You can look into the var/log/debug.log and confirm the method execution sequence.

app/code/Iyngaran/DemoOverridePlugin/Plugin/ProductPluginView.php

<?php
namespace Iyngaran\DemoOverridePlugin\Plugin;

class ProductPluginView
{
    public function beforeGetProduct(\Magento\Catalog\Block\Product\View $subject)
    {
        // logging to test override
        $logger = \Magento\Framework\App\ObjectManager::getInstance()->get('\Psr\Log\LoggerInterface');
        $logger->debug('It is from DemoOverridePlugin '.__METHOD__ . ' line - ' . __LINE__);
    }

    public function afterGetProduct(\Magento\Catalog\Block\Product\View $subject, $result)
    {
        // logging to test override
        $logger = \Magento\Framework\App\ObjectManager::getInstance()->get('\Psr\Log\LoggerInterface');
        $logger->debug('It is from DemoOverridePlugin '.__METHOD__ . ' line - ' . __LINE__);

        return $result;
    }

    public function aroundGetProduct(\Magento\Catalog\Block\Product\View $subject, \Closure $proceed)
    {
        // logging to test override
        $logger = \Magento\Framework\App\ObjectManager::getInstance()->get('\Psr\Log\LoggerInterface');
        $logger->debug('It is from DemoOverridePlugin '.__METHOD__ . ' line - ' . __LINE__);

        // call the core observed function
        $returnValue = $proceed();

        // logging to test override
        $logger->debug('It is from DemoOverridePlugin '.__METHOD__ . ' line - ' . __LINE__);

        return $returnValue;
    }
}
?>

That’s it!.

Clear cache and reload the product page.

The log message is saved something like below in var/log/debug.log:

[2017-03-30 12:43:45] main.DEBUG: It is from DemoOverridePlugin 
Iyngaran\DemoOverridePlugin\Plugin\ProductPluginView::aroundGetProduct line - 32 {"is_exception":false} []
[2017-03-30 12:43:45] main.DEBUG: It is from DemoOverridePlugin 
Iyngaran\DemoOverridePlugin\Plugin\ProductPluginView::afterGetProduct line - 17 {"is_exception":false} []
[2017-03-30 12:43:45] main.DEBUG: It is from DemoOverridePlugin 
Iyngaran\DemoOverridePlugin\Plugin\ProductPluginView::beforeGetProduct line - 10 {"is_exception":false} []
[2017-03-30 12:43:45] main.DEBUG: It is from DemoOverridePlugin 
Iyngaran\DemoOverridePlugin\Plugin\ProductPluginView::aroundGetProduct line - 26 {"is_exception":false} []
[2017-03-30 12:43:45] main.DEBUG: It is from DemoOverridePlugin 
Iyngaran\DemoOverridePlugin\Plugin\ProductPluginView::aroundGetProduct line - 32 {"is_exception":false} []
[2017-03-30 12:43:45] main.DEBUG: It is from DemoOverridePlugin 
Iyngaran\DemoOverridePlugin\Plugin\ProductPluginView::afterGetProduct line - 17 {"is_exception":false} []

Model Override

app/code/Iyngaran/DemoOverridePlugin/etc/di.xml

<?xml version="1.0"?>

<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
    xsi:noNamespaceSchemaLocation="urn:magento:framework:ObjectManager/etc/config.xsd">
    <type name="Magento\Catalog\Model\Product">
        <plugin name="iyngaran-demo-override-plugin-model" 
            type="Iyngaran\DemoOverridePlugin\Plugin\ProductPluginModel" 
            sortOrder="1" />
    </type>    
</config>

Here, we use “before” and “after” method to execute code before and after the observed method getName($product).

app/code/Iyngaran/DemoOverridePlugin/Plugin/ProductPluginModel.php

<?php
namespace Iyngaran\DemoOverridePlugin\Plugin;

class ProductPluginModel
{
    public function beforeSetName(\Magento\Catalog\Model\Product $subject, $name)
    {
        // logging to test override
        $logger = \Magento\Framework\App\ObjectManager::getInstance()->get('\Psr\Log\LoggerInterface');
        $logger->debug('Iyngaran Model Override Test before by Plugin');

        return $name;
    }

    public function afterGetName(\Magento\Catalog\Model\Product $subject, $result)
    {
        // logging to test override
        $logger = \Magento\Framework\App\ObjectManager::getInstance()->get('\Psr\Log\LoggerInterface');
        $logger->debug('Iyngaran Model Override Test after by Plugin');

        return $result;
    }
}
?>

That’s it!.

Clear cache and reload the product page.

The log message is saved something like below in var/log/debug.log:

[2017-03-30 12:59:14] main.DEBUG: Model Override Test - Iyngaran {"is_exception":false} []
[2017-03-30 12:59:14] main.DEBUG: Iyngaran Model Override Test after by Plugin {"is_exception":false} []

Controller Override

app/code/Iyngaran/DemoOverridePlugin/etc/di.xml

<?xml version="1.0"?>

<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
    xsi:noNamespaceSchemaLocation="urn:magento:framework:ObjectManager/etc/config.xsd">
    <type name="Magento\Catalog\Controller\Product\View">
        <plugin name="iyngaran-demo-override-plugin-controller" 
            type="Iyngaran\DemoOverridePlugin\Plugin\ProductPluginController" 
            sortOrder="10" />
    </type>
</config>

Here, we use “around” method to execute code before and after the observed method executepresent in class Magento\Catalog\Controller\Product\View.

app/code/Iyngaran/DemoOverridePlugin/Plugin/ProductPluginController.php

<?php
namespace Iyngaran\DemoOverridePlugin\Plugin;

class ProductPluginController
{
    public function aroundExecute(\Magento\Catalog\Controller\Product\View $subject, \Closure $proceed)
    {
        // logging to test override
        $logger = \Magento\Framework\App\ObjectManager::getInstance()->get('\Psr\Log\LoggerInterface');
        $logger->debug('It is from DemoOverridePlugin '.__METHOD__ . ' line - ' . __LINE__);

        // call the core observed function
        $returnValue = $proceed();

        // logging to test override
        $logger->debug('It is from DemoOverridePlugin '.__METHOD__ . ' line - ' . __LINE__);

        return $returnValue;
    }
}
?>

That’s it!.

Clear cache and reload the product page.

The log message is saved something like below in var/log/debug.log:

[2017-03-30 13:14:44] main.DEBUG: It is from DemoOverridePlugin 
Iyngaran\DemoOverridePlugin\Plugin\ProductPluginController::aroundExecute 
line - 10 {"is_exception":false} []

Helper Override

app/code/Iyngaran/DemoOverridePlugin/etc/di.xml

<?xml version="1.0"?>

<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
    xsi:noNamespaceSchemaLocation="urn:magento:framework:ObjectManager/etc/config.xsd">
    <type name="Magento\Catalog\Helper\Data">
        <plugin name="iyngaran-demo-override-plugin-helper" 
            type="Iyngaran\DemoOverridePlugin\Plugin\ProductPluginHelper" 
            sortOrder="10"/>
    </type>
</config>

Here, we use “around” method to execute code before and after the observed method getProduct() present in class Magento\Catalog\Helper\Data.

app/code/Iyngaran/DemoOverridePlugin/Plugin/ProductPluginHelper.php

<?php
namespace Iyngaran\DemoOverridePlugin\Plugin;

class ProductPluginHelper
{
    public function aroundGetProduct(\Magento\Catalog\Helper\Data $subject, \Closure $proceed)
    {
        // logging to test override
        $logger = \Magento\Framework\App\ObjectManager::getInstance()->get('\Psr\Log\LoggerInterface');
        $logger->debug('Helper override '.__METHOD__ .__LINE__);

        // call the core observed function
        $returnValue = $proceed();

        // logging to test override
        $logger->debug('Helper override '.__METHOD__ . __LINE__);

        return $returnValue;
    }
}
?>

That’s it!.

Clear cache and reload the product page.

The log message is saved something like below in var/log/debug.log:

[2017-03-31 03:40:11] main.DEBUG: Helper override 
Iyngaran\DemoOverridePlugin\Plugin\ProductPluginHelper::aroundGetProduct16 
{"is_exception":false} []
[2017-03-31 03:40:11] main.DEBUG: Helper override 
Iyngaran\DemoOverridePlugin\Plugin\ProductPluginHelper::aroundGetProduct10 
{"is_exception":false} []

Here is the complete source code for DemoOverridePlugin

Method 2: Using Preference

Here are the examples of rewriting Block, Model, Controller, and Helper class using Preference. I suppose the module name as Iyngaran_DemoOverridePreference.

We have to define preference in in app/code/Iyngaran/DemoOverridePreference/etc/di.xml. There we define the core/target class which we want to rewrite. We also define our module’s class that is going to rewrite the core/target class.

Block Override

app/code/Iyngaran/DemoOverridePreference/etc/di.xml

<?xml version="1.0"?>

<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
    xsi:noNamespaceSchemaLocation="urn:magento:framework:ObjectManager/etc/config.xsd">
    <preference for="Magento\Catalog\Block\Product\View" 
        type="Iyngaran\DemoOverridePreference\Block\Catalog\Product\View" />
</config>

Let’s rewrite getProduct() function of class Magento\Catalog\Block\Product\View. We will just log some message on var/log/debug.log for this test.

app/code/Iyngaran/DemoOverridePreference/Block/Catalog/Product/View.php

<?php

namespace Iyngaran\DemoOverridePreference\Block\Catalog\Product;

class View extends \Magento\Catalog\Block\Product\View
{
    /**
     * Retrieve current product model
     *
     * @return \Magento\Catalog\Model\Product
     */
    public function getProduct()
    {
        // logging to test override
        $logger = \Magento\Framework\App\ObjectManager::getInstance()->get('\Psr\Log\LoggerInterface');
        $logger->debug('Block Override '.__METHOD__ .__LINE__);

        if (!$this->_coreRegistry->registry('product') && $this->getProductId()) {
            $product = $this->productRepository->getById($this->getProductId());
            $this->_coreRegistry->register('product', $product);
        }
        return $this->_coreRegistry->registry('product');
    }
}
?>

That’s it!.

Clear cache and reload the product page.

The log message is saved something like below in var/log/debug.log:

[2017-03-31 04:15:23] main.DEBUG: Block Override 
Iyngaran\DemoOverridePreference\Block\Catalog\Product\View::getProduct 
Line - 16 {"is_exception":false} []

Model Override

app/code/Iyngaran/DemoOverridePreference/etc/di.xml

<?xml version="1.0"?>

<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
    xsi:noNamespaceSchemaLocation="urn:magento:framework:ObjectManager/etc/config.xsd">
    <preference for="Magento\Catalog\Model\Product" 
        type="Iyngaran\DemoOverridePreference\Model\Catalog\Product" />
</config>

Let’s rewrite getName() function of class Magento\Catalog\Model\Product and will add ‘Hello – ’ to the product name. So we will be able to see the ‘Hello – ’ text in front of the product name in product page. And also we will just log some message on var/log/debug.log for this test.

app/code/Iyngaran/DemoOverridePreference/Model/Catalog/Product.php

<?php

namespace Iyngaran\DemoOverridePreference\Model\Catalog;

class Product extends \Magento\Catalog\Model\Product
{
    /**
     * Get product name
     *
     * @return string
     */
    public function getName()
    {
        // logging to test override
        $logger = \Magento\Framework\App\ObjectManager::getInstance()->get('\Psr\Log\LoggerInterface');
        $logger->debug('Model Override '.__METHOD__ .' Line - '.__LINE__);
        return "Hello - ".$this->_getData(self::NAME);
    }
}
?>

That’s it!.

Clear cache and reload the product page.

Look at the product name in product page and category page, all products will have ‘Hello – ’ in front of the name.

The log message is saved something like below in var/log/debug.log:

[2017-03-31 04:29:49] main.DEBUG: Model Override 
Iyngaran\DemoOverridePreference\Model\Catalog\Product::getName 
Line - 17 {"is_exception":false} []

Controller Override

app/code/Iyngaran/DemoOverridePreference/etc/di.xml

<?xml version="1.0"?>

<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
    xsi:noNamespaceSchemaLocation="urn:magento:framework:ObjectManager/etc/config.xsd">
    <preference for="Magento\Catalog\Controller\Product\View" 
        type="Iyngaran\DemoOverridePreference\Controller\Catalog\Product\View" />
</config>

Let’s rewrite execute() function of class Magento\Catalog\Controller\Product\View. We will just log some message on var/log/debug.log for this test.

app/code/Iyngaran/DemoOverridePreference/Controller/Product/View.php

<?php

namespace Iyngaran\DemoOverridePreference\Controller\Catalog\Product;

class View extends \Magento\Catalog\Controller\Product\View
{
    /**
     * Product view action
     *
     * @return \Magento\Framework\Controller\Result\Forward|\Magento\Framework\Controller\Result\Redirect
     */
    public function execute()
    {
        // logging to test override
        $logger = \Magento\Framework\App\ObjectManager::getInstance()->get('\Psr\Log\LoggerInterface');
        $logger->debug('Controller Override '.__METHOD__ .' Line - '.__LINE__);

        // Get initial data from request
        $categoryId = (int) $this->getRequest()->getParam('category', false);
        $productId = (int) $this->getRequest()->getParam('id');
        $specifyOptions = $this->getRequest()->getParam('options');

        if ($this->getRequest()->isPost() && $this->getRequest()->getParam(self::PARAM_NAME_URL_ENCODED)) {
            $product = $this->_initProduct();
            if (!$product) {
                return $this->noProductRedirect();
            }
            if ($specifyOptions) {
                $notice = $product->getTypeInstance()->getSpecifyOptionMessage();
                $this->messageManager->addNotice($notice);
            }
            if ($this->getRequest()->isAjax()) {
                $this->getResponse()->representJson(
                    $this->_objectManager->get('Magento\Framework\Json\Helper\Data')->jsonEncode([
                        'backUrl' => $this->_redirect->getRedirectUrl()
                    ])
                );
                return;
            }
            $resultRedirect = $this->resultRedirectFactory->create();
            $resultRedirect->setRefererOrBaseUrl();
            return $resultRedirect;
        }

        // Prepare helper and params
        $params = new \Magento\Framework\DataObject();
        $params->setCategoryId($categoryId);
        $params->setSpecifyOptions($specifyOptions);

        // Render page
        try {
            $page = $this->resultPageFactory->create(false, ['isIsolated' => true]);
            $this->viewHelper->prepareAndRender($page, $productId, $this, $params);
            return $page;
        } catch (\Magento\Framework\Exception\NoSuchEntityException $e) {
            return $this->noProductRedirect();
        } catch (\Exception $e) {
            $this->_objectManager->get('Psr\Log\LoggerInterface')->critical($e);
            $resultForward = $this->resultForwardFactory->create();
            $resultForward->forward('noroute');
            return $resultForward;
        }
    }
}
?>

That’s it!.

Clear cache and reload the product page.

The log message is saved something like below in var/log/debug.log:

[2017-03-31 04:43:30] main.DEBUG: Controller Override 
Iyngaran\DemoOverridePreference\Controller\Catalog\Product\View::execute 
Line - 16 {"is_exception":false} []

Helper Override

app/code/Iyngaran/DemoOverridePreference/etc/di.xml

<?xml version="1.0"?>

<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
    xsi:noNamespaceSchemaLocation="urn:magento:framework:ObjectManager/etc/config.xsd">
    <preference for="Magento\Catalog\Helper\Data" 
        type="Iyngaran\DemoOverridePreference\Helper\Catalog\Data" />
</config>

Let’s rewrite getProduct() function of class Magento\Catalog\Helper\Data. We will just log some message on var/log/debug.log for this test.

app/code/Iyngaran/DemoOverridePreference/Helper/Catalog/Data.php

<?php

namespace Iyngaran\DemoOverridePreference\Helper\Catalog;

class Data extends \Magento\Catalog\Helper\Data
{
    /**
     * Retrieve current Product object
     *
     * @return \Magento\Catalog\Model\Product|null
     */
    public function getProduct()
    {
        // logging to test override
        $logger = \Magento\Framework\App\ObjectManager::getInstance()->get('\Psr\Log\LoggerInterface');
        $logger->debug('Helper Override '.__METHOD__ .' Line - '.__LINE__);

        return $this->_coreRegistry->registry('current_product');
    }
}
?>

That’s it!.

Clear cache and reload the product page.

The log message is saved something like below in var/log/debug.log:

[2017-03-31 05:00:36] main.DEBUG: Helper Override 
Iyngaran\DemoOverridePreference\Helper\Catalog\Data::getProduct 
Line - 16 {"is_exception":false} []

Here is the complete source code for DemoOverridePreference

Hope this helps.