How to override Navigation or Menu View Helper Plugin

Hi, I want to know how to override Navigation helper and it’s plugins, namely Menu, Breadcrumb, Links, SiteMap. In Zf2 this can be done by finding out the configuration key and override it. But in Laminas this is not the case. As Navigation is no longer a built in part of Laminas View and you can see that in Laminas\View\PluginManager, if I’m correct. So, how to override Navigation helper and it’s menu. The code which use to work in zf2 was something like below:

'view_helpers' => [
  'factories' => [
    'navigations' => \Application\View\Navigation::class,
  ]
]

But this above code gives error “Bad method call: Unknown method Laminas\Navigation\Navigation::menu”. Even if I change “navigation” to FCQN it gives error. In my getPluginManager() method I’ve override the values of the property aliases like below:

class PluginManager extends LaminasPluginManager{

  protected $aliases = [
        'breadcrumbs' => \Laminas\View\Helper\Navigation\Breadcrumbs::class,
        'links'       => \Laminas\View\Helper\Navigation\Links::class,
        'menu'        => \Application\View\Helper\Navigation\Menu::class,
        'sitemap'     => \Laminas\View\Helper\Navigation\Sitemap::class,
  ];

Thanks in advance!

Hello and welcome to our forums! :smiley:

In this case there were no major changes between the different versions like ZF2, ZF3 and Laminas. laminas-view or zend-view always knew only one helper: Laminas\View\Helper\Navigation which works as proxy to the helper like Menu, Sitemap, Links, Breadcrumbs. Therefore, this helper has its own plugin manager.

At the moment I don’t have a computer in front of me, but I will submit a solution later.

Dear @froschdesign,
From Zf2 to Zf3 there is a change in Navigation. Navigation was part of Zf2 view but in Zf3 it was removed. Now, in Zf3 and Laminas you’ve to install Navigation as a separate component to be able to use its functionality. But I’m not here to argue, if by any means my sentence gives an impression of argument. For that I apologize. I’ve extended from the same Plugin Manager which you’ve referred. The code of my question is like below:

class Navigation extends \Laminas\View\Helper\Navigation{
    public function getPluginManager(){
        if (null === $this->plugins) {
            $this->setPluginManager(new \Application\View\Helper\Navigation\PluginManager());
        }
        
        return $this->plugins;
   }
}

// My Plugin Manager Class
use Laminas\View\Helper\Navigation\PluginManager as LaminasPluginManager;
class PluginManager extends LaminasPluginManager{
    protected $aliases = [
        'breadcrumbs' => \Laminas\View\Helper\Navigation\Breadcrumbs::class,
        'links'       => \Laminas\View\Helper\Navigation\Links::class,
        'menu'        => \Application\View\Helper\Navigation\Menu::class,//Override Default with My Menu Class
        'sitemap'     => \Laminas\View\Helper\Navigation\Sitemap::class,
   ];
}
// This above code works in zf2

Thanks in advance!

After reading your reply again and focusing on the proxy point which you’ve made. Even after that not able to make it work. As the point of registering path is not clear in the Navigation document. Which is the end of the second paragraph. So, what I’ve done is below and it doesn’t work.

'view_helpers' => [
        'aliases' => [
            'myMenu' => Application\View\Helper\Navigation\Menu::class,
        ],
        'factories' => [
            Application\View\Helper\Navigation\Menu::class => \Laminas\ServiceManager\Factory\InvokableFactory::class,
        ],
]

myMenu class implements Laminas\View\Helper\Navigation\HelperInterface

in the *.phtml file, I’ve called it like below.

$this->navigations('default')->myMenu();

This gives error Fatal error: Uncaught Laminas\Navigation\Exception\BadMethodCallException: Bad method call: Unknown method Laminas\Navigation\Navigation::myMenu.

Thanks in advance!

Some history:
The navigation component has its own namespace since version 2.0.0 and with version 2.5.0 all components were extracted to their own repositories. But that was a long, long time ago – 2015.
All in all, the handling of laminas-view and laminas-navigation are the same since version 2.5.0.


But here my suggestion: you can use a delegator to configure the plugin manager of the navigation proxy helper.

Based on the mvc-skeleton application, you must create a class for the delegator and extend the configuration:

Delegator

module/Application/src/Navigation/NavigationHelperDelegator.php:

namespace Application\Navigation;

use Laminas\View\Helper\Navigation as NavigationHelper;
use Laminas\View\Helper\Navigation\Menu;
use Psr\Container\ContainerInterface;

class NavigationHelperDelegator
{
    public function __invoke(
        ContainerInterface $container,
        string $name,
        callable $callback,
        array $options = null
    ): NavigationHelper {
        /** @var NavigationHelper $helper */
        $helper = $callback();

        // Configure the plugin manager for navigation proxy helper
        $helper->getPluginManager()->setService(
            Menu::class,
            new class() extends NavigationHelper\AbstractHelper {
                public function render($container = null): string
                {
                    return '<nav>Custom menu</nav>';
                }

                public function __invoke($container = null)
                {
                    if (null !== $container) {
                        $this->setContainer($container);
                    }

                    return $this;
                }
            }
        );

        return $helper;
    }
}

More informations to delegators can be found in the documentation of laminas-servicemanager.

Module Configuration

module/Application/config/module.config.php:

return [
    'navigation_helpers' => [
        'delegators' => [
            \Laminas\View\Helper\Navigation::class => [
                \Application\Navigation\NavigationHelperDelegator::class,
            ],
        ],
    ],
    // …
];

Layout Script

module/Application/view/layout/layout.phtml:

<?= $this->navigation()->menu() ?>

Output:

<nav>Custom menu</nav>

Thanks, @froschdesign. Your answer will help someone but I couldn’t extract the point which I wanted. But someone will find this useful.

Your initial question was:

I want to know how to override Navigation helper and it’s plugins, namely Menu, Breadcrumb, Links, SiteMap.

That’s exactly what you can do with it. You can set everything you want to the „setService“ method. See also: Configuring the service manager - laminas-servicemanager - Laminas Docs
The anonymous class from above is only an example! And as you can see in the layout script, the menu helper is overwritten.

Could you describe what you are missing?

@froschdesign, The thing which I wanted to achieve is to learn how to override and which a part of it you’ve shared. But overriding was just a first step. What I wanted to achieve with override is to just play around with “renderMenu” method. Which I was not able to do with a simple change which is provided in your solution:

