Commit 2b1f9f48 authored by alexpott's avatar alexpott
Browse files

Issue #2368769 by kgoel, dawehner: All route enhancers are run on every request

parent 39268b17
......@@ -527,7 +527,17 @@ services:
calls:
- [setFinalMatcher, ['@router.matcher.final_matcher']]
tags:
- { name: service_collector, tag: route_filter, call: addRouteFilter }
- { name: service_collector, tag: non_lazy_route_filter, call: addRouteFilter }
route_filter.lazy_collector:
class: Drupal\Core\Routing\LazyRouteFilter
tags:
- { name: non_lazy_route_filter }
parent: container.trait
route_filter_subscriber:
class: Drupal\Core\EventSubscriber\RouteFilterSubscriber
arguments: ['@route_filter.lazy_collector']
tags:
- { name: event_subscriber }
url_generator:
class: Drupal\Core\Routing\UrlGenerator
arguments: ['@router.route_provider', '@path_processor_manager', '@route_processor_manager', '@config.factory', '@logger.channel.default', '@request_stack']
......@@ -546,7 +556,7 @@ services:
class: Symfony\Cmf\Component\Routing\DynamicRouter
arguments: ['@router.request_context', '@router.matcher', '@url_generator']
tags:
- { name: service_collector, tag: route_enhancer, call: addRouteEnhancer }
- { name: service_collector, tag: non_lazy_route_enhancer, call: addRouteEnhancer }
router.no_access_checks:
class: Symfony\Cmf\Component\Routing\ChainRouter
calls:
......@@ -661,6 +671,16 @@ services:
class: Drupal\Core\EventSubscriber\AjaxSubscriber
tags:
- { name: event_subscriber }
route_enhancer.lazy_collector:
class: Drupal\Core\Routing\LazyRouteEnhancer
tags:
- { name: non_lazy_route_enhancer, priority: 5000 }
parent: container.trait
route_enhancer_subscriber:
class: Drupal\Core\EventSubscriber\RouteEnhancerSubscriber
arguments: ['@route_enhancer.lazy_collector']
tags:
- { name: event_subscriber }
route_enhancer.param_conversion:
class: Drupal\Core\Routing\Enhancer\ParamConversionEnhancer
arguments: ['@paramconverter_manager']
......
......@@ -10,6 +10,8 @@
use Drupal\Core\Cache\CacheContextsPass;
use Drupal\Core\Cache\ListCacheBinsPass;
use Drupal\Core\DependencyInjection\Compiler\BackendCompilerPass;
use Drupal\Core\DependencyInjection\Compiler\RegisterLazyRouteEnhancers;
use Drupal\Core\DependencyInjection\Compiler\RegisterLazyRouteFilters;
use Drupal\Core\DependencyInjection\Compiler\StackedKernelPass;
use Drupal\Core\DependencyInjection\Compiler\RegisterStreamWrappersPass;
use Drupal\Core\DependencyInjection\ServiceProviderInterface;
......@@ -70,6 +72,8 @@ public function register(ContainerBuilder $container) {
$container->addCompilerPass(new RegisterKernelListenersPass(), PassConfig::TYPE_AFTER_REMOVING);
$container->addCompilerPass(new RegisterAccessChecksPass());
$container->addCompilerPass(new RegisterLazyRouteEnhancers());
$container->addCompilerPass(new RegisterLazyRouteFilters());
// Add a compiler pass for registering services needing destruction.
$container->addCompilerPass(new RegisterServicesForDestructionPass());
......
<?php
/**
* @file
* Contains \Drupal\Core\DependencyInjection\Compiler\RegisterLazyRouteEnhancers.
*/
namespace Drupal\Core\DependencyInjection\Compiler;
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
use Symfony\Component\DependencyInjection\ContainerBuilder;
/**
* Registers all lazy route enhancers onto the lazy route enhancers.
*/
class RegisterLazyRouteEnhancers implements CompilerPassInterface {
/**
* {@inheritdoc}
*/
public function process(ContainerBuilder $container) {
if (!$container->hasDefinition('route_enhancer.lazy_collector')) {
return;
}
$service_ids = [];
foreach ($container->findTaggedServiceIds('route_enhancer') as $id => $attributes) {
$service_ids[$id] = $id;
}
$container
->getDefinition('route_enhancer.lazy_collector')
->addArgument($service_ids);
}
}
<?php
/**
* @file
* Contains \Drupal\Core\DependencyInjection\Compiler\RegisterLazyRouteFilters.
*/
namespace Drupal\Core\DependencyInjection\Compiler;
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
use Symfony\Component\DependencyInjection\ContainerBuilder;
/**
* Registers all lazy route filters onto the lazy route filter.
*/
class RegisterLazyRouteFilters implements CompilerPassInterface {
/**
* {@inheritdoc}
*/
public function process(ContainerBuilder $container) {
if (!$container->hasDefinition('route_filter.lazy_collector')) {
return;
}
$service_ids = [];
foreach ($container->findTaggedServiceIds('route_filter') as $id => $attributes) {
$service_ids[$id] = $id;
}
$container
->getDefinition('route_filter.lazy_collector')
->addArgument($service_ids);
}
}
......@@ -7,8 +7,9 @@
namespace Drupal\Core\Entity\Enhancer;
use Drupal\Core\Routing\Enhancer\RouteEnhancerInterface;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Cmf\Component\Routing\Enhancer\RouteEnhancerInterface;
use Symfony\Component\Routing\Route;
use Symfony\Cmf\Component\Routing\RouteObjectInterface;
/**
......@@ -80,4 +81,15 @@ public function enhance(array $defaults, Request $request) {
return $defaults;
}
/**
* {@inheritdoc}
*/
public function applies(Route $route) {
return !$route->hasDefault('_controller') &&
($route->hasDefault('_entity_form')
|| $route->hasDefault('_entity_list')
|| $route->hasDefault('_entity_view')
);
}
}
<?php
/**
* @file
* Contains \Drupal\Core\EventSubscriber\RouteEnhancerSubscriber.
*/
namespace Drupal\Core\EventSubscriber;
use Drupal\Core\Routing\LazyRouteEnhancer;
use Drupal\Core\Routing\RouteBuildEvent;
use Drupal\Core\Routing\RoutingEvents;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
/**
* Listens to the new routes before they get saved.
*/
class RouteEnhancerSubscriber implements EventSubscriberInterface {
/**
* @var \Drupal\Core\Routing\LazyRouteEnhancer
*/
protected $routeEnhancer;
/**
* Constructs the RouteEnhancerSubscriber object.
*
* @param \Drupal\Core\Routing\LazyRouteEnhancer $route_enhancer
* The lazy route enhancer.
*/
public function __construct(LazyRouteEnhancer $route_enhancer) {
$this->routeEnhancer = $route_enhancer;
}
/**
* Adds the route_enhancer object to the route collection.
*
* @param \Drupal\Core\Routing\RouteBuildEvent $event
* The route build event.
*/
public function onRouteAlter(RouteBuildEvent $event) {
$this->routeEnhancer->setEnhancers($event->getRouteCollection());
}
/**
* {@inheritdoc}
*/
static function getSubscribedEvents() {
$events[RoutingEvents::ALTER][] = array('onRouteAlter', -300);
return $events;
}
}
<?php
/**
* @file
* Contains \Drupal\Core\EventSubscriber\RouteFilterSubscriber.
*/
namespace Drupal\Core\EventSubscriber;
use Drupal\Core\Routing\LazyRouteFilter;
use Drupal\Core\Routing\RouteBuildEvent;
use Drupal\Core\Routing\RoutingEvents;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
/**
* Listens to the filtered collection of route instances.
*/
class RouteFilterSubscriber implements EventSubscriberInterface {
/**
* The lazy route filter.
*
* @var \Drupal\Core\Routing\LazyRouteFilter
*/
protected $routeFilter;
/**
* Constructs the RouteFilterSubscriber object.
*
* @param \Drupal\Core\Routing\LazyRouteFilter $route_filter
* The lazy route filter.
*/
public function __construct(LazyRouteFilter $route_filter) {
$this->routeFilter = $route_filter;
}
/**
* Get the Response object from filtered route collection.
*
* @param \Drupal\Core\Routing\RouteBuildEvent $event
* The route build event.
*/
public function onRouteAlter(RouteBuildEvent $event) {
$this->routeFilter->setFilters($event->getRouteCollection());
}
/**
* {@inheritdoc}
*/
static function getSubscribedEvents() {
$events[RoutingEvents::ALTER][] = array('onRouteAlter', -300);
return $events;
}
}
......@@ -9,9 +9,9 @@
use Drupal\Component\Utility\String;
use Drupal\Core\ContentNegotiation;
use Symfony\Cmf\Component\Routing\NestedMatcher\RouteFilterInterface;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpKernel\Exception\NotAcceptableHttpException;
use Symfony\Component\Routing\Route;
use Symfony\Component\Routing\RouteCollection;
/**
......@@ -82,4 +82,11 @@ public function filter(RouteCollection $collection, Request $request) {
throw new NotAcceptableHttpException(String::format('No route found for the specified formats @formats.', array('@formats' => implode(' ', $acceptable_mime_types))));
}
/**
* {@inheritdoc}
*/
public function applies(Route $route) {
return TRUE;
}
}
......@@ -7,9 +7,9 @@
namespace Drupal\Core\Routing;
use Symfony\Cmf\Component\Routing\NestedMatcher\RouteFilterInterface;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpKernel\Exception\UnsupportedMediaTypeHttpException;
use Symfony\Component\Routing\Route;
use Symfony\Component\Routing\RouteCollection;
/**
......@@ -50,4 +50,11 @@ public function filter(RouteCollection $collection, Request $request) {
throw new UnsupportedMediaTypeHttpException('No route found that matches the Content-Type header.');
}
/**
* {@inheritdoc}
*/
public function applies(Route $route) {
return TRUE;
}
}
......@@ -10,8 +10,8 @@
use Drupal\Core\Authentication\AuthenticationManagerInterface;
use Drupal\Core\Session\AccountProxyInterface;
use Drupal\Core\Session\AnonymousUserSession;
use Symfony\Cmf\Component\Routing\Enhancer\RouteEnhancerInterface;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Routing\Route;
use Symfony\Cmf\Component\Routing\RouteObjectInterface;
/**
......@@ -75,4 +75,12 @@ public function enhance(array $defaults, Request $request) {
}
return $defaults;
}
/**
* {@inheritdoc}
*/
public function applies(Route $route) {
return TRUE;
}
}
......@@ -9,7 +9,6 @@
use Drupal\Core\ParamConverter\ParamConverterManagerInterface;
use Drupal\Core\ParamConverter\ParamNotConvertedException;
use Symfony\Cmf\Component\Routing\Enhancer\RouteEnhancerInterface;
use Symfony\Cmf\Component\Routing\RouteObjectInterface;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\HttpFoundation\ParameterBag;
......@@ -17,6 +16,7 @@
use Symfony\Component\HttpKernel\Event\GetResponseForExceptionEvent;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
use Symfony\Component\HttpKernel\KernelEvents;
use Symfony\Component\Routing\Route;
/**
* Provides a route enhancer that handles parameter conversion.
......@@ -90,4 +90,11 @@ public static function getSubscribedEvents() {
return $events;
}
/**
* {@inheritdoc}
*/
public function applies(Route $route) {
return TRUE;
}
}
<?php
/**
* @file
* Contains \Drupal\Core\Routing\Enhancer\RouteEnhancerInterface.
*/
namespace Drupal\Core\Routing\Enhancer;
use Symfony\Cmf\Component\Routing\Enhancer\RouteEnhancerInterface as BaseRouteEnhancerInterface;
use Symfony\Component\Routing\Route;
/**
* A route enhance service to determine route enhance rules.
*/
interface RouteEnhancerInterface extends BaseRouteEnhancerInterface {
/**
* Declares if the route enhancer applies to the given route.
*
* @param \Symfony\Component\Routing\Route $route
* The route to consider attaching to.
*
* @return bool
* TRUE if the check applies to the passed route, False otherwise.
*/
public function applies(Route $route);
}
<?php
/**
* @file
* Contains \Drupal\Core\Routing\LazyRouteEnhancer.
*/
namespace Drupal\Core\Routing;
use Drupal\Core\Routing\Enhancer\RouteEnhancerInterface;
use Symfony\Cmf\Component\Routing\Enhancer\RouteEnhancerInterface as BaseRouteEnhancerInterface;
use Symfony\Cmf\Component\Routing\RouteObjectInterface;
use Symfony\Component\DependencyInjection\ContainerAwareInterface;
use Symfony\Component\DependencyInjection\ContainerAwareTrait;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Routing\RouteCollection;
/**
* A route enhancer which lazily loads route enhancers, depending on the route.
*
* We lazy initialize route enhancers, because otherwise all dependencies of
* all route enhancers are initialized on every request, which is slow. However,
* with the use of lazy loading, dependencies are instantiated only when used.
*/
class LazyRouteEnhancer implements BaseRouteEnhancerInterface, ContainerAwareInterface {
use ContainerAwareTrait;
/**
* Array of enhancers service IDs.
*
* @var array
*/
protected $serviceIds = [];
/**
* The initialized route enhancers.
*
* @var \Symfony\Cmf\Component\Routing\Enhancer\RouteEnhancerInterface[]|\Drupal\Core\Routing\Enhancer\RouteEnhancerInterface[]
*/
protected $enhancers = NULL;
/**
* Constructs the LazyRouteEnhancer object.
*
* @param $service_ids
* Array of enhancers service IDs.
*/
public function __construct($service_ids) {
$this->serviceIds = $service_ids;
}
/**
* For each route, saves a list of applicable enhancers to the route.
*
* @param \Symfony\Component\Routing\RouteCollection $route_collection
* A collection of routes to apply enhancer checks to.
*/
public function setEnhancers(RouteCollection $route_collection) {
/** @var \Symfony\Component\Routing\Route $route **/
foreach ($route_collection as $route_name => $route) {
$service_ids = [];
foreach ($this->getEnhancers() as $service_id => $enhancer) {
if ((!$enhancer instanceof RouteEnhancerInterface) || $enhancer->applies($route)) {
$service_ids[] = $service_id;
}
}
if ($service_ids) {
$route->setOption('_route_enhancers', array_unique($service_ids));
}
}
}
/**
* For each route, gets a list of applicable enhancer to the route.
*
* @return \Symfony\Cmf\Component\Routing\Enhancer\RouteEnhancerInterface[]|\Drupal\Core\Routing\Enhancer\RouteEnhancerInterface[]
*/
protected function getEnhancers() {
if (!isset($this->enhancers)) {
foreach ($this->serviceIds as $service_id) {
$this->enhancers[$service_id] = $this->container->get($service_id);
}
}
return $this->enhancers;
}
/**
* {@inheritdoc}
*/
public function enhance(array $defaults, Request $request) {
/** @var \Symfony\Component\Routing\Route $route */
$route = $defaults[RouteObjectInterface::ROUTE_OBJECT];
$enhancer_ids = $route->getOption('_route_enhancers');
if (isset($enhancer_ids)) {
foreach ($enhancer_ids as $enhancer_id) {
$defaults = $this->container->get($enhancer_id)->enhance($defaults, $request);
}
}
return $defaults;
}
}
<?php
/**
* @file
* Contains \Drupal\Core\Routing\LazyRouteFilter.
*/
namespace Drupal\Core\Routing;
use Symfony\Cmf\Component\Routing\NestedMatcher\RouteFilterInterface as BaseRouteFilterInterface;
use Symfony\Component\DependencyInjection\ContainerAwareInterface;
use Symfony\Component\DependencyInjection\ContainerAwareTrait;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Routing\RouteCollection;
/**
* A route filter which lazily loads route filters, depending on the route.
*
* We lazy initialize route filters, because otherwise all dependencies of all
* route filters are initialized on every request, which is slow. However, with
* the use of lazy loading, dependencies are instantiated only when used.
*/
class LazyRouteFilter implements BaseRouteFilterInterface, ContainerAwareInterface {
use ContainerAwareTrait;
/**
* Array of route filter service IDs.
*
* @var array
*/
protected $serviceIds = [];
/**
* The initialized route filters.
*
* @var \Drupal\Core\Routing\RouteFilterInterface[]
*/
protected $filters = NULL;
/**
* Constructs the LazyRouteEnhancer object.
*
* @param $service_ids
* Array of route filter service IDs.
*/
public function __construct($service_ids) {
$this->serviceIds = $service_ids;
}
/**
* For each route, filter down the route collection.
*
* @param \Symfony\Component\Routing\RouteCollection $route_collection
* A collection of routes to apply filter checks to.
*/
public function setFilters(RouteCollection $route_collection) {
/** @var \Symfony\Component\Routing\Route $route **/
foreach ($route_collection as $route) {
$service_ids = [];
foreach ($this->getFilters() as $service_id => $filter) {
if ($filter instanceof RouteFilterInterface && $filter->applies($route)) {
$service_ids[] = $service_id;
}
}
if ($service_ids) {
$route->setOption('_route_filters', array_unique($service_ids));
}
}
}
/**
* For each route, gets a list of applicable enhancers to the route.
*
* @return \Symfony\Cmf\Component\Routing\Enhancer\RouteEnhancerInterface[]|\Drupal\Core\Routing\Enhancer\RouteEnhancerInterface[]
*/
protected function getFilters() {
if (!isset($this->filters)) {
foreach ($this->serviceIds as $service_id) {
$this->filters[$service_id] = $this->container->get($service_id);
}
}
return $this->filters;
}
/**
* {@inheritdoc}
*/
public function filter(RouteCollection $collection, Request $request) {
$filter_ids = [];
foreach ($collection->all() as $route) {
$filter_ids = array_merge($filter_ids, $route->getOption('_route_filters') ?: []);
}