How to use Plugin, Preference to rewrite Block, Model, Controller, Helper in Magento 2

When you use block, model, controller, or helper in Magento 2, it is risky if you modify the core files, which may have certain influence on another program. Therefore, we highly recommend a great solution: to rewrite all files in a convenient way. Today’s tutorial will show you how to rewrite block, model, controller, helper when using plugin and preference in Magento 2.

What are Block, Model, Controller, Helper?

Blocks are PHP classes that are used to create a link between your store’s templates and layout. Models, an integral aspect of MVC design, are utilized to carry out data actions. Controllers, which are included in the module controller folder, are in charge of performing upcoming requests. And a Helper offers functionality for the Magento website’s numerous features.

Explore How to Create Controller in Magento 2

Overview of rewriting block, model, controller, helper

Method 1: Using Plugin

Because of the big inconvenience, if using the preference, Plugin appears as the clever choice to rewrite block, model, controller, helper in Magento 2. With Plugin, you can execute the code before, after and around the code/target class’s function. Without replacing, this is just inserting some code before/after the core code and then observing the core/target class’s function and running our code in-between the core/target class’s function. In addition, it is possible for the plugins from multiple modules to insert their own code before/after/around the same core/target class’s function.

BLOCK OVERRIDE

app/code/Mageplaza/HelloWorld/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="Mageplaza-yourmodule-product-block" type="Mageplaza\HelloWorld\Plugin\ProductPlugin" sortOrder="5" />
    </type>
</config>

Here enable all the methods containing before, after, and around methods.

  • Firstly, beforeGetProduct method will be active.
  • Next, aroundGetPrduct will be active.

Note: Using the “around” method means you can insert code directly both before and after the observed function. Specifically, the function I want to mention is getProduct() and that is observed in the class Magento\Catalog\Block\Product\View.

  • Finally, afterGetProduct method will be active. You can look into the var/log/debug.log and confirm the method execution sequence.

app/code/Mageplaza/HelloWorld/Plugin/ProductPlugin.php

<?php
 
namespace Mageplaza\HelloWorld\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__ . ' - ' . __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__ . ' - ' . __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__ . ' - ' . __LINE__);
        
        // call the core observed function
        $returnValue = $proceed(); 
        
        // logging to test override        
        $logger->debug(__METHOD__ . ' - ' . __LINE__);
        
        return $returnValue;
    }    
}
?>

MODEL OVERRIDE

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

In this command, “before” and “after” methods are enabled to run the code before and after the observed method getName($product).

app/code/Mageplaza/HelloWorld/Plugin/ProductPlugin.php

<?php
 
namespace Mageplaza\HelloWorld\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/Mageplaza/HelloWorld/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="MageplazaHelloWorldControllerProductView" type="Mageplaza\HelloWorld\Plugin\ProductPlugin" sortOrder="10"/>
    </type>
</config>

In this command, “around” methods are enabled to run the code before and after the observed method execute present in class Magento\Catalog\Controller\Product\View.

app/code/Mageplaza/HelloWorld/Plugin/ProductPlugin.php

<?php
 
namespace Mageplaza\HelloWorld\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__ . ' - ' . __LINE__);
        
        // call the core observed function
        $returnValue = $proceed(); 
        
        // logging to test override        
        $logger->debug(__METHOD__ . ' - ' . __LINE__);
        
        return $returnValue;
    }
}
?>

HELPER OVERRIDE

app/code/Mageplaza/HelloWorld/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="MageplazaHelloWorldHelperData" type="Mageplaza\HelloWorld\Plugin\ProductPlugin" sortOrder="10"/>
    </type>
</config>

In this command, “around” methods are enabled to run the code before and after the observed method getProduct() present in class Magento\Catalog\Controller\Product\View.

app/code/Mageplaza/HelloWorld/Plugin/ProductPlugin.php

<?php
 
namespace Mageplaza\HelloWorld\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__ . ' - ' . __LINE__);
        
        // call the core observed function
        $returnValue = $proceed(); 
        
        // logging to test override        
        $logger->debug(__METHOD__ . ' - ' . __LINE__);
        
        return $returnValue;
    }
}
?>

Method 2: Using Preference

Preference is called as a class rewrite in Magento 1. Now you will know what you should do with the preference in order to rewrite block, model, controller, helper.

BLOCK OVERRIDE

app/code/Mageplaza/HelloWorld/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="Mageplaza\HelloWorld\Block\Catalog\Product\View" />
</config>

At that time, you are allowed to rewrite getProduct() function of class Magento\Catalog\Block\Product\View, then you only need to log some message on var/log/debug.log for this test.

app/code/Mageplaza/HelloWorld/Block/Catalog/Product/View.php

<?php
 
namespace Mageplaza\HelloWorld\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');
    }
}
?>

MODEL OVERRIDE

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

At that time, you are allowed to rewrite getName() function of class Magento\Catalog\Model\Product, then you only need to log some message on var/log/debug.log for this test.

app/code/Mageplaza/HelloWorld/Model/Catalog/Product.php

<?php
 
namespace Mageplaza\HelloWorld\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);
    }
}
?>

CONTROLLER OVERRIDE

app/code/Mageplaza/HelloWorld/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="Mageplaza\HelloWorld\Controller\Catalog\Product\View" />
</config>

At that time, you are allowed to rewrite execute() function of class Magento\Catalog\Controller\Product\View, then you only need to log some message on var/log/debug.log for this test.

app/code/Mageplaza/HelloWorld/Controller/Product/View.php

<?php
 
namespace Mageplaza\HelloWorld\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;
        }
    }    
}
?>

HELPER OVERRIDE

app/code/Mageplaza/HelloWorld/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="Mageplaza\HelloWorld\Helper\Catalog\Data" />
</config>

At that time, you are allowed to rewrite the getProduct() function of class Magento\Catalog\Helper\Data, then you only need to log some message on var/log/debug.log for this test.

app/code/Mageplaza/HelloWorld/Helper/Catalog/Data.php

<?php
 
namespace Mageplaza\HelloWorld\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');
    }
}
?>

However, the preference may cause inconvenient conflicts if two or more modules try to rewrite the same core class.

This tutorial works for all Magento 2 stores, so stores like you can follow with ease. If you still meet any trouble, please leave a comment on this topic, we will assist you as soon as possible.

Before you go, here are topics I think you’d love:

Image Description
Hello, I'm the Chief Technology Officer of Mageplaza, and I am thrilled to share my story with you. My deep love and passion for technology have fueled my journey as a professional coder and an ultra-marathon runner. Over the past decade, I have accumulated extensive experience and honed my expertise in PHP development.

People also searched for

  • magento 2 preference
  • preference magento 2
  • magento 2 plugin
  • preference in magento 2
  • magento2 preference
  • plugin in magento 2
  • magento 2 plugin example
  • plugin magento 2
  • magento 2 plugin for controller
  • magento 2 before plugin
  • magento2 plugin
  • magento preference
  • before plugin magento 2
  • magento 2 plugin after
  • magento 2 plugin before
  • magento 2 after plugin
  • magento 2 override model
  • what is plugin in magento 2
  • magento 2 override block using plugin
  • plugins magento 2
  • magento 2 override controller with plugin
  • plugin and preference in magento 2
  • create plugin magento 2
  • after plugin magento 2
  • override model in magento 2
  • plugin magento2
  • override model magento 2
  • plugin before magento 2
  • magento 2 before plugin example
  • 2.3.x, 2.4.x
x