KnpLabs / KnpMenuBundle

Object Oriented menus for your Symfony project.
http://knplabs.com
MIT License
1.4k stars 203 forks source link

Symfony3 : How to get KnpMenu in a controller ? #316

Closed eved42 closed 8 years ago

eved42 commented 8 years ago

Hello everyone,

sorry for this stupid question but I don't find any doc about this.

I followed this symfony tutorial : Creating Menu Builders as Services Now, how can I get my KnpMenu in a controller ?

For example, I have two methods in my builder : createMainMenu() and createAdminMenu().

services:
   app.menu_builder:
        class: AppBundle\Menu\MenuBuilder
        arguments: ["@knp_menu.factory"]
        tags:
            - { name: knp_menu.menu_builder, method: createMainMenu, alias: main }
            - { name: knp_menu.menu_builder, method: createAdminMenu, alias: admin }

In my controller, I would like something like this :

// get main menu
$menu = $this->get("app.menu_builder.main");

But it doesn't work... I don't know the correct syntax. Can you help me ?

stof commented 8 years ago

menus are not defined as services. Use the knp_menu.menu_provider service to get your menu (it will use the builder internally):

// get main menu
$menu = $this->get('knp_menu.menu_provider')->get('main');
eved42 commented 8 years ago

Ok thanks, it works ! I try to get a particular children in order to set the current state explicitly. But it doesn't work and I have no error.

In my controller :

// get admin menu
$menu = $this->get('knp_menu.menu_provider')->get('admin');
$item = $menu->getChildren('user');
$item['user']->setCurrent(true);

$item['user']->isCurrent() returns true but I don't have the "current" css class on the corresponding li.

Any idea ?

stof commented 8 years ago

Well, you are passing your $menu variable to your template, or are you using the menu provider to build the menu again in the template ? If you rebuild it when using it in the template, you will deal with a different Menu object tree, where you manual change won't be available.

A better solution to achieve your use case is probably to handle this logic in your menu builder based on options, and using the knp_menu_get argument allowing to pass options to the menu provider.

eved42 commented 8 years ago

I don't understand very well. Let me explain to you my project.

I want a simple menu without children, like this :

When I go to the corresponding route, users item is set to current, at this point, everything is ok. But, if I have a url like this : /users/add (using route "user_show"), I would like that the current state stay on users. Actually it desappears.

That's why, in my showAction() function in my UserController.php, I want to get my admin menu in order to put the current state manually.

So, I created MenuBuilder.php with 2 methods (createMainMenu() and createAdminMenu() like in the tutorial) and I use it as a service.

<?php

namespace AppBundle\Menu;

use Knp\Menu\FactoryInterface;

class MenuBuilder
{
    private $factory;

    /**
     * @param FactoryInterface $factory
     *
     * Add any other dependency you need
     */
    public function __construct(FactoryInterface $factory)
    {
        $this->factory = $factory;
    }

    public function createAdminMenu(array $options)
    {
        $menu = $this->factory->createItem('root');
        $menu->setChildrenAttribute('class', 'nav nav-sidebar');

        // menu architecture
        $items = array(
          'user' => array(
             'title'  => 'Users management',
             'params' => array(
                'label'    => 'Users',
                'route'    => 'user_show'
             )
          )
       );

       // menu building
       foreach ($items as $name => $item) {
           $menu->addChild($name, $item['params']);
           # ...
       }

       return $menu;
    }
}
services:
   app.menu_builder:
        class: AppBundle\Menu\MenuBuilder
        arguments: ["@knp_menu.factory"]
        tags:
            - { name: knp_menu.menu_builder, method: createMainMenu, alias: main }
            - { name: knp_menu.menu_builder, method: createAdminMenu, alias: admin }

Then, I render it in my layout.html.twig {{ knp_menu_render('admin') }}

UserController.php

class UserController extends Controller
{
   /**
    * @Route("/users", name="user")
    */
    public function indexAction() {
      return $this->render('users/index.html.twig');
    }

   /**
    * @Route("/users/{action}", name="user_show")
    */
    public function showAction($action) {
      // get admin menu
      $menu = $this->get('knp_menu.menu_provider')->get('admin');
      $item = $menu->getChildren('user');
      $item['user']->setCurrent(true);

      # ...
    }
}

What should I change ? Can you explain me in details please. I'm new to Symfony so it's a little bit difficult for me to understand very well, sorry.

stof commented 8 years ago

Well, your controller builds a menu, then alter it, and then drop the object into nowhere. and then, the template builds a menu again because you ask for a admin menu.

Instead, you should use one of these alternatives:

stof commented 8 years ago

the best solution in your case is probably the last one, i.e. registering additional matched routes for the menu item.

eved42 commented 8 years ago

Ah okay. I didn't understand that this code created, in fact, a new menu ! $menu = $this->get('knp_menu.menu_provider')->get('admin');

I thought that I got back my admin menu that I created in my menu builder... I will try your last solution. I've noticed that I had a route issue, I edited my previous post. Now I have 2 differents routes, one for index and the other one for add action.

Thanks !

eved42 commented 8 years ago

Great ! Your last solution is very simple et it's exactly what I was trying to do.

Thank you very much !