This commit is contained in:
Xes
2025-08-14 22:41:49 +02:00
parent 2de81ccc46
commit 8ce45119b6
39774 changed files with 4309466 additions and 0 deletions

View File

@@ -0,0 +1,46 @@
<?php
namespace Knp\Bundle\MenuBundle\DependencyInjection\Compiler;
use Symfony\Component\Config\Definition\Exception\InvalidConfigurationException;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
use Symfony\Component\DependencyInjection\Reference;
/**
* This compiler pass registers the renderers in the RendererProvider.
*
* @author Christophe Coevoet <stof@notk.org>
*
* @internal
*/
class AddExtensionsPass implements CompilerPassInterface
{
public function process(ContainerBuilder $container)
{
if (!$container->has('knp_menu.factory')) {
return;
}
$taggedServiceIds = $container->findTaggedServiceIds('knp_menu.factory_extension');
if (0 === count($taggedServiceIds)) {
return;
}
$definition = $container->findDefinition('knp_menu.factory');
if (!method_exists($container->getParameterBag()->resolveValue($definition->getClass()), 'addExtension')) {
throw new InvalidConfigurationException(sprintf(
'To use factory extensions, the service of class "%s" registered as knp_menu.factory must implement the "addExtension" method',
$definition->getClass()
));
}
foreach ($taggedServiceIds as $id => $tags) {
foreach ($tags as $tag) {
$priority = isset($tag['priority']) ? $tag['priority'] : 0;
$definition->addMethodCall('addExtension', [new Reference($id), $priority]);
}
}
}
}

View File

@@ -0,0 +1,43 @@
<?php
namespace Knp\Bundle\MenuBundle\DependencyInjection\Compiler;
use Symfony\Component\DependencyInjection\Argument\IteratorArgument;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
use Symfony\Component\DependencyInjection\Reference;
/**
* This compiler pass registers the providers in the ChainProvider.
*
* @author Christophe Coevoet <stof@notk.org>
*
* @internal
*/
class AddProvidersPass implements CompilerPassInterface
{
public function process(ContainerBuilder $container)
{
if (!$container->hasDefinition('knp_menu.menu_provider.chain')) {
return;
}
$providers = [];
foreach ($container->findTaggedServiceIds('knp_menu.provider') as $id => $tags) {
$providers[] = new Reference($id);
}
if (1 === count($providers)) {
// Use an alias instead of wrapping it in the ChainProvider for performances
// when using only one (the default case as the bundle defines one provider)
$container->setAlias('knp_menu.menu_provider', (string) reset($providers));
} else {
if (class_exists(IteratorArgument::class)) {
$providers = new IteratorArgument($providers);
}
$definition = $container->getDefinition('knp_menu.menu_provider.chain');
$definition->replaceArgument(0, $providers);
$container->setAlias('knp_menu.menu_provider', 'knp_menu.menu_provider.chain');
}
}
}

View File

@@ -0,0 +1,50 @@
<?php
namespace Knp\Bundle\MenuBundle\DependencyInjection\Compiler;
use Knp\Menu\Renderer\PsrProvider;
use Symfony\Component\DependencyInjection\Compiler\ServiceLocatorTagPass;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
use Symfony\Component\DependencyInjection\Reference;
/**
* This compiler pass registers the renderers in the RendererProvider.
*
* @author Christophe Coevoet <stof@notk.org>
*
* @internal
*/
class AddRenderersPass implements CompilerPassInterface
{
public function process(ContainerBuilder $container)
{
if (!$container->hasDefinition('knp_menu.renderer_provider')) {
return;
}
$renderers = [];
$rendererReferences = [];
foreach ($container->findTaggedServiceIds('knp_menu.renderer', true) as $id => $tags) {
foreach ($tags as $attributes) {
if (empty($attributes['alias'])) {
throw new \InvalidArgumentException(sprintf('The alias is not defined in the "knp_menu.renderer" tag for the service "%s"', $id));
}
$renderers[$attributes['alias']] = $id;
$rendererReferences[$attributes['alias']] = new Reference($id);
}
}
if (class_exists(ServiceLocatorTagPass::class)) {
$locator = ServiceLocatorTagPass::register($container, $rendererReferences);
// Replace the service definition with a PsrProvider
$container->register('knp_menu.renderer_provider', PsrProvider::class)
->addArgument($locator)
->addArgument('%knp_menu.default_renderer%');
} else {
// BC for Symfony < 3.3
$definition = $container->getDefinition('knp_menu.renderer_provider');
$definition->replaceArgument(2, $renderers);
}
}
}

View File

@@ -0,0 +1,67 @@
<?php
namespace Knp\Bundle\MenuBundle\DependencyInjection\Compiler;
use Symfony\Component\DependencyInjection\Argument\IteratorArgument;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
use Symfony\Component\DependencyInjection\Reference;
/**
* This compiler pass registers the voters in the Matcher.
*
* @author Christophe Coevoet <stof@notk.org>
*
* @internal
*/
class AddVotersPass implements CompilerPassInterface
{
public function process(ContainerBuilder $container)
{
if (!$container->hasDefinition('knp_menu.matcher')) {
return;
}
$definition = $container->getDefinition('knp_menu.matcher');
$listener = $container->getDefinition('knp_menu.listener.voters');
$hasRequestAwareVoter = false;
$voters = [];
foreach ($container->findTaggedServiceIds('knp_menu.voter') as $id => $tags) {
// Process only the first tag. Registering the same voter multiple time
// does not make any sense, and this allows user to overwrite the tag added
// by the autoconfiguration to change the priority (autoconfigured tags are
// always added at the end of the list).
$tag = $tags[0];
$priority = isset($tag['priority']) ? (int) $tag['priority'] : 0;
$voters[$priority][] = new Reference($id);
if (isset($tag['request']) && $tag['request']) {
@trigger_error('Using the "request" attribute of the "knp_menu.voter" tag is deprecated since version 2.2. Inject the RequestStack in the voter instead.', E_USER_DEPRECATED);
$hasRequestAwareVoter = true;
$listener->addMethodCall('addVoter', [new Reference($id)]);
}
}
if (!$hasRequestAwareVoter) {
$container->removeDefinition('knp_menu.listener.voters');
}
if (empty($voters)) {
return;
}
krsort($voters);
$sortedVoters = call_user_func_array('array_merge', $voters);
if (class_exists(IteratorArgument::class)) {
$definition->replaceArgument(0, new IteratorArgument($sortedVoters));
} else {
// BC layer for Symfony DI < 3.3
$definition->replaceArgument(0, $sortedVoters);
}
}
}

View File

@@ -0,0 +1,49 @@
<?php
namespace Knp\Bundle\MenuBundle\DependencyInjection\Compiler;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
/**
* This compiler pass registers the menu builders in the BuilderServiceProvider.
*
* @author Christophe Coevoet <stof@notk.org>
*
* @internal
*/
class MenuBuilderPass implements CompilerPassInterface
{
public function process(ContainerBuilder $container)
{
if (!$container->hasDefinition('knp_menu.menu_provider.builder_service')) {
return;
}
$definition = $container->getDefinition('knp_menu.menu_provider.builder_service');
$menuBuilders = [];
foreach ($container->findTaggedServiceIds('knp_menu.menu_builder') as $id => $tags) {
$builderDefinition = $container->getDefinition($id);
if (!$builderDefinition->isPublic()) {
throw new \InvalidArgumentException(sprintf('Menu builder services must be public but "%s" is a private service.', $id));
}
if ($builderDefinition->isAbstract()) {
throw new \InvalidArgumentException(sprintf('Abstract services cannot be registered as menu builders but "%s" is.', $id));
}
foreach ($tags as $attributes) {
if (empty($attributes['alias'])) {
throw new \InvalidArgumentException(sprintf('The alias is not defined in the "knp_menu.menu_builder" tag for the service "%s"', $id));
}
if (empty($attributes['method'])) {
throw new \InvalidArgumentException(sprintf('The method is not defined in the "knp_menu.menu_builder" tag for the service "%s"', $id));
}
$menuBuilders[$attributes['alias']] = [$id, $attributes['method']];
}
}
$definition->replaceArgument(1, $menuBuilders);
}
}

