Issue #2524082 by pfrenssen, Gábor Hojtsy, Wim Leers, Berdir, Fabianx, dawehner, catch: Config overrides should provide cacheability metadata

8.0.x
Alex Pott 2015-07-24 21:27:30 +01:00
parent 58a6dbb8a4
commit 61603f58f6
29 changed files with 825 additions and 38 deletions

View File

@ -319,10 +319,12 @@ abstract class AccessResult implements AccessResultInterface, CacheableDependenc
* The entity whose cache tag to set on the access result.
*
* @return $this
*
* @deprecated in Drupal 8.0.x-dev, will be removed before Drupal 9.0.0. Use
* ::addCacheableDependency() instead.
*/
public function cacheUntilEntityChanges(EntityInterface $entity) {
$this->addCacheTags($entity->getCacheTags());
return $this;
return $this->addCacheableDependency($entity);
}
/**
@ -332,9 +334,33 @@ abstract class AccessResult implements AccessResultInterface, CacheableDependenc
* The configuration object whose cache tag to set on the access result.
*
* @return $this
*
* @deprecated in Drupal 8.0.x-dev, will be removed before Drupal 9.0.0. Use
* ::addCacheableDependency() instead.
*/
public function cacheUntilConfigurationChanges(ConfigBase $configuration) {
$this->addCacheTags($configuration->getCacheTags());
return $this->addCacheableDependency($configuration);
}
/**
* Adds a dependency on an object: merges its cacheability metadata.
*
* @param \Drupal\Core\Cache\CacheableDependencyInterface|object $other_object
* The dependency. If the object implements CacheableDependencyInterface,
* then its cacheability metadata will be used. Otherwise, the passed in
* object must be assumed to be uncacheable, so max-age 0 is set.
*
* @return $this
*/
public function addCacheableDependency($other_object) {
if ($other_object instanceof CacheableDependencyInterface) {
$this->contexts = Cache::mergeContexts($this->contexts, $other_object->getCacheContexts());
$this->tags = Cache::mergeTags($this->tags, $other_object->getCacheTags());
$this->maxAge = Cache::mergeMaxAges($this->maxAge, $other_object->getCacheMaxAge());
}
else {
$this->maxAge = 0;
}
return $this;
}

View File

@ -52,4 +52,18 @@ interface RefinableCacheableDependencyInterface extends CacheableDependencyInter
*/
public function mergeCacheMaxAge($max_age);
/**
* Adds a dependency on an object: merges its cacheability metadata.
*
* @param \Drupal\Core\Cache\CacheableDependencyInterface|object $other_object
* The dependency. If the object implements CacheableDependencyInterface,
* then its cacheability metadata will be used. Otherwise, the passed in
* object must be assumed to be uncacheable, so max-age 0 is set.
*
* @return $this
*
* @see \Drupal\Core\Cache\CacheableMetadata::createFromObject()
*/
public function addCacheableDependency($other_object);
}

View File

@ -33,6 +33,22 @@ trait RefinableCacheableDependencyTrait {
*/
protected $cacheMaxAge = Cache::PERMANENT;
/**
* {@inheritdoc}
*/
public function addCacheableDependency($other_object) {
if ($other_object instanceof CacheableDependencyInterface) {
$this->addCacheContexts($other_object->getCacheContexts());
$this->addCacheTags($other_object->getCacheTags());
$this->mergeCacheMaxAge($other_object->getCacheMaxAge());
}
else {
// Not a cacheable dependency, this can not be cached.
$this->maxAge = 0;
}
return $this;
}
/**
* {@inheritdoc}
*/

View File

@ -10,7 +10,8 @@ namespace Drupal\Core\Config;
use Drupal\Component\Utility\NestedArray;
use Drupal\Component\Utility\SafeMarkup;
use Drupal\Core\Cache\Cache;
use Drupal\Core\Cache\CacheableDependencyInterface;
use Drupal\Core\Cache\RefinableCacheableDependencyInterface;
use Drupal\Core\Cache\RefinableCacheableDependencyTrait;
use \Drupal\Core\DependencyInjection\DependencySerializationTrait;
/**
@ -28,8 +29,9 @@ use \Drupal\Core\DependencyInjection\DependencySerializationTrait;
* @see \Drupal\Core\Config\Config
* @see \Drupal\Core\Theme\ThemeSettings
*/
abstract class ConfigBase implements CacheableDependencyInterface {
abstract class ConfigBase implements RefinableCacheableDependencyInterface {
use DependencySerializationTrait;
use RefinableCacheableDependencyTrait;
/**
* The name of the configuration object.
@ -269,21 +271,21 @@ abstract class ConfigBase implements CacheableDependencyInterface {
* {@inheritdoc}
*/
public function getCacheContexts() {
return [];
return $this->cacheContexts;
}
/**
* {@inheritdoc}
*/
public function getCacheTags() {
return ['config:' . $this->name];
return Cache::mergeTags(['config:' . $this->name], $this->cacheTags);
}
/**
* {@inheritdoc}
*/
public function getCacheMaxAge() {
return Cache::PERMANENT;
return $this->cacheMaxAge;
}
}

View File

@ -126,6 +126,9 @@ class ConfigFactory implements ConfigFactoryInterface, EventSubscriberInterface
$this->cache[$cache_key]->setSettingsOverride($GLOBALS['config'][$name]);
}
}
$this->propagateConfigOverrideCacheability($cache_key, $name);
return $this->cache[$cache_key];
}
}
@ -183,6 +186,9 @@ class ConfigFactory implements ConfigFactoryInterface, EventSubscriberInterface
$this->cache[$cache_key]->setSettingsOverride($GLOBALS['config'][$name]);
}
}
$this->propagateConfigOverrideCacheability($cache_key, $name);
$list[$name] = $this->cache[$cache_key];
}
}
@ -209,6 +215,20 @@ class ConfigFactory implements ConfigFactoryInterface, EventSubscriberInterface
return $overrides;
}
/**
* Propagates cacheability of config overrides to cached config objects.
*
* @param string $cache_key
* The key of the cached config object to update.
* @param string $name
* The name of the configuration object to construct.
*/
protected function propagateConfigOverrideCacheability($cache_key, $name) {
foreach ($this->configFactoryOverrides as $override) {
$this->cache[$cache_key]->addCacheableDependency($override->getCacheableMetadata($name));
}
}
/**
* {@inheritdoc}
*/

