52

I'm stuck in overriding core model Blocks and Controllers in Magento 2. Can any one help on this?

Let's take list toolbar as example where I need to add a new sort option called sort by most popular. How do I add it? I guess for this we need to add option in block level and condition in List.php collection level.

6
  • 1
    Overriding core classes is bad idea, and can be done in many different ways. Can you describe your specific case?
    – KAndy
    Commented Oct 15, 2015 at 5:22
  • @KAndy :- lets take example of list toolbar where i need to add new sort option called sort by most popular then how to add it i hope for this we need to add option in block level and condition in List.php collection level Commented Oct 15, 2015 at 5:32
  • You exactly need to use after Execute Plugin on \Magento\Catalog\Block\Product\ProductList\Toolbar::getAvailableOrders for this. If any other will use plugins customer gets all orders. in case of using rewrites, you gets module conflicts and one module will not work
    – KAndy
    Commented Oct 15, 2015 at 7:12
  • @KAndy :- can you please give example code i am not getting plugin i need di.xml and plugin php code how it works and also how to add new column for admin grid using plugin ex order grid please help me in plugin ex code Commented Oct 15, 2015 at 10:22
  • @Kandy :- please share any plugin code example in product model add some text with product getname () Commented Oct 16, 2015 at 4:42

8 Answers 8

29

Magento2 gave very good concept called Plugin

we can do what ever after and before core function and also we have one more called around which will do both before and after below is code which will cover all info

Create a file di.xml in Mymodule/etc/di.xml

<?xml version="1.0"?>
<!--
/**
 * Copyright © 2015 Magento. All rights reserved.
 * See COPYING.txt for license details.
 */
-->
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../../../lib/internal/Magento/Framework/ObjectManager/etc/config.xsd">
   <type name="Magento\Catalog\Block\Product\View">
        <plugin name="inroduct-custom-module" type="Sugarcode\Test\Block\Plugin\Product\View" sortOrder="1"/>
    </type>
    <type name="Magento\Catalog\Model\Product">
        <plugin name="getname-test-module" type="Sugarcode\Test\Model\Plugin\Product" sortOrder="10"/>
    </type>
</config>

in this i took example of product Model and Product View Block

I used around in Product View block that is any function use prefix around and then make sure 2 parameter should be there is first one is which object your using 2nd one Closure which is retain old return info

<?php
namespace Sugarcode\Test\Block\Plugin\Product;

class View 
{ 
    public function aroundGetProduct(\Magento\Catalog\Block\Product\View $subject, \Closure $proceed)
    {

        echo 'Do Some Logic Before <br>';
        $returnValue = $proceed(); // it get you old function return value
        //$name='#'.$returnValue->getName().'#';
        //$returnValue->setName($name);
        echo 'Do Some Logic  After <br>';
        return $returnValue; // if its object make sure it return same object which you addition data
    }


}

In model i Used before and after that is

<?php
/**
 * Copyright © 2015 Magento. All rights reserved.
 * See COPYING.txt for license details.
 */
namespace Sugarcode\Test\Model\Plugin;

class Product
{        
    public function beforeSetName(\Magento\Catalog\Model\Product $subject, $name)
    {
        return array('(' . $name . ')');
    }

     public function afterGetName(\Magento\Catalog\Model\Product $subject, $result)
    {
        return '|' . $result . '|';
    }

}

in this way we can retain old code so if tomorrow Magento core code is updated we will have both new updated code and our custom logic if we directly override then we lost new updated code of that function or file :-)

http://devdocs.magento.com/guides/v2.0/extension-dev-guide/plugins.html

4
  • What if you want to add new method to a class? Other than preference what option do we have?
    – MagePsycho
    Commented Sep 24, 2016 at 17:40
  • @MagePsycho :- if you have something new function it means its out from magento. if its block create new block and extend it from core but with out preference. if some model then write sequence i hope no other way Commented Sep 28, 2016 at 13:16
  • Only 'public' methods can be served from plugins. If you need to extend or override any private or protected method, then you need to re-write that class Commented Nov 11, 2020 at 10:25
  • @AhsanHorani :-- you have to check in which public function that protected function is called for that public function write around plugin and modify the functions behavior Commented Nov 20, 2020 at 5:16
