Commit 5859ca2c authored by alexpott's avatar alexpott

Issue #2040135 by Wim Leers, Berdir: Caches dependent on simple config are...

Issue #2040135 by Wim Leers, Berdir: Caches dependent on simple config are only invalidated on form submissions
parent 2f628af6
......@@ -406,7 +406,7 @@ services:
arguments: ['@plugin.manager.menu.link']
menu.tree_storage:
class: Drupal\Core\Menu\MenuTreeStorage
arguments: ['@database', '@cache.menu', 'menu_tree']
arguments: ['@database', '@cache.menu', '@cache_tags.invalidator', 'menu_tree']
public: false # Private to plugin.manager.menu.link and menu.link_tree
tags:
- { name: backend_overridable }
......
......@@ -69,7 +69,7 @@ function system_list_reset() {
// @todo Trigger an event upon module install/uninstall and theme
// enable/disable, and move this into an event subscriber.
// @see https://drupal.org/node/2206347
Cache::invalidateTags(array('extension'));
Cache::invalidateTags(['config:core.extension']);
}
/**
......
......@@ -97,7 +97,7 @@ public function checkNamedRoute($route_name, array $parameters = array(), Accoun
}
catch (RouteNotFoundException $e) {
// Cacheable until extensions change.
$result = AccessResult::forbidden()->addCacheTags(array('extension'));
$result = AccessResult::forbidden()->addCacheTags(['config:core.extension']);
return $return_as_object ? $result : $result->isAllowed();
}
catch (ParamNotConvertedException $e) {
......
......@@ -8,6 +8,7 @@
use Drupal\Core\Cache\Cache;
use Drupal\Core\Cache\CacheableInterface;
use Drupal\Core\Config\ConfigBase;
use Drupal\Core\Entity\EntityInterface;
use Drupal\Core\Session\AccountInterface;
......@@ -360,6 +361,19 @@ public function cacheUntilEntityChanges(EntityInterface $entity) {
return $this;
}
/**
* Convenience method, adds the configuration object's cache tag.
*
* @param \Drupal\Core\Config\ConfigBase $configuration
* The configuration object whose cache tag to set on the access result.
*
* @return $this
*/
public function cacheUntilConfigurationChanges(ConfigBase $configuration) {
$this->addCacheTags($configuration->getCacheTags());
return $this;
}
/**
* {@inheritdoc}
*/
......
......@@ -81,14 +81,16 @@ public static function validateTags(array $tags) {
* A prefix string.
* @param array $suffixes
* An array of suffixes. Will be cast to strings.
* @param string $glue
* A string to be used as glue for concatenation. Defaults to a colon.
*
* @return string[]
* An array of cache tags.
*/
public static function buildTags($prefix, array $suffixes) {
public static function buildTags($prefix, array $suffixes, $glue = ':') {
$tags = [];
foreach ($suffixes as $suffix) {
$tags[] = $prefix . ':' . $suffix;
$tags[] = $prefix . $glue . $suffix;
}
return $tags;
}
......
......@@ -8,6 +8,7 @@
namespace Drupal\Core\Config;
use Drupal\Component\Utility\NestedArray;
use Drupal\Core\Cache\Cache;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
/**
......@@ -223,6 +224,9 @@ public function save() {
}
$this->storage->write($this->name, $this->data);
if (!$this->isNew) {
Cache::invalidateTags($this->getCacheTags());
}
$this->isNew = FALSE;
$this->eventDispatcher->dispatch(ConfigEvents::SAVE, new ConfigCrudEvent($this));
$this->originalData = $this->data;
......@@ -238,6 +242,7 @@ public function save() {
public function delete() {
$this->data = array();
$this->storage->delete($this->name);
Cache::invalidateTags($this->getCacheTags());
$this->isNew = TRUE;
$this->resetOverriddenData();
$this->eventDispatcher->dispatch(ConfigEvents::DELETE, new ConfigCrudEvent($this));
......
......@@ -262,4 +262,15 @@ public function merge(array $data_to_merge) {
$this->setData(NestedArray::mergeDeepArray(array($this->data, $data_to_merge), TRUE));
return $this;
}
/**
* The unique cache tag associated with this configuration object.
*
* @return string[]
* An array of cache tags.
*/
public function getCacheTags() {
return ['config:' . $this->name];
}
}
......@@ -8,6 +8,7 @@
namespace Drupal\Core\Config;
use Drupal\Component\Utility\NestedArray;
use Drupal\Core\Cache\Cache;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
......@@ -218,6 +219,7 @@ public function reset($name = NULL) {
* {@inheritdoc}
*/
public function rename($old_name, $new_name) {
Cache::invalidateTags($this->get($old_name)->getCacheTags());
$this->storage->rename($old_name, $new_name);
// Clear out the static cache of any references to the old name.
......
......@@ -14,6 +14,7 @@
use Drupal\Core\Entity\Entity;
use Drupal\Core\Config\ConfigDuplicateUUIDException;
use Drupal\Core\Entity\EntityStorageInterface;
use Drupal\Core\Entity\EntityTypeInterface;
use Drupal\Core\Entity\EntityWithPluginCollectionInterface;
use Drupal\Core\Language\LanguageInterface;
use Drupal\Core\Plugin\PluginDependencyTrait;
......@@ -371,6 +372,15 @@ public function link($text = NULL, $rel = 'edit-form', array $options = []) {
return parent::link($text, $rel, $options);
}
/**
* {@inheritdoc}
*/
public function getCacheTags() {
// Use cache tags that match the underlying config object's name.
// @see \Drupal\Core\Config\ConfigBase::getCacheTags()
return ['config:' . $this->getConfigDependencyName()];
}
/**
* Overrides \Drupal\Core\Entity\DependencyTrait:addDependency().
*
......@@ -411,4 +421,24 @@ public function getConfigDependencyName() {
public function onDependencyRemoval(array $dependencies) {
}
/**
* {@inheritdoc}
*
* Override to never invalidate the entity's cache tag; the config system
* already invalidates it.
*/
protected function invalidateTagsOnSave($update) {
Cache::invalidateTags($this->getEntityType()->getListCacheTags());
}
/**
* {@inheritdoc}
*
* Override to never invalidate the individual entities' cache tags; the
* config system already invalidates them.
*/
protected static function invalidateTagsOnDelete(EntityTypeInterface $entity_type, array $entities) {
Cache::invalidateTags($entity_type->getListCacheTags());
}
}
......@@ -64,6 +64,12 @@ class ConfigEntityType extends EntityType {
* {@inheritdoc}
*/
public function __construct($definition) {
// Ensure a default list cache tag is set; do this before calling the parent
// constructor, because we want "Configuration System style" cache tags.
if (empty($this->list_cache_tags)) {
$this->list_cache_tags = ['config:' . $definition['id'] . '_list'];
}
parent::__construct($definition);
// Always add a default 'uuid' key.
$this->entity_keys['uuid'] = 'uuid';
......
......@@ -66,16 +66,18 @@ abstract class StorableConfigBase extends ConfigBase {
/**
* Saves the configuration object.
*
* @return \Drupal\Core\Config\Config
* The configuration object.
* Must invalidate the cache tags associated with the configuration object.
*
* @return $this
*/
abstract public function save();
/**
* Deletes the configuration object.
*
* @return \Drupal\Core\Config\Config
* The configuration object.
* Must invalidate the cache tags associated with the configuration object.
*
* @return $this
*/
abstract public function delete();
......
......@@ -407,7 +407,7 @@ public static function preDelete(EntityStorageInterface $storage, array $entitie
* {@inheritdoc}
*/
public static function postDelete(EntityStorageInterface $storage, array $entities) {
self::invalidateTagsOnDelete($storage->getEntityType(), $entities);
static::invalidateTagsOnDelete($storage->getEntityType(), $entities);
}
/**
......
......@@ -399,7 +399,7 @@ public function getTypedData();
/**
* The unique cache tag associated with this entity.
*
* @return array
* @return string[]
* An array of cache tags.
*/
public function getCacheTags();
......
......@@ -125,7 +125,7 @@ public function getCurrentRouteMenuTreeParameters($menu_name) {
// expanded.
->addExpandedParents($this->treeStorage->getExpanded($menu_name, $active_trail));
$this->cache->set($cid, $parameters, CacheBackendInterface::CACHE_PERMANENT, array('menu:' . $menu_name));
$this->cache->set($cid, $parameters, CacheBackendInterface::CACHE_PERMANENT, array('config:system.menu.' . $menu_name));
}
$this->cachedCurrentRouteParameters[$menu_name] = $parameters;
}
......@@ -255,7 +255,7 @@ public function build(array $tree, $level = 0) {
$build['#theme'] = 'menu__' . strtr($menu_name, '-', '_');
$build['#items'] = $items;
// Set cache tag.
$build['#cache']['tags'][] = 'menu:' . $menu_name;
$build['#cache']['tags'][] = 'config:system.menu.' . $menu_name;
return $build;
}
else {
......
......@@ -12,6 +12,7 @@
use Drupal\Component\Utility\UrlHelper;
use Drupal\Core\Cache\Cache;
use Drupal\Core\Cache\CacheBackendInterface;
use Drupal\Core\Cache\CacheTagsInvalidatorInterface;
use Drupal\Core\Database\Connection;
use Drupal\Core\Database\Database;
use Drupal\Core\Database\Query\SelectInterface;
......@@ -41,6 +42,13 @@ class MenuTreeStorage implements MenuTreeStorageInterface {
*/
protected $menuCacheBackend;
/**
* The cache tags invalidator.
*
* @var \Drupal\Core\Cache\CacheTagsInvalidatorInterface
*/
protected $cacheTagsInvalidator;
/**
* The database table name.
*
......@@ -109,14 +117,17 @@ class MenuTreeStorage implements MenuTreeStorageInterface {
* A Database connection to use for reading and writing configuration data.
* @param \Drupal\Core\Cache\CacheBackendInterface $menu_cache_backend
* Cache backend instance for the extracted tree data.
* @param \Drupal\Core\Cache\CacheTagsInvalidatorInterface $cache_tags_invalidator
* The cache tags invalidator.
* @param string $table
* A database table name to store configuration data in.
* @param array $options
* (optional) Any additional database connection options to use in queries.
*/
public function __construct(Connection $connection, CacheBackendInterface $menu_cache_backend, $table, array $options = array()) {
public function __construct(Connection $connection, CacheBackendInterface $menu_cache_backend, CacheTagsInvalidatorInterface $cache_tags_invalidator, $table, array $options = array()) {
$this->connection = $connection;
$this->menuCacheBackend = $menu_cache_backend;
$this->cacheTagsInvalidator = $cache_tags_invalidator;
$this->table = $table;
$this->options = $options;
}
......@@ -180,8 +191,8 @@ public function rebuild(array $definitions) {
$this->resetDefinitions();
$affected_menus = $this->getMenuNames() + $before_menus;
// Invalidate any cache tagged with any menu name.
$cache_tags = Cache::buildTags('menu', $affected_menus);
Cache::invalidateTags($cache_tags);
$cache_tags = Cache::buildTags('config:system.menu', $affected_menus, '.');
$this->cacheTagsInvalidator->invalidateTags($cache_tags);
$this->resetDefinitions();
// Every item in the cache bin should have one of the menu cache tags but it
// is not guaranteed, so invalidate everything in the bin.
......@@ -241,8 +252,8 @@ protected function safeExecuteSelect(SelectInterface $query) {
public function save(array $link) {
$affected_menus = $this->doSave($link);
$this->resetDefinitions();
$cache_tags = Cache::buildTags('menu', $affected_menus);
Cache::invalidateTags($cache_tags);
$cache_tags = Cache::buildTags('config:system.menu', $affected_menus, '.');
$this->cacheTagsInvalidator->invalidateTags($cache_tags);
return $affected_menus;
}
......@@ -394,7 +405,7 @@ public function delete($id) {
$this->updateParentalStatus($item);
// Many children may have moved.
$this->resetDefinitions();
Cache::invalidateTags(array('menu:' . $item['menu_name']));
$this->cacheTagsInvalidator->invalidateTags(['config:system.menu.' . $item['menu_name']]);
}
}
......@@ -823,7 +834,7 @@ public function loadTreeData($menu_name, MenuTreeParameters $parameters) {
$data['tree'] = $this->doBuildTreeData($links, $parameters->activeTrail, $parameters->minDepth);
$data['definitions'] = array();
$data['route_names'] = $this->collectRoutesAndDefinitions($data['tree'], $data['definitions']);
$this->menuCacheBackend->set($tree_cid, $data, Cache::PERMANENT, array('menu:' . $menu_name));
$this->menuCacheBackend->set($tree_cid, $data, Cache::PERMANENT, ['config:system.menu.' . $menu_name]);
// The definitions were already added to $this->definitions in
// $this->doBuildTreeData()
unset($data['definitions']);
......
......@@ -143,6 +143,13 @@ public function saveOverride($id, array $definition) {
return array_keys($definition);
}
/**
* {@inheritdoc}
*/
public function getCacheTags() {
return $this->getConfig()->getCacheTags();
}
/**
* Encodes the ID by replacing dots with double underscores.
*
......
......@@ -84,4 +84,12 @@ public function loadMultipleOverrides(array $ids);
*/
public function saveOverride($id, array $definition);
/**
* The unique cache tag associated with this menu link override.
*
* @return string[]
* An array of cache tags.
*/
public function getCacheTags();
}
......@@ -20,29 +20,11 @@ class Page extends RenderElement {
* {@inheritdoc}
*/
public function getInfo() {
$class = get_class($this);
return array(
'#show_messages' => TRUE,
'#pre_render' => array(
array($class, 'preRenderPage'),
),
'#theme' => 'page',
'#title' => '',
);
}
/**
* #pre_render callback for the page element type.
*
* @param array $element
* A structured array containing the page element type build properties.
*
* @return array
*/
public static function preRenderPage($element) {
$element['#cache']['tags'][] = 'theme:' . \Drupal::theme()->getActiveTheme()->getName();
$element['#cache']['tags'][] = 'theme_global_settings';
return $element;
}
}
......@@ -42,8 +42,8 @@ public function __construct(ThemeHandlerInterface $theme_handler) {
* The access result.
*/
public function access($theme) {
// Cacheable until the theme is modified.
return AccessResult::allowedIf($this->checkAccess($theme))->addCacheTags(array('theme:' . $theme));
// Cacheable until the theme settings are modified.
return AccessResult::allowedIf($this->checkAccess($theme))->addCacheTags(['config:' . $theme . '.settings']);
}
/**
......
......@@ -46,4 +46,12 @@ public function __construct($theme) {
public function getTheme() {
return $this->theme;
}
/**
* {@inheritdoc}
*/
public function getCacheTags() {
return ['rendered'];
}
}
......@@ -237,19 +237,6 @@ public function postSave(EntityStorageInterface $storage, $update = TRUE) {
}
}
/**
* {@inheritdoc}
*
* Block configuration entities are a special case: one block entity stores
* the placement of one block in one theme. Changing these entities may affect
* any page that is rendered in a certain theme, even if the block doesn't
* appear there currently. Hence a block configuration entity must also return
* the associated theme's cache tag.
*/
public function getCacheTags() {
return Cache::mergeTags(parent::getCacheTags(), ['theme:' . $this->theme]);
}
/**
* {@inheritdoc}
*/
......
......@@ -12,6 +12,7 @@
use Drupal\block\Event\BlockEvents;
use Drupal\Core\Block\MainContentBlockPluginInterface;
use Drupal\Core\Display\PageVariantInterface;
use Drupal\Core\Entity\EntityTypeInterface;
use Drupal\Core\Entity\EntityViewBuilderInterface;
use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
use Drupal\Core\Display\VariantBase;
......@@ -43,11 +44,11 @@ class BlockPageVariant extends VariantBase implements PageVariantInterface, Cont
protected $blockViewBuilder;
/**
* The current theme.
* The Block entity type list cache tags.
*
* @var string
* @var string[]
*/
protected $theme;
protected $blockListCacheTags;
/**
* The render array representing the main page content.
......@@ -71,12 +72,15 @@ class BlockPageVariant extends VariantBase implements PageVariantInterface, Cont
* The block view builder.
* @param \Symfony\Component\EventDispatcher\EventDispatcherInterface $dispatcher
* The event dispatcher.
* @param string[] $block_list_cache_tags
* The Block entity type list cache tags.
*/
public function __construct(array $configuration, $plugin_id, $plugin_definition, BlockRepositoryInterface $block_repository, EntityViewBuilderInterface $block_view_builder, EventDispatcherInterface $dispatcher) {
public function __construct(array $configuration, $plugin_id, $plugin_definition, BlockRepositoryInterface $block_repository, EntityViewBuilderInterface $block_view_builder, EventDispatcherInterface $dispatcher, array $block_list_cache_tags) {
parent::__construct($configuration, $plugin_id, $plugin_definition);
$this->blockRepository = $block_repository;
$this->blockViewBuilder = $block_view_builder;
$this->dispatcher = $dispatcher;
$this->blockListCacheTags = $block_list_cache_tags;
}
/**
......@@ -89,7 +93,8 @@ public static function create(ContainerInterface $container, array $configuratio
$plugin_definition,
$container->get('block.repository'),
$container->get('entity.manager')->getViewBuilder('block'),
$container->get('event_dispatcher')
$container->get('event_dispatcher'),
$container->get('entity.manager')->getDefinition('block')->getListCacheTags()
);
}
......@@ -108,7 +113,11 @@ public function build() {
// Track whether a block that shows the main content is displayed or not.
$main_content_block_displayed = FALSE;
$build = array();
$build = [
'#cache' => [
'tags' => $this->blockListCacheTags,
],
];
$contexts = $this->getActiveBlockContexts();
// Load all region content assigned via blocks.
foreach ($this->blockRepository->getVisibleBlocksPerRegion($contexts) as $region => $blocks) {
......
......@@ -327,10 +327,9 @@ public function testBlockCacheTags() {
$cid = implode(':', $cid_parts);
$cache_entry = \Drupal::cache('render')->get($cid);
$expected_cache_tags = array(
'theme:classy',
'theme_global_settings',
'config:block_list',
'block_view',
'block:powered',
'config:block.block.powered',
'block_plugin:system_powered_by_block',
'rendered',
);
......@@ -339,8 +338,7 @@ public function testBlockCacheTags() {
$cache_entry = \Drupal::cache('render')->get('entity_view:block:powered:en:classy');
$expected_cache_tags = array(
'block_view',
'block:powered',
'theme:classy',
'config:block.block.powered',
'block_plugin:system_powered_by_block',
'rendered',
);
......@@ -369,11 +367,10 @@ public function testBlockCacheTags() {
$cid = implode(':', $cid_parts);
$cache_entry = \Drupal::cache('render')->get($cid);
$expected_cache_tags = array(
'theme:classy',
'theme_global_settings',
'config:block_list',
'block_view',
'block:powered-2',
'block:powered',
'config:block.block.powered',
'config:block.block.powered-2',
'block_plugin:system_powered_by_block',
'rendered',
);
......@@ -381,8 +378,7 @@ public function testBlockCacheTags() {
$this->assertEqual($cache_entry->tags, $expected_cache_tags);
$expected_cache_tags = array(
'block_view',
'block:powered',
'theme:classy',
'config:block.block.powered',
'block_plugin:system_powered_by_block',
'rendered',
);
......@@ -391,8 +387,7 @@ public function testBlockCacheTags() {
$this->assertIdentical($cache_entry->tags, $expected_cache_tags);
$expected_cache_tags = array(
'block_view',
'block:powered-2',
'theme:classy',
'config:block.block.powered-2',
'block_plugin:system_powered_by_block',
'rendered',
);
......
......@@ -215,7 +215,7 @@ public function testBlockViewBuilderAlter() {
$request->setMethod('GET');
$default_keys = array('entity_view', 'block', 'test_block', 'en', 'cache_context.theme');
$default_tags = array('block_view', 'block:test_block', 'theme:stark', 'block_plugin:test_cache');
$default_tags = array('block_view', 'config:block.block.test_block', 'block_plugin:test_cache');
// Advanced: cached block, but an alter hook adds an additional cache key.
$this->setBlockCacheConfig(array(
......@@ -230,7 +230,7 @@ public function testBlockViewBuilderAlter() {
$this->assertIdentical(drupal_render($build), '');
$cache_entry = $this->container->get('cache.render')->get($cid);
$this->assertTrue($cache_entry, 'The block render element has been cached with the expected cache ID.');
$expected_tags = array('block_view', 'block:test_block', 'theme:stark', 'block_plugin:test_cache', 'rendered');
$expected_tags = array_merge($default_tags, ['rendered']);
sort($expected_tags);
$this->assertIdentical($cache_entry->tags, $expected_tags, 'The block render element has been cached with the expected cache tags.');
$this->container->get('cache.render')->delete($cid);
......@@ -246,7 +246,7 @@ public function testBlockViewBuilderAlter() {
$this->assertIdentical(drupal_render($build), '');
$cache_entry = $this->container->get('cache.render')->get($cid);
$this->assertTrue($cache_entry, 'The block render element has been cached with the expected cache ID.');
$expected_tags = array('block_view', 'block:test_block', 'theme:stark', 'block_plugin:test_cache', $alter_add_tag, 'rendered');
$expected_tags = array_merge($default_tags, [$alter_add_tag, 'rendered']);
sort($expected_tags);
$this->assertIdentical($cache_entry->tags, $expected_tags, 'The block render element has been cached with the expected cache tags.');
$this->container->get('cache.render')->delete($cid);
......
......@@ -61,9 +61,8 @@ public function setUpDisplayVariant($configuration = array(), $definition = arra
$this->dispatcher->expects($this->any())
->method('dispatch')
->willReturnArgument(1);
$this->contextHandler = $this->getMock('Drupal\Core\Plugin\Context\ContextHandlerInterface');
return $this->getMockBuilder('Drupal\block\Plugin\DisplayVariant\BlockPageVariant')
->setConstructorArgs(array($configuration, 'test', $definition, $this->blockRepository, $this->blockViewBuilder, $this->dispatcher, $this->contextHandler))
->setConstructorArgs(array($configuration, 'test', $definition, $this->blockRepository, $this->blockViewBuilder, $this->dispatcher, ['config:block_list']))
->setMethods(array('getRegionNames'))
->getMock();
}
......@@ -89,6 +88,11 @@ public function providerBuild() {
$test_cases = [];
$test_cases[] = [$blocks_config, 4,
[
'#cache' => [
'tags' => [
'config:block_list',
],
],
'top' => [
'block1' => [],
'#sorted' => TRUE,
......@@ -108,6 +112,11 @@ public function providerBuild() {
unset($blocks_config['block4']);
$test_cases[] = [$blocks_config, 3,
[
'#cache' => [
'tags' => [
'config:block_list',
],
],
'top' => [
'block1' => [],
'#sorted' => TRUE,
......@@ -170,7 +179,16 @@ public function testBuildWithoutMainContent() {
->method('getVisibleBlocksPerRegion')
->willReturn([]);
$expected = ['content' => ['system_main' => []]];
$expected = [
'#cache' => [
'tags' => [
'config:block_list',
],
],
'content' => [
'system_main' => [],
],
];
$this->assertSame($expected, $display_variant->build());
}
......
......@@ -55,7 +55,7 @@ protected function createEntity() {
* Each comment must have a comment body, which always has a text format.
*/
protected function getAdditionalCacheTagsForEntity(EntityInterface $entity) {
return array('filter_format:plain_text');
return ['config:filter.format.plain_text'];
}
}
......@@ -86,7 +86,7 @@ protected function createEntity() {
protected function getAdditionalCacheTagsForEntity(EntityInterface $entity) {
/** @var \Drupal\comment\CommentInterface $entity */
return array(
'filter_format:plain_text',
'config:filter.format.plain_text',
'user:' . $entity->getOwnerId(),
'user_view',
);
......
......@@ -107,7 +107,7 @@ public function testCacheTags() {
'comment_list',
'comment_view',
'comment:' . $comment->id(),
'filter_format:plain_text',
'config:filter.format.plain_text',
'user_view',
'user:2',
);
......
......@@ -93,7 +93,9 @@ public function access(UserInterface $user, AccountInterface $account) {
}
// If the requested user did not save a preference yet, deny access if the
// configured default is disabled.
else if (!$this->configFactory->get('contact.settings')->get('user_default_enabled')) {
$contact_settings = $this->configFactory->get('contact.settings');
$access->cacheUntilConfigurationChanges($contact_settings);
if (!isset($account_data) && !$contact_settings->get('user_default_enabled')) {
return $access;
}
......
......@@ -247,7 +247,7 @@ function testProcessedTextElement() {
$this->assertEqual($expected_assets, $build['#attached'], 'Expected assets present');
$expected_cache_tags = array(
// The cache tag set by the processed_text element itself.
'filter_format:element_test',
'config:filter.format.element_test',