View File

@ -58,4 +58,15 @@ interface ConfigFactoryOverrideInterface {
*/
public function createConfigObject($name, $collection = StorageInterface::DEFAULT_COLLECTION);
/**
* Gets the cacheability metadata associated with the config factory override.
*
* @param string $name
* The name of the configuration override to get metadata for.
*
* @return \Drupal\Core\Cache\CacheableMetadata
* A cacheable metadata object.
*/
public function getCacheableMetadata($name);
}

View File

@ -8,13 +8,13 @@
namespace Drupal\Core\Config\Entity;
use Drupal\Component\Utility\SafeMarkup;
use Drupal\Core\Cache\CacheableMetadata;
use Drupal\Core\Config\ConfigFactoryInterface;
use Drupal\Core\Config\ConfigImporterException;
use Drupal\Core\Entity\EntityInterface;
use Drupal\Core\Entity\EntityMalformedException;
use Drupal\Core\Entity\EntityStorageBase;
use Drupal\Core\Config\Config;
use Drupal\Core\Config\StorageInterface;
use Drupal\Core\Config\Entity\Exception\ConfigEntityIdLengthException;
use Drupal\Core\Entity\EntityTypeInterface;
use Drupal\Component\Uuid\UuidInterface;
@ -184,11 +184,41 @@ class ConfigEntityStorage extends EntityStorageBase implements ConfigEntityStora
}
// Load all of the configuration entities.
$records = array();
/** @var \Drupal\Core\Config\Config[] $configs */
$configs = [];
$records = [];
foreach ($this->configFactory->loadMultiple($names) as $config) {
$records[$config->get($this->idKey)] = $this->overrideFree ? $config->getOriginal(NULL, FALSE) : $config->get();
$id = $config->get($this->idKey);
$records[$id] = $this->overrideFree ? $config->getOriginal(NULL, FALSE) : $config->get();
$configs[$id] = $config;
}
return $this->mapFromStorageRecords($records);
$entities = $this->mapFromStorageRecords($records, $configs);
// Config entities wrap config objects, and therefore they need to inherit
// the cacheability metadata of config objects (to ensure e.g. additional
// cacheability metadata added by config overrides is not lost).
foreach ($entities as $id => $entity) {
// But rather than simply inheriting all cacheability metadata of config
// objects, we need to make sure the self-referring cache tag that is
// present on Config objects is not added to the Config entity. It must be
// removed for 3 reasons:
// 1. When renaming/duplicating a Config entity, the cache tag of the
// original config object would remain present, which would be wrong.
// 2. Some Config entities choose to not use the cache tag that the under-
// lying Config object provides by default (For performance and
// cacheability reasons it may not make sense to have a unique cache
// tag for every Config entity. The DateFormat Config entity specifies
// the 'rendered' cache tag for example, because A) date formats are
// changed extremely rarely, so invalidating all render cache items is
// fine, B) it means fewer cache tags per page.).
// 3. Fewer cache tags is better for performance.
$self_referring_cache_tag = ['config:' . $configs[$id]->getName()];
$config_cacheability = CacheableMetadata::createFromObject($configs[$id]);
$config_cacheability->setCacheTags(array_diff($config_cacheability->getCacheTags(), $self_referring_cache_tag));
$entity->addCacheableDependency($config_cacheability);
}
return $entities;
}
/**

View File

@ -70,7 +70,10 @@ class BlockViewBuilder extends EntityViewBuilder {
'#id' => $entity->id(),
'#cache' => [
'keys' => ['entity_view', 'block', $entity->id()],
'contexts' => $plugin->getCacheContexts(),
'contexts' => Cache::mergeContexts(
$entity->getCacheContexts(),
$plugin->getCacheContexts()
),
'tags' => $cache_tags,
'max-age' => $plugin->getCacheMaxAge(),
],

View File

@ -0,0 +1,65 @@
<?php
/**
* @file
* Contains \Drupal\config\Tests\CacheabilityMetadataConfigOverrideIntegrationTest.
*/
namespace Drupal\config\Tests;
use Drupal\simpletest\WebTestBase;
/**
* Tests if configuration overrides correctly affect cacheability metadata.
*
* @group config
*/
class CacheabilityMetadataConfigOverrideIntegrationTest extends WebTestBase {
/**
* {@inheritdoc}
*/
public static $modules = [
'block_test',
'config_override_integration_test',
];
/**
* {@inheritdoc}
*/
public function setUp() {
parent::setUp();
// @todo If our block does not contain any content then the cache context
// is not bubbling up and the test fails. Remove this line once the cache
// contexts are properly set. See https://www.drupal.org/node/2529980.
\Drupal::state()->set('block_test.content', 'Needs to have some content');
$this->drupalLogin($this->drupalCreateUser());
}
/**
* Tests if config overrides correctly set cacheability metadata.
*/
public function testConfigOverride() {
// Check the default (disabled) state of the cache context. The block label
// should not be overridden.
$this->drupalGet('<front>');
$this->assertNoText('Overridden block label');
// Both the cache context and tag should be present.
$this->assertCacheContext('config_override_integration_test');
$this->assertCacheTag('config_override_integration_test_tag');
// Flip the state of the cache context. The block label should now be
// overridden.
\Drupal::state()->set('config_override_integration_test.enabled', TRUE);
$this->drupalGet('<front>');
$this->assertText('Overridden block label');
// Both the cache context and tag should still be present.
$this->assertCacheContext('config_override_integration_test');
$this->assertCacheTag('config_override_integration_test_tag');
}
}

