How to inject non-acl object into navigation?

Hi,

I want to know how to inject non-acl into the navigation helper. The answer given in this post is for injecting ACL into the navigation helper via the delegator.

I’m able to somehow achieve my goal by the answer given in this post. But asking for a better solution. Thanks!

A delegator allows you to extend the desired service using the laminas-servicemanager. Because the service manager already exists in a laminas-mvc based application, therefore it is an easy way without creating your own navigation helper or overwriting an existing one.

Or you can explain what you would like to achieve in concrete terms, and maybe there will be another way.

Hi @froschdesign,

I did achieve my goal by extending the desired service in my case it is the navigation menu helper, which was achieved with your help in this post. So the way I did it is by adding my method in the menu helper called setAuthorizationService(). Then to access the authorization service in the view, I created another helper to access my authorization service. So the code looks something like below.

$this->navigation('navigation')
         ->bootstrapMenu()
         ->setAuthorizationService($this->myAuthorizationService());
/*
 * Below method returns the LmcRbac/RouteGuard
 * $this->myAuthorizationService()
*/

This is the way I’ve done it. I want to know if this is the right way. Thanks!

I have another idea for you: decorate the navigation container, not the view helper.

Check the access at the container and remove any pages that are not allowed or make them invisible. The navigation container can be retrieved from the service container of your application.
This is also what the helpers do:

And a delegator can help here too.

The access does not need to be checked at the view layer.

Hi @froschdesign,

I didn’t get your idea of decorating the navigation container. As it can’t access the accept method of the view helper. My view helper at the moment is able to handle any navigation configuration. At the moment my view helper can be used like below.

$this->navigation('navigation')
    ->bootstrapMenu()
    ->setAuthorizationService($this->myAuthorizationService());

$this->navigation('admin_navigation')
    ->bootstrapMenu()
    ->setAuthorizationService($this->myAuthorizationService());

$this->navigation('discourse_navigation')
    ->bootstrapMenu()
    ->setAuthorizationService($this->myAuthorizationService());

use Laminas\Navigation\Navigation;

$pages = [
    [
        'label'      => 'Home',
        'title'      => 'Go Home',
        'uri'      => $this->serverUrl(),
        'resource' => 'home', // route name
        'order'      => -100, // make sure home is the first page
    ],
    [
        'label'      => 'About',
        'title'      => 'Go to about',
        'resource' => 'about',
        'uri'      => $this->serverUrl(),
        
    ],
];
$onDemandContainer = new Navigation($pages);
$this->navigation('navigation')
    ->setContainer($onDemandContainer)
    ->bootstrapMenu()
    ->setAuthorizationService($this->myAuthorizationService());

So, I didn’t get your idea about decorating the container and where to look at it. As for your decorator option, a hint would be great. Thanks!

You do not need this method because you use your own authorization service. Ignore this part of the view helper and you do not need to pass your own authorization service to the helper.
Ignore the entire view layer when dealing with the issue of permissions. Therefore, I suggest filtering the navigation container when it is retrieved from the service container and before it is used to create an output.

Here some pseudo code:

$iterator = new RecursiveIteratorIterator(
    $navigation,
    RecursiveIteratorIterator::SELF_FIRST
 );

foreach ($iterator as $page) {
    if ($myAuthorizationService->isAllowed($page) === false) {
        $page->setVisible(false);
    }
}

This means you can retrieve a navigation container from the service manager which is filtered. Create and register a factory and apply a filter to the navigation in this factory. This means you can fetch the normal navigation from your service manager and a filtered one:

  • Laminas\Navigation\Default
  • Navigation\Filtered\Default

Here an example which illustrates the idea based on the navigation container from the documentation:

