diff --git a/core/lib/Drupal/Core/Config/ConfigBase.php b/core/lib/Drupal/Core/Config/ConfigBase.php index eee76a2..a57c9c0 100644 --- a/core/lib/Drupal/Core/Config/ConfigBase.php +++ b/core/lib/Drupal/Core/Config/ConfigBase.php @@ -10,7 +10,8 @@ use Drupal\Component\Utility\NestedArray; use Drupal\Component\Utility\SafeMarkup; use Drupal\Core\Cache\Cache; -use Drupal\Core\Cache\CacheableDependencyInterface; +use Drupal\Core\Cache\MutableCacheableDependencyInterface; +use Drupal\Core\Cache\MutableCacheableDependencyTrait; use \Drupal\Core\DependencyInjection\DependencySerializationTrait; /** @@ -28,8 +29,9 @@ * @see \Drupal\Core\Config\Config * @see \Drupal\Core\Theme\ThemeSettings */ -abstract class ConfigBase implements CacheableDependencyInterface { +abstract class ConfigBase implements MutableCacheableDependencyInterface { use DependencySerializationTrait; + use MutableCacheableDependencyTrait; /** * The name of the configuration object. @@ -269,21 +271,21 @@ public function merge(array $data_to_merge) { * {@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; } } diff --git a/core/lib/Drupal/Core/Config/ConfigFactory.php b/core/lib/Drupal/Core/Config/ConfigFactory.php index cdfd951..f347bee 100644 --- a/core/lib/Drupal/Core/Config/ConfigFactory.php +++ b/core/lib/Drupal/Core/Config/ConfigFactory.php @@ -126,6 +126,10 @@ protected function doGet($name, $immutable = TRUE) { $this->cache[$cache_key]->setSettingsOverride($GLOBALS['config'][$name]); } } + + // Propagate cache contexts to the config object. + $this->propagateCacheableDependencyOverrides($cache_key); + return $this->cache[$cache_key]; } } @@ -183,6 +187,10 @@ protected function doLoadMultiple(array $names, $immutable = TRUE) { $this->cache[$cache_key]->setSettingsOverride($GLOBALS['config'][$name]); } } + + // Propagate cacheable dependencies to the config object. + $this->propagateCacheableDependencyOverrides($cache_key); + $list[$name] = $this->cache[$cache_key]; } } @@ -210,6 +218,21 @@ protected function loadOverrides(array $names) { } /** + * Propagates overridden cacheable dependencies to cached config objects. + * + * @param string $cache_key + * The key of the cached config object to update. + */ + protected function propagateCacheableDependencyOverrides($cache_key) { + foreach ($this->configFactoryOverrides as $override) { + $this->cache[$cache_key] + ->addCacheContexts($override->getCacheContexts()) + ->addCacheTags($override->getCacheTags()) + ->setCacheMaxAgeIfLower($override->getCacheMaxAge()); + } + } + + /** * {@inheritdoc} */ public function reset($name = NULL) { diff --git a/core/lib/Drupal/Core/Config/ConfigFactoryOverrideInterface.php b/core/lib/Drupal/Core/Config/ConfigFactoryOverrideInterface.php index 56dbc19..7b07c1a 100644 --- a/core/lib/Drupal/Core/Config/ConfigFactoryOverrideInterface.php +++ b/core/lib/Drupal/Core/Config/ConfigFactoryOverrideInterface.php @@ -7,10 +7,12 @@ namespace Drupal\Core\Config; +use Drupal\Core\Cache\CacheableDependencyInterface; + /** * Defines the interface for a configuration factory override object. */ -interface ConfigFactoryOverrideInterface { +interface ConfigFactoryOverrideInterface extends CacheableDependencyInterface { /** * Returns config overrides. diff --git a/core/lib/Drupal/Core/Config/Entity/ConfigEntityStorage.php b/core/lib/Drupal/Core/Config/Entity/ConfigEntityStorage.php index 59776aa..67b106b 100644 --- a/core/lib/Drupal/Core/Config/Entity/ConfigEntityStorage.php +++ b/core/lib/Drupal/Core/Config/Entity/ConfigEntityStorage.php @@ -186,7 +186,21 @@ protected function doLoadMultiple(array $ids = NULL) { // Load all of the configuration entities. $records = array(); 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(); + + // Add cacheability metadata to the record. + $records[$id]['cacheContexts'] = $config->getCacheContexts(); + $records[$id]['cacheTags'] = $config->getCacheTags(); + $records[$id]['cacheMaxAge'] = $config->getCacheMaxAge(); + + // Remove the self-referring cache tag that is present on Config objects, + // a ConfigEntity doesn't need this since it will be dynamically generated + // in EntityInterface::getCacheTagsForInvalidation(). The cache tags are + // merged during rendering, and having fewer tags available improves + // performance. + $key = array_search('config:' . $config->getName(), $records[$id]['cacheTags']); + unset($records[$id]['cacheTags'][$key]); } return $this->mapFromStorageRecords($records); } diff --git a/core/modules/config/src/Tests/CacheabilityMetadataConfigOverrideTest.php b/core/modules/config/src/Tests/CacheabilityMetadataConfigOverrideTest.php new file mode 100644 index 0000000..cc6e024 --- /dev/null +++ b/core/modules/config/src/Tests/CacheabilityMetadataConfigOverrideTest.php @@ -0,0 +1,84 @@ +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()); + } + +} diff --git a/core/modules/config/tests/config_entity_static_cache_test/src/ConfigOverrider.php b/core/modules/config/tests/config_entity_static_cache_test/src/ConfigOverrider.php index 2867c7d..8dad5bb 100644 --- a/core/modules/config/tests/config_entity_static_cache_test/src/ConfigOverrider.php +++ b/core/modules/config/tests/config_entity_static_cache_test/src/ConfigOverrider.php @@ -7,6 +7,7 @@ namespace Drupal\config_entity_static_cache_test; +use Drupal\Core\Cache\Cache; use Drupal\Core\Config\ConfigFactoryOverrideInterface; use Drupal\Core\Config\StorageInterface; @@ -40,4 +41,25 @@ public function createConfigObject($name, $collection = StorageInterface::DEFAUL return NULL; } + /** + * {@inheritdoc} + */ + public function getCacheContexts() { + return []; + } + + /** + * {@inheritdoc} + */ + public function getCacheTags() { + return []; + } + + /** + * {@inheritdoc} + */ + public function getCacheMaxAge() { + return Cache::PERMANENT; + } + } diff --git a/core/modules/config/tests/config_override_test/config/install/block.block.call_to_action.yml b/core/modules/config/tests/config_override_test/config/install/block.block.call_to_action.yml new file mode 100644 index 0000000..8951c0d --- /dev/null +++ b/core/modules/config/tests/config_override_test/config/install/block.block.call_to_action.yml @@ -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 diff --git a/core/modules/config/tests/config_override_test/config_override_test.services.yml b/core/modules/config/tests/config_override_test/config_override_test.services.yml index c3fae64..4dba664 100644 --- a/core/modules/config/tests/config_override_test/config_override_test.services.yml +++ b/core/modules/config/tests/config_override_test/config_override_test.services.yml @@ -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_cache_context_override: + class: Drupal\config_override_test\PirateDayCacheabilityMetadataConfigOverride + tags: + - { name: config.factory.override } diff --git a/core/modules/config/tests/config_override_test/src/Cache/PirateDayCacheContext.php b/core/modules/config/tests/config_override_test/src/Cache/PirateDayCacheContext.php new file mode 100644 index 0000000..80e6000 --- /dev/null +++ b/core/modules/config/tests/config_override_test/src/Cache/PirateDayCacheContext.php @@ -0,0 +1,52 @@ + ['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 getCacheContexts() { + return ['pirate_day']; + } + + /** + * {@inheritdoc} + */ + public function getCacheTags() { + return ['pirate-day-tag']; + } + + /** + * {@inheritdoc} + */ + public function getCacheMaxAge() { + return PirateDayCacheContext::PIRATE_DAY_MAX_AGE; + } + +} diff --git a/core/modules/language/src/Config/LanguageConfigFactoryOverride.php b/core/modules/language/src/Config/LanguageConfigFactoryOverride.php index 3d0a67b..e2e8c3b 100644 --- a/core/modules/language/src/Config/LanguageConfigFactoryOverride.php +++ b/core/modules/language/src/Config/LanguageConfigFactoryOverride.php @@ -7,6 +7,7 @@ namespace Drupal\language\Config; +use Drupal\Core\Cache\Cache; use Drupal\Core\Config\ConfigCollectionInfo; use Drupal\Core\Config\ConfigCrudEvent; use Drupal\Core\Config\ConfigFactoryOverrideBase; @@ -222,4 +223,25 @@ public function onConfigDelete(ConfigCrudEvent $event) { } } + /** + * {@inheritdoc} + */ + public function getCacheContexts() { + return ['languages:language_interface']; + } + + /** + * {@inheritdoc} + */ + public function getCacheTags() { + return []; + } + + /** + * {@inheritdoc} + */ + public function getCacheMaxAge() { + return Cache::PERMANENT; + } + } diff --git a/core/tests/Drupal/Tests/Core/Config/Entity/ConfigEntityStorageTest.php b/core/tests/Drupal/Tests/Core/Config/Entity/ConfigEntityStorageTest.php index c4413eb..ae1aa1d 100644 --- a/core/tests/Drupal/Tests/Core/Config/Entity/ConfigEntityStorageTest.php +++ b/core/tests/Drupal/Tests/Core/Config/Entity/ConfigEntityStorageTest.php @@ -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; @@ -611,6 +612,18 @@ public function testSaveChangedUuid() { array('', array('id' => 'foo')), array('id', 'foo'), ))); + $config_object->expects($this->exactly(1)) + ->method('getCacheContexts') + ->will($this->returnValue([])); + $config_object->expects($this->exactly(1)) + ->method('getCacheTags') + ->will($this->returnValue(['config:foo'])); + $config_object->expects($this->exactly(1)) + ->method('getCacheMaxAge') + ->will($this->returnValue(Cache::PERMANENT)); + $config_object->expects($this->exactly(1)) + ->method('getName') + ->will($this->returnValue('foo')); $this->cacheTagsInvalidator->expects($this->never()) ->method('invalidateTags'); @@ -664,6 +677,18 @@ public function testLoad() { array('', array('id' => 'foo')), array('id', 'foo'), ))); + $config_object->expects($this->exactly(1)) + ->method('getCacheContexts') + ->will($this->returnValue([])); + $config_object->expects($this->exactly(1)) + ->method('getCacheTags') + ->will($this->returnValue(['config:foo'])); + $config_object->expects($this->exactly(1)) + ->method('getCacheMaxAge') + ->will($this->returnValue(Cache::PERMANENT)); + $config_object->expects($this->exactly(1)) + ->method('getName') + ->will($this->returnValue('foo')); $this->configFactory->expects($this->once()) ->method('loadMultiple') @@ -694,6 +719,19 @@ public function testLoadMultipleAll() { array('', array('id' => 'foo')), array('id', 'foo'), ))); + $foo_config_object->expects($this->exactly(1)) + ->method('getCacheContexts') + ->will($this->returnValue([])); + $foo_config_object->expects($this->exactly(1)) + ->method('getCacheTags') + ->will($this->returnValue(['config:foo'])); + $foo_config_object->expects($this->exactly(1)) + ->method('getCacheMaxAge') + ->will($this->returnValue(Cache::PERMANENT)); + $foo_config_object->expects($this->exactly(1)) + ->method('getName') + ->will($this->returnValue('foo')); + $bar_config_object = $this->getMockBuilder('Drupal\Core\Config\Config') ->disableOriginalConstructor() ->getMock(); @@ -703,6 +741,18 @@ public function testLoadMultipleAll() { array('', array('id' => 'bar')), array('id', 'bar'), ))); + $bar_config_object->expects($this->exactly(1)) + ->method('getCacheContexts') + ->will($this->returnValue([])); + $bar_config_object->expects($this->exactly(1)) + ->method('getCacheTags') + ->will($this->returnValue(['config:bar'])); + $bar_config_object->expects($this->exactly(1)) + ->method('getCacheMaxAge') + ->will($this->returnValue(Cache::PERMANENT)); + $bar_config_object->expects($this->exactly(1)) + ->method('getName') + ->will($this->returnValue('foo')); $this->configFactory->expects($this->once()) ->method('listAll') @@ -742,6 +792,18 @@ public function testLoadMultipleIds() { array('', array('id' => 'foo')), array('id', 'foo'), ))); + $config_object->expects($this->exactly(1)) + ->method('getCacheContexts') + ->will($this->returnValue([])); + $config_object->expects($this->exactly(1)) + ->method('getCacheTags') + ->will($this->returnValue(['config:foo'])); + $config_object->expects($this->exactly(1)) + ->method('getCacheMaxAge') + ->will($this->returnValue(Cache::PERMANENT)); + $config_object->expects($this->exactly(1)) + ->method('getName') + ->will($this->returnValue('foo')); $this->configFactory->expects($this->once()) ->method('loadMultiple')