View File

@ -0,0 +1,95 @@
<?php
/**
* @file
* Contains \Drupal\config\Tests\CacheabilityMetadataConfigOverrideTest.
*/
namespace Drupal\config\Tests;
use Drupal\Core\Entity\EntityManagerInterface;
use Drupal\config_override_test\Cache\PirateDayCacheContext;
use Drupal\simpletest\KernelTestBase;
/**
* Tests if configuration overrides correctly affect cacheability metadata.
*
* @group config
*/
class CacheabilityMetadataConfigOverrideTest extends KernelTestBase {
/**
* {@inheritdoc}
*/
public static $modules = [
'block',
'block_content',
'config',
'config_override_test',
'system',
];
/**
* {@inheritdoc}
*/
public function setUp() {
parent::setUp();
$this->installEntitySchema('block_content');
$this->installConfig(['config_override_test']);
}
/**
* Tests if config overrides correctly set cacheability metadata.
*/
public function testConfigOverride() {
// It's pirate day today!
$GLOBALS['it_is_pirate_day'] = TRUE;
$config_factory = $this->container->get('config.factory');
$config = $config_factory->get('system.theme');
// Check that we are using the Pirate theme.
$theme = $config->get('default');
$this->assertEqual('pirate', $theme);
// Check that the cacheability metadata is correct.
$this->assertEqual(['pirate_day'], $config->getCacheContexts());
$this->assertEqual(['config:system.theme', 'pirate-day-tag'], $config->getCacheTags());
$this->assertEqual(PirateDayCacheContext::PIRATE_DAY_MAX_AGE, $config->getCacheMaxAge());
}
/**
* Tests if config overrides set cacheability metadata on config entities.
*/
public function testConfigEntityOverride() {
// It's pirate day today!
$GLOBALS['it_is_pirate_day'] = TRUE;
// Load the User login block and check that its cacheability metadata is
// overridden correctly. This verifies that the metadata is correctly
// applied to config entities.
/** @var EntityManagerInterface $entity_manager */
$entity_manager = $this->container->get('entity.manager');
$block = $entity_manager->getStorage('block')->load('call_to_action');
// Check that our call to action message is appealing to filibusters.
$this->assertEqual($block->label(), 'Draw yer cutlasses!');
// Check that the cacheability metadata is correct.
$this->assertEqual(['pirate_day'], $block->getCacheContexts());
$this->assertEqual(['config:block.block.call_to_action', 'pirate-day-tag'], $block->getCacheTags());
$this->assertEqual(PirateDayCacheContext::PIRATE_DAY_MAX_AGE, $block->getCacheMaxAge());
// Check that duplicating a config entity does not have the original config
// entity's cache tag.
$this->assertEqual(['config:block.block.', 'pirate-day-tag'], $block->createDuplicate()->getCacheTags());
// Check that renaming a config entity does not have the original config
// entity's cache tag.
$block->set('id', 'call_to_looting')->save();
$this->assertEqual(['pirate_day'], $block->getCacheContexts());
$this->assertEqual(['config:block.block.call_to_looting', 'pirate-day-tag'], $block->getCacheTags());
$this->assertEqual(PirateDayCacheContext::PIRATE_DAY_MAX_AGE, $block->getCacheMaxAge());
}
}

