14

I'm trying to override the Topmenu block in Magento 2.1 but can't find any guide to do so. Everything I've found on here and elsewhere either seems to apply only to version 2.0 which appears to use a different folder structure or only has partial code examples which expects me to already know their proper context (which I don't).

My current folder structure for a custom theme is app/design/frontend/Vendor/theme_name. Within this I have the registration, theme, and composer files as well as folders for the various modules, e.g. Magento_Theme and Magento_Search.

From what I understand I need to start with an etc/di.xml file like below, edited from here:

<?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\Theme\Block\Html\Topmenu" type="[Namespace]\[Module]\Block\Html\Topmenu" />
</config>

I also understand that the next step is to add a Block/Html/Topmenu.php file like the below (again edited from the above source):

namespace [Namespace]\[Module]\Block\Html;

class Topmenu extends \Magento\Theme\Block\Html\Topmenu
{

  protected function _addSubMenu($child, $childLevel, $childrenWrapClass, $limit)
  {

  }

}

However, it's not clear to me what I should use for [Namespace] and [Module], or where to place these files. I've tried using the vendor and theme name, and placing the etc and Block folders in app/design/frontend/Vendor/theme_name, as well as placing them in app/design/frontend/Vendor/theme_name/Magento_Theme, amending the namespaces to Vendor\theme_name\Magento_Theme\Block\Html, but neither have any effect.

If anyone could help explain exactly what I need to do to override the Topmenu block (and by inference any other block) in version 2.1 I would be much appreciated.

Addendum

I've attempted Khoa TruongDinh's answer but it has had no affect. I've used the following files:

app/code/Vendor/MagentoTheme/Block/Html/Topmenu.php

<?php

namespace Vendor\MagentoTheme\Block\Html;

class Topmenu extends \Magento\Theme\Block\Html\Topmenu
{

  protected function _addSubMenu($child, $childLevel, $childrenWrapClass, $limit)
  {

    $html = '';

    if (!$child->hasChildren())
    {
      return $html;
    }

    $colStops = null;

    if ($childLevel == 0 && $limit)
    {
      $colStops = $this->_columnBrake($child->getChildren(), $limit);
    }

    // Added "test" class to test
    $html .= '<ul class="level' . $childLevel . ' test submenu">';
    $html .= $this->_getHtml($child, $childrenWrapClass, $limit, $colStops);
    $html .= '</ul>';

    return $html;

  }

}

app/code/Vendor/MagentoTheme/composer.json

{
    "name": "vendor/magento-theme",
    "description": "",
    "require": {
        "php": "~5.5.0|~5.6.0|~7.0.0",
        "magento/framework": "100.0.*"
    },
    "type": "magento2-module",
    "version": "100.0.1",
    "license": [
        "OSL-3.0",
        "AFL-3.0"
    ],
    "autoload": {
        "files": [ "registration.php" ],
        "psr-4": {
            "Vendor\\MagentoTheme\\": ""
        }
    }
}

app/code/Vendor/MagentoTheme/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\Theme\Block\Html\Topmenu" type="Vendor\MagentoTheme\Block\Html\Topmenu" />
</config>

app/code/Vendor/MagentoTheme/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="Vendor_MagentoTheme" setup_version="1.0.0"></module>
</config>

app/code/Vendor/MagentoTheme/registration.php

<?php

\Magento\Framework\Component\ComponentRegistrar::register(
  \Magento\Framework\Component\ComponentRegistrar::MODULE,
  'Vendor_MagentoTheme',
  __DIR__
);

I've then removed the contents of pub/static/frontend, var/generation, and var/view_preprocessed, and flushed the Magento cache. The submenu doesn't have the intended "test" class added:

<ul class="level0 submenu ui-menu ui-widget ui-widget-content ui-corner-all" role="menu" aria-expanded="false" style="display: none; top: 52.6719px; left: 487.5px;" aria-hidden="true">...</ul>
6
  • You tried to create multi sub categories? Sep 1, 2016 at 11:59
  • I'm not sure what you mean. At the moment I'm just trying to add a "test" class to the submenu ul to confirm that I've successfully overriden the Topmenu class.
    – Michael
    Sep 1, 2016 at 12:58
  • How you can do this? My guide can help you? Sep 1, 2016 at 14:29
  • I followed your instructions as best I understood but it didn't work. My custom Topmenu module is ignored and the default behaviour is being used.
    – Michael
    Sep 1, 2016 at 15:03
  • At the weekend, I will check again and give you my solution. Sep 1, 2016 at 15:07

4 Answers 4

20

Override block:

Create your own module under app/code folder.
We can use preference to override the class in Magento 2.