View File

@@ -0,0 +1,30 @@
<?php
namespace Knp\Bundle\MenuBundle\DependencyInjection\Compiler;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
/**
* @internal
*/
class MenuPass implements CompilerPassInterface
{
public function process(ContainerBuilder $container)
{
if (!$container->hasDefinition('knp_menu.menu_provider.container_aware')) {
return;
}
$definition = $container->getDefinition('knp_menu.menu_provider.container_aware');
$menus = [];
foreach ($container->findTaggedServiceIds('knp_menu.menu') as $id => $tags) {
foreach ($tags as $attributes) {
if (empty($attributes['alias'])) {
throw new \InvalidArgumentException(sprintf('The alias is not defined in the "knp_menu.menu" tag for the service "%s"', $id));
}
$menus[$attributes['alias']] = $id;
}
}
$definition->replaceArgument(1, $menus);
}
}

View File

@@ -0,0 +1,61 @@
<?php
namespace Knp\Bundle\MenuBundle\DependencyInjection\Compiler;
use Symfony\Component\DependencyInjection\Argument\ServiceClosureArgument;
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Reference;
/**
* This compiler pass registers the menu builders in the LazyProvider.
*
* @author Christophe Coevoet <stof@notk.org>
*
* @internal
*/
class RegisterMenusPass implements CompilerPassInterface
{
public function process(ContainerBuilder $container)
{
if (!$container->hasDefinition('knp_menu.menu_provider.lazy')) {
return;
}
// When using Symfony < 3.3, the LazyProvider cannot be used (at least not in a lazy way)
// so the older providers will be used.
if (!class_exists(ServiceClosureArgument::class)) {
$container->removeDefinition('knp_menu.menu_provider.lazy');
return;
}
// Remove the old way of handling this feature.
$container->removeDefinition('knp_menu.menu_provider.container_aware');
$container->removeDefinition('knp_menu.menu_provider.builder_service');
$menuBuilders = [];
foreach ($container->findTaggedServiceIds('knp_menu.menu_builder', true) as $id => $tags) {
foreach ($tags as $attributes) {
if (empty($attributes['alias'])) {
throw new \InvalidArgumentException(sprintf('The alias is not defined in the "knp_menu.menu_builder" tag for the service "%s"', $id));
}
if (empty($attributes['method'])) {
throw new \InvalidArgumentException(sprintf('The method is not defined in the "knp_menu.menu_builder" tag for the service "%s"', $id));
}
$menuBuilders[$attributes['alias']] = [new ServiceClosureArgument(new Reference($id)), $attributes['method']];
}
}
foreach ($container->findTaggedServiceIds('knp_menu.menu', true) as $id => $tags) {
foreach ($tags as $attributes) {
if (empty($attributes['alias'])) {
throw new \InvalidArgumentException(sprintf('The alias is not defined in the "knp_menu.menu" tag for the service "%s"', $id));
}
$menuBuilders[$attributes['alias']] = new ServiceClosureArgument(new Reference($id));
}
}
$container->getDefinition('knp_menu.menu_provider.lazy')->replaceArgument(0, $menuBuilders);
}
}

View File

@@ -0,0 +1,54 @@
<?php
namespace Knp\Bundle\MenuBundle\DependencyInjection;
use Symfony\Component\Config\Definition\Builder\TreeBuilder;
use Symfony\Component\Config\Definition\ConfigurationInterface;
/**
* This class contains the configuration information for the bundle
*
* @author Christophe Coevoet <stof@notk.org>
*/
class Configuration implements ConfigurationInterface
{
/**
* Generates the configuration tree.
*
* @return TreeBuilder
*/
public function getConfigTreeBuilder()
{
$treeBuilder = new TreeBuilder('knp_menu');
// Keep compatibility with symfony/config < 4.2
if (method_exists($treeBuilder, 'getRootNode')) {
$rootNode = $treeBuilder->getRootNode();
} else {
$rootNode = $treeBuilder->root('knp_menu');
}
$rootNode
->children()
->arrayNode('providers')
->addDefaultsIfNotSet()
->children()
->booleanNode('builder_alias')->defaultTrue()->end()
->booleanNode('container_aware')->defaultTrue()->end()
->booleanNode('builder_service')->defaultTrue()->end()
->end()
->end()
->arrayNode('twig')
->addDefaultsIfNotSet()
->canBeUnset()
->children()
->scalarNode('template')->defaultValue('@KnpMenu/menu.html.twig')->end()
->end()
->end()
->booleanNode('templating')->defaultFalse()->end()
->scalarNode('default_renderer')->cannotBeEmpty()->defaultValue('twig')->end()
->end();
return $treeBuilder;
}
}

View File

@@ -0,0 +1,80 @@
<?php
namespace Knp\Bundle\MenuBundle\DependencyInjection;
use Knp\Menu\ItemInterface;
use Knp\Menu\Matcher\Voter\VoterInterface;
use Symfony\Component\DependencyInjection\Extension\PrependExtensionInterface;
use Symfony\Component\HttpKernel\DependencyInjection\Extension;
use Symfony\Component\DependencyInjection\Loader\XmlFileLoader;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\Config\FileLocator;
class KnpMenuExtension extends Extension implements PrependExtensionInterface
{
/**
* Handles the knp_menu configuration.
*
* @param array $configs The configurations being loaded
* @param ContainerBuilder $container
*/
public function load(array $configs, ContainerBuilder $container)
{
$loader = new XmlFileLoader($container, new FileLocator(__DIR__.'/../Resources/config'));
$loader->load('menu.xml');
$configuration = new Configuration();
$config = $this->processConfiguration($configuration, $configs);
foreach ($config['providers'] as $builder => $enabled) {
if ($enabled) {
$container->getDefinition(sprintf('knp_menu.menu_provider.%s', $builder))->addTag('knp_menu.provider');
}
}
if (isset($config['twig'])) {
$loader->load('twig.xml');
$container->setParameter('knp_menu.renderer.twig.template', $config['twig']['template']);
}
if ($config['templating']) {
$loader->load('templating.xml');
}
$container->setParameter('knp_menu.default_renderer', $config['default_renderer']);
// Register autoconfiguration rules for Symfony DI 3.3+
if (method_exists($container, 'registerForAutoconfiguration')) {
$container->registerForAutoconfiguration(VoterInterface::class)
->addTag('knp_menu.voter');
}
}
/**
* {@inheritdoc}
*/
public function getNamespace()
{
return 'http://knplabs.com/schema/dic/menu';
}
/**
* {@inheritdoc}
*/
public function getXsdValidationBasePath()
{
return __DIR__ . '/../Resources/config/schema';
}
public function prepend(ContainerBuilder $container)
{
if (!$container->hasExtension('twig')) {
return;
}
$refl = new \ReflectionClass(ItemInterface::class);
$path = dirname($refl->getFileName()).'/Resources/views';
$container->prependExtensionConfig('twig', ['paths' => [$path]]);
}
}

View File