View File

@ -7,6 +7,7 @@
namespace Drupal\config_entity_static_cache_test;
use Drupal\Core\Cache\CacheableMetadata;
use Drupal\Core\Config\ConfigFactoryOverrideInterface;
use Drupal\Core\Config\StorageInterface;
@ -40,4 +41,11 @@ class ConfigOverrider implements ConfigFactoryOverrideInterface {
return NULL;
}
/**
* {@inheritdoc}
*/
public function getCacheableMetadata($name) {
return new CacheableMetadata();
}
}

View File

@ -0,0 +1,24 @@
id: config_override_test
theme: classy
weight: 0
status: true
langcode: en
region: content
plugin: test_cache
settings:
label: 'Test HTML block'
provider: block_test
label_display: visible
status: true
info: ''
view_mode: default
dependencies:
module:
- block_test
theme:
- classy
visibility:
request_path:
id: request_path
pages: ''
negate: false

View File

@ -0,0 +1,9 @@
name: 'Configuration override integration test'
type: module
package: Testing
version: VERSION
core: 8.x
dependencies:
- block
- block_test

View File

@ -0,0 +1,9 @@
services:
cache_context.config_override_integration_test:
class: Drupal\config_override_integration_test\Cache\ConfigOverrideIntegrationTestCacheContext
tags:
- { name: cache.context }
config_override_integration_test.config_override:
class: Drupal\config_override_integration_test\CacheabilityMetadataConfigOverride
tags:
- { name: config.factory.override }

View File

@ -0,0 +1,47 @@
<?php
/**
* @file
* Contains \Drupal\config_override_integration_test\Cache\ConfigOverrideIntegrationTestCacheContext.
*/
namespace Drupal\config_override_integration_test\Cache;
use Drupal\Core\Cache\CacheableMetadata;
use Drupal\Core\Cache\Context\CacheContextInterface;
/**
* A cache context service intended for the config override integration test.
*
* Cache context ID: 'config_override_integration_test'.
*/
class ConfigOverrideIntegrationTestCacheContext implements CacheContextInterface {
/**
* {@inheritdoc}
*/
public static function getLabel() {
return t('Config override integration test');
}
/**
* {@inheritdoc}
*/
public function getContext() {
// Default to the 'disabled' state.
$state = \Drupal::state()->get('config_override_integration_test.enabled', FALSE) ? 'yes' : 'no';
return 'config_override_integration_test.' . $state;
}
/**
* {@inheritdoc}
*/
public function getCacheableMetadata() {
// Since this depends on State this can change at any time and is not
// cacheable.
$metadata = new CacheableMetadata();
$metadata->setCacheMaxAge(0);
return $metadata;
}
}

View File

@ -0,0 +1,65 @@
<?php
/**
* @file
* Contains \Drupal\config_override_integration_test\CacheabilityMetadataConfigOverride.
*/
namespace Drupal\config_override_integration_test;
use Drupal\Core\Cache\CacheableMetadata;
use Drupal\Core\Config\ConfigFactoryOverrideInterface;
use Drupal\Core\Config\StorageInterface;
/**
* Test implementation of a config override that provides cacheability metadata.
*/
class CacheabilityMetadataConfigOverride implements ConfigFactoryOverrideInterface {
/**
* {@inheritdoc}
*/
public function loadOverrides($names) {
$overrides = [];
// Override the test block depending on the state set in the test.
$state = \Drupal::state()->get('config_override_integration_test.enabled', FALSE);
if (in_array('block.block.config_override_test', $names) && $state !== FALSE) {
$overrides = $overrides + [
'block.block.config_override_test' => [
'settings' => ['label' => 'Overridden block label'],
],
];
}
return $overrides;
}
/**
* {@inheritdoc}
*/
public function getCacheSuffix() {
return 'config_override_integration_test';
}
/**
* {@inheritdoc}
*/
public function createConfigObject($name, $collection = StorageInterface::DEFAULT_COLLECTION) {
return NULL;
}
/**
* {@inheritdoc}
*/
public function getCacheableMetadata($name) {
$metadata = new CacheableMetadata();
if ($name === 'block.block.config_override_test') {
$metadata
->setCacheContexts(['config_override_integration_test'])
->setCacheTags(['config_override_integration_test_tag']);
}
return $metadata;
}
}

View File