20

Finally I got It !!!!
I follow below steps to override Block, Controller and Model I took Exmaple of Product Model and Product View Block and View Controller/Action

Create a file called di.xml in your /etc/di.xml

<?xml version="1.0"?>
<!--
/**
 * Copyright © 2015 Magento. All rights reserved.
 * See COPYING.txt for license details.
 */
-->
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../../../lib/internal/Magento/Framework/ObjectManager/etc/config.xsd">
    <preference for="Magento\Catalog\Model\Product" type="Sugarcode\Test\Model\Product" />
    <preference for="Magento\Catalog\Block\Product\View" type="Sugarcode\Test\Block\Product\View" />
    <preference for="Magento\Catalog\Controller\Product\View" type="Sugarcode\Test\Controller\Product\View" />
</config>

Then I created Model File in /Model/Product.php

<?php
/**
 * Copyright © 2015 Magento. All rights reserved.
 * See COPYING.txt for license details.
 */
namespace Sugarcode\Test\Model;

class Product extends \Magento\Catalog\Model\Product
{
    /**
     * Get product name
     *
     * @return string
     * @codeCoverageIgnoreStart
     */
    public function getName()
    {
        return $this->_getData(self::NAME).'Local';
    }    
}

Then I created Block file in /Block/Product/View.php

<?php
/**
 * Copyright © 2015 Magento. All rights reserved.
 * See COPYING.txt for license details.
 */
namespace Sugarcode\Test\Block\Product;
/**
 * Product View block
 * @SuppressWarnings(PHPMD.CouplingBetweenObjects)
 */
class View extends \Magento\Catalog\Block\Product\View
{


    /**
     * Retrieve current product model
     *
     * @return \Magento\Catalog\Model\Product
     */
    public function getProduct()
    {
       echo 'Local Block';
       if (!$this->_coreRegistry->registry('product') && $this->getProductId()) {
            $product = $this->productRepository->getById($this->getProductId());
            $this->_coreRegistry->register('product', $product);
        }
        return $this->_coreRegistry->registry('product');
    }


}

Now Create Product View Controller /Controller/Product/View.php

<?php
/**
 *
 * Copyright © 2015 Magento. All rights reserved.
 * See COPYING.txt for license details.
 */
namespace Sugarcode\Test\Controller\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()
    {
        // Get initial data from request
       echo 'I Am in Local Controller';
       $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\Object();
        $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;
        }
    }
}

Its Working Fine for me :-)

0
6

There are two step to override Block, Model And Controller file

1) Add preference in di.xml

2) Create block, model and controller file in your module

Namespace: Prince

Module Name: Helloworld

For example to override catalog product ListProduct block

1) Create di.xml file in Folder Prince/Helloworld/etc

<?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="Prince\Helloworld\Model\Rewrite\Catalog\Product" />
</config>

2) Create ListProduct.php in Folder Prince/Helloworld/Block/Rewrite/Product

<?php
    namespace Prince\Helloworld\Block\Rewrite\Product;

    class ListProduct extends \Magento\Catalog\Block\Product\ListProduct
    {
        public function _getProductCollection()
        {
            // Do your code here
        }
    }

For example to override catalog product model.

1) Add preference in di.xml at Prince/Helloworld/etc

<preference for="Magento\Catalog\Model\Product" type="Prince\Helloworld\Model\Rewrite\Catalog\Product" /> 

2) Create Product.php Model file in Folder Prince/Helloworld/Model/Rewrite/Catalog

<?php
namespace Prince\Helloworld\Model\Rewrite\Catalog;

class Product extends \Magento\Catalog\Model\Product
{
    public function isSalable()
    {
        // Do your code here

        return parent::isSalable();
    }

}