@@ -0,0 +1,51 @@
<?php
namespace Knp\Bundle\MenuBundle\EventListener;
@trigger_error(sprintf('The %s class is deprecated since 2.2 and will be removed in 3.0.', VoterInitializerListener::class), E_USER_DEPRECATED);
use Knp\Menu\Matcher\Voter\VoterInterface;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\HttpKernel\HttpKernelInterface;
use Symfony\Component\HttpKernel\Event\GetResponseEvent;
use Symfony\Component\HttpKernel\KernelEvents;
/**
* VoterInitializerListener sets the master request in voters needing it.
*
* @author Fabien Potencier <fabien@symfony.com>
*/
class VoterInitializerListener implements EventSubscriberInterface
{
protected $voters = [];
public function onKernelRequest(GetResponseEvent $event)
{
if (HttpKernelInterface::MASTER_REQUEST !== $event->getRequestType()) {
return;
}
foreach ($this->voters as $voter) {
if (method_exists($voter, 'setRequest')) {
$voter->setRequest($event->getRequest());
}
}
}
/**
* Adds a voter in the matcher.
*
* @param VoterInterface $voter
*/
public function addVoter(VoterInterface $voter)
{
$this->voters[] = $voter;
}
public static function getSubscribedEvents()
{
return [
KernelEvents::REQUEST => 'onKernelRequest',
];
}
}

View File

@@ -0,0 +1,29 @@
<?php
namespace Knp\Bundle\MenuBundle;
use Knp\Bundle\MenuBundle\DependencyInjection\Compiler\AddExtensionsPass;
use Knp\Bundle\MenuBundle\DependencyInjection\Compiler\AddProvidersPass;
use Knp\Bundle\MenuBundle\DependencyInjection\Compiler\AddRenderersPass;
use Knp\Bundle\MenuBundle\DependencyInjection\Compiler\AddVotersPass;
use Knp\Bundle\MenuBundle\DependencyInjection\Compiler\MenuBuilderPass;
use Knp\Bundle\MenuBundle\DependencyInjection\Compiler\MenuPass;
use Knp\Bundle\MenuBundle\DependencyInjection\Compiler\RegisterMenusPass;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\HttpKernel\Bundle\Bundle;
class KnpMenuBundle extends Bundle
{
public function build(ContainerBuilder $container)
{
parent::build($container);
$container->addCompilerPass(new RegisterMenusPass());
$container->addCompilerPass(new MenuPass());
$container->addCompilerPass(new MenuBuilderPass());
$container->addCompilerPass(new AddExtensionsPass());
$container->addCompilerPass(new AddProvidersPass());
$container->addCompilerPass(new AddRenderersPass());
$container->addCompilerPass(new AddVotersPass());
}
}

View File

@@ -0,0 +1,140 @@
<?php
namespace Knp\Bundle\MenuBundle\Provider;
use Knp\Menu\FactoryInterface;
use Knp\Menu\ItemInterface;
use Knp\Menu\Provider\MenuProviderInterface;
use Symfony\Component\DependencyInjection\ContainerAwareInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\HttpKernel\KernelInterface;
/**
* A menu provider that allows for an AcmeBundle:Builder:mainMenu shortcut syntax
*
* @author Ryan Weaver <ryan@knplabs.com>
*/
class BuilderAliasProvider implements MenuProviderInterface
{
private $kernel;
private $container;
private $menuFactory;
private $builders = [];
public function __construct(KernelInterface $kernel, ContainerInterface $container, FactoryInterface $menuFactory)
{
$this->kernel = $kernel;
$this->container = $container;
$this->menuFactory = $menuFactory;
}
/**
* Looks for a menu with the bundle:class:method format
*
* For example, AcmeBundle:Builder:mainMenu would create and instantiate
* an Acme\DemoBundle\Menu\Builder class and call the mainMenu() method
* on it. The method is passed the menu factory.
*
* @param string $name The alias name of the menu
* @param array $options
*
* @return \Knp\Menu\ItemInterface
* @throws \InvalidArgumentException
*/
public function get($name, array $options = [])
{
if (!$this->has($name)) {
throw new \InvalidArgumentException(sprintf('Invalid pattern passed to AliasProvider - expected "bundle:class:method", got "%s".', $name));
}
list($bundleName, $className, $methodName) = explode(':', $name);
$builder = $this->getBuilder($bundleName, $className);
if (!method_exists($builder, $methodName)) {
throw new \InvalidArgumentException(sprintf('Method "%s" was not found on class "%s" when rendering the "%s" menu.', $methodName, $className, $name));
}
$menu = $builder->$methodName($this->menuFactory, $options);
if (!$menu instanceof ItemInterface) {
throw new \InvalidArgumentException(sprintf('Method "%s" did not return an ItemInterface menu object for menu "%s"', $methodName, $name));
}
return $menu;
}
/**
* Verifies if the given name follows the bundle:class:method alias syntax.
*
* @param string $name The alias name of the menu
* @param array $options
*
* @return Boolean
*/
public function has($name, array $options = [])
{
return 2 == substr_count($name, ':');
}
/**
* Creates and returns the builder that lives in the given bundle
*
* The convention is to look in the Menu namespace of the bundle for
* this class, to instantiate it with no arguments, and to inject the
* container if the class is ContainerAware.
*
* @param string $bundleName
* @param string $className The class name of the builder
*
* @return object
*
* @throws \InvalidArgumentException If the class does not exist
*/
protected function getBuilder($bundleName, $className)
{
$name = sprintf('%s:%s', $bundleName, $className);
if (!isset($this->builders[$name])) {
$class = null;
$logs = [];
$bundles = [];
$allBundles = $this->kernel->getBundle($bundleName, false);
// In Symfony 4, bundle inheritance is gone, so there is no way to get an array anymore.
if (!is_array($allBundles)) {
$allBundles = [$allBundles];
}
foreach ($allBundles as $bundle) {
$try = $bundle->getNamespace().'\\Menu\\'.$className;
if (class_exists($try)) {
$class = $try;
break;
}
$logs[] = sprintf('Class "%s" does not exist for menu builder "%s".', $try, $name);
$bundles[] = $bundle->getName();
}
if (null === $class) {
if (1 === count($logs)) {
throw new \InvalidArgumentException($logs[0]);
}
throw new \InvalidArgumentException(sprintf('Unable to find menu builder "%s" in bundles %s.', $name, implode(', ', $bundles)));
}
$builder = new $class();
if ($builder instanceof ContainerAwareInterface) {
$builder->setContainer($this->container);
}
$this->builders[$name] = $builder;
}
return $this->builders[$name];
}
}

View File

@@ -0,0 +1,43 @@
<?php
namespace Knp\Bundle\MenuBundle\Provider;
use Knp\Menu\Provider\MenuProviderInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* This provider uses methods of services to build menus.
*
* @author Christophe Coevoet <stof@notk.org>
*/
class BuilderServiceProvider implements MenuProviderInterface
{
private $container;
private $menuBuilders;
public function __construct(ContainerInterface $container, array $menuBuilders = [])
{
$this->container = $container;
$this->menuBuilders = $menuBuilders;
}
public function get($name, array $options = [])
{
if (!isset($this->menuBuilders[$name])) {
throw new \InvalidArgumentException(sprintf('The menu "%s" is not defined.', $name));
}
if (!is_array($this->menuBuilders[$name]) || 2 !== count($this->menuBuilders[$name])) {
throw new \InvalidArgumentException(sprintf('The menu builder definition for the menu "%s" is invalid. It should be an array (serviceId, method)', $name));
}
list($id, $method) = $this->menuBuilders[$name];
return $this->container->get($id)->$method($options);
}
public function has($name, array $options = [])
{
return isset($this->menuBuilders[$name]);
}
}

