integration of knp menu bundle

This commit is contained in:
Julien Fastré 2018-04-30 17:42:24 +02:00
parent 5c045abda4
commit 395787f1bb
10 changed files with 204 additions and 46 deletions

View File

@ -11,6 +11,7 @@ use Chill\MainBundle\DependencyInjection\RoleProvidersCompilerPass;
use Chill\MainBundle\DependencyInjection\CompilerPass\ExportsCompilerPass; use Chill\MainBundle\DependencyInjection\CompilerPass\ExportsCompilerPass;
use Chill\MainBundle\DependencyInjection\CompilerPass\WidgetsCompilerPass; use Chill\MainBundle\DependencyInjection\CompilerPass\WidgetsCompilerPass;
use Chill\MainBundle\DependencyInjection\CompilerPass\NotificationCounterCompilerPass; use Chill\MainBundle\DependencyInjection\CompilerPass\NotificationCounterCompilerPass;
use Chill\MainBundle\DependencyInjection\CompilerPass\MenuCompilerPass;
class ChillMainBundle extends Bundle class ChillMainBundle extends Bundle
@ -25,5 +26,6 @@ class ChillMainBundle extends Bundle
$container->addCompilerPass(new ExportsCompilerPass()); $container->addCompilerPass(new ExportsCompilerPass());
$container->addCompilerPass(new WidgetsCompilerPass()); $container->addCompilerPass(new WidgetsCompilerPass());
$container->addCompilerPass(new NotificationCounterCompilerPass()); $container->addCompilerPass(new NotificationCounterCompilerPass());
$container->addCompilerPass(new MenuCompilerPass());
} }
} }

View File

@ -95,6 +95,7 @@ class ChillMainExtension extends Extension implements PrependExtensionInterface,
$loader->load('services/validator.yml'); $loader->load('services/validator.yml');
$loader->load('services/widget.yml'); $loader->load('services/widget.yml');
$loader->load('services/controller.yml'); $loader->load('services/controller.yml');
$loader->load('services/routing.yml');
} }
public function getConfiguration(array $config, ContainerBuilder $container) public function getConfiguration(array $config, ContainerBuilder $container)

View File

@ -0,0 +1,49 @@
<?php
/*
* Copyright (C) 2018 Champs Libres Cooperative <info@champs-libres.coop>
*
* 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 <http://www.gnu.org/licenses/>.
*/
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é <julien.fastre@champs-libres.coop>
*/
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]);
}
}
}
}

View File

@ -2,27 +2,6 @@ parameters:
# cl_chill_main.example.class: Chill\MainBundle\Example # cl_chill_main.example.class: Chill\MainBundle\Example
services: 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: twig_intl:
class: Twig_Extensions_Extension_Intl class: Twig_Extensions_Extension_Intl

View File

@ -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 }

View File

@ -24,8 +24,8 @@
</a> </a>
</div> </div>
<ul class="submenu width-11-em"> <ul class="submenu width-11-em">
{% for route in routes %} {% for menu in menus %}
<li><a href="{{ path(route.key, args ) }}" style="font-family: 'Open Sans'; font-weight:300; font-size: 0.9em;">{% if route.icon is defined %}<i class="fa fa-{{ route.icon }}"></i> {% endif %}{{ route.label|trans }}</a></li> <li><a href="{{ menu.uri }}" style="font-family: 'Open Sans'; font-weight:300; font-size: 0.9em;">{% if menu.extras.icon is defined %}<i class="fa fa-{{ menu.extras.icon }}"></i> {% endif %}{{ menu.label|trans }}</a></li>
{% endfor %} {% endfor %}
</ul> </ul>
</li> </li>

View File

@ -0,0 +1,38 @@
<?php
/*
* Copyright (C) 2018 Champs Libres Cooperative <info@champs-libres.coop>
*
* 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 <http://www.gnu.org/licenses/>.
*/
namespace Chill\MainBundle\Routing;
use Knp\Menu\MenuItem;
/**
*
* @author Julien Fastré <julien.fastre@champs-libres.coop>
*/
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);
}

View File

@ -3,8 +3,9 @@
namespace Chill\MainBundle\Routing; namespace Chill\MainBundle\Routing;
use Symfony\Component\Routing\RouteCollection; use Symfony\Component\Routing\RouteCollection;
use Symfony\Component\DependencyInjection\ContainerAwareInterface; use Symfony\Component\Routing\RouterInterface;
use Symfony\Component\DependencyInjection\ContainerInterface; use Knp\Menu\FactoryInterface;
/** /**
* This class permit to build menu from the routing information * This class permit to build menu from the routing information
@ -14,27 +15,34 @@ use Symfony\Component\DependencyInjection\ContainerInterface;
* *
* @author julien * @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 * @var FactoryInterface
* @param ContainerInterface $container
*/ */
public function setContainer(ContainerInterface $container = null) private $menuFactory;
{
if (NULL === $container) { /**
throw new \LogicException('container should not be null'); *
} * @var
//see remark in MenuComposer::setRouteCollection */
$this->container = $container; 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()) public function getRoutesFor($menuId, array $parameters = array())
{ {
$routes = array(); $routes = array();
$routeCollection = $this->container->get('router')->getRouteCollection(); $routeCollection = $this->router->getRouteCollection();
foreach ($routeCollection->all() as $routeKey => $route) { foreach ($routeCollection->all() as $routeKey => $route) {
if ($route->hasOption('menus')) { if ($route->hasOption('menus')) {
@ -83,6 +91,35 @@ class MenuComposer implements ContainerAwareInterface
return $routes; 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. * recursive function to resolve the order of a array of routes.
* If the order chosen in routing.yml is already in used, find the * If the order chosen in routing.yml is already in used, find the
@ -99,5 +136,25 @@ class MenuComposer implements ContainerAwareInterface
return $order; 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);
}
} }

View File

@ -64,7 +64,10 @@ class MenuTwig extends \Twig_Extension implements ContainerAwareInterface
public function getFunctions() public function getFunctions()
{ {
return [new \Twig_SimpleFunction('chill_menu', 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 string $menuId
* @param mixed[] $params * @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); $resolvedParams = array_merge($this->defaultParams, $params);
$layout = $resolvedParams['layout']; $layout = $resolvedParams['layout'];
unset($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') return $env->render($layout, $resolvedParams);
->render($layout, $resolvedParams); } else {
$resolvedParams['menus'] = $this->menuComposer->getMenuFor($menuId, $resolvedParams);
return $env->render($layout, $resolvedParams);
}
} }
public function getName() public function getName()

View File

@ -38,7 +38,8 @@
"doctrine/doctrine-migrations-bundle": "~1.3", "doctrine/doctrine-migrations-bundle": "~1.3",
"doctrine/migrations": "~1.0", "doctrine/migrations": "~1.0",
"phpoffice/phpspreadsheet": "~1.2", "phpoffice/phpspreadsheet": "~1.2",
"sensio/distribution-bundle": "^5.0" "sensio/distribution-bundle": "^5.0",
"knplabs/knp-menu-bundle": "^2.2"
}, },
"require-dev": { "require-dev": {
"symfony/dom-crawler": "~3.4", "symfony/dom-crawler": "~3.4",