@ -0,0 +1,26 @@
langcode: en
status: true
dependencies:
module:
- block_content
theme:
- classy
id: call_to_action
theme: classy
region: content
weight: null
provider: null
plugin: 'block_content:d7c9d8ba-663f-41b4-8756-86bc55c44653'
settings:
id: 'block_content:d7c9d8ba-663f-41b4-8756-86bc55c44653'
label: 'Shop for cheap now!'
provider: block_content
label_display: visible
status: true
info: ''
view_mode: default
visibility:
request_path:
id: request_path
pages: ''
negate: false

View File

@ -3,3 +3,7 @@ type: module
package: Testing
version: VERSION
core: 8.x
dependencies:
- block
- block_content

View File

@ -1,4 +1,8 @@
services:
cache_context.pirate_day:
class: Drupal\config_override_test\Cache\PirateDayCacheContext
tags:
- { name: cache.context }
config_override_test.overrider:
class: Drupal\config_override_test\ConfigOverrider
tags:
@ -7,3 +11,7 @@ services:
class: Drupal\config_override_test\ConfigOverriderLowPriority
tags:
- { name: config.factory.override, priority: -100 }
config_override_test.pirate_day_cacheability_metadata_override:
class: Drupal\config_override_test\PirateDayCacheabilityMetadataConfigOverride
tags:
- { name: config.factory.override }

View File

@ -0,0 +1,66 @@
<?php
/**
* @file
* Contains \Drupal\config_override_test\Cache\PirateDayCacheContext.
*/
namespace Drupal\config_override_test\Cache;
use Drupal\Core\Cache\CacheableMetadata;
use Drupal\Core\Cache\Context\CacheContextInterface;
/**
* Defines the PirateDayCacheContext service that allows to cache the booty.
*
* Cache context ID: 'pirate_day'.
*/
class PirateDayCacheContext implements CacheContextInterface {
/**
* The length of Pirate Day. It lasts 24 hours.
*
* This is a simplified test implementation. In a real life Pirate Day module
* this data wouldn't be defined in a constant, but calculated in a static
* method. If it were Pirate Day it should return the number of seconds until
* midnight, and on all other days it should return the number of seconds
* until the start of the next Pirate Day.
*/
const PIRATE_DAY_MAX_AGE = 86400;
/**
* {@inheritdoc}
*/
public static function getLabel() {
return t('Pirate day');
}
/**
* {@inheritdoc}
*/
public function getContext() {
$is_pirate_day = static::isPirateDay() ? 'yarr' : 'nay';
return "pirate_day." . $is_pirate_day;
}
/**
* Returns whether or not it is Pirate Day.
*
* To ease testing this is determined with a global variable rather than using
* the traditional compass and sextant.
*
* @return bool
* Returns TRUE if it is Pirate Day today.
*/
public static function isPirateDay() {
return !empty($GLOBALS['it_is_pirate_day']);
}
/**
* {@inheritdoc}
*/
public function getCacheableMetadata() {
return new CacheableMetadata();
}
}

View File

@ -7,6 +7,7 @@
namespace Drupal\config_override_test;
use Drupal\Core\Cache\CacheableMetadata;
use Drupal\Core\Config\ConfigFactoryOverrideInterface;
/**
@ -44,5 +45,11 @@ class ConfigOverrider implements ConfigFactoryOverrideInterface {
return NULL;
}
}
/**
* {@inheritdoc}
*/
public function getCacheableMetadata($name) {
return new CacheableMetadata();
}
}

View File

@ -7,6 +7,7 @@
namespace Drupal\config_override_test;
use Drupal\Core\Cache\CacheableMetadata;
use Drupal\Core\Config\ConfigFactoryOverrideInterface;
use Drupal\Core\Config\StorageInterface;
@ -49,5 +50,11 @@ class ConfigOverriderLowPriority implements ConfigFactoryOverrideInterface {
return NULL;
}
}
/**
* {@inheritdoc}
*/
public function getCacheableMetadata($name) {
return new CacheableMetadata();
}
}

View File

