Issue #2699627 by catch, dawehner, Wim Leers: url.path cache context for breadcrumbs is unnecessarily granular

8.2.x
Alex Pott 2016-04-27 10:59:24 +01:00
parent ae015a0ded
commit ebe946b9e7
7 changed files with 157 additions and 11 deletions

View File

@ -80,6 +80,11 @@ services:
arguments: ['@request_stack']
tags:
- { 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:
class: Drupal\Core\Cache\Context\QueryArgsCacheContext
arguments: ['@request_stack']

View File

@ -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();
}
}

View File

@ -25,7 +25,7 @@ class NodeTranslationUITest extends ContentTranslationUITestBase {
'theme',
'route',
'timezone',
'url.path',
'url.path.parent',
'url.query_args:_wrapper_format',
'user'
];

View File

@ -136,9 +136,9 @@ class PathBasedBreadcrumbBuilder implements BreadcrumbBuilderInterface {
// /user is just a redirect, so skip it.
// @todo Find a better way to deal with /user.
$exclude['/user'] = TRUE;
// Because this breadcrumb builder is entirely path-based, vary by the
// 'url.path' cache context.
$breadcrumb->addCacheContexts(['url.path']);
// Add the url.path.parent cache context. This code ignores the last path
// part so the result only depends on the path parents.
$breadcrumb->addCacheContexts(['url.path.parent']);
while (count($path_elements) > 1) {
array_pop($path_elements);
// Copy the path elements for up-casting.

View File

@ -142,7 +142,7 @@ class PathBasedBreadcrumbBuilderTest extends UnitTestCase {
$breadcrumb = $this->builder->build($this->getMock('Drupal\Core\Routing\RouteMatchInterface'));
$this->assertEquals([], $breadcrumb->getLinks());
$this->assertEquals(['url.path'], $breadcrumb->getCacheContexts());
$this->assertEquals(['url.path.parent'], $breadcrumb->getCacheContexts());
$this->assertEquals([], $breadcrumb->getCacheTags());
$this->assertEquals(Cache::PERMANENT, $breadcrumb->getCacheMaxAge());
}
@ -159,7 +159,7 @@ class PathBasedBreadcrumbBuilderTest extends UnitTestCase {
$breadcrumb = $this->builder->build($this->getMock('Drupal\Core\Routing\RouteMatchInterface'));
$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(Cache::PERMANENT, $breadcrumb->getCacheMaxAge());
}
@ -194,7 +194,7 @@ class PathBasedBreadcrumbBuilderTest extends UnitTestCase {
$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(['url.path', 'user.permissions'], $breadcrumb->getCacheContexts());
$this->assertEquals(['url.path.parent', 'user.permissions'], $breadcrumb->getCacheContexts());
$this->assertEquals([], $breadcrumb->getCacheTags());
$this->assertEquals(Cache::PERMANENT, $breadcrumb->getCacheMaxAge());
}
@ -245,7 +245,7 @@ class PathBasedBreadcrumbBuilderTest extends UnitTestCase {
new Link('Example', new Url('example')),
new Link('Bar', new Url('example_bar')),
], $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(Cache::PERMANENT, $breadcrumb->getCacheMaxAge());
}
@ -272,7 +272,7 @@ class PathBasedBreadcrumbBuilderTest extends UnitTestCase {
// No path matched, though at least the frontpage is displayed.
$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(Cache::PERMANENT, $breadcrumb->getCacheMaxAge());
}
@ -316,7 +316,7 @@ class PathBasedBreadcrumbBuilderTest extends UnitTestCase {
// No path matched, though at least the frontpage is displayed.
$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(Cache::PERMANENT, $breadcrumb->getCacheMaxAge());
}
@ -364,7 +364,7 @@ class PathBasedBreadcrumbBuilderTest extends UnitTestCase {
$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(['url.path', 'user.permissions'], $breadcrumb->getCacheContexts());
$this->assertEquals(['url.path.parent', 'user.permissions'], $breadcrumb->getCacheContexts());
$this->assertEquals([], $breadcrumb->getCacheTags());
$this->assertEquals(Cache::PERMANENT, $breadcrumb->getCacheMaxAge());
}

View File

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

View File

@ -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', ''],
['/', ''],
];
}
}