View File

@@ -0,0 +1,32 @@
<?php
namespace Knp\Bundle\MenuBundle\Provider;
use Knp\Menu\Provider\MenuProviderInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
class ContainerAwareProvider implements MenuProviderInterface
{
private $container;
private $menuIds;
public function __construct(ContainerInterface $container, array $menuIds = [])
{
$this->container = $container;
$this->menuIds = $menuIds;
}
public function get($name, array $options = [])
{
if (!isset($this->menuIds[$name])) {
throw new \InvalidArgumentException(sprintf('The menu "%s" is not defined.', $name));
}
return $this->container->get($this->menuIds[$name]);
}
public function has($name, array $options = [])
{
return isset($this->menuIds[$name]);
}
}

View File

@@ -0,0 +1,45 @@
<?php
namespace Knp\Bundle\MenuBundle\Renderer;
use Knp\Menu\Renderer\RendererProviderInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* @deprecated since version 2.2. Use "Knp\Menu\Renderer\PsrProvider" instead.
*/
class ContainerAwareProvider implements RendererProviderInterface
{
private $container;
private $rendererIds;
private $defaultRenderer;
public function __construct(ContainerInterface $container, $defaultRenderer, array $rendererIds, $triggerDeprecation = true)
{
$this->container = $container;
$this->rendererIds = $rendererIds;
$this->defaultRenderer = $defaultRenderer;
if ($triggerDeprecation) {
@trigger_error(sprintf('The %s class is deprecated since 2.2 and will be removed in 3.0. USe "Knp\Menu\Renderer\PsrProvider" instead.', __CLASS__),E_USER_DEPRECATED);
}
}
public function get($name = null)
{
if (null === $name) {
$name = $this->defaultRenderer;
}
if (!isset($this->rendererIds[$name])) {
throw new \InvalidArgumentException(sprintf('The renderer "%s" is not defined.', $name));
}
return $this->container->get($this->rendererIds[$name]);
}
public function has($name)
{
return isset($this->rendererIds[$name]);
}
}

View File

@@ -0,0 +1,99 @@
<?xml version="1.0" ?>
<container xmlns="http://symfony.com/schema/dic/services"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd">
<parameters>
<parameter key="knp_menu.factory.class">Knp\Menu\MenuFactory</parameter>
<parameter key="knp_menu.factory_extension.routing.class">Knp\Menu\Integration\Symfony\RoutingExtension</parameter>
<parameter key="knp_menu.helper.class">Knp\Menu\Twig\Helper</parameter>
<parameter key="knp_menu.matcher.class">Knp\Menu\Matcher\Matcher</parameter>
<parameter key="knp_menu.menu_provider.chain.class">Knp\Menu\Provider\ChainProvider</parameter>
<parameter key="knp_menu.menu_provider.container_aware.class">Knp\Bundle\MenuBundle\Provider\ContainerAwareProvider</parameter>
<parameter key="knp_menu.menu_provider.builder_alias.class">Knp\Bundle\MenuBundle\Provider\BuilderAliasProvider</parameter>
<parameter key="knp_menu.renderer_provider.class">Knp\Bundle\MenuBundle\Renderer\ContainerAwareProvider</parameter>
<parameter key="knp_menu.renderer.list.class">Knp\Menu\Renderer\ListRenderer</parameter>
<parameter key="knp_menu.renderer.list.options" type="collection"></parameter>
<parameter key="knp_menu.listener.voters.class">Knp\Bundle\MenuBundle\EventListener\VoterInitializerListener</parameter>
<parameter key="knp_menu.voter.router.class">Knp\Menu\Matcher\Voter\RouteVoter</parameter>
</parameters>
<services>
<service id="knp_menu.factory" class="%knp_menu.factory.class%" public="true" />
<service id="knp_menu.factory_extension.routing" class="%knp_menu.factory_extension.routing.class%" public="false">
<argument type="service" id="router" />
<tag name="knp_menu.factory_extension" />
</service>
<service id="knp_menu.helper" class="%knp_menu.helper.class%" public="false">
<argument type="service" id="knp_menu.renderer_provider" />
<argument type="service" id="knp_menu.menu_provider" />
<argument type="service" id="knp_menu.manipulator" />
<argument type="service" id="knp_menu.matcher" />
</service>
<service id="knp_menu.matcher" class="%knp_menu.matcher.class%" public="true">
<argument type="collection" />
</service>
<service id="Knp\Menu\Provider\MenuProviderInterface" alias="knp_menu.menu_provider" public="false"/>
<service id="knp_menu.menu_provider.chain" class="%knp_menu.menu_provider.chain.class%" public="false">
<argument type="collection" />
</service>
<service id="knp_menu.menu_provider.lazy" class="Knp\Menu\Provider\LazyProvider" public="false">
<argument type="collection" />
<tag name="knp_menu.provider" />
</service>
<service id="knp_menu.menu_provider.container_aware" class="%knp_menu.menu_provider.container_aware.class%" public="false">
<argument type="service" id="service_container" />
<argument type="collection" />
</service>
<service id="knp_menu.menu_provider.builder_service" class="Knp\Bundle\MenuBundle\Provider\BuilderServiceProvider" public="false">
<argument type="service" id="service_container" />
<argument type="collection" />
</service>
<service id="knp_menu.menu_provider.builder_alias" class="%knp_menu.menu_provider.builder_alias.class%" public="false">
<argument type="service" id="kernel" />
<argument type="service" id="service_container" />
<argument type="service" id="knp_menu.factory" />
</service>
<service id="knp_menu.renderer_provider" class="%knp_menu.renderer_provider.class%">
<argument type="service" id="service_container" />
<argument>%knp_menu.default_renderer%</argument>
<argument type="collection" />
<argument>false</argument>
</service>
<service id="knp_menu.renderer.list" class="%knp_menu.renderer.list.class%">
<tag name="knp_menu.renderer" alias="list" />
<argument type="service" id="knp_menu.matcher" />
<argument>%knp_menu.renderer.list.options%</argument>
<argument>%kernel.charset%</argument>
</service>
<service id="knp_menu.listener.voters" class="%knp_menu.listener.voters.class%">
<tag name="kernel.event_listener" event="kernel.request" method="onKernelRequest" />
</service>
<service id="knp_menu.voter.router" class="%knp_menu.voter.router.class%">
<argument type="service" id="request_stack" />
<tag name="knp_menu.voter" />
</service>
<service id="knp_menu.manipulator" class="Knp\Menu\Util\MenuManipulator" public="false" />
<!-- autowiring aliases -->
<service id="Knp\Menu\FactoryInterface" alias="knp_menu.factory" public="false" />
<service id="Knp\Menu\Matcher\MatcherInterface" alias="knp_menu.matcher" public="false" />
<service id="Knp\Menu\Util\MenuManipulator" alias="knp_menu.manipulator" public="false" />
</services>
</container>

View File