$navigation = new Laminas\Navigation\Navigation(
    [
        [
            'label' => 'Page 1',
            'id'    => 'home-link',
            'uri'   => '/',
        ],
        [
            'label' => 'Laminas',
            'uri'   => 'http://www.laminas-project.com/',
            'order' => 100,
        ],
        [
            'label'      => 'Page 2',
            'controller' => 'page2',
            'pages'      => [
                [
                    'label'      => 'Page 2.1',
                    'action'     => 'page2_1',
                    'controller' => 'page2',
                    'class'      => 'special-one',
                    'title'      => 'This element has a special class',
                    'active'     => true,
                ],
                [
                    'label'      => 'Page 2.2',
                    'action'     => 'page2_2',
                    'controller' => 'page2',
                    'class'      => 'special-two',
                    'title'      => 'This element has a special class too',
                ],
            ],
        ],
        [
            'label'      => 'Page 2 with params',
            'action'     => 'index',
            'controller' => 'page2',
            // specify a param or two,
            'params'     => [
                'format' => 'json',
                'foo'    => 'bar',
            ],
        ],
        [
            'label'      => 'Page 2 with params and a route',
            'action'     => 'index',
            'controller' => 'page2',

            // specify a route name and a param for the route
            'route'      => 'nav-route-example',
            'params'     => [
                'format' => 'json',
            ],
        ],
        [
            'label'        => 'Page 3',
            'action'       => 'index',
            'controller'   => 'index',
            'module'       => 'mymodule',
            'reset_params' => false,
        ],
        [
            'label' => 'Page 4',
            'uri'   => '#',
            'pages' => [
                [
                    'label' => 'Page 4.1',
                    'uri'   => '/page4',
                    'title' => 'Page 4 using uri',
                    'pages' => [
                        [
                            'label'      => 'Page 4.1.1',
                            'title'      => 'Page 4 using mvc params',
                            'action'     => 'index',
                            'controller' => 'page4',
                            // let's say this page is active
                            'active'     => '1',
                        ],
                    ],
                ],
            ],
        ],
        [
            'label' => 'Page 0?',
            'uri'   => '/setting/the/order/option',

            // setting order to -1 should make it appear first
            'order' => -1,
        ],
        [
            'label'   => 'Page 5',
            'uri'     => '/',

            // this page should not be visible
            'visible' => false,
            'pages'   => [
                [
                    'label' => 'Page 5.1',
                    'uri'   => '#',
                    'pages' => [
                        [
                            'label' => 'Page 5.1.1',
                            'uri'   => '#',
                            'pages' => [
                                [
                                    'label'  => 'Page 5.1.2',
                                    'uri'    => '#',

                                    // let's say this page is active
                                    'active' => true,
                                ],
                            ],
                        ],
                    ],
                ],
            ],
        ],
        [
            'label'    => 'ACL page 1 (guest)',
            'uri'      => '#acl-guest',
            'resource' => 'nav-guest',
            'pages'    => [
                [
                    'label'    => 'ACL page 1.1 (foo)',
                    'uri'      => '#acl-foo',
                    'resource' => 'nav-foo',
                ],
                [
                    'label'    => 'ACL page 1.2 (bar)',
                    'uri'      => '#acl-bar',
                    'resource' => 'nav-bar',
                ],
                [
                    'label'    => 'ACL page 1.3 (baz)',
                    'uri'      => '#acl-baz',
                    'resource' => 'nav-baz',
                ],
                [
                    'label'    => 'ACL page 1.4 (bat)',
                    'uri'      => '#acl-bat',
                    'resource' => 'nav-bat',
                ],
            ],
        ],
        [
            'label'    => 'ACL page 2 (member)',
            'uri'      => '#acl-member',
            'resource' => 'nav-member',
        ],
        [
            'label'    => 'ACL page 3 (admin',
            'uri'      => '#acl-admin',
            'resource' => 'nav-admin',
            'pages'    => [
                [
                    'label' => 'ACL page 3.1 (nothing)',
                    'uri'   => '#acl-nada',
                ],
            ],
        ],
        [
            'label' => 'Laminas',
            'route' => 'api-tools-route',
        ],
    ]
);

$iterator  = new RecursiveIteratorIterator(
    $navigation,
    RecursiveIteratorIterator::SELF_FIRST
);
$isAllowed =
    static function (Laminas\Navigation\Page\AbstractPage $page): bool {
        return $page->getResource() !== null;
    };

iterator_apply(
    $iterator,
    static function (Iterator $iterator, $isAllowed): bool {
        /** @var Laminas\Navigation\Page\AbstractPage $page */
        $page = $iterator->current();
        $page->setVisible($isAllowed($page));

        return true;
    },
    [$iterator, $isAllowed]
);

var_dump($navigation->findOneBy('label', 'Page 1')->getVisible()); // false
var_dump($navigation->findOneBy('label', 'ACL page 1 (guest)')->getVisible()); // true

The invisible pages will not be rendered by the navigation helpers.

Hi @froschdesign,

Your answer opens up a new way of solving the issue altogether. For some reason, the route configuration doesn’t work in Laminas\Navigation\Navigation.

It gives the error no route match found. I think it is because the reason Navigation is no longer a part of the MVC base view helpers and it gets injected when someone uses it. Your suggestion looks awesome but it goes above my head. Sorry for not completely understanding your idea but I’ll go with what I’ve working for me at the moment.