@ -0,0 +1,82 @@
<?php
/**
* @file
* Contains \Drupal\config_override_test\PirateDayCacheabilityMetadataConfigOverride.
*/
namespace Drupal\config_override_test;
use Drupal\config_override_test\Cache\PirateDayCacheContext;
use Drupal\Core\Cache\CacheableMetadata;
use Drupal\Core\Config\ConfigFactoryOverrideInterface;
use Drupal\Core\Config\StorageInterface;
/**
* Test implementation of a config override that provides cacheability metadata.
*/
class PirateDayCacheabilityMetadataConfigOverride implements ConfigFactoryOverrideInterface {
/**
* {@inheritdoc}
*/
public function loadOverrides($names) {
$overrides = [];
// Override the theme and the 'call_to_action' block on Pirate Day.
if (PirateDayCacheContext::isPirateDay()) {
if (in_array('system.theme', $names)) {
$overrides = $overrides + ['system.theme' => ['default' => 'pirate']];
}
if (in_array('block.block.call_to_action', $names)) {
$overrides = $overrides + [
'block.block.call_to_action' => [
'settings' => ['label' => 'Draw yer cutlasses!'],
],
];
}
}
return $overrides;
}
/**
* {@inheritdoc}
*/
public function getCacheSuffix() {
return 'PirateDayConfigOverrider';
}
/**
* {@inheritdoc}
*/
public function createConfigObject($name, $collection = StorageInterface::DEFAULT_COLLECTION) {
return NULL;
}
/**
* {@inheritdoc}
*/
public function getCacheableMetadata($name) {
$metadata = new CacheableMetadata();
$metadata
->setCacheContexts(['pirate_day'])
->setCacheTags(['pirate-day-tag'])
->setCacheMaxAge(PirateDayCacheContext::PIRATE_DAY_MAX_AGE);
return $metadata;
}
/**
* Returns whether or not our overrides are potentially applicable.
*
* @param string $name
* The name of the config object that is being constructed.
*
* @return bool
* TRUE if the merchant ship will be boarded. FALSE if we drink rum instead.
*/
protected function isCacheabilityMetadataApplicable($name) {
return in_array($name, ['system.theme', 'block.block.call_to_action']);
}
}

View File

@ -9,6 +9,7 @@ namespace Drupal\Tests\content_translation\Unit\Access;
use Drupal\content_translation\Access\ContentTranslationManageAccessCheck;
use Drupal\Core\Access\AccessResult;
use Drupal\Core\DependencyInjection\ContainerBuilder;
use Drupal\Core\Language\Language;
use Drupal\Tests\UnitTestCase;
use Symfony\Component\Routing\Route;
@ -22,6 +23,28 @@ use Symfony\Component\Routing\Route;
*/
class ContentTranslationManageAccessCheckTest extends UnitTestCase {
/**
* The cache contexts manager.
*
* @var \Drupal\Core\Cache\Context\CacheContextsManager|\PHPUnit_Framework_MockObject_MockObject
*/
protected $cacheContextsManager;
/**
* {@inheritdoc}
*/
protected function setUp() {
parent::setUp();
$this->cacheContextsManager = $this->getMockBuilder('Drupal\Core\Cache\Context\CacheContextsManager')
->disableOriginalConstructor()
->getMock();
$container = new ContainerBuilder();
$container->set('cache_contexts_manager', $this->cacheContextsManager);
\Drupal::setContainer($container);
}
/**
* Tests the create access method.
*
@ -76,6 +99,9 @@ class ContentTranslationManageAccessCheckTest extends UnitTestCase {
$entity->expects($this->once())
->method('getCacheTags')
->will($this->returnValue(array('node:1337')));
$entity->expects($this->once())
->method('getCacheContexts')
->willReturn(array());
// Set the route requirements.
$route = new Route('test_route');

View File

@ -7,6 +7,7 @@
namespace Drupal\language\Config;
use Drupal\Core\Cache\CacheableMetadata;
use Drupal\Core\Config\ConfigCollectionInfo;
use Drupal\Core\Config\ConfigCrudEvent;
use Drupal\Core\Config\ConfigFactoryOverrideBase;
@ -222,4 +223,15 @@ class LanguageConfigFactoryOverride extends ConfigFactoryOverrideBase implements
}
}
/**
* {@inheritdoc}
*/
public function getCacheableMetadata($name) {
$metadata = new CacheableMetadata();
if ($this->language) {
$metadata->setCacheContexts(['languages:language_interface']);
}
return $metadata;
}
}

View File