@@ -0,0 +1,30 @@
<?xml version="1.0" encoding="UTF-8" ?>
<xsd:schema xmlns="http://knplabs.com/schema/dic/menu"
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
targetNamespace="http://knplabs.com/schema/dic/menu"
elementFormDefault="qualified">
<xsd:element name="config" type="config"/>
<xsd:complexType name="config">
<xsd:all>
<xsd:element name="providers" type="providers" minOccurs="0"/>
<xsd:element name="twig" type="twig" minOccurs="0"/>
</xsd:all>
<xsd:attribute name="templating" type="xsd:boolean" default="false"/>
<xsd:attribute name="default-renderer" type="xsd:string" default="twig"/>
</xsd:complexType>
<xsd:complexType name="providers">
<xsd:attribute name="builder-alias" type="xsd:boolean" default="true"/>
<xsd:attribute name="container-aware" type="xsd:boolean" default="true"/>
<xsd:attribute name="builder-service" type="xsd:boolean" default="true"/>
</xsd:complexType>
<xsd:complexType name="twig">
<xsd:attribute name="template" type="xsd:string" default="knp_menu.html.twig"/>
</xsd:complexType>
</xsd:schema>

View File

@@ -0,0 +1,20 @@
<?xml version="1.0" ?>
<container xmlns="http://symfony.com/schema/dic/services"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd">
<parameters>
<parameter key="knp_menu.templating.helper.class">Knp\Bundle\MenuBundle\Templating\Helper\MenuHelper</parameter>
</parameters>
<services>
<service id="knp_menu.templating.helper" class="%knp_menu.templating.helper.class%">
<tag name="templating.helper" alias="knp_menu" />
<argument type="service" id="knp_menu.helper" />
<argument type="service" id="knp_menu.matcher" />
<argument type="service" id="knp_menu.manipulator" />
</service>
</services>
</container>

View File

@@ -0,0 +1,29 @@
<?xml version="1.0" encoding="UTF-8"?>
<container xmlns="http://symfony.com/schema/dic/services"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd">
<parameters>
<parameter key="knp_menu.twig.extension.class">Knp\Menu\Twig\MenuExtension</parameter>
<parameter key="knp_menu.renderer.twig.class">Knp\Menu\Renderer\TwigRenderer</parameter>
<parameter key="knp_menu.renderer.twig.options" type="collection"></parameter>
</parameters>
<services>
<service id="knp_menu.twig.extension" class="%knp_menu.twig.extension.class%" public="false">
<tag name="twig.extension" />
<argument type="service" id="knp_menu.helper" />
<argument type="service" id="knp_menu.matcher" />
<argument type="service" id="knp_menu.manipulator" />
</service>
<service id="knp_menu.renderer.twig" class="%knp_menu.renderer.twig.class%">
<tag name="knp_menu.renderer" alias="twig" />
<argument type="service" id="twig" />
<argument>%knp_menu.renderer.twig.template%</argument>
<argument type="service" id="knp_menu.matcher" />
<argument>%knp_menu.renderer.twig.options%</argument>
</service>
</services>
</container>

View File

@@ -0,0 +1,99 @@
Registering your own provider
=============================
Registering your own menu provider allows you to feed your menu with your own
data, accessed by your code. It can for example go through a PHPCR repository
and create the corresponding menu elements.
Create first your Provider class, in the Provider directory of your bundle:
.. code-block:: php
namespace AppBundle\Provider;
use Knp\Menu\FactoryInterface;
use Knp\Menu\Provider\MenuProviderInterface;
class CustomMenuProvider implements MenuProviderInterface
{
/**
* @var FactoryInterface
*/
protected $factory = null;
/**
* @param FactoryInterface $factory the menu factory used to create the menu item
*/
public function __construct(FactoryInterface $factory)
{
$this->factory = $factory;
}
/**
* Retrieves a menu by its name
*
* @param string $name
* @param array $options
* @return \Knp\Menu\ItemInterface
* @throws \InvalidArgumentException if the menu does not exists
*/
public function get($name, array $options = [])
{
if ('demo' == $name) { //several menu could call this provider
$menu = /* construct / get a \Knp\Menu\NodeInterface */;
if ($menu === null) {
throw new \InvalidArgumentException(sprintf('The menu "%s" is not defined.', $name));
}
/*
* Populate your menu here
*/
$menuItem = $this->factory->createFromNode($menu);
return $menuItem;
}
}
/**
* Checks whether a menu exists in this provider
*
* @param string $name
* @param array $options
* @return bool
*/
public function has($name, array $options = [])
{
$menu = /* find the menu called $name */;
return $menu !== null;
}
}
Then, configure the services linked to this new provider.
.. code-block:: yaml
# app/config/services.yml
services:
app.menu_provider:
class: AppBundle\Provider\CustomMenuProvider
arguments:
- @knp_menu.factory
tags:
- { name: knp_menu.provider }
# ...
Finally, to generate the menu, for example inside a twig template type:
.. code-block:: html+jinja
{{ knp_menu_render('demo') }}
The `Symfony CMF MenuBundle`_ provides a complete working example.
.. _`Symfony CMF MenuBundle`: https://github.com/symfony-cmf/MenuBundle

View File

@@ -0,0 +1,57 @@
Registering your own renderer
=============================
Registering your own renderer in the renderer provider is simply a matter
of creating a service tagged with ``knp_menu.renderer``:
.. code-block:: yaml
# app/config/services.yml
services:
app.menu_renderer:
# The class implements Knp\Menu\Renderer\RendererInterface
class: AppBundle\Menu\CustomRenderer
arguments: ["%kernel.charset%"] # set your own dependencies here
tags:
# The alias is what is used to retrieve the menu
- { name: knp_menu.renderer, alias: custom }
# ...
If your renderer extends ``ListRenderer``, you need to provide a ``Matcher`` instance.
The configuration is then the following:
.. code-block:: yaml
# app/config/services.yml
services:
app.menu_renderer:
# The class implements Knp\Menu\Renderer\RendererInterface
class: AppBundle\Menu\CustomRenderer
arguments:
- @knp_menu.matcher
- "%knp_menu.renderer.list.options%"
- "%kernel.charset%"
# add your own dependencies here
tags:
# The alias is what is used to retrieve the menu
- { name: knp_menu.renderer, alias: custom }
# ...
.. note::
The renderer service must be public as it will be retrieved at runtime to
keep it lazy-loaded.
You can now use your renderer to render your menu:
.. code-block:: html+jinja
{{ knp_menu_render('main', {}, 'custom') }}
.. note::
As the renderer is responsible to render some HTML code, the ``knp_menu_render``
function is marked as safe. Take care to handle escaping data in your renderer
to avoid XSS if you use some user input in the menu.

View File

@@ -0,0 +1,22 @@
Disabling the Core Menu Providers
=================================
To be able to use different menu providers together (the builder-service-based
one, the container-based one and the convention-based one for instance),
a chain provider is used. However, it is not used when only one provider
is enabled to increase performance by getting rid of the wrapping. If you
don't want to use the built-in providers, you can disable them through the
configuration:
.. code-block:: yaml
#app/config/config.yml
knp_menu:
providers:
builder_alias: false # disable the builder-alias-based provider
builder_service: false
container_aware: true # keep this one enabled. Can be omitted as it is the default
.. note::
All providers are enabled by default.

View File