app/code/Vendor/Module/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\Theme\Block\Html\Topmenu" type="Vendor\Module\Block\Html\Topmenu" />
</config>

app/code/Vendor/Module/Block/Html/Topmenu.php

<?php

namespace Vendor\Module\Block\Html;

class Topmenu extends \Magento\Theme\Block\Html\Topmenu
{

    protected function _addSubMenu($child, $childLevel, $childrenWrapClass, $limit)
    {

    }

}

Temporary solution:
Currently, seem that above steps cannot override the the block completely. We need to create new custom theme. And then, create the default.xml file:

app/design/frontend/Vendor/Theme/Magento_Theme/layout/default.xml

<?xml version="1.0"?>

<page layout="3columns" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:View/Layout/etc/page_configuration.xsd">
    <update handle="default_head_blocks"/>
    <referenceBlock name="catalog.topnav" class="Vendor\Module\Block\Html\Topmenu" template="Magento_Theme::html/topmenu.phtml"/>
</page>

It's may be a Magento bug: Are we forced to rewrite a template in Magento2 when rewritting a block?

[EDIT]

1) We can set the template:

app/code/Vendor/Module/Block/Html/Topmenu.php

public function setTemplate($template)
{
    return parent::setTemplate('Vendor_Module::custom-menu.phtml');
}

2) Set template via Xml:

For example:

app/code/Vendor/Module/view/frontend/layout/checkout_cart_index.xml

<?xml version="1.0"?>
<page xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" layout="1column" xsi:noNamespaceSchemaLocation="urn:magento:framework:View/Layout/etc/page_configuration.xsd">
    <body>
        <referenceBlock name="checkout.cart">
            <action method="setTemplate">
                <argument name="template" xsi:type="string">Magento_Checkout::cart.phtml</argument>
            </action>
        </referenceBlock>
    </body>
</page>

Remember to create registration.php and module.xml.

We create the new module because we're overriding the class of Magento. When we want to override any class, we have to create new module.

The custom theme under app/design/frontend contains:
--layout
--templates
--js
--html templates (Knockout templates)
--less, css
--etc...

Read more here and here.

Autoloading standard and naming convention:

For [Namespace] and [Module], we should read more here:

http://www.php-fig.org/psr/psr-0/
http://www.php-fig.org/psr/psr-4/
http://alanstorm.com/magento_2_autoloader_and_class_generation

6
  • Thanks, but I wasn't able to get this to work. I've edited my attempt into my question so you can see where I inevitably went wrong.
    – Michael
    Sep 1, 2016 at 9:55
  • What's the template?
    – Michael
    Sep 1, 2016 at 10:11
  • I updated my answer. Seem that there is a Magento bug. We need to create new custom theme. And then, create the layout to set the class again. Sep 2, 2016 at 3:07
  • Yes, that worked. Thank you very much. A day wasted on a bug...
    – Michael
    Sep 2, 2016 at 8:29
  • i have to override block file in custom_account_create.xml located in my custom theme.xml file is in Magento_Customer folder.which default.xml i have to change magento_theme or magento_customer??i am unable to override block.i am using Magento2.1 is it better to move Magento 2.1.3??
    – vijay b
    Feb 11, 2017 at 5:43
3

For override catalog product ListProduct block.

1) Create di.xml file in Folder Vendor/Module/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="Vendor\Module\Model\Rewrite\Catalog\Product" />
</config>

2) Create ListProduct.php Block file in Folder Vendor/Module/Block/Rewrite/Product

<?php
namespace Vendor\Module\Block\Rewrite\Product;

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

For override catalog product model.

1) Add preference in di.xml before

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

2) Create Product.php Model file in Folder Vendor/Module/Model/Rewrite/Catalog

<?php
namespace Vendor\Module\Model\Rewrite\Catalog;

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

        return parent::isSalable();
    }

}

For Overriding Controller

1) Add preference in di.xml

2) Create View.php Controller file at Vendor/Module/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 this same approach.

1
0

To override the class you have to create a module where you can add the etc/di.xml and Block/Html/Topmenu.php file (above code posted by you)

where Namespace is your vendor name and Module is your module name. For ex: Magento is Namespace and Theme is Module name.

For more info on how to create a module, http://devdocs.magento.com/guides/v2.1/extension-dev-guide/build/module-file-structure.html#module-file-structure

0

Because of this bug: https://github.com/magento/magento2/issues/3724 you cannot just preference block classes.

1) (Preferable) What works instead is to use a Plugin for that class and change what you need to. http://devdocs.magento.com/guides/v2.0/extension-dev-guide/plugins.html

2) Or if you really want to do the preference way then you need to also copy the template from core to your module/theme and update with xml so that uses that template instead, then it will magically start working..

Your Answer

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

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