@ -75,6 +75,12 @@ class UserTokenReplaceTest extends WebTestBase {
$metadata_tests['[user:url]'] = $base_bubbleable_metadata;
$metadata_tests['[user:edit-url]'] = $base_bubbleable_metadata;
$bubbleable_metadata = clone $base_bubbleable_metadata;
// This test runs with the Language module enabled, which means config is
// overridden by LanguageConfigFactoryOverride (to provide translations of
// config). This causes the interface language cache context to be added for
// config entities. The four next tokens use DateFormat Config entities, and
// therefore have the interface language cache context.
$bubbleable_metadata->addCacheContexts(['languages:language_interface']);
$metadata_tests['[user:last-login]'] = $bubbleable_metadata->addCacheTags(['rendered']);
$metadata_tests['[user:last-login:short]'] = $bubbleable_metadata;
$metadata_tests['[user:created]'] = $bubbleable_metadata;

View File

@ -10,7 +10,6 @@ namespace Drupal\views_ui;
use Drupal\Component\Utility\Html;
use Drupal\Component\Utility\Timer;
use Drupal\Component\Utility\Xss;
use Drupal\Core\Config\Entity\ThirdPartySettingsInterface;
use Drupal\Core\EventSubscriber\AjaxResponseSubscriber;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Url;
@ -18,7 +17,6 @@ use Drupal\views\Views;
use Drupal\Core\Entity\EntityStorageInterface;
use Drupal\views\ViewExecutable;
use Drupal\Core\Database\Database;
use Drupal\Core\TypedData\TypedDataInterface;
use Drupal\Core\Session\AccountInterface;
use Drupal\views\Plugin\views\query\Sql;
use Drupal\views\Entity\View;
@ -1336,6 +1334,14 @@ class ViewUI implements ViewEntityInterface {
return $this->storage->hasTrustedData();
}
/**
* {@inheritdoc}
*/
public function addCacheableDependency($other_object) {
$this->storage->addCacheableDependency($other_object);
return $this;
}
/**
* {@inheritdoc}
*/

View File

@ -12,6 +12,7 @@ use Drupal\Core\Access\AccessResultInterface;
use Drupal\Core\Access\AccessResultNeutral;
use Drupal\Core\Cache\Cache;
use Drupal\Core\Cache\CacheableDependencyInterface;
use Drupal\Core\DependencyInjection\ContainerBuilder;
use Drupal\Tests\UnitTestCase;
/**
@ -20,6 +21,28 @@ use Drupal\Tests\UnitTestCase;
*/
class AccessResultTest extends UnitTestCase {
/**
* The cache contexts manager.
*
* @var \Drupal\Core\Cache\Context\CacheContextsManager|\PHPUnit_Framework_MockObject_MockObject
*/
protected $cacheContextsManager;
/**
* {@inheritdoc}
*/
protected function setUp() {
parent::setUp();
$this->cacheContextsManager = $this->getMockBuilder('Drupal\Core\Cache\Context\CacheContextsManager')
->disableOriginalConstructor()
->getMock();
$container = new ContainerBuilder();
$container->set('cache_contexts_manager', $this->cacheContextsManager);
\Drupal::setContainer($container);
}
protected function assertDefaultCacheability(AccessResult $access) {
$this->assertSame([], $access->getCacheContexts());
$this->assertSame([], $access->getCacheTags());
@ -382,18 +405,17 @@ class AccessResultTest extends UnitTestCase {
/**
* @covers ::addCacheTags
* @covers ::resetCacheTags
* @covers ::addCacheableDependency
* @covers ::getCacheTags
* @covers ::cacheUntilEntityChanges
* @covers ::cacheUntilConfigurationChanges
* @covers ::resetCacheTags
*/
public function testCacheTags() {
$verify = function (AccessResult $access, array $tags) {
$verify = function (AccessResult $access, array $tags, array $contexts = [], $max_age = Cache::PERMANENT) {
$this->assertFalse($access->isAllowed());
$this->assertFalse($access->isForbidden());
$this->assertTrue($access->isNeutral());
$this->assertSame(Cache::PERMANENT, $access->getCacheMaxAge());
$this->assertSame([], $access->getCacheContexts());
$this->assertSame($max_age, $access->getCacheMaxAge());
$this->assertSame($contexts, $access->getCacheContexts());
$this->assertSame($tags, $access->getCacheTags());
};
@ -420,29 +442,26 @@ class AccessResultTest extends UnitTestCase {
->addCacheTags(['bar:baz']);
$verify($access, ['bar:baz', 'bar:qux', 'foo:bar', 'foo:baz']);
// ::cacheUntilEntityChanges() convenience method.
// ::addCacheableDependency() convenience method.
$node = $this->getMock('\Drupal\node\NodeInterface');
$node->expects($this->any())
->method('getCacheTags')
->will($this->returnValue(array('node:20011988')));
$node->expects($this->any())
->method('getCacheMaxAge')
->willReturn(600);
$node->expects($this->any())
->method('getCacheContexts')
->willReturn(['user']);
$tags = array('node:20011988');
$a = AccessResult::neutral()->addCacheTags($tags);
$verify($a, $tags);
$b = AccessResult::neutral()->cacheUntilEntityChanges($node);
$verify($b, $tags);
$this->assertEquals($a, $b);
$b = AccessResult::neutral()->addCacheableDependency($node);
$verify($b, $tags, ['user'], 600);
// ::cacheUntilConfigurationChanges() convenience method.
$configuration = $this->getMock('\Drupal\Core\Config\ConfigBase');
$configuration->expects($this->any())
->method('getCacheTags')
->will($this->returnValue(array('config:foo.bar.baz')));
$tags = array('config:foo.bar.baz');
$a = AccessResult::neutral()->addCacheTags($tags);
$verify($a, $tags);
$b = AccessResult::neutral()->cacheUntilConfigurationChanges($configuration);
$verify($b, $tags);
$this->assertEquals($a, $b);
$non_cacheable_dependency = new \stdClass();
$non_cacheable = AccessResult::neutral()->addCacheableDependency($non_cacheable_dependency);
$verify($non_cacheable, [], [], 0);
}
/**

View File

@ -7,6 +7,7 @@
namespace Drupal\Tests\Core\Config\Entity {
use Drupal\Core\Cache\Cache;
use Drupal\Core\Config\Entity\ConfigEntityInterface;
use Drupal\Core\Entity\EntityInterface;
use Drupal\Core\Language\Language;
@ -103,6 +104,13 @@ class ConfigEntityStorageTest extends UnitTestCase {
*/
protected $configManager;
/**
* The cache contexts manager.
*
* @var \Drupal\Core\Cache\Context\CacheContextsManager|\PHPUnit_Framework_MockObject_MockObject
*/
protected $cacheContextsManager;
/**
* {@inheritdoc}
*
@ -170,12 +178,17 @@ class ConfigEntityStorageTest extends UnitTestCase {
$this->configManager = $this->getMock('Drupal\Core\Config\ConfigManagerInterface');
$this->cacheContextsManager = $this->getMockBuilder('Drupal\Core\Cache\Context\CacheContextsManager')
->disableOriginalConstructor()
->getMock();
$container = new ContainerBuilder();
$container->set('entity.manager', $this->entityManager);
$container->set('config.typed', $this->typedConfigManager);
$container->set('cache_tags.invalidator', $this->cacheTagsInvalidator);
$container->set('config.manager', $this->configManager);
$container->set('language_manager', $this->languageManager);
$container->set('cache_contexts_manager', $this->cacheContextsManager);
\Drupal::setContainer($container);
}
@ -611,6 +624,18 @@ class ConfigEntityStorageTest extends UnitTestCase {
array('', array('id' => 'foo')),
array('id', 'foo'),
)));
$config_object->expects($this->exactly(1))
->method('getCacheContexts')
->willReturn([]);
$config_object->expects($this->exactly(1))
->method('getCacheTags')
->willReturn(['config:foo']);
$config_object->expects($this->exactly(1))
->method('getCacheMaxAge')
->willReturn(Cache::PERMANENT);
$config_object->expects($this->exactly(1))
->method('getName')
->willReturn('foo');
$this->cacheTagsInvalidator->expects($this->never())
->method('invalidateTags');
@ -664,6 +689,18 @@ class ConfigEntityStorageTest extends UnitTestCase {
array('', array('id' => 'foo')),
array('id', 'foo'),
)));
$config_object->expects($this->exactly(1))
->method('getCacheContexts')
->willReturn([]);
$config_object->expects($this->exactly(1))
->method('getCacheTags')
->willReturn(['config:foo']);
$config_object->expects($this->exactly(1))
->method('getCacheMaxAge')
->willReturn(Cache::PERMANENT);
$config_object->expects($this->exactly(1))
->method('getName')
->willReturn('foo');
$this->configFactory->expects($this->once())
->method('loadMultiple')
@ -694,6 +731,19 @@ class ConfigEntityStorageTest extends UnitTestCase {
array('', array('id' => 'foo')),
array('id', 'foo'),
)));
$foo_config_object->expects($this->exactly(1))
->method('getCacheContexts')
->willReturn([]);
$foo_config_object->expects($this->exactly(1))
->method('getCacheTags')
->willReturn(['config:foo']);
$foo_config_object->expects($this->exactly(1))
->method('getCacheMaxAge')
->willReturn(Cache::PERMANENT);
$foo_config_object->expects($this->exactly(1))
->method('getName')
->willReturn('foo');
$bar_config_object = $this->getMockBuilder('Drupal\Core\Config\Config')
->disableOriginalConstructor()
->getMock();
@ -703,6 +753,18 @@ class ConfigEntityStorageTest extends UnitTestCase {
array('', array('id' => 'bar')),
array('id', 'bar'),
)));
$bar_config_object->expects($this->exactly(1))
->method('getCacheContexts')
->willReturn([]);
$bar_config_object->expects($this->exactly(1))
->method('getCacheTags')
->willReturn(['config:bar']);
$bar_config_object->expects($this->exactly(1))
->method('getCacheMaxAge')
->willReturn(Cache::PERMANENT);
$bar_config_object->expects($this->exactly(1))
->method('getName')
->willReturn('foo');
$this->configFactory->expects($this->once())
->method('listAll')
@ -742,6 +804,18 @@ class ConfigEntityStorageTest extends UnitTestCase {
array('', array('id' => 'foo')),
array('id', 'foo'),
)));
$config_object->expects($this->exactly(1))
->method('getCacheContexts')
->willReturn([]);
$config_object->expects($this->exactly(1))
->method('getCacheTags')
->willReturn(['config:foo']);
$config_object->expects($this->exactly(1))
->method('getCacheMaxAge')
->willReturn(Cache::PERMANENT);
$config_object->expects($this->exactly(1))
->method('getName')
->willReturn('foo');
$this->configFactory->expects($this->once())
->method('loadMultiple')