Overriding Controller

1) Add preference in di.xml at Prince/Helloworld/etc

<preference for="Magento\Catalog\Controller\Product\View" type="Prince\Helloworld\Controller\Rewrite\Product\View" />

2) Create View.php in Folder Prince/Helloworld/Controller/Rewrite/Product

class View extends \Magento\Catalog\Controller\Product\View
{
    public function execute()
    {
        // Do your stuff here
        return parent::execute();
    }
}

You can override other block,model and controllers using same approach.

2
  • Do we need to add rewrite after Controller, Model & Block? For me without adding rewrite also worked. Commented May 13, 2017 at 10:26
  • @sagarsapkota Yes you can use Controller, Model & Block directly without rewrite folder. Commented Jun 17, 2017 at 11:58
5

Small correction but big usefull , we no need to create n number of file for each function in plugin concept. For one module one plugin file is enough in that you can extend all module , all model and blocks and controller of complete Magento it self check below code

<?xml version="1.0"?>
<!--
/**
 * Copyright © 2015 Magento. All rights reserved.
 * See COPYING.txt for license details.
 */
-->
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../../../lib/internal/Magento/Framework/ObjectManager/etc/config.xsd">

    <type name="Magento\Catalog\Block\Product\View">
        <plugin name="inroduct-custom-module" type="Sugarcode\Test\Model\Plugin\Product" sortOrder="1"/>
    </type>
    <type name="Magento\Catalog\Model\Product">
        <plugin name="getname-test-module" type="Sugarcode\Test\Model\Plugin\Product" sortOrder="10"/>
    </type>
    <type name="Magento\Catalog\Controller\Product\View">
        <plugin name="product-cont-test-module" type="Sugarcode\Test\Model\Plugin\Product" sortOrder="10"/>
    </type>
</config>

and in plugin php file

<?php
/**
 * Copyright © 2015 Magento. All rights reserved.
 * See COPYING.txt for license details.
 */
namespace Sugarcode\Test\Model\Plugin;

class Product
{        
    public function beforeSetName(\Magento\Catalog\Model\Product $subject, $name)
    {
        return array('(' . $name . ')');
    }

     public function afterGetName(\Magento\Catalog\Model\Product $subject, $result)
    {
        return '|' . $result . '|';
    } 
    public function aroundGetProduct(\Magento\Catalog\Block\Product\View $subject, \Closure $proceed)
    {

        echo 'Do Some Logic Before <br>';
        $returnValue = $proceed(); // it get you old function return value
        $name='#'.$returnValue->getName().'#';
        $returnValue->setName($name);
        echo 'Do Some Logic  After <br>';
        return $returnValue;// if its object make sure it return same object which you addition data
    }
    public function aroundExecute(\Magento\Catalog\Controller\Product\View $subject, \Closure $proceed)
    {
        echo 'I Am in Local Controller Before <br>';
        $returnValue = $proceed(); // it get you old function return value
        //$name='#'.$returnValue->getName().'#';
        //$returnValue->setName($name);
        echo 'I Am in Local Controller  After <br>';
        return $returnValue;// if its object make sure it return same object which you addition data
    }
}

Magento2 Rocks

2
  • Hi Pradeep - you've posted three answers on this question - it would probably be worth you combining them into a single answer
    – scrowler
    Commented Dec 15, 2015 at 23:15
  • I tried with this answer, Its showing error Uncaught Error: Call to undefined method Magento\\Backend\\Model\\View\\Result\\Redirect\\Interceptor::getEntityId() Here \Clousure $proceed getting obejct from Magento\\Backend\\Model\\View\\Result\\Redirect\\Interceptor Commented Feb 18, 2016 at 6:09
3

You can directly extends block or controller class of magento in your custom block or controller.For example, in extending PDF invoice model in my custom module to change logo of invoice PDF generate in same way you can override block or controller.You don't to create di.xml file and don't need to set preferences.

class Invoice extends \Magento\Sales\Model\Order\Pdf\Invoice
{


