Issue #2699627 by catch, dawehner, Wim Leers: url.path cache context for breadcrumbs is unnecessarily granular
parent
ae015a0ded
commit
ebe946b9e7
|
@ -80,6 +80,11 @@ services:
|
||||||
arguments: ['@request_stack']
|
arguments: ['@request_stack']
|
||||||
tags:
|
tags:
|
||||||
- { name: cache.context }
|
- { name: cache.context }
|
||||||
|
cache_context.url.path.parent:
|
||||||
|
class: Drupal\Core\Cache\Context\PathParentCacheContext
|
||||||
|
arguments: ['@request_stack']
|
||||||
|
tags:
|
||||||
|
- { name: cache.context }
|
||||||
cache_context.url.query_args:
|
cache_context.url.query_args:
|
||||||
class: Drupal\Core\Cache\Context\QueryArgsCacheContext
|
class: Drupal\Core\Cache\Context\QueryArgsCacheContext
|
||||||
arguments: ['@request_stack']
|
arguments: ['@request_stack']
|
||||||
|
|
|
@ -0,0 +1,41 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Drupal\Core\Cache\Context;
|
||||||
|
|
||||||
|
use Drupal\Core\Cache\CacheableMetadata;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Defines a cache context service for path parents.
|
||||||
|
*
|
||||||
|
* Cache context ID: 'url.path.parent'.
|
||||||
|
*
|
||||||
|
* This allows for caching based on the path, excluding everything after the
|
||||||
|
* last forward slash.
|
||||||
|
*/
|
||||||
|
class PathParentCacheContext extends RequestStackCacheContextBase implements CacheContextInterface {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public static function getLabel() {
|
||||||
|
return t('Parent path');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function getContext() {
|
||||||
|
$request = $this->requestStack->getCurrentRequest();
|
||||||
|
$path_elements = explode('/', trim($request->getPathInfo(), '/'));
|
||||||
|
array_pop($path_elements);
|
||||||
|
return implode('/', $path_elements);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function getCacheableMetadata() {
|
||||||
|
return new CacheableMetadata();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -25,7 +25,7 @@ class NodeTranslationUITest extends ContentTranslationUITestBase {
|
||||||
'theme',
|
'theme',
|
||||||
'route',
|
'route',
|
||||||
'timezone',
|
'timezone',
|
||||||
'url.path',
|
'url.path.parent',
|
||||||
'url.query_args:_wrapper_format',
|
'url.query_args:_wrapper_format',
|
||||||
'user'
|
'user'
|
||||||
];
|
];
|
||||||
|
|
|
@ -136,9 +136,9 @@ class PathBasedBreadcrumbBuilder implements BreadcrumbBuilderInterface {
|
||||||
// /user is just a redirect, so skip it.
|
// /user is just a redirect, so skip it.
|
||||||
// @todo Find a better way to deal with /user.
|
// @todo Find a better way to deal with /user.
|
||||||
$exclude['/user'] = TRUE;
|
$exclude['/user'] = TRUE;
|
||||||
// Because this breadcrumb builder is entirely path-based, vary by the
|
// Add the url.path.parent cache context. This code ignores the last path
|
||||||
// 'url.path' cache context.
|
// part so the result only depends on the path parents.
|
||||||
$breadcrumb->addCacheContexts(['url.path']);
|
$breadcrumb->addCacheContexts(['url.path.parent']);
|
||||||
while (count($path_elements) > 1) {
|
while (count($path_elements) > 1) {
|
||||||
array_pop($path_elements);
|
array_pop($path_elements);
|
||||||
// Copy the path elements for up-casting.
|
// Copy the path elements for up-casting.
|
||||||
|
|
|
@ -142,7 +142,7 @@ class PathBasedBreadcrumbBuilderTest extends UnitTestCase {
|
||||||
|
|
||||||
$breadcrumb = $this->builder->build($this->getMock('Drupal\Core\Routing\RouteMatchInterface'));
|
$breadcrumb = $this->builder->build($this->getMock('Drupal\Core\Routing\RouteMatchInterface'));
|
||||||
$this->assertEquals([], $breadcrumb->getLinks());
|
$this->assertEquals([], $breadcrumb->getLinks());
|
||||||
$this->assertEquals(['url.path'], $breadcrumb->getCacheContexts());
|
$this->assertEquals(['url.path.parent'], $breadcrumb->getCacheContexts());
|
||||||
$this->assertEquals([], $breadcrumb->getCacheTags());
|
$this->assertEquals([], $breadcrumb->getCacheTags());
|
||||||
$this->assertEquals(Cache::PERMANENT, $breadcrumb->getCacheMaxAge());
|
$this->assertEquals(Cache::PERMANENT, $breadcrumb->getCacheMaxAge());
|
||||||
}
|
}
|
||||||
|
@ -159,7 +159,7 @@ class PathBasedBreadcrumbBuilderTest extends UnitTestCase {
|
||||||
|
|
||||||
$breadcrumb = $this->builder->build($this->getMock('Drupal\Core\Routing\RouteMatchInterface'));
|
$breadcrumb = $this->builder->build($this->getMock('Drupal\Core\Routing\RouteMatchInterface'));
|
||||||
$this->assertEquals([0 => new Link('Home', new Url('<front>'))], $breadcrumb->getLinks());
|
$this->assertEquals([0 => new Link('Home', new Url('<front>'))], $breadcrumb->getLinks());
|
||||||
$this->assertEquals(['url.path'], $breadcrumb->getCacheContexts());
|
$this->assertEquals(['url.path.parent'], $breadcrumb->getCacheContexts());
|
||||||
$this->assertEquals([], $breadcrumb->getCacheTags());
|
$this->assertEquals([], $breadcrumb->getCacheTags());
|
||||||
$this->assertEquals(Cache::PERMANENT, $breadcrumb->getCacheMaxAge());
|
$this->assertEquals(Cache::PERMANENT, $breadcrumb->getCacheMaxAge());
|
||||||
}
|
}
|
||||||
|
@ -194,7 +194,7 @@ class PathBasedBreadcrumbBuilderTest extends UnitTestCase {
|
||||||
|
|
||||||
$breadcrumb = $this->builder->build($this->getMock('Drupal\Core\Routing\RouteMatchInterface'));
|
$breadcrumb = $this->builder->build($this->getMock('Drupal\Core\Routing\RouteMatchInterface'));
|
||||||
$this->assertEquals([0 => new Link('Home', new Url('<front>')), 1 => new Link('Example', new Url('example'))], $breadcrumb->getLinks());
|
$this->assertEquals([0 => new Link('Home', new Url('<front>')), 1 => new Link('Example', new Url('example'))], $breadcrumb->getLinks());
|
||||||
$this->assertEquals(['url.path', 'user.permissions'], $breadcrumb->getCacheContexts());
|
$this->assertEquals(['url.path.parent', 'user.permissions'], $breadcrumb->getCacheContexts());
|
||||||
$this->assertEquals([], $breadcrumb->getCacheTags());
|
$this->assertEquals([], $breadcrumb->getCacheTags());
|
||||||
$this->assertEquals(Cache::PERMANENT, $breadcrumb->getCacheMaxAge());
|
$this->assertEquals(Cache::PERMANENT, $breadcrumb->getCacheMaxAge());
|
||||||
}
|
}
|
||||||
|
@ -245,7 +245,7 @@ class PathBasedBreadcrumbBuilderTest extends UnitTestCase {
|
||||||
new Link('Example', new Url('example')),
|
new Link('Example', new Url('example')),
|
||||||
new Link('Bar', new Url('example_bar')),
|
new Link('Bar', new Url('example_bar')),
|
||||||
], $breadcrumb->getLinks());
|
], $breadcrumb->getLinks());
|
||||||
$this->assertEquals(['bar', 'url.path', 'user.permissions'], $breadcrumb->getCacheContexts());
|
$this->assertEquals(['bar', 'url.path.parent', 'user.permissions'], $breadcrumb->getCacheContexts());
|
||||||
$this->assertEquals(['example'], $breadcrumb->getCacheTags());
|
$this->assertEquals(['example'], $breadcrumb->getCacheTags());
|
||||||
$this->assertEquals(Cache::PERMANENT, $breadcrumb->getCacheMaxAge());
|
$this->assertEquals(Cache::PERMANENT, $breadcrumb->getCacheMaxAge());
|
||||||
}
|
}
|
||||||
|
@ -272,7 +272,7 @@ class PathBasedBreadcrumbBuilderTest extends UnitTestCase {
|
||||||
|
|
||||||
// No path matched, though at least the frontpage is displayed.
|
// No path matched, though at least the frontpage is displayed.
|
||||||
$this->assertEquals([0 => new Link('Home', new Url('<front>'))], $breadcrumb->getLinks());
|
$this->assertEquals([0 => new Link('Home', new Url('<front>'))], $breadcrumb->getLinks());
|
||||||
$this->assertEquals(['url.path'], $breadcrumb->getCacheContexts());
|
$this->assertEquals(['url.path.parent'], $breadcrumb->getCacheContexts());
|
||||||
$this->assertEquals([], $breadcrumb->getCacheTags());
|
$this->assertEquals([], $breadcrumb->getCacheTags());
|
||||||
$this->assertEquals(Cache::PERMANENT, $breadcrumb->getCacheMaxAge());
|
$this->assertEquals(Cache::PERMANENT, $breadcrumb->getCacheMaxAge());
|
||||||
}
|
}
|
||||||
|
@ -316,7 +316,7 @@ class PathBasedBreadcrumbBuilderTest extends UnitTestCase {
|
||||||
|
|
||||||
// No path matched, though at least the frontpage is displayed.
|
// No path matched, though at least the frontpage is displayed.
|
||||||
$this->assertEquals([0 => new Link('Home', new Url('<front>'))], $breadcrumb->getLinks());
|
$this->assertEquals([0 => new Link('Home', new Url('<front>'))], $breadcrumb->getLinks());
|
||||||
$this->assertEquals(['url.path'], $breadcrumb->getCacheContexts());
|
$this->assertEquals(['url.path.parent'], $breadcrumb->getCacheContexts());
|
||||||
$this->assertEquals([], $breadcrumb->getCacheTags());
|
$this->assertEquals([], $breadcrumb->getCacheTags());
|
||||||
$this->assertEquals(Cache::PERMANENT, $breadcrumb->getCacheMaxAge());
|
$this->assertEquals(Cache::PERMANENT, $breadcrumb->getCacheMaxAge());
|
||||||
}
|
}
|
||||||
|
@ -364,7 +364,7 @@ class PathBasedBreadcrumbBuilderTest extends UnitTestCase {
|
||||||
|
|
||||||
$breadcrumb = $this->builder->build($this->getMock('Drupal\Core\Routing\RouteMatchInterface'));
|
$breadcrumb = $this->builder->build($this->getMock('Drupal\Core\Routing\RouteMatchInterface'));
|
||||||
$this->assertEquals([0 => new Link('Home', new Url('<front>')), 1 => new Link('Admin', new Url('user_page'))], $breadcrumb->getLinks());
|
$this->assertEquals([0 => new Link('Home', new Url('<front>')), 1 => new Link('Admin', new Url('user_page'))], $breadcrumb->getLinks());
|
||||||
$this->assertEquals(['url.path', 'user.permissions'], $breadcrumb->getCacheContexts());
|
$this->assertEquals(['url.path.parent', 'user.permissions'], $breadcrumb->getCacheContexts());
|
||||||
$this->assertEquals([], $breadcrumb->getCacheTags());
|
$this->assertEquals([], $breadcrumb->getCacheTags());
|
||||||
$this->assertEquals(Cache::PERMANENT, $breadcrumb->getCacheMaxAge());
|
$this->assertEquals(Cache::PERMANENT, $breadcrumb->getCacheMaxAge());
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,57 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Drupal\FunctionalTests\Breadcrumb;
|
||||||
|
|
||||||
|
use Drupal\simpletest\BlockCreationTrait;
|
||||||
|
use Drupal\Tests\BrowserTestBase;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests the breadcrumb of 404 pages.
|
||||||
|
*
|
||||||
|
* @group breadcrumb
|
||||||
|
*/
|
||||||
|
class Breadcrumb404Test extends BrowserTestBase {
|
||||||
|
|
||||||
|
use BlockCreationTrait;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public static $modules = ['system', 'block'];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests that different 404s don't create unnecessary cache entries.
|
||||||
|
*/
|
||||||
|
public function testBreadcrumbOn404Pages() {
|
||||||
|
$this->placeBlock('system_breadcrumb_block', ['id' => 'breadcrumb']);
|
||||||
|
|
||||||
|
// Prime the cache first.
|
||||||
|
$this->drupalGet('/not-found-1');
|
||||||
|
$base_count = count($this->getBreadcrumbCacheEntries());
|
||||||
|
|
||||||
|
$this->drupalGet('/not-found-2');
|
||||||
|
$next_count = count($this->getBreadcrumbCacheEntries());
|
||||||
|
$this->assertEquals($base_count, $next_count);
|
||||||
|
|
||||||
|
$this->drupalGet('/not-found-3');
|
||||||
|
$next_count = count($this->getBreadcrumbCacheEntries());
|
||||||
|
$this->assertEquals($base_count, $next_count);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the breadcrumb cache entries.
|
||||||
|
*
|
||||||
|
* @return array
|
||||||
|
* The breadcrumb cache entries.
|
||||||
|
*/
|
||||||
|
protected function getBreadcrumbCacheEntries() {
|
||||||
|
$database = \Drupal::database();
|
||||||
|
$cache_entries = $database->select('cache_render')
|
||||||
|
->fields('cache_render')
|
||||||
|
->condition('cid', $database->escapeLike('entity_view:block:breadcrumb') . '%', 'LIKE')
|
||||||
|
->execute()
|
||||||
|
->fetchAllAssoc('cid');
|
||||||
|
return $cache_entries;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,43 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Drupal\Tests\Core\Cache\Context;
|
||||||
|
|
||||||
|
use Drupal\Core\Cache\Context\PathParentCacheContext;
|
||||||
|
use Drupal\Tests\UnitTestCase;
|
||||||
|
use Symfony\Component\HttpFoundation\Request;
|
||||||
|
use Symfony\Component\HttpFoundation\RequestStack;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @coversDefaultClass \Drupal\Core\Cache\Context\PathParentCacheContext
|
||||||
|
* @group Cache
|
||||||
|
*/
|
||||||
|
class PathParentCacheContextTest extends UnitTestCase {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @covers ::getContext
|
||||||
|
*
|
||||||
|
* @dataProvider providerTestGetContext
|
||||||
|
*/
|
||||||
|
public function testgetContext($original_path, $context) {
|
||||||
|
$request_stack = new RequestStack();
|
||||||
|
$request = Request::create($original_path);
|
||||||
|
$request_stack->push($request);
|
||||||
|
$cache_context = new PathParentCacheContext($request_stack);
|
||||||
|
$this->assertSame($cache_context->getContext(), $context);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Provides a list of paths and expected cache contexts.
|
||||||
|
*/
|
||||||
|
public function providerTestGetContext() {
|
||||||
|
return [
|
||||||
|
['/some/path', 'some'],
|
||||||
|
['/some/other-path', 'some'],
|
||||||
|
['/some/other/path', 'some/other'],
|
||||||
|
['/some/other/path?q=foo&b=bar', 'some/other'],
|
||||||
|
['/some', ''],
|
||||||
|
['/', ''],
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
Loading…
Reference in New Issue