From 395787f1bb9ffee95690ca2e8f0bfa0aaf7551d0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Fastr=C3=A9?= Date: Mon, 30 Apr 2018 17:42:24 +0200 Subject: [PATCH] integration of knp menu bundle --- ChillMainBundle.php | 2 + DependencyInjection/ChillMainExtension.php | 1 + .../CompilerPass/MenuCompilerPass.php | 49 ++++++++++ Resources/config/services.yml | 21 ----- Resources/config/services/routing.yml | 23 +++++ Resources/views/Menu/user.html.twig | 4 +- Routing/LocalMenuBuilderInterface.php | 38 ++++++++ Routing/MenuComposer.php | 91 +++++++++++++++---- Routing/MenuTwig.php | 18 +++- composer.json | 3 +- 10 files changed, 204 insertions(+), 46 deletions(-) create mode 100644 DependencyInjection/CompilerPass/MenuCompilerPass.php create mode 100644 Resources/config/services/routing.yml create mode 100644 Routing/LocalMenuBuilderInterface.php diff --git a/ChillMainBundle.php b/ChillMainBundle.php index 7f82c70c1..7e6620522 100644 --- a/ChillMainBundle.php +++ b/ChillMainBundle.php @@ -11,6 +11,7 @@ use Chill\MainBundle\DependencyInjection\RoleProvidersCompilerPass; use Chill\MainBundle\DependencyInjection\CompilerPass\ExportsCompilerPass; use Chill\MainBundle\DependencyInjection\CompilerPass\WidgetsCompilerPass; use Chill\MainBundle\DependencyInjection\CompilerPass\NotificationCounterCompilerPass; +use Chill\MainBundle\DependencyInjection\CompilerPass\MenuCompilerPass; class ChillMainBundle extends Bundle @@ -25,5 +26,6 @@ class ChillMainBundle extends Bundle $container->addCompilerPass(new ExportsCompilerPass()); $container->addCompilerPass(new WidgetsCompilerPass()); $container->addCompilerPass(new NotificationCounterCompilerPass()); + $container->addCompilerPass(new MenuCompilerPass()); } } diff --git a/DependencyInjection/ChillMainExtension.php b/DependencyInjection/ChillMainExtension.php index dd0b7c119..ea6214ae5 100644 --- a/DependencyInjection/ChillMainExtension.php +++ b/DependencyInjection/ChillMainExtension.php @@ -95,6 +95,7 @@ class ChillMainExtension extends Extension implements PrependExtensionInterface, $loader->load('services/validator.yml'); $loader->load('services/widget.yml'); $loader->load('services/controller.yml'); + $loader->load('services/routing.yml'); } public function getConfiguration(array $config, ContainerBuilder $container) diff --git a/DependencyInjection/CompilerPass/MenuCompilerPass.php b/DependencyInjection/CompilerPass/MenuCompilerPass.php new file mode 100644 index 000000000..b6b66a755 --- /dev/null +++ b/DependencyInjection/CompilerPass/MenuCompilerPass.php @@ -0,0 +1,49 @@ + + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +namespace Chill\MainBundle\DependencyInjection\CompilerPass; + +use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; +use Symfony\Component\DependencyInjection\Reference; +use Chill\MainBundle\Routing\MenuComposer; + +/** + * + * + * @author Julien Fastré + */ +class MenuCompilerPass implements CompilerPassInterface +{ + public function process(ContainerBuilder $container) + { + if (!$container->hasDefinition('chill.main.menu_composer')) { + throw new \LogicException(sprintf("The service %s does not exists in " + . "container.", MenuComposer::class)); + } + + $menuComposerDefinition = $container->getDefinition('chill.main.menu_composer'); + + foreach ($container->findTaggedServiceIds('chill.menu_builder') as $id => $tags) { + $class = $container->getDefinition($id)->getClass(); + foreach ($class::getMenuIds() as $menuId) { + $menuComposerDefinition + ->addMethodCall('addLocalMenuBuilder', [new Reference($id), $menuId]); + } + } + } +} diff --git a/Resources/config/services.yml b/Resources/config/services.yml index c0364f3cd..b2013e5ee 100644 --- a/Resources/config/services.yml +++ b/Resources/config/services.yml @@ -2,27 +2,6 @@ parameters: # cl_chill_main.example.class: Chill\MainBundle\Example services: - chill.main.routes_loader: - class: Chill\MainBundle\Routing\Loader\ChillRoutesLoader - arguments: - - "%chill_main.routing.resources%" - tags: - - { name: routing.loader } - - chill.main.menu_composer: - class: Chill\MainBundle\Routing\MenuComposer - #must be set in function to avoid circular reference with chill.main.twig.chill_menu - calls: - - [setContainer, ["@service_container"]] - - chill.main.twig.chill_menu: - class: Chill\MainBundle\Routing\MenuTwig - arguments: - - "@chill.main.menu_composer" - calls: - - [setContainer, ["@service_container"]] - tags: - - { name: twig.extension } twig_intl: class: Twig_Extensions_Extension_Intl diff --git a/Resources/config/services/routing.yml b/Resources/config/services/routing.yml new file mode 100644 index 000000000..fcb71ddc7 --- /dev/null +++ b/Resources/config/services/routing.yml @@ -0,0 +1,23 @@ +services: + chill.main.menu_composer: + class: Chill\MainBundle\Routing\MenuComposer + arguments: + - '@Symfony\Component\Routing\RouterInterface' + - '@Knp\Menu\FactoryInterface' + Chill\MainBundle\Routing\MenuComposer: '@chill.main.menu_composer' + + chill.main.routes_loader: + class: Chill\MainBundle\Routing\Loader\ChillRoutesLoader + arguments: + - "%chill_main.routing.resources%" + tags: + - { name: routing.loader } + + chill.main.twig.chill_menu: + class: Chill\MainBundle\Routing\MenuTwig + arguments: + - "@chill.main.menu_composer" + calls: + - [setContainer, ["@service_container"]] + tags: + - { name: twig.extension } diff --git a/Resources/views/Menu/user.html.twig b/Resources/views/Menu/user.html.twig index 243d2d279..420c82809 100644 --- a/Resources/views/Menu/user.html.twig +++ b/Resources/views/Menu/user.html.twig @@ -24,8 +24,8 @@ diff --git a/Routing/LocalMenuBuilderInterface.php b/Routing/LocalMenuBuilderInterface.php new file mode 100644 index 000000000..37f7ead40 --- /dev/null +++ b/Routing/LocalMenuBuilderInterface.php @@ -0,0 +1,38 @@ + + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +namespace Chill\MainBundle\Routing; + +use Knp\Menu\MenuItem; + +/** + * + * @author Julien Fastré + */ +interface LocalMenuBuilderInterface +{ + /** + * return an array of menu ids + * + * @internal this method is static to 1. keep all config in the class, + * instead of tags arguments; 2. avoid a "supports" method, which could lead + * to parsing all instances to get only one or two working instance. + */ + public static function getMenuIds(): array; + + public function buildMenu($menuId, MenuItem $menu, array $parameters); +} diff --git a/Routing/MenuComposer.php b/Routing/MenuComposer.php index 4143231ad..6cc73b4f2 100644 --- a/Routing/MenuComposer.php +++ b/Routing/MenuComposer.php @@ -3,8 +3,9 @@ namespace Chill\MainBundle\Routing; use Symfony\Component\Routing\RouteCollection; -use Symfony\Component\DependencyInjection\ContainerAwareInterface; -use Symfony\Component\DependencyInjection\ContainerInterface; +use Symfony\Component\Routing\RouterInterface; +use Knp\Menu\FactoryInterface; + /** * This class permit to build menu from the routing information @@ -14,27 +15,34 @@ use Symfony\Component\DependencyInjection\ContainerInterface; * * @author julien */ -class MenuComposer implements ContainerAwareInterface +class MenuComposer { /** * - * @var ContainerInterface + * @var RouterInterface */ - private $container; - + private $router; + /** - * - * @internal using the service router in container cause circular references - * @param ContainerInterface $container + * + * @var FactoryInterface */ - public function setContainer(ContainerInterface $container = null) - { - if (NULL === $container) { - throw new \LogicException('container should not be null'); - } - //see remark in MenuComposer::setRouteCollection - $this->container = $container; + private $menuFactory; + + /** + * + * @var + */ + private $localMenuBuilders = []; + + + function __construct( + RouterInterface $router, + FactoryInterface $menuFactory + ) { + $this->router = $router; + $this->menuFactory = $menuFactory; } /** @@ -61,7 +69,7 @@ class MenuComposer implements ContainerAwareInterface public function getRoutesFor($menuId, array $parameters = array()) { $routes = array(); - $routeCollection = $this->container->get('router')->getRouteCollection(); + $routeCollection = $this->router->getRouteCollection(); foreach ($routeCollection->all() as $routeKey => $route) { if ($route->hasOption('menus')) { @@ -83,6 +91,35 @@ class MenuComposer implements ContainerAwareInterface return $routes; } + public function getMenuFor($menuId, array $parameters = array()) + { + $routes = $this->getRoutesFor($menuId, $parameters); + $menu = $this->menuFactory->createItem($menuId); + + // build menu from routes + foreach ($routes as $order => $route) { + $menu->addChild($route['label'], [ + 'route' => $route['key'], + 'routeParameters' => $parameters, + 'order' => $order + ]) + ->setExtras([ + 'icon' => $route['icon'], + 'order' => $order + ]) + ; + } + + if ($this->hasLocalMenuBuilder($menuId)) { + foreach ($this->localMenuBuilders[$menuId] as $builder) { + /* @var $builder LocalMenuBuilderInterface */ + $builder->buildMenu($menuId, $menu, $parameters); + } + } + + return $menu; + } + /** * recursive function to resolve the order of a array of routes. * If the order chosen in routing.yml is already in used, find the @@ -99,5 +136,25 @@ class MenuComposer implements ContainerAwareInterface return $order; } } + + public function addLocalMenuBuilder(LocalMenuBuilderInterface $menuBuilder, $menuId) + { + $this->localMenuBuilders[$menuId][] = $menuBuilder; + } + + /** + * Return true if the menu has at least one builder. + * + * This function is a helper to determine if the method `getMenuFor` + * should be used, or `getRouteFor`. The method `getMenuFor` should be used + * if the result is true (it **does** exists at least one menu builder. + * + * @param string $menuId + * @return bool + */ + public function hasLocalMenuBuilder($menuId): bool + { + return \array_key_exists($menuId, $this->localMenuBuilders); + } } diff --git a/Routing/MenuTwig.php b/Routing/MenuTwig.php index 90f69d98b..fe911d395 100644 --- a/Routing/MenuTwig.php +++ b/Routing/MenuTwig.php @@ -64,7 +64,10 @@ class MenuTwig extends \Twig_Extension implements ContainerAwareInterface public function getFunctions() { return [new \Twig_SimpleFunction('chill_menu', - array($this, 'chillMenu'), array('is_safe' => array('html'))) + array($this, 'chillMenu'), array( + 'is_safe' => array('html'), + 'needs_environment' => true + )) ]; } @@ -81,17 +84,22 @@ class MenuTwig extends \Twig_Extension implements ContainerAwareInterface * @param string $menuId * @param mixed[] $params */ - public function chillMenu($menuId, array $params = array()) + public function chillMenu(\Twig_Environment $env, $menuId, array $params = array()) { $resolvedParams = array_merge($this->defaultParams, $params); $layout = $resolvedParams['layout']; unset($resolvedParams['layout']); - $resolvedParams['routes'] = $this->menuComposer->getRoutesFor($menuId); + if ($this->menuComposer->hasLocalMenuBuilder($menuId) === false) { + $resolvedParams['routes'] = $this->menuComposer->getRoutesFor($menuId, $resolvedParams); - return $this->container->get('templating') - ->render($layout, $resolvedParams); + return $env->render($layout, $resolvedParams); + } else { + $resolvedParams['menus'] = $this->menuComposer->getMenuFor($menuId, $resolvedParams); + + return $env->render($layout, $resolvedParams); + } } public function getName() diff --git a/composer.json b/composer.json index 2defc5ef3..c477163a6 100644 --- a/composer.json +++ b/composer.json @@ -38,7 +38,8 @@ "doctrine/doctrine-migrations-bundle": "~1.3", "doctrine/migrations": "~1.0", "phpoffice/phpspreadsheet": "~1.2", - "sensio/distribution-bundle": "^5.0" + "sensio/distribution-bundle": "^5.0", + "knplabs/knp-menu-bundle": "^2.2" }, "require-dev": { "symfony/dom-crawler": "~3.4",