    /**
     * Return PDF document
     *
     * @param array|Collection $invoices
     * @return \Zend_Pdf
     */
    public function getPdf($invoices = [])
    {

        $this->_beforeGetPdf();
        $this->_initRenderer('invoice');

        $pdf = new \Zend_Pdf();
        $this->_setPdf($pdf);
        $style = new \Zend_Pdf_Style();
        $this->_setFontBold($style, 10);

        foreach ($invoices as $invoice) {
            if ($invoice->getStoreId()) {
                $this->_localeResolver->emulate($invoice->getStoreId());
                $this->_storeManager->setCurrentStore($invoice->getStoreId());
            }
            $page = $this->newPage();
            $order = $invoice->getOrder();
            /* Add image */
            $this->insertCustomLogo($page, $invoice->getStore());
            /* Add address */
            $this->insertCustomAddress($page, $invoice->getStore());
            /* Add head */

            $this->insertOrder(
                $page,
                $order,
                $this->_scopeConfig->isSetFlag(
                    self::XML_PATH_SALES_PDF_INVOICE_PUT_ORDER_ID,
                    \Magento\Store\Model\ScopeInterface::SCOPE_STORE,
                    $order->getStoreId()

                )
            );

            /* Add document text and number */
            $this->insertDocumentNumber($page, __('Invoice # ') . $invoice->getIncrementId());
            /* Add table */

            $this->_drawHeader($page);
            /* Add body */

            foreach ($invoice->getAllItems() as $item) {
                if ($item->getOrderItem()->getParentItem()) {
                    continue;
                }

                /* Draw item */
                $this->_drawItem($item, $page, $order);

                $page = end($pdf->pages);
            }

            /* Add totals */
            $this->insertTotals($page, $invoice);
            if ($invoice->getStoreId()) {
                $this->_localeResolver->revert();
            }
        }

        $this->_afterGetPdf();
        return $pdf;
    } 

   protected function insertCustomLogo(&$page, $store = null)
   {

     $image='demo.png'

     if ($image) {
        $imagePath = '/logo/' . $image;
        if ($this->_mediaDirectory->isFile($imagePath)) {
            $image = \Zend_Pdf_Image::imageWithPath($this->_mediaDirectory->getAbsolutePath($imagePath));
            $top = 830;
            //top border of the page
            $widthLimit = 270;
            //half of the page width
            $heightLimit = 270;
            //assuming the image is not a "skyscraper"
            $width = $image->getPixelWidth();
            $height = $image->getPixelHeight();

            //preserving aspect ratio (proportions)
            $ratio = $width / $height;
            if ($ratio > 1 && $width > $widthLimit) {
                $width = $widthLimit;
                $height = $width / $ratio;
            } elseif ($ratio < 1 && $height > $heightLimit) {
                $height = $heightLimit;
                $width = $height * $ratio;
            } elseif ($ratio == 1 && $height > $heightLimit) {
                $height = $heightLimit;
                $width = $widthLimit;
            }

            $y1 = $top - $height;
            $y2 = $top;
            $x1 = 25;
            $x2 = $x1 + $width;

            //coordinates after transformation are rounded by Zend
            $page->drawImage($image, $x1, $y1, $x2, $y2);

            $this->y = $y1 - 10;
        }
    }
}

}

3
  • Is this really the way to go in M2?
    – Max
    Commented Oct 25, 2017 at 22:12
  • The trick in Magento 2 is to define a preference in the di.xml. I'm missing this rather essential part in your answer...
    – 7ochem
    Commented Jun 26, 2018 at 10:51
  • I think without declaring preference in di.xml, It will work but when we run the magento php bin/magento dev:di:info "Magento\Sales\Model\Order\Pdf\Invoice" command it will not list this override core class name in the preference list. Commented May 20, 2023 at 8:41
