Zend Framework 2: Navigation

自闭症网瘾萝莉.ら 提交于 2019-12-04 10:15:27
Jurian Sluiman

The problem is a missing Router (or to be more precise, a Zend\Mvc\Router\RouteStackInterface). A route stack is a collection of routes and can use a route name to turn that into an url. Basically it accepts a route name and creates an url for you:

$url = $routeStack->assemble('my/route');

This happens inside the MVC Pages of Zend\Navigation too. The page has a route parameter and when there is a router available, the page assembles it's own url (or in Zend\Navigation terms, an href). If you do not provide the router, it cannot assemble the route and thus throws an exception.

You must inject the router in every page of the navigation:

$navigation = new Navigation($config);
$router     = $serviceLocator->get('router');

function injectRouter($navigation, $router) {
  foreach ($navigation->getPages() as $page) {
    if ($page instanceof MvcPage) {
      $page->setRouter($router);
    }

    if ($page->hasPages()) {
      injectRouter($page, $router);
    }
  }
}

As you see it is a recursive function, injecting the router into every page. Tedious! Therefore there is a factory to do this for you. There are four simple steps to make this happen.

STEP ONE

Put the navigation configuration in your module configuration first. Just as you have a default navigation, you can create a second one secondary.

'navigation' => array(
    'secondary' => array(
        'page-1' => array(
            'label' => 'First page',
            'route' => 'route-1'
        ),
        'page-2' => array(
            'label' => 'Second page',
            'route' => 'route-2'
        ),
    ),
),

You have routes to your first page (route-1) and second page (route-2).

STEP TWO

A factory will convert this into a navigation object structure, you need to create a class for that first. Create a file SecondaryNavigationFactory.php in your MyModule/Navigation/Service directory.

namespace MyModule\Navigation\Service;

use Zend\Navigation\Service\DefaultNavigationFactory;

class SecondaryNavigationFactory extends DefaultNavigationFactory
{
    protected function getName()
    {
        return 'secondary';
    }
}

See I put the name secondary here, which is the same as your navigation key.

STEP THREE

You must register this factory to the service manager. Then the factory can do it's work and turn the configuration file into a Zend\Navigation object. You can do this in your module.config.php:

'service_manager' => array(
    'factories' => array(
        'secondary_navigation' => 'MyModule\Navigation\Service\SecondaryNavigationFactory'
    ),
)

See I made a service secondary_navigation here, where the factory will return a Zend\Navigation instance then. If you do now $sm->get('secondary_navigation') you will see that is a Zend\Navigation\Navigation object.

STEP FOUR

Tell the view helper to use this navigation and not the default one. The navigation view helper accepts a "navigation" parameter where you can state which navigation you want. In this case, the service manager has a service secondary_navigation and that is the one we need.

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

Now you will have the navigation secondary used in this view helper.

Disclosure: this answer is the same as I gave on this question: https://stackoverflow.com/a/12973806/434223

btw. you don't need to define controller and action if you define a route, only if your route is generic and controller/action are variable segments.

The problem is indeed that the routes can't be resolved without the router. I would expect the navigation class to solve that issue, but obviously you have to do it on your own. I just wrote a view helper to introduce the router with the MVC pages.

Here's how I use it within the view:

$navigation = $this->navigation();
$navigation->addPage(
    array(
        'route' => 'language',
        'label' => 'language.list.nav'
    )
);
$this->registerNavigationRouter($navigation);
echo $navigation->menu()->render();

The view helper:

<?php
namespace JarJar\View\Helper;

use Zend\View\Helper\AbstractHelper;
use Zend\View\Helper\Navigation;
use Zend\ServiceManager\ServiceLocatorAwareInterface;
use Zend\ServiceManager\ServiceLocatorInterface;
use Zend\Navigation\Page\Mvc;

class RegisterNavigationRouter extends AbstractHelper implements ServiceLocatorAwareInterface
{
    protected $serviceLocator;

    public function setServiceLocator(ServiceLocatorInterface $serviceLocator)
    {
        $this->serviceLocator = $serviceLocator;
    }

    public function getServiceLocator()
    {
        return $this->serviceLocator;
    }

    public function __invoke(Navigation $navigation)
    {
        $router = $this->getRouter();
        foreach ($navigation->getPages() as $page) {
            if ($page instanceof Mvc) {
                $page->setRouter($router);
            }
        }
    }

    protected function getRouter()
    {
        $router = $this->getServiceLocator()->getServiceLocator()->get('router');
        return $router;
    }
}

Don't forget to add the view helper in your config as invokable instance:

'view_helpers' => array(
    'invokables' => array(
        'registerNavigationRouter' => 'JarJar\View\Helper\RegisterNavigationRouter'
    )
),

It's not a great solution, but it works.

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!