@@ -0,0 +1,147 @@
Using events to allow a menu to be extended
===========================================
If you want to let different parts of your system hook into the building of your
menu, a good way is to use an approach based on the Symfony EventDispatcher
component.
Create the menu builder
-----------------------
Your menu builder will create the base menu item and then dispatch an event
to allow other parts of your application to add more stuff to it.
.. code-block:: php
// src/AppBundle/Menu/MainBuilder.php
namespace AppBundle\Menu;
use AppBundle\Event\ConfigureMenuEvent;
use Knp\Menu\FactoryInterface;
use Symfony\Component\DependencyInjection\ContainerAwareInterface;
use Symfony\Component\DependencyInjection\ContainerAwareTrait;
class MainBuilder implements ContainerAwareInterface
{
use ContainerAwareTrait;
public function build(FactoryInterface $factory)
{
$menu = $factory->createItem('root');
$menu->addChild('Dashboard', ['route' => '_acp_dashboard']);
$this->container->get('event_dispatcher')->dispatch(
ConfigureMenuEvent::CONFIGURE,
new ConfigureMenuEvent($factory, $menu)
);
return $menu;
}
}
.. note::
This implementation assumes you use the ``BuilderAliasProvider`` (getting
your menu as ``AppBundle:MainBuilder:build``) but you could also define
it as a service and inject the ``event_dispatcher`` service as a dependency.
Create the Event object
-----------------------
The event object allows to pass some data to the listener. In this case,
it will hold the menu being created and the factory.
.. code-block:: php
// src/AppBundle/Event/ConfigureMenuEvent.php
namespace AppBundle\Event;
use Knp\Menu\FactoryInterface;
use Knp\Menu\ItemInterface;
use Symfony\Component\EventDispatcher\Event;
class ConfigureMenuEvent extends Event
{
const CONFIGURE = 'app.menu_configure';
private $factory;
private $menu;
/**
* @param \Knp\Menu\FactoryInterface $factory
* @param \Knp\Menu\ItemInterface $menu
*/
public function __construct(FactoryInterface $factory, ItemInterface $menu)
{
$this->factory = $factory;
$this->menu = $menu;
}
/**
* @return \Knp\Menu\FactoryInterface
*/
public function getFactory()
{
return $this->factory;
}
/**
* @return \Knp\Menu\ItemInterface
*/
public function getMenu()
{
return $this->menu;
}
}
.. note::
Following the Symfony best practices, the first segment of the event name will
be the alias of the bundle, which allows avoiding conflicts.
That's it. Your builder now provides a hook. Let's see how you can use it!
Create a listener
-----------------
You can register as many listeners as you want for the event. Let's add one.
.. code-block:: php
// src/Acme/AdminBundle/EventListener/ConfigureMenuListener.php
namespace Acme\AdminBundle\EventListener;
use AppBundle\Event\ConfigureMenuEvent;
class ConfigureMenuListener
{
/**
* @param \AppBundle\Event\ConfigureMenuEvent $event
*/
public function onMenuConfigure(ConfigureMenuEvent $event)
{
$menu = $event->getMenu();
$menu->addChild('Matches', ['route' => 'versus_rankedmatch_acp_matches_index']);
$menu->addChild('Participants', ['route' => 'versus_rankedmatch_acp_participants_index']);
}
}
You can now register the listener.
.. code-block:: yaml
# app/config/services.yml
services:
app.admin_configure_menu_listener:
class: Acme\AdminBundle\EventListener\ConfigureMenuListener
tags:
- { name: kernel.event_listener, event: app.menu_configure, method: onMenuConfigure }
You could also create your listener as a subscriber and use the ``kernel.event_subscriber``
tag, which does not have any additional attributes.

View File

@@ -0,0 +1,64 @@
I18n for your Menu Labels
=========================
The KnpMenuBundle translates all menu items by default. Assume you've built a menu
like this::
$menu = $factory->createItem('root');
$menu->addChild('Home', ['route' => 'homepage']);
$menu->addChild('Login', ['route' => 'login']);
The items "Home" and "Login" can now be translated in the message domain:
.. configuration-block::
.. code-block:: yaml
# app/Resources/translations/messages.fr.yml
Home: Accueil
Login: Connexion
.. code-block:: xml
<!-- app/Resources/translations/messages.fr.xlf -->
<?xml version="1.0"?>
<xliff version="1.2" xmlns="urn:oasis:names:tc:xliff:document:1.2">
<file source-language="en" datatype="plaintext" original="file.ext">
<body>
<trans-unit id="menu.home">
<source>Home</source>
<target>Accueil</target>
</trans-unit>
<trans-unit id="menu.login">
<source>Login</source>
<target>Connexion</target>
</trans-unit>
</body>
</file>
</xliff>
.. code-block:: php
// app/Resources/translations/messages.fr.php
return [
'Home' => 'Accueil',
'Login' => 'Connexion',
];
Configure the Translation Domain
--------------------------------
You can configure the translation domain that's used in the extras of the menu
item::
// ...
$menu->addChild('Home', ['route' => 'homepage'])
->setExtra('translation_domain', 'AcmeAdminBundle');
Disabling Translation
---------------------
You can disable translation of the menu item by setting ``translation_domain``
to ``false``.

View File