            $helper->getPluginManager()->setService(
            Menu::class,
            // extend from MyMenu class which is extended from Laminas Menu and Laminas Menu is extended from AbstractHelper
            new class() extends MyMenu {
            }

You would say that can be done with partial. Why go through all the trouble. True. The second question will be, how can I add my proxies to navigations? So, by playing around with overriding Menu I could have generated menu the way I like according to the project requirement. Also, then be able to contribute it via LM-Commons. Thanks!

To be clear in this case: there is only one proxy helper and this is Laminas\View\Helper\Navigation. This helper creates no output, it is the interface to concrete navigation helpers Breadcrumbs, Menu, Links and Sitemap.

This proxy helper uses internally a plugin manager to hold the concrete helpers. If you want to override or to add new helpers to this proxy helper, then these custom helpers must be set to the plugin manager.
The proxy helper will find the helper via the internal plugin manager and you can work with them.

Example

Custom helper

module/Application/src/Navigation/CustomMenuHelper.php:

namespace Application\Navigation;

use Laminas\View\Helper\Navigation\Menu;

class CustomMenuHelper extends Menu
{
    public function renderMenu($container = null, array $options = []): string
    {
        var_dump('Custom');
        
        return parent::renderMenu(
            $container,
            $options
        );
    }
}

Delegator

module/Application/src/Navigation/NavigationHelperDelegator.php:

namespace Application\Navigation;

use Laminas\View\Helper\Navigation as NavigationHelper;
use Laminas\View\Helper\Navigation\Menu;
use Psr\Container\ContainerInterface;

class NavigationHelperDelegator
{
    public function __invoke(
        ContainerInterface $container,
        string $name,
        callable $callback,
        array $options = null
    ): NavigationHelper {
        /** @var NavigationHelper $helper */
        $helper = $callback();

        // Override existing menu helper
        $helper->getPluginManager()->setInvokableClass(
            Menu::class,
            CustomMenuHelper::class
        );

        // Add new helper
        $helper->getPluginManager()->setInvokableClass(
            CustomMenuHelper::class,
            CustomMenuHelper::class
        );
        $helper->getPluginManager()->setAlias(
            'customMenu',
            CustomMenuHelper::class
        );

        return $helper;
    }
}

Layout Script

module/Application/view/layout/layout.phtml:

<?= $this->navigation()->menu() ?>

<?= $this->navigation()->customMenu() ?>

This produces the same output.


I hope the example is not so abstract this time. :wink:

Why not for the official component? Is there a reason?

@froschdesign, Thanks! The provided overriding code works exactly what I was looking for. Now, to make a bootstrap version of it and contribute. I don’t understand the legality of LM-Commons and Laminas. For me, both are the same. If what I’ll work on gets any reviews or gets accepted then to go for the official component is a good approach. In my opinion. Second, to make it for the official component, I would require to write a test. That is my weak part. But I will learn it in time. As it’s a daunting task. I’ve to take a dive but when I’ve no idea. Maybe contribution will make me learn it. :slight_smile:

It is not. Laminas Project is an official Linux Foundation project and it is the umbrella for Mezzio, laminas-mvc, Components and API Tools.

https://getlaminas.org

And for LM Commons see on GitHub:

Community developed packages for the Laminas MVC. This is not an official Laminas Project.

@froschdesign, I agree that LM-Commons is not the official part of the Laminas umbrella. But the people there are using or maintaining repositories which are built with Laminas and more specifically for Laminas MVC.

Whereas Laminas Mezzio is and as Matthew always said in his talk is a library of individual components. Use whichever you like or use our skeleton MVC. The result of using individual components can be seen in Magento. But developing Magento is not an everyday task. Whereas people like me rely on MVC, therefore I would like to contribute to a repository which can be plug in an MVC skeleton. So, that is my intention. How much I will succeed in it, only time will tell. Thanks!

Don’t worry, I know the ecosystem and I have already seen that you are working on LmcAdmin. Do not forget the tests! :grin: