Search code examples
phpsymfonyknpmenubundleknpmenu

Remove RouteVoter on a KnpMenu


Each bundle of the extranet is a independent application. In my menu every app is listed and I like to mark the current one depending the actual route prefix.

First the twig code in the base.html.twig:

{{ knp_menu_render('AppBundle:Builder:mainMenu', { 'currentClass': 'active'}) }}

The builder function:

public function mainMenu(FactoryInterface $factory, array $options){
  $main = $factory->createItem('root');

  foreach($this->getExtranetBundles() as $bundle){
    $main->addChild($bundle->getAcronym(), array('route' => $bundle->getRoute()));
  }

  // Current Element
  $matcher = new Matcher();
  $matcher->addVoter(new BundleVoter($this->getCurrentBundle()));
  $renderer = new ListRenderer($matcher);
  $renderer->render($main);

  return $main;
}

My BundleVoter class works correct and returns true if the current menu is found. But in the HTML the current element never contains the "active" class.

I read myself a bit more in the KnpMenuBundle and added some debug code in the Knp\Menu\Matcher class:

public function addVoter(VoterInterface $voter)
{
    echo "add voter: " . get_class($voter);
    $this->voters[] = $voter;
}

And got this output:

add voter: AppBundle\Menu\BundleVoter
add voter: Knp\Menu\Matcher\Voter\RouteVoter

From where does the mysterious RouteVoter come from? Does it overwrite my BundleVoter selection of current elements? And how can I deactivate / overwrite it?


Solution

  • Found a way to change the standard knp_menu class. I edited the services.yml file to the follow:

    parameters:
        knp_menu.voter.router.class: AppBundle\Menu\BundleVoter
    
    services:
        appbundle.menu.voter.request:
            class: AppBundle\Menu\BundleVoter
            arguments: [@service_container]
            tags:
              - { name: knp_menu.voter }
    

    The class is still instantiated two times, unfortunately I have to check if the passed parameter if its empty or not. And the $container param has to be optional...

    class BundleVoter implements VoterInterface
    {
      private $container;
    
      public function __construct($container = null)
      {
        if($container != null)
          $this->container = $container;
      }
    
      public function matchItem(ItemInterface $item)
      {
        if($this->container != null){
          $bundle = $this->container->get('menubundles')->getCurrentBundle();
          if (null === $bundle || null === $item->getName()) {
            return null;
          }
    
          if ($item->getName() == $bundle->getAcronym()) {
            return true;
          }
        }
    
        return null;
      }
    }
    

    Please write if you found a better solution :-) Thx