@@ -0,0 +1,331 @@
Using KnpMenuBundle
===================
Welcome to KnpMenuBundle - creating menus is fun again!
Installation
------------
Step 1: Download the Bundle
~~~~~~~~~~~~~~~~~~~~~~~~~~~
Open a command console, enter your project directory and execute the
following command to download the latest stable version of this bundle:
.. code-block:: bash
$ composer require knplabs/knp-menu-bundle "^2.0"
This command requires you to have Composer installed globally, as explained
in the `installation chapter`_ of the Composer documentation.
Step 2: Enable the Bundle
~~~~~~~~~~~~~~~~~~~~~~~~~
Then, enable the bundle by adding the following line in the ``app/AppKernel.php``
file of your project:
.. code-block:: php
// app/AppKernel.php
// ...
class AppKernel extends Kernel
{
public function registerBundles()
{
$bundles = [
// ...
new Knp\Bundle\MenuBundle\KnpMenuBundle(),
];
// ...
}
// ...
}
Step 3: (optional) Configure the bundle
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
The bundle comes with a sensible default configuration, which is listed below.
You can define these options if you need to change them:
.. configuration-block::
.. code-block:: yaml
# app/config/config.yml
knp_menu:
# use "twig: false" to disable the Twig extension and the TwigRenderer
twig:
template: KnpMenuBundle::menu.html.twig
# if true, enables the helper for PHP templates
templating: false
# the renderer to use, list is also available by default
default_renderer: twig
.. code-block:: xml
<!-- app/config/config.xml -->
<?xml version="1.0" charset="UTF-8" ?>
<container xmlns="http://symfony.com/schema/dic/services"
xmlns:knp-menu="http://knplabs.com/schema/dic/menu">
<!--
templating: if true, enabled the helper for PHP templates
default-renderer: the renderer to use, list is also available by default
-->
<knp-menu:config
templating="false"
default-renderer="twig"
>
<!-- add enabled="false" to disable the Twig extension and the TwigRenderer -->
<knp-menu:twig template="KnpMenuBundle::menu.html.twig"/>
</knp-menu:config>
</container>
.. code-block:: php
// app/config/config.php
$container->loadFromExtension('knp_menu', [
// use 'twig' => false to disable the Twig extension and the TwigRenderer
'twig' => [
'template' => 'KnpMenuBundle::menu.html.twig'
],
// if true, enabled the helper for PHP templates
'templating' => false,
// the renderer to use, list is also available by default
'default_renderer' => 'twig',
]);
.. versionadded::2.1.2
The template used to be ``knp_menu.html.twig`` which did not translate menu entries.
Version 2.1.2 adds the template that translates menu entries.
.. note::
Take care to change the default renderer if you disable the Twig support.
Create your first menu!
-----------------------
There are two ways to create a menu: the "easy" way, and the more flexible
method of creating a menu as a service.
Method a) The Easy Way (yay)!
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
To create a menu, first create a new class in the ``Menu`` directory of one
of your bundles. This class - called ``Builder`` in our example - will have
one method for each menu that you need to build.
An example builder class would look like this:
.. code-block:: php
// src/AppBundle/Menu/Builder.php
namespace AppBundle\Menu;
use Knp\Menu\FactoryInterface;
use Symfony\Component\DependencyInjection\ContainerAwareInterface;
use Symfony\Component\DependencyInjection\ContainerAwareTrait;
class Builder implements ContainerAwareInterface
{
use ContainerAwareTrait;
public function mainMenu(FactoryInterface $factory, array $options)
{
$menu = $factory->createItem('root');
$menu->addChild('Home', ['route' => 'homepage']);
// access services from the container!
$em = $this->container->get('doctrine')->getManager();
// findMostRecent and Blog are just imaginary examples
$blog = $em->getRepository('AppBundle:Blog')->findMostRecent();
$menu->addChild('Latest Blog Post', [
'route' => 'blog_show',
'routeParameters' => ['id' => $blog->getId()]
]);
// create another menu item
$menu->addChild('About Me', ['route' => 'about']);
// you can also add sub levels to your menus as follows
$menu['About Me']->addChild('Edit profile', ['route' => 'edit_profile']);
// ... add more children
return $menu;
}
}
With the standard ``knp_menu.html.twig`` template and your current page being
'Home', your menu would render with the following markup:
.. code-block:: html
<ul>
<li class="current first">
<a href="#route_to/homepage">Home</a>
</li>
<li class="current_ancestor">
<a href="#route_to/page_show/?id=42">About Me</a>
<ul class="menu_level_1">
<li class="current first last">
<a href="#route_to/edit_profile">Edit profile</a>
</li>
</ul>
</li>
</ul>
.. note::
You only need to implement ``ContainerAwareInterface`` if you need the
service container. The more elegant way to handle your dependencies is to
inject them in the constructor. If you want to do that, see method below.
.. note::
The menu builder can be overwritten using the bundle inheritance.
To actually render the menu, just do the following from anywhere in any template:
.. configuration-block::
.. code-block:: html+jinja
{{ knp_menu_render('AppBundle:Builder:mainMenu') }}
.. code-block:: html+php
<?php echo $view['knp_menu']->render('AppBundle:Builder:mainMenu') ?>
With this method, you refer to the menu using a three-part string:
**bundle**:**class**:**method**.
If you needed to create a second menu, you'd simply add another method to
the ``Builder`` class (e.g. ``sidebarMenu``), build and return the new menu,
then render it via ``AppBundle:Builder:sidebarMenu``.
That's it! The menu is *very* configurable. For more details, see the
`KnpMenu documentation`_.
Method b) A menu builder as a service
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
For information on how to register a menu builder as a service, read
:doc:`Creating Menu Builders as Services <menu_builder_service>`.
Method c) A menu as a service
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
For information on how to register a service and tag it as a menu, read
:doc:`Creating Menus as Services <menu_service>`.
.. note::
To improve performances, you can :doc:`disable providers you don't need <disabling_providers>`.
Rendering Menus
---------------
Once you've setup your menu, rendering it easy. If you've used the "easy"
way, then do the following:
.. configuration-block::
.. code-block:: html+jinja
{{ knp_menu_render('AppBundle:Builder:mainMenu') }}
.. code-block:: html+php
<?php echo $view['knp_menu']->render('AppBundle:Builder:mainMenu') ?>
Additionally, you can pass some options to the renderer:
.. configuration-block::
.. code-block:: html+jinja
{{ knp_menu_render('AppBundle:Builder:mainMenu', {'depth': 2, 'currentAsLink': false}) }}
.. code-block:: html+php
<?php echo $view['knp_menu']->render('AppBundle:Builder:mainMenu', [
'depth' => 2,
'currentAsLink' => false,
]) ?>
For a full list of options, see the "Other rendering options" header on the
`KnpMenu documentation`_.
You can also "get" a menu, which you can use to render later:
.. configuration-block::
.. code-block:: html+jinja
{% set menuItem = knp_menu_get('AppBundle:Builder:mainMenu') %}
{{ knp_menu_render(menuItem) }}
.. code-block:: html+php
<?php $menuItem = $view['knp_menu']->get('AppBundle:Builder:mainMenu') ?>
<?php echo $view['knp_menu']->render($menuItem) ?>
If you want to only retrieve a certain branch of the menu, you can do the
following, where 'Contact' is one of the root menu items and has children
beneath it.
.. configuration-block::
.. code-block:: html+jinja
{% set menuItem = knp_menu_get('AppBundle:Builder:mainMenu', ['Contact']) %}
{{ knp_menu_render(['AppBundle:Builder:mainMenu', 'Contact']) }}
.. code-block:: html+php
<?php $menuItem = $view['knp_menu']->get('AppBundle:Builder:mainMenu', ['Contact']) ?>
<?php echo $view['knp_menu']->render(['AppBundle:Builder:mainMenu', 'Contact']) ?>
If you want to pass some options to the builder, you can use the third parameter
of the ``knp_menu_get`` function:
.. configuration-block::
.. code-block:: html+jinja
{% set menuItem = knp_menu_get('AppBundle:Builder:mainMenu', [], {'some_option': 'my_value'}) %}
{{ knp_menu_render(menuItem) }}
.. code-block:: html+php
<?php $menuItem = $view['knp_menu']->get('AppBundle:Builder:mainMenu', [], [
'some_option' => 'my_value'
]) ?>
<?php echo $view['knp_menu']->render($menuItem) ?>
More Advanced Stuff
-------------------
.. toctree::
:maxdepth: 1
menu_service
menu_builder_service
i18n
events
custom_renderer
custom_provider
disabling_providers
.. _`installation chapter`: https://getcomposer.org/doc/00-intro.md
.. _`KnpMenu documentation`: https://github.com/KnpLabs/KnpMenu/blob/master/doc/01-Basic-Menus.markdown

View File

@@ -0,0 +1,124 @@
Creating Menu Builders as Services
==================================
This bundle gives you a really convenient way to create menus by following
a convention and - if needed - injecting the entire container.
However, if you want to, you can instead choose to create a service for your
menu builder. The advantage of this method is that you can inject the exact
dependencies that your menu builder needs, instead of injecting the entire
service container. This can lead to code that is more testable and also potentially
more reusable. The disadvantage is that it needs just a little more setup.
Start by creating a builder for your menu. You can stick as many menus into
a builder as you want, so you may only have one (or just a few) of these
builder classes in your application:
.. code-block:: php
// src/AppBundle/Menu/MenuBuilder.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 createMainMenu(array $options)
{
$menu = $this->factory->createItem('root');
$menu->addChild('Home', ['route' => 'homepage']);
// ... add more children
return $menu;
}
}
Next, register your menu builder as service and register its ``createMainMenu`` method as a menu builder:
.. code-block:: yaml
# app/config/services.yml
services:
app.menu_builder:
class: AppBundle\Menu\MenuBuilder
arguments: ["@knp_menu.factory"]
tags:
- { name: knp_menu.menu_builder, method: createMainMenu, alias: main } # The alias is what is used to retrieve the menu
# ...
.. note::
The menu service must be public as it will be retrieved at runtime to keep
it lazy-loaded.
You can now render the menu directly in a template via the name given in the
``alias`` key above:
.. code-block:: html+jinja
{{ knp_menu_render('main') }}
Suppose now we need to create a second menu for the sidebar. The process
is simple! Start by adding a new method to your builder:
.. code-block:: php
// src/AppBundle/Menu/MenuBuilder.php
// ...
class MenuBuilder
{
// ...
public function createSidebarMenu(array $options)
{
$menu = $this->factory->createItem('sidebar');
if (isset($options['include_homepage']) && $options['include_homepage']) {
$menu->addChild('Home', ['route' => 'homepage']);
}
// ... add more children
return $menu;
}
}
Now, create a service for *just* your new menu, giving it a new name, like
``sidebar``:
.. code-block:: yaml
# app/config/services.yml
services:
app.menu_builder:
class: AppBundle\Menu\MenuBuilder
arguments: ["@knp_menu.factory"]
tags:
- { name: knp_menu.menu_builder, method: createMainMenu, alias: main } # the previous menu
- { name: knp_menu.menu_builder, method: createSidebarMenu, alias: sidebar } # Named "sidebar" this time
# ...
It can now be rendered, just like the other menu:
.. code-block:: html+jinja
{% set menu = knp_menu_get('sidebar', [], {include_homepage: false}) %}
{{ knp_menu_render(menu) }}

