KnpMenu Documentation

repository·master·Indexed 23 days ago

https://github.com/knplabs/knpmenu

An object-oriented PHP library for creating and managing menus. KnpMenu allows for the construction of menu trees using MenuFactory, customization of labels and URIs, and flexible rendering via ListRenderer or Twig integration. It supports advanced features such as NodeLoader for tree structures, a Matcher system with voters (UriVoter, RouteVoter, RegexVoter) to identify current items, and a MenuExtension for Twig templates.

Tokens
10.1K
Snippets
18
Records
41
Agent score
76%

What's inside KnpMenu

  1. How current menu items are matched

    master

    A menu item is considered "current" if it matches the current request. This state can be:

    1. Explicitly set: Using $item->setCurrent(true) or $item->setCurrent(false).
    2. Matched via Voters: Using a Knp\Menu\Matcher\Matcher with registered voters.

    By default, the current class is added to the <li> of the matching item, and current_ancestor is added to its parents. If currentAsLink is set to false in the renderer options, the current item is rendered as a <span> instead of an <a>.

    Available Voters

    • Knp\Menu\Matcher\Voter\UriVoter: Matches against the item's URI.
    • Knp\Menu\Matcher\Voter\RouteVoter: Matches the _route attribute of a Symfony Request against the item's routes extra.
    • Knp\Menu\Matcher\Voter\RegexVoter: Matches the item's URI using a regular expression.
    <?php
    use Knp\Menu\Matcher\Matcher;
    use Knp\Menu\Matcher\Voter\UriVoter;
    use Knp\Menu\MenuFactory;
    use Knp\Menu\Renderer\ListRenderer;
    
    $factory = new MenuFactory();
    $menu = $factory->createItem('My menu');
    
    // set the current state explicitly
    $menu['current_item']->setCurrent(true);
    $menu['non_current_item']->setCurrent(false);
    
    // Use the voter
    $menu['other_item']->setCurrent(null); // default value for items
    
    $matcher = new Matcher();
    $matcher->addVoter(new UriVoter($_SERVER['REQUEST_URI']));
    
    $renderer = new ListRenderer($matcher);
    <?php
    use Knp\Menu\Matcher\Matcher;
    use Knp\Menu\Matcher\Voter\UriVoter;
    use Knp\Menu\MenuFactory;
    use Knp\Menu\Renderer\ListRenderer;
    
    $factory = new MenuFactory();
    $menu = $factory->createItem('My menu');
    
    // set the current state explicitly
    $menu['current_item']->setCurrent(true);
    $menu['non_current_item']->setCurrent(false);
    
    // Use the voter
    $menu['other_item']->setCurrent(null); // default value for items
    
    $matcher = new Matcher();
    $matcher->addVoter(new UriVoter($_SERVER['REQUEST_URI']));
    
    $renderer = new ListRenderer($matcher);
  2. How menu items and trees work together

    master

    The menu framework is based on Knp\Menu\ItemInterface objects, which represent individual menu items (conceptually an <li> tag). These items can contain child items, forming a tree structure.

    A menu tree implements ArrayAccess, Countable, and Iterator, allowing you to interact with it like a multidimensional array using the names assigned to items.

    <?php
    use Knp\Menu\MenuFactory;
    
    $factory = new MenuFactory();
    $menu = $factory->createItem('My menu');
    $menu->addChild('Home', ['uri' => '/']);
    $menu->addChild('Comments');
    
    // Accessing items via ArrayAccess using the name assigned during creation
    $menu['Comments']->setUri('#comments');
    $menu['Comments']->addChild('My comments', ['uri' => '/my_comments']);
    
    // Using Countable
    echo count($menu); // returns 2
    
    // Using Iterator
    foreach ($menu as $child) {
      echo $child->getLabel();
    }
    <?php
    use Knp\Menu\MenuFactory;
    
    $factory = new MenuFactory();
    $menu = $factory->createItem('My menu');
    $menu->addChild('Home', ['uri' => '/']);
    $menu->addChild('Comments');
    
    // ArrayAccess
    $menu['Comments']->setUri('#comments');
    $menu['Comments']->addChild('My comments', ['uri' => '/my_comments']);
    
    // Countable
    echo count($menu); // returns 2
    
    // Iterator
    foreach ($menu as $child) {
      echo $child->getLabel();
    }
  3. How the Matcher determines the current page

    master

    A Matcher is used to determine if a menu item represents the current page or one of its ancestors. The default implementation uses a voter pattern, where a collection of Voter objects are evaluated to decide if a menu item matches the current request context.

    To use a Matcher, you must instantiate it with a list of voters and then pass that matcher instance to your renderer (e.g., ListRenderer).

    use Knp\
    Menu\
    Matcher\
    Matcher;
    use Knp\
    Menu\
    Renderer\
    ListRenderer;
    use Knp\
    Menu\
    Matcher\
    Voter\
    UriVoter;
    
    // 1. Create the matcher with voters
    $itemMatcher = new Matcher([new UriVoter(str_replace('/index.php', '', $_SERVER['REQUEST_URI']))]);
    
    // 2. Pass the matcher to the renderer
    $renderer = new ListRenderer($itemMatcher);
  4. Configure the TwigRenderer and custom templates

    master

    The TwigRenderer uses a Twig template to render the menu structure (typically as <ul> and <li> elements).

    Registration

    Ensure the KnpMenu view directory is in your Twig FilesystemLoader:

    $twigLoader = new \\Twig\\Loader\\FilesystemLoader([
        __DIR__.'/vendor/KnpMenu/src/Knp/Menu/Resources/views',
        // your own paths
    ]);
    $menuRenderer = new \\Knp\\Menu\\Renderer\\TwigRenderer($twig, 'knp_menu.html.twig', $itemMatcher);
    ```n
    ### Customizing Templates
    You can customize the rendering in two ways:
    1. **Globally**: Pass a different template name as the second argument to the `TwigRenderer` constructor.
    2. **Locally**: Pass a `template` option when calling `render()`:
       ```php
       echo $menuRenderer->render($menu, ['template' => 'my_menu.html.twig']);

    Template Structure

    Custom templates should extend the built-in one and must contain two blocks: root and compressed_root to handle the menu root.

    To render an ordered list instead of an unordered list, use the template name knp_menu_ordered.html.twig.

  5. Install KnpMenu via Composer

    master

    Install knp-menu into your project using Composer. This will add the dependency to your composer.json and update your composer.lock file. The library follows PSR-4 conventions for easy autoloader integration.

    composer require knplabs/knp-menu
  6. Create a menu from a tree structure using NodeLoader

    master

    If you have a data structure that represents a tree (such as a nested set), you can automatically generate a KnpMenu by making your tree nodes implement Knp\Menu\NodeInterface.

    To convert these nodes into a menu, use the Knp\Menu\Loader\NodeLoader. You pass your FactoryInterface to the NodeLoader constructor, and then call the load() method passing your root node (which must implement Knp\Menu\NodeInterface). This returns an ItemInterface representing the root of the menu.

    <?php
    
    namespace App\Menu;
    
    use Knp\Menu\FactoryInterface;
    use Knp\Menu\ItemInterface;
    use Knp\Menu\Loader\NodeLoader;
    
    class Builder
    {
        private $factory;
    
        public function __construct(FactoryInterface $factory)
        {
            $this->factory = $factory;
        }
    
        public function createMenu(): ItemInterface
        {
            $loader = new NodeLoader($this->factory);
            $rootNode = /* ... get an object implementing \Knp\Menu\NodeInterface */;
            $menu = $loader->load($rootNode);
    
            return $menu;
        }
    }
  7. Get started with KnpMenu

    master

    To create and render a menu, use the MenuFactory to create menu items and the ListRenderer to convert the menu object into HTML. You can add children to menu items using addChild(), passing a label and an optional array of attributes (like uri).

    <?php
    
    // Include dependencies installed with composer
    require 'vendor/autoload.php';
    
    use Knp\
    Menu\
    Factory;
    use Knp\
    Menu\
    Renderer\
    ListRenderer;
    
    $factory = new MenuFactory();
    $menu = $factory->createItem('My menu');
    $menu->addChild('Home', ['uri' => '/']);
    $menu->addChild('Comments', ['uri' => '#comments']);
    $menu->addChild('Symfony', ['uri' => 'http://symfony.com/']);
    $menu->addChild('Happy Awesome Developers');
    
    $renderer = new ListRenderer(new \Knp\\Menu\\Matcher\\Matcher());
    echo $renderer->render($menu);
  8. Filter only displayed menu items

    master

    To iterate only over items that are currently visible (not hidden), use Knp\Menu\Iterator\DisplayedItemFilterIterator.

    Important: Because hiding an item also hides its children, this is a recursive filter iterator. It should be applied to the RecursiveItemIterator before you wrap it in a \RecursiveIteratorIterator. This ensures the filtering logic respects the tree structure and correctly prunes hidden branches.

    <?php
    
    $menu = /* get your root item from somewhere */;
    
    // 1. Wrap root in ArrayIterator to include it
    $rootIterator = new \ArrayIterator([$menu]);
    
    // 2. Create the recursive item iterator
    $itemIterator = new \Knp\Menu\Iterator\RecursiveItemIterator($rootIterator);
    
    // 3. Wrap in the DisplayedItemFilterIterator (applies recursively)
    $filteredIterator = new \Knp\Menu\Iterator\DisplayedItemFilterIterator($itemIterator);
    
    // 4. Flatten for iteration
    $iterator = new \RecursiveIteratorIterator($filteredIterator, \RecursiveIteratorIterator::SELF_FIRST);
    
    foreach ($iterator as $item) {
        echo $item->getName() . " ";
    }
  9. Create a basic menu with MenuFactory and ListRenderer

    master

    To create a menu, use Knp\Menu\MenuFactory to generate an ItemInterface object. You can build a tree by adding children using addChild(). To display the menu, use a RendererInterface implementation like ListRenderer along with a Matcher.

    <?php
    
    use Knp\Menu\Matcher\Matcher;
    use Knp\Menu\MenuFactory;
    use Knp\Menu\Renderer\ListRenderer;
    
    $factory = new MenuFactory();
    $menu = $factory->createItem('My menu');
    $menu->addChild('Home', ['uri' => '/']);
    $menu->addChild('Comments', ['uri' => '#comments']);
    $menu->addChild('Symfony', ['uri' => 'https://symfony.com/']);
    
    $renderer = new ListRenderer(new Matcher());
    echo $renderer->render($menu);
    <?php
    
    use Knp\Menu\Matcher\Matcher;
    use Knp\Menu\MenuFactory;
    use Knp\Menu\Renderer\ListRenderer;
    
    $factory = new MenuFactory();
    $menu = $factory->createItem('My menu');
    $menu->addChild('Home', ['uri' => '/']);
    $menu->addChild('Comments', ['uri' => '#comments']);
    $menu->addChild('Symfony', ['uri' => 'https://symfony.com/']);
    
    $renderer = new ListRenderer(new Matcher());
    echo $renderer->render($menu);
  10. Integrate KnpMenu with Silex 1

    master
    The Silex service provider included directly within the knplabs/knpmenu package is deprecated as of version 2.3. To integrate KnpMenu with a Silex 1 application, you must use the dedicated standalone package knplabs/knp-menu-silex instead of the built-in provider.
  11. Filter only current menu items

    master

    If you need to iterate only over items that are currently active/selected, use Knp\Menu\Iterator\CurrentItemFilterIterator. This is a filter iterator that must be applied to an existing iterator and requires a Knp\Menu\Matcher\Matcher instance to determine the current state of items.

    To filter the entire tree for current items, combine a RecursiveItemIterator (wrapped in an ArrayIterator to include the root) with the CurrentItemFilterIterator.

    <?php
    
    $menu = /* get your root item from somewhere */;
    $itemMatcher = new \Knp\Menu\Matcher\Matcher();
    
    // Create a recursive iterator that includes the root
    $treeIterator = new \RecursiveIteratorIterator(
        new \Knp\Menu\Iterator\RecursiveItemIterator(
            new \ArrayIterator([$menu])
        ),
        \RecursiveIteratorIterator::SELF_FIRST
    );
    
    // Apply the current item filter
    $iterator = new \Knp\Menu\Iterator\CurrentItemFilterIterator($treeIterator, $itemMatcher);
    
    foreach ($iterator as $item) {
        echo $item->getName() . " ";
    }
  12. Iterate recursively over a menu tree

    master

    To traverse an entire menu tree (including all descendants), use Knp\Menu\Iterator\RecursiveItemIterator wrapped in a standard PHP \RecursiveIteratorIterator.

    By default, RecursiveItemIterator starts iteration on the children of the provided item, meaning the root item itself is excluded. To include the root item in your iteration, wrap the root item in an \ArrayIterator before passing it to the RecursiveItemIterator.

    Use \RecursiveIteratorIterator::SELF_FIRST to visit parents before children, or \RecursiveIteratorIterator::CHILD_FIRST to visit children before parents.

    <?php
    
    $menu = /* get your root item from somewhere */;
    
    // To include the root item, wrap it in an ArrayIterator
    $rootIterator = new \ArrayIterator([$menu]);
    
    // Create the recursive item iterator
    $itemIterator = new \Knp\Menu\Iterator\RecursiveItemIterator($rootIterator);
    
    // Wrap in RecursiveIteratorIterator to flatten the tree
    $iterator = new \RecursiveIteratorIterator($itemIterator, \RecursiveIteratorIterator::SELF_FIRST);
    
    foreach ($iterator as $item) {
        echo $item->getName() . " ";
    }