02-Twig-Integration.markdown 8.0 KB

Twig Integration

KnpMenu provides 2 different (and complementary) integrations with Twig:

  • MenuExtension: a Twig extension allowing you to easily render menus from within a Twig template

  • TwigRenderer: a Twig renderer that (behind the scenes) uses a Twig template to render the menu

Using the MenuExtension

The easiest (but not best) way to render a menu inside a Twig template is to pass both the renderer and menu into a template:

<?php

// bootstrap Twig
$twigLoader = new \Twig_Loader_Filesystem(array(
    // path to your templates
));
$twig = new \Twig_Environment($twigLoader);

$itemMatcher = new \Knp\Menu\Matcher\Matcher();
// setup some renderer
$renderer = new \Knp\Menu\Renderer\ListRenderer($itemMatcher);
//$menuRenderer = new \Knp\Menu\Renderer\TwigRenderer($twig, 'knp_menu.html.twig', $itemMatcher);

// render a template
$template = $twig->loadTemplate('menu.twig');
echo $template->display(array(
    'renderer' => $renderer,
    'menu' => $menu
));

To render the menu, your template would look like this:

{{ renderer.render(menu) | raw }}

This is ok, but there is a better way. By configuring all of your renderers and menus in a central location, you can use a convenient and shorthand syntax to render your menus inside a Twig template.

Loading your renderers from a provider

To make life simpler, a good option is to centralize the setup of all of your renderers. To do this, you'll need to create a "renderer provider", which is some object - implementing Knp\Menu\Renderer\RendererProviderInterface - which acts like a container for all of your renderers.

The default implementation of the renderer provider is based on Pimple so that your renderers can be lazy-loaded.

<?php
// setup pimple, and assign the renderer to "menu_renderer"
$pimple = new \Pimple();
$itemMatcher = \Knp\Menu\Matcher\Matcher();
$pimple['list_renderer'] = function() {
    return new \Knp\Menu\Renderer\ListRenderer($itemMatcher);
};

$rendererProvider = new \Knp\Menu\Renderer\PimpleProvider(
    $pimple,
    // common name of the renderer used by default
    'main',
    // common name for the renderer => name of the renderer in pimple
    array('main' => 'list_renderer')
);
$helper = new \Knp\Menu\Twig\Helper($rendererProvider);
$menuExtension = new \Knp\Menu\Twig\MenuExtension($helper);
$twig->addExtension($menuExtension);

Now, the renderer is aliased to the name main. You can render the menu with the default renderer simply via:

{{ knp_menu_render(menu) }}

In this example, menu variable is the MenuItem object you've passed into your template.

You can also pass options when rendering the template:

{{ knp_menu_render(menu, {'currentAsLink': false, 'compressed': true}) }}

You can also use another renderer than the default one by passing its name:

{{ knp_menu_render(menu, {}, 'main') }}

Retrieving an item by its path in the tree

The Twig extension allow you to retrieve an item in your menu tree by its path (the name of the item is used in the path):

{# The menu variable contains a Knp\Menu\ItemInterface object #}
{% set item = knp_menu_get(menu, ['Comment', 'My comments']) %}

{# The following could be used but would throw a Fatal Error for some invalid
paths instead of an exception: #}
{% set item = menu['Comment']['My comments'] %}

{# actually render the part of the menu #}
{{ knp_menu_render(item) }}

NOTE An InvalidArgumentException will be thrown if the path is invalid.

Using the path is also supported when using the knp_menu_render function by using an array:

{# The menu variable contains a Knp\Menu\ItemInterface object #}
{{ knp_menu_render([menu, 'Comment', 'My comments']) }}

Loading the menu from a provider

The MenuExtension also supports retrieving the menus from a provider implementing Knp\Menu\Provider\MenuProviderInterface which works the same way as the RendererProviderInterface. The default implementation is also based on Pimple.

<?php
$factory = new MenuFactory();

$pimple = new \Pimple();
// setup the renderer(s) in Pimple

$pimple['menu_main'] = function() use ($factory) {
    $menu = $factory->createItem('My menu');
    // setup the menu

    return $menu;
};
$pimple['menu_sidebar'] = ... //

// $rendererProvider = ...
$menuProvider = new \Knp\Menu\Provider\PimpleProvider(
    $pimple,
    array('main' => 'main_menu', 'sidebar' => 'menu_sidebar')
);
$helper = new \Knp\Menu\Twig\Helper($rendererProvider, $menuProvider);
$menuExtension = new \Knp\Menu\Twig\MenuExtension($helper);

You can now retrieve the menu by its name in the template:

{% set menu = knp_menu_get('sidebar') %}
{# The menu variable now contains a Knp\Menu\ItemInterface object #}

When a menu provider is set, you can also use the menu name instead of the menu object in the other functions:

{{ knp_menu_render('main', {'depth': 1}) }}

{{ knp_menu_render(['main', 'Comments', 'My comments'], {'depth': 2}) }}

{% set item = knp_menu_get('sidebar', ['First section']) %}

In some cases, you may want to build the menu differently according to the place it is used. As of KnpMenu 1.1, the knp_menu_get function supports passing an array of options for the menu provider.

To be able to use these options in the Pimple provide, you should register the menu as a factory closure through $pimple->protect(). It will then be called with the array of options as first argument and the pimple instance as second argument:

<?php
$factory = new MenuFactory();

$pimple = new \Pimple();
// setup the renderer(s) in Pimple

$pimple['menu_main'] = $pimple->protect(function(array $options, $c) use ($factory) {
    $menu = $factory->createItem('My menu');
    // setup the menu
    // you can use the options you passed to the provider
    // and access the pimple container for this.

    return $menu;
});

Using the TwigRenderer

Registering the renderer

To use the TwigRenderer, you need to add the path of the template in the loader when bootstrapping Twig.

<?php

$twigLoader = new \Twig_Loader_Filesystem(array(
    __DIR__.'/vendor/KnpMenu/src/Knp/Menu/Resources/views',
    // your own paths
));
$twig = new \Twig_Environment($twigLoader);
$itemMatcher = \Knp\Menu\Matcher\Matcher();
$menuRenderer = new \Knp\Menu\Renderer\TwigRenderer($twig, 'knp_menu.html.twig', $itemMatcher);

This works just like any other renderer, and will output an un-ordered list of menu items (e.g. ul and li elements):

<?php
echo $menuRenderer->render($menu);

Behind the scenes, the renderer is using a Twig template to render the menu. This template can be customized by you.

NOTE A second template named knp_menu_ordered.html.twig can be used if you want to render the menu using an ordered list. This template extends the previous one which must be available using the knp_menu.html.twig name (which is the case when configuring the loader like previously).

Using a custom template

If you need to customize how the template is rendered - beyond all of the options given to you by modifying the menu items themselves - you can customize the Twig template that renders the menu.

You can change the template used to render the menu in two different ways:

1) Globally: Change the second argument passed to the constructor of the renderer.

2) Locally: Pass a template option when rendering the menu

<?php
echo $menuRenderer->render($menu, array('template' => 'my_menu.html.twig'));

The template needs to contain 2 blocks: root and compressed_root which are used to display the root of the menu. The easiest way to customize the rendering is to extend the built-in template and to replace the block you want.

NOTE The built-in templates contains some additional blocks to make it easier to customize it when using the inheritance.