View File

@@ -0,0 +1,143 @@
Creating Menus as Services
==========================
.. note::
Registering a menu as service comes with several limitations:
- it does not allow to use builder options
- it reuses the same instance several times in case you render the same
menu several times, which can have weird side-effects.
It is recommended to register only :doc:`menu builders as services <menu_builder_service>`
instead.
This bundle gives you a really convenient way to create menus by following
a convention and - if needed - injecting the entire container.
However, if you want to, you can instead choose to create a service for your
menu object. The advantage of this method is that you can inject the exact
dependencies that your menu needs, instead of injecting the entire service
container. This can lead to code that is more testable and also potentially
more reusable. The disadvantage is that it needs just a little more setup.
Start by creating a builder for your menu. You can stick as many menus into
a builder as you want, so you may only have one (or just a few) of these
builder classes in your application:
.. code-block:: php
// src/AppBundle/Menu/MenuBuilder.php
namespace AppBundle\Menu;
use Knp\Menu\FactoryInterface;
use Symfony\Component\HttpFoundation\RequestStack;
class MenuBuilder
{
private $factory;
/**
* @param FactoryInterface $factory
*/
public function __construct(FactoryInterface $factory)
{
$this->factory = $factory;
}
public function createMainMenu(RequestStack $requestStack)
{
$menu = $this->factory->createItem('root');
$menu->addChild('Home', ['route' => 'homepage']);
// ... add more children
return $menu;
}
}
Next, register two services: one for your menu builder, and one for the menu
object created by the ``createMainMenu`` method:
.. code-block:: yaml
# app/config/services.yml
services:
app.menu_builder:
class: AppBundle\Menu\MenuBuilder
arguments: ["@knp_menu.factory"]
app.main_menu:
class: Knp\Menu\MenuItem # the service definition requires setting the class
factory: ["@app.menu_builder", createMainMenu]
arguments: ["@request_stack"]
tags:
- { name: knp_menu.menu, alias: main } # The alias is what is used to retrieve the menu
# ...
.. note::
The menu service must be public as it will be retrieved at runtime to keep
it lazy-loaded.
.. note::
If you are using Symfony `2.5` or older version please check the `Using a Factory to Create Services`_
article for correct factories syntax corresponding to your version.
You can now render the menu directly in a template via the name given in the
``alias`` key above:
.. code-block:: html+jinja
{{ knp_menu_render('main') }}
Suppose now we need to create a second menu for the sidebar. The process
is simple! Start by adding a new method to your builder:
.. code-block:: php
// src/AppBundle/Menu/MenuBuilder.php
// ...
class MenuBuilder
{
// ...
public function createSidebarMenu(RequestStack $requestStack)
{
$menu = $this->factory->createItem('sidebar');
$menu->addChild('Home', ['route' => 'homepage']);
// ... add more children
return $menu;
}
}
Now, create a service for *just* your new menu, giving it a new name, like
``sidebar``:
.. code-block:: yaml
# app/config/services.yml
services:
app.sidebar_menu:
class: Knp\Menu\MenuItem
factory: ["@app.menu_builder", createSidebarMenu]
arguments: ["@request_stack"]
tags:
- { name: knp_menu.menu, alias: sidebar } # Named "sidebar" this time
# ...
It can now be rendered, just like the other menu:
.. code-block:: html+jinja
{{ knp_menu_render('sidebar') }}
.. _`Using a Factory to Create Services`: http://symfony.com/doc/2.5/components/dependency_injection/factories.html

View File

@@ -0,0 +1,10 @@
{% extends 'knp_menu.html.twig' %}
{% block label %}
{%- set translation_domain = item.extra('translation_domain', 'messages') -%}
{%- set label = item.label -%}
{%- if translation_domain is not same as(false) -%}
{%- set label = label|trans(item.extra('translation_params', {}), translation_domain) -%}
{%- endif -%}
{%- if options.allow_safe_labels and item.extra('safe_label', false) %}{{ label|raw }}{% else %}{{ label }}{% endif -%}
{% endblock %}

View File

@@ -0,0 +1,127 @@
<?php
namespace Knp\Bundle\MenuBundle\Templating\Helper;
use Symfony\Component\Templating\Helper\Helper as TemplatingHelper;
use Knp\Menu\Matcher\MatcherInterface;
use Knp\Menu\ItemInterface;
use Knp\Menu\Twig\Helper;
use Knp\Menu\Util\MenuManipulator;
class MenuHelper extends TemplatingHelper
{
private $helper;
private $matcher;
private $menuManipulator;
/**
* @param Helper $helper
*/
public function __construct(Helper $helper, MatcherInterface $matcher, MenuManipulator $menuManipulator)
{
$this->helper = $helper;
$this->matcher = $matcher;
$this->menuManipulator = $menuManipulator;
}
/**
* Retrieves an item following a path in the tree.
*
* @param \Knp\Menu\ItemInterface|string $menu
* @param array $path
* @param array $options
*
* @return \Knp\Menu\ItemInterface
*/
public function get($menu, array $path = [], array $options = [])
{
return $this->helper->get($menu, $path, $options);
}
/**
* Renders a menu with the specified renderer.
*
* @param \Knp\Menu\ItemInterface|string|array $menu
* @param array $options
* @param string $renderer
*
* @return string
*/
public function render($menu, array $options = [], $renderer = null)
{
return $this->helper->render($menu, $options, $renderer);
}
/**
* Returns an array ready to be used for breadcrumbs.
*
* @param ItemInterface|array|string $menu
* @param string|array|null $subItem
*
* @return array
*/
public function getBreadcrumbsArray($menu, $subItem = null)
{
return $this->helper->getBreadcrumbsArray($menu, $subItem);
}
/**
* A string representation of this menu item
*
* e.g. Top Level 1 > Second Level > This menu
*
* @param ItemInterface $menu
* @param string $separator
*
* @return string
*/
public function getPathAsString(ItemInterface $menu, $separator = ' > ')
{
return $this->menuManipulator->getPathAsString($menu, $separator);
}
/**
* Checks whether an item is current.
*
* @param ItemInterface $item
*
* @return boolean
*/
public function isCurrent(ItemInterface $item)
{
return $this->matcher->isCurrent($item);
}
/**
* Checks whether an item is the ancestor of a current item.
*
* @param ItemInterface $item
* @param integer $depth The max depth to look for the item
*
* @return boolean
*/
public function isAncestor(ItemInterface $item, $depth = null)
{
return $this->matcher->isAncestor($item, $depth);
}
/**
* Returns the current item of a menu.
*
* @param ItemInterface|array|string $menu
*
* @return ItemInterface|null
*/
public function getCurrentItem($menu)
{
return $this->helper->getCurrentItem($menu);
}
/**
* @return string
*/
public function getName()
{
return 'knp_menu';
}
}