diff --git a/core/core.services.yml b/core/core.services.yml index 7b41a1d..5a80b67 100644 --- a/core/core.services.yml +++ b/core/core.services.yml @@ -639,7 +639,7 @@ services: arguments: ['@current_route_match'] router.route_provider: class: Drupal\Core\Routing\RouteProvider - arguments: ['@database', '@state', '@path.current'] + arguments: ['@database', '@state', '@path.current', '@cache.data', '@event_dispatcher'] tags: - { name: event_subscriber } - { name: backend_overridable } diff --git a/core/lib/Drupal/Core/EventSubscriber/CacheRouterRebuildSubscriber.php b/core/lib/Drupal/Core/EventSubscriber/CacheRouterRebuildSubscriber.php index 9589348..4ec0822 100644 --- a/core/lib/Drupal/Core/EventSubscriber/CacheRouterRebuildSubscriber.php +++ b/core/lib/Drupal/Core/EventSubscriber/CacheRouterRebuildSubscriber.php @@ -21,7 +21,7 @@ class CacheRouterRebuildSubscriber implements EventSubscriberInterface { */ public function onRouterFinished() { // Requested URLs that formerly gave a 403/404 may now be valid. - Cache::invalidateTags(['4xx-response']); + Cache::invalidateTags(['4xx-response', 'route_match']); } /** diff --git a/core/lib/Drupal/Core/EventSubscriber/PathSubscriber.php b/core/lib/Drupal/Core/EventSubscriber/PathSubscriber.php index ec24484..884973c 100644 --- a/core/lib/Drupal/Core/EventSubscriber/PathSubscriber.php +++ b/core/lib/Drupal/Core/EventSubscriber/PathSubscriber.php @@ -9,10 +9,10 @@ use Drupal\Core\Path\AliasManagerInterface; use Drupal\Core\Path\CurrentPathStack; +use Drupal\Core\Path\InboundPathEvent; use Drupal\Core\PathProcessor\InboundPathProcessorInterface; use Symfony\Component\HttpKernel\HttpKernelInterface; use Symfony\Component\HttpKernel\KernelEvents; -use Symfony\Component\HttpKernel\Event\GetResponseEvent; use Symfony\Component\HttpKernel\Event\PostResponseEvent; use Symfony\Component\EventDispatcher\EventSubscriberInterface; @@ -47,8 +47,8 @@ class PathSubscriber implements EventSubscriberInterface { * * @param \Drupal\Core\Path\AliasManagerInterface $alias_manager * @param \Drupal\Core\PathProcessor\InboundPathProcessorInterface $path_processor - * @param \Drupal\Core\Path\CurrentPathStack $current_path - * The current path. + * @param \Symfony\Component\HttpFoundation\RequestStack + * The request stack. */ public function __construct(AliasManagerInterface $alias_manager, InboundPathProcessorInterface $path_processor, CurrentPathStack $current_path) { $this->aliasManager = $alias_manager; @@ -59,19 +59,18 @@ public function __construct(AliasManagerInterface $alias_manager, InboundPathPro /** * Converts the request path to a system path. * - * @param Symfony\Component\HttpKernel\Event\GetResponseEvent $event + * @param Symfony\Component\EventDispatcher\Event\Event $event * The Event to process. */ - public function onKernelRequestConvertPath(GetResponseEvent $event) { + public function onIncomingPathConvertPath(InboundPathEvent $event) { $request = $event->getRequest(); $path = trim($request->getPathInfo(), '/'); $path = $this->pathProcessor->processInbound($path, $request); $this->currentPath->setPath('/' . $path, $request); - // Set the cache key on the alias manager cache decorator. - if ($event->getRequestType() == HttpKernelInterface::MASTER_REQUEST) { - $this->aliasManager->setCacheKey($path); - } + // Set the cache key on the alias manager cache decorator when dealing with + // master requests. + $this->aliasManager->setCacheKey($path); } /** @@ -88,7 +87,7 @@ public function onKernelTerminate(PostResponseEvent $event) { * An array of event listener definitions. */ static function getSubscribedEvents() { - $events[KernelEvents::REQUEST][] = array('onKernelRequestConvertPath', 200); + $events['incoming_path'][] = array('onIncomingPathConvertPath', 200); $events[KernelEvents::TERMINATE][] = array('onKernelTerminate', 200); return $events; } diff --git a/core/lib/Drupal/Core/Path/AliasStorage.php b/core/lib/Drupal/Core/Path/AliasStorage.php index bd027c8..033f6f2 100644 --- a/core/lib/Drupal/Core/Path/AliasStorage.php +++ b/core/lib/Drupal/Core/Path/AliasStorage.php @@ -7,6 +7,7 @@ namespace Drupal\Core\Path; +use Drupal\Core\Cache\Cache; use Drupal\Core\Database\Connection; use Drupal\Core\Extension\ModuleHandlerInterface; use Drupal\Core\Language\LanguageInterface; @@ -77,6 +78,7 @@ public function save($source, $alias, $langcode = LanguageInterface::LANGCODE_NO if ($pid) { // @todo Switch to using an event for this instead of a hook. $this->moduleHandler->invokeAll('path_' . $operation, array($fields)); + Cache::invalidateTags(['route_match']); return $fields; } return FALSE; @@ -110,6 +112,7 @@ public function delete($conditions) { $deleted = $query->execute(); // @todo Switch to using an event for this instead of a hook. $this->moduleHandler->invokeAll('path_delete', array($path)); + Cache::invalidateTags(['route_match']); return $deleted; } diff --git a/core/lib/Drupal/Core/Path/InboundPathEvent.php b/core/lib/Drupal/Core/Path/InboundPathEvent.php new file mode 100644 index 0000000..ebd1103 --- /dev/null +++ b/core/lib/Drupal/Core/Path/InboundPathEvent.php @@ -0,0 +1,17 @@ +request = $request; + } + + + public function getRequest() { + return $this->request; + } +} diff --git a/core/lib/Drupal/Core/Routing/RouteProvider.php b/core/lib/Drupal/Core/Routing/RouteProvider.php index 8f98802..f0f1801 100644 --- a/core/lib/Drupal/Core/Routing/RouteProvider.php +++ b/core/lib/Drupal/Core/Routing/RouteProvider.php @@ -7,11 +7,14 @@ namespace Drupal\Core\Routing; +use Drupal\Core\Cache\CacheBackendInterface; use Drupal\Core\Path\CurrentPathStack; +use Drupal\Core\Path\InboundPathEvent; use Drupal\Core\State\StateInterface; use Symfony\Cmf\Component\Routing\PagedRouteCollection; use Symfony\Cmf\Component\Routing\PagedRouteProviderInterface; use Symfony\Component\EventDispatcher\EventSubscriberInterface; +use Symfony\Component\EventDispatcher\EventDispatcherInterface; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\Routing\Exception\RouteNotFoundException; use Symfony\Component\Routing\RouteCollection; @@ -67,6 +70,20 @@ class RouteProvider implements PreloadableRouteProviderInterface, PagedRouteProv protected $currentPath; /** + * The cache backend. + * + * @var \Drupal\Core\Cache\CacheBackendInterface + */ + protected $cache; + + /** + * The event dispatcher. + * + * @var \Symfony\Component\EventDispatcher\EventDispatcherInterface + */ + protected $eventDispatcher; + + /** * Constructs a new PathMatcher. * * @param \Drupal\Core\Database\Connection $connection @@ -74,15 +91,21 @@ class RouteProvider implements PreloadableRouteProviderInterface, PagedRouteProv * @param \Drupal\Core\State\StateInterface $state * The state. * @param \Drupal\Core\Path\CurrentPathStack $current_path - * THe current path. + * The current path. + * @param \Drupal\Core\Cache\CacheBackendInterface + * The cache backend. + * @param \Symfony\Component\EventDispatcher\EventDispatcherInterface + * The event dispatcher. * @param string $table * The table in the database to use for matching. */ - public function __construct(Connection $connection, StateInterface $state, CurrentPathStack $current_path, $table = 'router') { + public function __construct(Connection $connection, StateInterface $state, CurrentPathStack $current_path, CacheBackendInterface $cache_backend, EventDispatcherInterface $event_dispatcher, $table = 'router') { $this->connection = $connection; $this->state = $state; $this->tableName = $table; $this->currentPath = $current_path; + $this->cache = $cache_backend; + $this->eventDispatcher = $event_dispatcher; } /** @@ -111,9 +134,22 @@ public function __construct(Connection $connection, StateInterface $state, Curre * @todo Should this method's found routes also be included in the cache? */ public function getRouteCollectionForRequest(Request $request) { - $path = $this->currentPath->getPath($request); - - return $this->getRoutesByPath(rtrim($path, '/')); + $cid = 'route:' . $request->getPathInfo() . ':' . $request->getQueryString(); + if ($cached = $this->cache->get($cid)) { + $this->currentPath->setPath($cached->data[0], $request); + $request->query->replace($cached->data[1]); + return $cached->data[2]; + } + else { + $event = new InboundPathEvent($request); + $this->eventDispatcher->dispatch('incoming_path', $event); + $path = $this->currentPath->getPath($request); + // Incoming path processors may also set query parameters. + $parameters = $request->query->all(); + $routes = $this->getRoutesByPath(rtrim($path, '/')); + $this->cache->set($cid, [$path, $parameters, $routes], CacheBackendInterface::CACHE_PERMANENT, ['route_match']); + return $routes; + } } /** diff --git a/core/modules/system/src/EventSubscriber/ThemeSettingsCacheTag.php b/core/modules/system/src/EventSubscriber/ConfigCacheTag.php similarity index 73% rename from core/modules/system/src/EventSubscriber/ThemeSettingsCacheTag.php rename to core/modules/system/src/EventSubscriber/ConfigCacheTag.php index eb68d75..1ece46d 100644 --- a/core/modules/system/src/EventSubscriber/ThemeSettingsCacheTag.php +++ b/core/modules/system/src/EventSubscriber/ConfigCacheTag.php @@ -2,7 +2,7 @@ /** * @file - * Contains \Drupal\system\EventSubscriber\ThemeSettingsCacheTag. + * Contains \Drupal\system\EventSubscriber\ConfigCacheTag. */ namespace Drupal\system\EventSubscriber; @@ -15,9 +15,9 @@ use Symfony\Component\EventDispatcher\EventSubscriberInterface; /** - * A subscriber invalidating the 'rendered' cache tag when saving theme settings. + * A subscriber invalidating cache tags when system config objects are saved. */ -class ThemeSettingsCacheTag implements EventSubscriberInterface { +class ConfigCacheTag implements EventSubscriberInterface { /** * The theme handler. @@ -34,7 +34,7 @@ class ThemeSettingsCacheTag implements EventSubscriberInterface { protected $cacheTagsInvalidator; /** - * Constructs a ThemeSettingsCacheTag object. + * Constructs a ConfigCacheTag object. * * @param \Drupal\Core\Extension\ThemeHandlerInterface $theme_handler * The theme handler. @@ -47,12 +47,19 @@ public function __construct(ThemeHandlerInterface $theme_handler, CacheTagsInval } /** - * Invalidate the 'rendered' cache tag whenever a theme setting is modified. + * Invalidate cache tags when particular system config objects are saved. * * @param \Drupal\Core\Config\ConfigCrudEvent $event * The Event to process. */ public function onSave(ConfigCrudEvent $event) { + // Changing the site settings may mean a change to the front page route. + // Additionally changing the system site settings invalidates the render + // cache - since site name and similar may be part of rendered items. + if ($event->getConfig()->getName() === 'system.site') { + $this->cacheTagsInvalidator->invalidateTags(['route_match', 'rendered']); + } + // Global theme settings. if ($event->getConfig()->getName() === 'system.theme.global') { $this->cacheTagsInvalidator->invalidateTags(['rendered']); diff --git a/core/modules/system/src/PathBasedBreadcrumbBuilder.php b/core/modules/system/src/PathBasedBreadcrumbBuilder.php index 45418a7b..56039bf 100644 --- a/core/modules/system/src/PathBasedBreadcrumbBuilder.php +++ b/core/modules/system/src/PathBasedBreadcrumbBuilder.php @@ -185,7 +185,7 @@ protected function getRequestForPath($path, array $exclude) { } // @todo Use the RequestHelper once https://www.drupal.org/node/2090293 is // fixed. - $request = Request::create($this->context->getCompleteBaseUrl() . '/' . $path); + $request = Request::create('/' . $path); // Performance optimization: set a short accept header to reduce overhead in // AcceptHeaderMatcher when matching the request. $request->headers->set('Accept', 'text/html'); diff --git a/core/modules/system/src/Tests/Routing/RouteProviderTest.php b/core/modules/system/src/Tests/Routing/RouteProviderTest.php index e316738..b5d3773 100644 --- a/core/modules/system/src/Tests/Routing/RouteProviderTest.php +++ b/core/modules/system/src/Tests/Routing/RouteProviderTest.php @@ -11,15 +11,18 @@ use Drupal\Core\Path\CurrentPathStack; use Drupal\Core\State\State; use Drupal\simpletest\KernelTestBase; +use Symfony\Component\EventDispatcher\EventDispatcher; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\RequestStack; use Symfony\Component\Routing\Exception\RouteNotFoundException; use Symfony\Component\Routing\Route; use Symfony\Component\Routing\RouteCollection; use Symfony\Component\Routing\Exception\ResourceNotFoundException; +use Drupal\Core\Cache\MemoryBackend; use Drupal\Core\Routing\RouteProvider; use Drupal\Core\Database\Database; use Drupal\Core\Routing\MatcherDumper; +use Drupal\Core\Site\Settings; use Drupal\Tests\Core\Routing\RoutingFixtures; /** @@ -55,6 +58,8 @@ protected function setUp() { $this->fixtures = new RoutingFixtures(); $this->state = new State(new KeyValueMemoryFactory()); $this->currentPath = new CurrentPathStack(new RequestStack()); + $this->cache = new MemoryBackend('data'); + $this->eventDispatcher = new EventDispatcher(); } protected function tearDown() { @@ -69,7 +74,7 @@ protected function tearDown() { public function testCandidateOutlines() { $connection = Database::getConnection(); - $provider = new RouteProvider($connection, $this->state, $this->currentPath, 'test_routes'); + $provider = new RouteProvider($connection, $this->state, $this->currentPath, $this->cache, $this->eventDispatcher, 'test_routes'); $parts = array('node', '5', 'edit'); @@ -92,7 +97,7 @@ public function testCandidateOutlines() { */ function testExactPathMatch() { $connection = Database::getConnection(); - $provider = new RouteProvider($connection, $this->state, $this->currentPath, 'test_routes'); + $provider = new RouteProvider($connection, $this->state, $this->currentPath, $this->cache, $this->eventDispatcher, 'test_routes'); $this->fixtures->createTables($connection); @@ -116,7 +121,7 @@ function testExactPathMatch() { */ function testOutlinePathMatch() { $connection = Database::getConnection(); - $provider = new RouteProvider($connection, $this->state, $this->currentPath, 'test_routes'); + $provider = new RouteProvider($connection, $this->state, $this->currentPath, $this->cache, $this->eventDispatcher, 'test_routes'); $this->fixtures->createTables($connection); @@ -145,7 +150,7 @@ function testOutlinePathMatch() { */ function testOutlinePathMatchTrailingSlash() { $connection = Database::getConnection(); - $provider = new RouteProvider($connection, $this->state, $this->currentPath, 'test_routes'); + $provider = new RouteProvider($connection, $this->state, $this->currentPath, $this->cache, $this->eventDispatcher, 'test_routes'); $this->fixtures->createTables($connection); @@ -174,7 +179,7 @@ function testOutlinePathMatchTrailingSlash() { */ function testOutlinePathMatchDefaults() { $connection = Database::getConnection(); - $provider = new RouteProvider($connection, $this->state, $this->currentPath, 'test_routes'); + $provider = new RouteProvider($connection, $this->state, $this->currentPath, $this->cache, $this->eventDispatcher, 'test_routes'); $this->fixtures->createTables($connection); @@ -212,7 +217,7 @@ function testOutlinePathMatchDefaults() { */ function testOutlinePathMatchDefaultsCollision() { $connection = Database::getConnection(); - $provider = new RouteProvider($connection, $this->state, $this->currentPath, 'test_routes'); + $provider = new RouteProvider($connection, $this->state, $this->currentPath, $this->cache, $this->eventDispatcher, 'test_routes'); $this->fixtures->createTables($connection); @@ -251,7 +256,7 @@ function testOutlinePathMatchDefaultsCollision() { */ function testOutlinePathMatchDefaultsCollision2() { $connection = Database::getConnection(); - $provider = new RouteProvider($connection, $this->state, $this->currentPath, 'test_routes'); + $provider = new RouteProvider($connection, $this->state, $this->currentPath, $this->cache, $this->eventDispatcher, 'test_routes'); $this->fixtures->createTables($connection); @@ -290,7 +295,7 @@ function testOutlinePathMatchDefaultsCollision2() { */ public function testOutlinePathMatchZero() { $connection = Database::getConnection(); - $provider = new RouteProvider($connection, $this->state, $this->currentPath, 'test_routes'); + $provider = new RouteProvider($connection, $this->state, $this->currentPath, $this->cache, $this->eventDispatcher, 'test_routes'); $this->fixtures->createTables($connection); @@ -325,7 +330,7 @@ public function testOutlinePathMatchZero() { */ function testOutlinePathNoMatch() { $connection = Database::getConnection(); - $provider = new RouteProvider($connection, $this->state, $this->currentPath, 'test_routes'); + $provider = new RouteProvider($connection, $this->state, $this->currentPath, $this->cache, $this->eventDispatcher, 'test_routes'); $this->fixtures->createTables($connection); @@ -350,7 +355,7 @@ function testOutlinePathNoMatch() { */ function testCurrentPath() { $connection = Database::getConnection(); - $provider = new RouteProvider($connection, $this->state, $this->currentPath, 'test_routes'); + $provider = new RouteProvider($connection, $this->state, $this->currentPath, $this->cache, $this->eventDispatcher, 'test_routes'); $this->fixtures->createTables($connection); @@ -375,7 +380,7 @@ function testCurrentPath() { */ public function testRouteByName() { $connection = Database::getConnection(); - $provider = new RouteProvider($connection, $this->state, $this->currentPath, 'test_routes'); + $provider = new RouteProvider($connection, $this->state, $this->currentPath, $this->cache, $this->eventDispatcher, 'test_routes'); $this->fixtures->createTables($connection); @@ -410,7 +415,7 @@ public function testRouteByName() { */ public function testGetRoutesByPatternWithLongPatterns() { $connection = Database::getConnection(); - $provider = new RouteProvider($connection, $this->state, $this->currentPath, 'test_routes'); + $provider = new RouteProvider($connection, $this->state, $this->currentPath, $this->cache, $this->eventDispatcher, 'test_routes'); $this->fixtures->createTables($connection); // This pattern has only 3 parts, so we will get candidates, but no routes, @@ -468,7 +473,7 @@ public function testGetRoutesByPatternWithLongPatterns() { */ public function testGetRoutesPaged() { $connection = Database::getConnection(); - $provider = new RouteProvider($connection, $this->state, $this->currentPath, 'test_routes'); + $provider = new RouteProvider($connection, $this->state, $this->currentPath, $this->cache, $this->eventDispatcher, 'test_routes'); $this->fixtures->createTables($connection); $dumper = new MatcherDumper($connection, $this->state, 'test_routes'); diff --git a/core/modules/system/system.services.yml b/core/modules/system/system.services.yml index 223f442..29ea018 100644 --- a/core/modules/system/system.services.yml +++ b/core/modules/system/system.services.yml @@ -42,8 +42,8 @@ services: arguments: ['@cron', '@config.factory', '@state'] tags: - { name: event_subscriber } - system.theme_settings_cache_tag: - class: Drupal\system\EventSubscriber\ThemeSettingsCacheTag + system.config_cache_tag: + class: Drupal\system\EventSubscriber\ConfigCacheTag arguments: ['@theme_handler', '@cache_tags.invalidator'] tags: - { name: event_subscriber }