3
  • Developer/Helloworld/registration.php

    
    \Magento\Framework\Component\ComponentRegistrar::register(
        \Magento\Framework\Component\ComponentRegistrar::MODULE,
        'Developer_Helloworld',
        __DIR__
    );
    
  • Developer/Helloworld/etc/module.xml

    <config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:Module/etc/module.xsd">
        <module name="Developer_Helloworld" setup_version="1.0.0">
        </module>
    </config>
    

  • Developer/Helloworld/etc/di.xml

<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="Developer\Helloworld\Controller\Catalog\Product\View" />
</config>

  • Developer/Helloworld/Controller/Catalog/Product/View.php

    namespace Developer\Helloworld\Controller\Catalog\Product;
    class View extends \Magento\Catalog\Controller\Product\View
    {
        public function execute(){
            echo '__TEST__';exit;
        }
    }

hope this helpful
2

An action class can be rewritten in same manner as in Magento 1. In Magento 1 we had before attribute around tags <routers>..<args><modules><... before="Mage_Catalog">Namespace_MyModule ..

In [module path]/etc/[nothing|adminhtml|frontend]/routes.xml:

<config>
    <router id="[admin|standard|]">
        <route id="catalog" frontName="catalog">
            <module name="Namespace_MyModule" before="Magento_Catalog"/>
        </route>
    </router>
</config>

And the action class \Namespace\MyModule\Controller\[same path of action as in core module]\SameActionName.php where class SameActionName.php extends \Magento\Catalog\...\SameActionName

This is Magento_Catalog module, file Magento\Catalog\etc\adminhtml\routes.xml registering a new route in admin:

<router id="admin">
    <route id="catalog" frontName="catalog">
        <module name="Magento_Catalog" before="Magento_Backend" />
    </route>
</router>

http://devdocs.magento.com/guides/v2.1/extension-dev-guide/routing.html

To replace the controller action in a route with custom one, add the custom controller class before the original controller.

The custom controller and action should share the same names with the original ones.

The system processes the custom controller before the original, while a route remains the same.

If you must reset a route and design, forward the request processing to another route:

$this->_forward('other/controller/action')

To remove the controller action, forward to noroute, for instance, in app/code/Company/SomeExtension/Controller/Account.php:

I don't believe preferences or plugins on Action classes are a good idea by Magento best practices. And there may be more to it.

0

To Override directly a class you are required to use preferences. See more on dev docs: https://devdocs.magento.com/guides/v2.0/extension-dev-guide/build/di-xml-file.html#abstraction-implementation-mappings
For most of time we use Interceptors (plugins) way because this is the best practice to rewrite or add part of your modifications. See dev docs: https://devdocs.magento.com/guides/v2.0/extension-dev-guide/plugins.html

By keeping your example of sorting list items by adding new sort order 'Most Popular' I am providing you the best way of modifying the result.
Create a custom module and create configuration app/code/Arsal/SortOption/etc/module.xml:

<?xml version="1.0" ?>
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"  xsi:noNamespaceSchemaLocation="urn:magento:framework:Module/etc/module.xsd">
   <module name="Arsal_SortOption" setup_version="1.0.0" />
</config> 

Now Register your module app/code/Arsal/SortOption/registration.php:

<?php
 \Magento\Framework\Component\ComponentRegistrar::register(
     \Magento\Framework\Component\ComponentRegistrar::MODULE,
     'Arsal_SortOption',
      __DIR__
 );

Now create di.xml app/code/Arsal/SortOption/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\ProductList\Toolbar">
       <plugin name="toolbar_instance" type="Arsal\SortOption\Block\Product\ProductList\Toolbar" />
    </type>
</config>

Now create a block class Arsal\SortOption\Block\Product\ProductListToolbar.php :

<?php
namespace Arsal\SortOption\Block\Product\ProductList;

class Toolbar {

    public function afterGetAvailableOrders (
        \Magento\Catalog\Block\Product\ProductList\Toolbar $subject, $result
    ) {
        $result ['most_popular'] = 'most popular';
        return $result;
    }

This will add custom sort order option to sort order list. enter image description here }

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.