Magento 2: Override/Rewrite Block, Model, Controller, Helper using Plugin & Preference

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

This can be done by two ways:

1) using Preference
2) using Plugin

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.

There is Plugin to address the limitation of Preference. With Plugin, we can execute our code before, after and around the core/target class’s function. 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. Multiple modules can use their own plugins to add their own code before/after/around the same core/target class’s function.

Using Preference

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

We have to define preference in in app/code/YourCompany/YourModule/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/YourCompany/YourModule/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="YourCompany\YourModule\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/YourCompany/YourModule/Block/Catalog/Product/View.php


<?php

namespace YourCompany\YourModule\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 Test');
        
        if (!$this->_coreRegistry->registry('product') && $this->getProductId()) {
            $product = $this->productRepository->getById($this->getProductId());
            $this->_coreRegistry->register('product', $product);
        }
        return $this->_coreRegistry->registry('product');
    }
}
?>

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

[2016-07-04 15:04:21] main.DEBUG: Block Override Test {“is_exception”:false} []

MODEL OVERRIDE

app/code/YourCompany/YourModule/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="YourCompany\YourModule\Model\Catalog\Product" />
</config>

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

app/code/YourCompany/YourModule/Model/Catalog/Product.php


<?php

namespace YourCompany\YourModule\Model\Catalog;

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

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

[2016-07-04 15:17:22] main.DEBUG: Model Override Test {“is_exception”:false} []

CONTROLLER OVERRIDE

app/code/YourCompany/YourModule/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="YourCompany\YourModule\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/YourCompany/YourModule/Controller/Product/View.php


<?php

namespace YourCompany\YourModule\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 Test');
        
        // 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;
        }
    }   
}
?>

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

[2016-07-04 15:30:36] main.DEBUG: Controller Override Test {“is_exception”:false} []

HELPER OVERRIDE

app/code/YourCompany/YourModule/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="YourCompany\YourModule\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/YourCompany/YourModule/Helper/Catalog/Data.php


<?php

namespace YourCompany\YourModule\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 Test');
        
        return $this->_coreRegistry->registry('current_product');
    }
}
?>

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

[2016-07-04 15:42:54] main.DEBUG: Helper Override Test {“is_exception”:false} []

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 YourCompany_YourModule.

BLOCK OVERRIDE

app/code/YourCompany/YourModule/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\Block\Product\View">
        <plugin name="yourcompany-yourmodule-product-block" type="YourCompany\YourModule\Plugin\ProductPlugin" 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/YourCompany/YourModule/Plugin/ProductPlugin.php


<?php

namespace YourCompany\YourModule\Plugin;

class ProductPlugin
{    
    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(__METHOD__ . ' -111- ' . __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(__METHOD__ . ' -222- ' . __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(__METHOD__ . ' -333- ' . __LINE__);
        
        // call the core observed function
        $returnValue = $proceed(); 
        
        // logging to test override     
        $logger->debug(__METHOD__ . ' -444- ' . __LINE__);
        
        return $returnValue;
    }    
}
?>

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

[2016-07-05 07:40:14] main.DEBUG: YourCompany\YourModule\Plugin\ProductPlugin::beforeGetProduct -111- 29 {“is_exception”:false} []
[2016-07-05 07:40:14] main.DEBUG: YourCompany\YourModule\Plugin\ProductPlugin::aroundGetProduct -333- 46 {“is_exception”:false} []
[2016-07-05 07:40:14] main.DEBUG: YourCompany\YourModule\Plugin\ProductPlugin::aroundGetProduct -444- 52 {“is_exception”:false} []
[2016-07-05 07:40:14] main.DEBUG: YourCompany\YourModule\Plugin\ProductPlugin::afterGetProduct -222- 38 {“is_exception”:false} []

MODEL OVERRIDE

app/code/YourCompany/YourModule/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="yourcompany-yourmodule-product-model" type="YourCompany\YourModule\Plugin\ProductPlugin" sortOrder="1" />
    </type> 
</config>

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

app/code/YourCompany/YourModule/Plugin/ProductPlugin.php


<?php

namespace YourCompany\YourModule\Plugin;

class ProductPlugin
{   
    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('Model Override Test before');
        
        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('Model Override Test after');
        
        return $result;
    }   
}
?>

CONTROLLER OVERRIDE

app/code/YourCompany/YourModule/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="yourCompanyYourModuleControllerProductView" type="YourCompany\YourModule\Plugin\ProductPlugin" sortOrder="10"/>
    </type>
</config>

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

app/code/YourCompany/YourModule/Plugin/ProductPlugin.php


<?php

namespace YourCompany\YourModule\Plugin;

class ProductPlugin
{       
    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(__METHOD__ . ' -111- ' . __LINE__);
        
        // call the core observed function
        $returnValue = $proceed(); 
        
        // logging to test override     
        $logger->debug(__METHOD__ . ' -222- ' . __LINE__);
        
        return $returnValue;
    }
}
?>

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

[2016-07-05 07:50:28] main.DEBUG: YourCompany\YourModule\Plugin\ProductPlugin::aroundExecute -111- 60 {“is_exception”:false} []
[2016-07-05 07:50:29] main.DEBUG: YourCompany\YourModule\Plugin\ProductPlugin::aroundExecute -222- 66 {“is_exception”:false} []

HELPER OVERRIDE

app/code/YourCompany/YourModule/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="yourCompanyYourModuleHelperData" type="YourCompany\YourModule\Plugin\ProductPlugin" 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/YourCompany/YourModule/Plugin/ProductPlugin.php


<?php

namespace YourCompany\YourModule\Plugin;

class ProductPlugin
{   
    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(__METHOD__ . ' -111- ' . __LINE__);
        
        // call the core observed function
        $returnValue = $proceed(); 
        
        // logging to test override     
        $logger->debug(__METHOD__ . ' -222- ' . __LINE__);
        
        return $returnValue;
    }
}
?>

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

[2016-07-05 07:58:27] main.DEBUG: YourCompany\YourModule\Plugin\ProductPlugin::aroundGetProduct -111- 75 {“is_exception”:false} []
[2016-07-05 07:58:27] main.DEBUG: YourCompany\YourModule\Plugin\ProductPlugin::aroundGetProduct -222- 81 {“is_exception”:false} []

Hope this helps. Thanks.