diff --git a/core/lib/Drupal/Core/Cache/Context/AccountPermissionsCacheContext.php b/core/lib/Drupal/Core/Cache/Context/AccountPermissionsCacheContext.php index 01e6ce8..f6a412a 100644 --- a/core/lib/Drupal/Core/Cache/Context/AccountPermissionsCacheContext.php +++ b/core/lib/Drupal/Core/Cache/Context/AccountPermissionsCacheContext.php @@ -7,6 +7,7 @@ namespace Drupal\Core\Cache\Context; +use Drupal\Core\Cache\CacheableMetadata; use Drupal\Core\Session\AccountInterface; use Drupal\Core\Session\PermissionsHashGeneratorInterface; @@ -51,4 +52,17 @@ public function getContext() { return 'ph.' . $this->permissionsHashGenerator->generate($this->user); } + /** + * {@inheritdoc} + */ + public function getCacheableMetadata() { + $cacheable_metadata = new CacheableMetadata(); + $tags = []; + foreach ($this->user->getRoles() as $rid) { + $tags[] = "config:user.role.$rid"; + } + + return $cacheable_metadata->setCacheTags($tags); + } + } diff --git a/core/lib/Drupal/Core/Cache/Context/CacheContextInterface.php b/core/lib/Drupal/Core/Cache/Context/CacheContextInterface.php index ada3174..0365661 100644 --- a/core/lib/Drupal/Core/Cache/Context/CacheContextInterface.php +++ b/core/lib/Drupal/Core/Cache/Context/CacheContextInterface.php @@ -31,4 +31,15 @@ public static function getLabel(); */ public function getContext(); + /** + * Gets the cacheable metadata for the context based on the parameter value. + * + * If the cache context is being optimized away, cacheable metadata provided + * by this method will be bubbled up. + * + * @return \Drupal\Core\Cache\CacheableMetadata|NULL + * A cacheable metadata object or NULL. + */ + public function getCacheableMetadata(); + } diff --git a/core/lib/Drupal/Core/Cache/Context/CacheContextKeys.php b/core/lib/Drupal/Core/Cache/Context/CacheContextKeys.php new file mode 100644 index 0000000..ed3dd1c --- /dev/null +++ b/core/lib/Drupal/Core/Cache/Context/CacheContextKeys.php @@ -0,0 +1,62 @@ +keys = $keys; + $this->cacheableMetadata = $cacheable_metadata; + } + + /** + * Gets the cache context keys. + * + * @return string[] + */ + public function getKeys() { + return $this->keys; + } + + /** + * Gets the cacheable metadata associated for the optimized cache contexts. + * + * @return \Drupal\Core\Cache\CacheableMetadata + */ + public function getCacheableMetadata() { + return $this->cacheableMetadata; + } + +} diff --git a/core/lib/Drupal/Core/Cache/Context/CacheContextsManager.php b/core/lib/Drupal/Core/Cache/Context/CacheContextsManager.php index 27b012a..dc05e29 100644 --- a/core/lib/Drupal/Core/Cache/Context/CacheContextsManager.php +++ b/core/lib/Drupal/Core/Cache/Context/CacheContextsManager.php @@ -8,6 +8,7 @@ namespace Drupal\Core\Cache\Context; use Drupal\Component\Utility\SafeMarkup; +use Drupal\Core\Cache\CacheableMetadata; use Symfony\Component\DependencyInjection\ContainerInterface; /** @@ -99,23 +100,38 @@ public function getLabels($include_calculated_cache_contexts = FALSE) { * @param string[] $context_tokens * An array of cache context tokens. * - * @return string[] - * The array of corresponding cache keys. + * @return \Drupal\Core\Cache\Context\CacheContextKeys * * @throws \InvalidArgumentException */ public function convertTokensToKeys(array $context_tokens) { - $context_tokens = $this->optimizeTokens($context_tokens); - sort($context_tokens); + $cacheable_metadata = new CacheableMetadata(); + $optimized_tokens = $this->optimizeTokens($context_tokens); + // Iterate through cache contexts that has been optimized away and get their + // cacheable metadata. + foreach (static::parseTokens(array_diff($context_tokens, $optimized_tokens)) as $context_token) { + list($context_id, $parameter) = $context_token; + if (!in_array($context_id, $this->contexts)) { + throw new \InvalidArgumentException(SafeMarkup::format('"@context" is not a valid cache context ID.', ['@context' => $context_id])); + } + $context = $this->getService($context_id); + // To make it possible to return NULL instead of CacheableMetadata. + if ($metadata = $context->getCacheableMetadata($parameter)) { + $cacheable_metadata = $cacheable_metadata->merge($metadata); + } + } + + sort($optimized_tokens); $keys = []; - foreach (static::parseTokens($context_tokens) as $context) { + foreach (static::parseTokens($optimized_tokens) as $context) { list($context_id, $parameter) = $context; if (!in_array($context_id, $this->contexts)) { throw new \InvalidArgumentException(SafeMarkup::format('"@context" is not a valid cache context ID.', ['@context' => $context_id])); } $keys[] = $this->getService($context_id)->getContext($parameter); } - return $keys; + + return new CacheContextKeys($keys, $cacheable_metadata); } /** @@ -129,6 +145,9 @@ public function convertTokensToKeys(array $context_tokens) { * possible of a set of cache context tokens, that still captures the entire * universe of variations. * + * If cache context is being optimized away, it is able to set cacheable + * metadata for itself which will be bubbled up. + * * E.g. when caching per user ('user'), also caching per role ('user.roles') * is meaningless because "per role" is implied by "per user". * diff --git a/core/lib/Drupal/Core/Cache/Context/CalculatedCacheContextInterface.php b/core/lib/Drupal/Core/Cache/Context/CalculatedCacheContextInterface.php index bb2f291..fcf3714 100644 --- a/core/lib/Drupal/Core/Cache/Context/CalculatedCacheContextInterface.php +++ b/core/lib/Drupal/Core/Cache/Context/CalculatedCacheContextInterface.php @@ -37,4 +37,18 @@ public static function getLabel(); */ public function getContext($parameter = NULL); + /** + * Gets the cacheable metadata for the context based on the parameter value. + * + * If the cache context is being optimized away, cacheable metadata provided + * by this method will be bubbled up. + * + * @param mixed $parameter + * The parameter to get context values for. + + * @return \Drupal\Core\Cache\CacheableMetadata|NULL + * A cacheable metadata object or NULL. + */ + public function getCacheableMetadata($parameter = NULL); + } diff --git a/core/lib/Drupal/Core/Cache/Context/CookiesCacheContext.php b/core/lib/Drupal/Core/Cache/Context/CookiesCacheContext.php index 534d10c..961209d 100644 --- a/core/lib/Drupal/Core/Cache/Context/CookiesCacheContext.php +++ b/core/lib/Drupal/Core/Cache/Context/CookiesCacheContext.php @@ -7,6 +7,8 @@ namespace Drupal\Core\Cache\Context; +use Drupal\Core\Cache\CacheableMetadata; + /** * Defines the CookiesCacheContext service, for "per cookie" caching. * @@ -35,4 +37,11 @@ public function getContext($cookie = NULL) { } } + /** + * {@inheritdoc} + */ + public function getCacheableMetadata() { + return NULL; + } + } diff --git a/core/lib/Drupal/Core/Cache/Context/HeadersCacheContext.php b/core/lib/Drupal/Core/Cache/Context/HeadersCacheContext.php index 8286b36..7cb14f2 100644 --- a/core/lib/Drupal/Core/Cache/Context/HeadersCacheContext.php +++ b/core/lib/Drupal/Core/Cache/Context/HeadersCacheContext.php @@ -35,4 +35,11 @@ public function getContext($header = NULL) { } } + /** + * {@inheritdoc} + */ + public function getCacheableMetadata($header = NULL) { + return NULL; + } + } diff --git a/core/lib/Drupal/Core/Cache/Context/IpCacheContext.php b/core/lib/Drupal/Core/Cache/Context/IpCacheContext.php index 9cc6713..313806d 100644 --- a/core/lib/Drupal/Core/Cache/Context/IpCacheContext.php +++ b/core/lib/Drupal/Core/Cache/Context/IpCacheContext.php @@ -28,4 +28,11 @@ public function getContext() { return $this->requestStack->getCurrentRequest()->getClientIp(); } + /** + * {@inheritdoc} + */ + public function getCacheableMetadata() { + return NULL; + } + } diff --git a/core/lib/Drupal/Core/Cache/Context/IsSuperUserCacheContext.php b/core/lib/Drupal/Core/Cache/Context/IsSuperUserCacheContext.php index 1f30cf1..051b443 100644 --- a/core/lib/Drupal/Core/Cache/Context/IsSuperUserCacheContext.php +++ b/core/lib/Drupal/Core/Cache/Context/IsSuperUserCacheContext.php @@ -28,4 +28,11 @@ public function getContext() { return ((int) $this->user->id()) === 1 ? '1' : '0'; } + /** + * {@inheritdoc} + */ + public function getCacheableMetadata() { + return NULL; + } + } diff --git a/core/lib/Drupal/Core/Cache/Context/LanguagesCacheContext.php b/core/lib/Drupal/Core/Cache/Context/LanguagesCacheContext.php index 2bf9a2f..cdc3bad 100644 --- a/core/lib/Drupal/Core/Cache/Context/LanguagesCacheContext.php +++ b/core/lib/Drupal/Core/Cache/Context/LanguagesCacheContext.php @@ -74,4 +74,11 @@ public function getContext($type = NULL) { } } + /** + * {@inheritdoc} + */ + public function getCacheableMetadata($type = NULL) { + return NULL; + } + } diff --git a/core/lib/Drupal/Core/Cache/Context/MenuActiveTrailsCacheContext.php b/core/lib/Drupal/Core/Cache/Context/MenuActiveTrailsCacheContext.php index 40cc077..94dfe1f 100644 --- a/core/lib/Drupal/Core/Cache/Context/MenuActiveTrailsCacheContext.php +++ b/core/lib/Drupal/Core/Cache/Context/MenuActiveTrailsCacheContext.php @@ -33,4 +33,11 @@ public function getContext($menu_name = NULL) { return 'menu_trail.' . $menu_name . '|' . implode('|', $active_trail); } + /** + * {@inheritdoc} + */ + public function getCacheableMetadata($menu_name = NULL) { + return NULL; + } + } diff --git a/core/lib/Drupal/Core/Cache/Context/PagersCacheContext.php b/core/lib/Drupal/Core/Cache/Context/PagersCacheContext.php index f53d464..4a537dd 100644 --- a/core/lib/Drupal/Core/Cache/Context/PagersCacheContext.php +++ b/core/lib/Drupal/Core/Cache/Context/PagersCacheContext.php @@ -38,4 +38,11 @@ public function getContext($pager_id = NULL) { return 'pager.' . $pager_id . '.' . pager_find_page($pager_id); } + /** + * {@inheritdoc} + */ + public function getCacheableMetadata($pager_id = NULL) { + return NULL; + } + } diff --git a/core/lib/Drupal/Core/Cache/Context/QueryArgsCacheContext.php b/core/lib/Drupal/Core/Cache/Context/QueryArgsCacheContext.php index faceb07..f9106bd 100644 --- a/core/lib/Drupal/Core/Cache/Context/QueryArgsCacheContext.php +++ b/core/lib/Drupal/Core/Cache/Context/QueryArgsCacheContext.php @@ -35,4 +35,11 @@ public function getContext($query_arg = NULL) { } } + /** + * {@inheritdoc} + */ + public function getCacheableMetadata($query_arg = NULL) { + return NULL; + } + } diff --git a/core/lib/Drupal/Core/Cache/Context/RequestStackCacheContextBase.php b/core/lib/Drupal/Core/Cache/Context/RequestStackCacheContextBase.php index 2a8f22d..9909399 100644 --- a/core/lib/Drupal/Core/Cache/Context/RequestStackCacheContextBase.php +++ b/core/lib/Drupal/Core/Cache/Context/RequestStackCacheContextBase.php @@ -31,4 +31,11 @@ public function __construct(RequestStack $request_stack) { $this->requestStack = $request_stack; } + /** + * {@inheritdoc} + */ + public function getCacheableMetadata() { + return NULL; + } + } diff --git a/core/lib/Drupal/Core/Cache/Context/RouteCacheContext.php b/core/lib/Drupal/Core/Cache/Context/RouteCacheContext.php index bb4dd8d..3059a44 100644 --- a/core/lib/Drupal/Core/Cache/Context/RouteCacheContext.php +++ b/core/lib/Drupal/Core/Cache/Context/RouteCacheContext.php @@ -7,6 +7,7 @@ namespace Drupal\Core\Cache\Context; +use Drupal\Core\Cache\CacheableMetadata; use Drupal\Core\Routing\RouteMatchInterface; /** @@ -47,4 +48,11 @@ public function getContext() { return $this->routeMatch->getRouteName() . hash('sha256', serialize($this->routeMatch->getRawParameters()->all())); } + /** + * {@inheritdoc} + */ + public function getCacheableMetadata() { + return NULL; + } + } diff --git a/core/lib/Drupal/Core/Cache/Context/ThemeCacheContext.php b/core/lib/Drupal/Core/Cache/Context/ThemeCacheContext.php index 0a66878..46ff397 100644 --- a/core/lib/Drupal/Core/Cache/Context/ThemeCacheContext.php +++ b/core/lib/Drupal/Core/Cache/Context/ThemeCacheContext.php @@ -7,7 +7,6 @@ namespace Drupal\Core\Cache\Context; -use Drupal\Core\Routing\RouteMatchInterface; use Drupal\Core\Theme\ThemeManagerInterface; /** @@ -48,4 +47,11 @@ public function getContext() { return $this->themeManager->getActiveTheme()->getName() ?: 'stark'; } + /** + * {@inheritdoc} + */ + public function getCacheableMetadata() { + return NULL; + } + } diff --git a/core/lib/Drupal/Core/Cache/Context/TimeZoneCacheContext.php b/core/lib/Drupal/Core/Cache/Context/TimeZoneCacheContext.php index 9b22e0e..84321ce 100644 --- a/core/lib/Drupal/Core/Cache/Context/TimeZoneCacheContext.php +++ b/core/lib/Drupal/Core/Cache/Context/TimeZoneCacheContext.php @@ -32,4 +32,11 @@ public function getContext() { return date_default_timezone_get(); } + /** + * {@inheritdoc} + */ + public function getCacheableMetadata() { + return NULL; + } + } diff --git a/core/lib/Drupal/Core/Cache/Context/UserCacheContext.php b/core/lib/Drupal/Core/Cache/Context/UserCacheContext.php index d7b8051..5ed0c1e 100644 --- a/core/lib/Drupal/Core/Cache/Context/UserCacheContext.php +++ b/core/lib/Drupal/Core/Cache/Context/UserCacheContext.php @@ -40,4 +40,11 @@ public function getContext() { return "u." . $this->user->id(); } + /** + * {@inheritdoc} + */ + public function getCacheableMetadata() { + return NULL; + } + } diff --git a/core/lib/Drupal/Core/Cache/Context/UserRolesCacheContext.php b/core/lib/Drupal/Core/Cache/Context/UserRolesCacheContext.php index e4bb71a..eed0d01 100644 --- a/core/lib/Drupal/Core/Cache/Context/UserRolesCacheContext.php +++ b/core/lib/Drupal/Core/Cache/Context/UserRolesCacheContext.php @@ -17,7 +17,7 @@ * Calculated cache context ID: 'user.roles:%role', e.g. 'user.roles:anonymous' * (to vary by the presence/absence of a specific role). */ -class UserRolesCacheContext extends UserCacheContext implements CalculatedCacheContextInterface{ +class UserRolesCacheContext extends UserCacheContext implements CalculatedCacheContextInterface { /** * {@inheritdoc} @@ -45,4 +45,11 @@ public function getContext($role = NULL) { } } + /** + * {@inheritdoc} + */ + public function getCacheableMetadata($role = NULL) { + return NULL; + } + } diff --git a/core/lib/Drupal/Core/Render/RenderCache.php b/core/lib/Drupal/Core/Render/RenderCache.php index 1a8339b..4a5c333 100644 --- a/core/lib/Drupal/Core/Render/RenderCache.php +++ b/core/lib/Drupal/Core/Render/RenderCache.php @@ -295,7 +295,7 @@ protected function maxAgeToExpire($max_age) { * @return string * The cache ID string, or FALSE if the element may not be cached. */ - protected function createCacheID(array $elements) { + protected function createCacheID(array &$elements) { // If the maximum age is zero, then caching is effectively prohibited. if (isset($elements['#cache']['max-age']) && $elements['#cache']['max-age'] === 0) { return FALSE; @@ -305,7 +305,10 @@ protected function createCacheID(array $elements) { $cid_parts = $elements['#cache']['keys']; if (!empty($elements['#cache']['contexts'])) { $contexts = $this->cacheContextsManager->convertTokensToKeys($elements['#cache']['contexts']); - $cid_parts = array_merge($cid_parts, $contexts); + $cid_parts = array_merge($cid_parts, $contexts->getKeys()); + CacheableMetadata::createFromRenderArray($elements) + ->merge($contexts->getCacheableMetadata()) + ->applyTo($elements); } return implode(':', $cid_parts); } diff --git a/core/modules/book/src/Cache/BookNavigationCacheContext.php b/core/modules/book/src/Cache/BookNavigationCacheContext.php index 04a09e8..439e0fb 100644 --- a/core/modules/book/src/Cache/BookNavigationCacheContext.php +++ b/core/modules/book/src/Cache/BookNavigationCacheContext.php @@ -70,4 +70,11 @@ public function getContext() { return 'book.' . implode('|', $active_trail); } + /** + * {@inheritdoc} + */ + public function getCacheableMetadata() { + return NULL; + } + } diff --git a/core/modules/comment/src/Tests/CommentDefaultFormatterCacheTagsTest.php b/core/modules/comment/src/Tests/CommentDefaultFormatterCacheTagsTest.php index 8e7bf27..6d52613 100644 --- a/core/modules/comment/src/Tests/CommentDefaultFormatterCacheTagsTest.php +++ b/core/modules/comment/src/Tests/CommentDefaultFormatterCacheTagsTest.php @@ -7,6 +7,7 @@ namespace Drupal\comment\Tests; +use Drupal\Core\Cache\Cache; use Drupal\Core\Session\UserSession; use Drupal\comment\CommentInterface; use Drupal\system\Tests\Entity\EntityUnitTestBase; @@ -69,7 +70,8 @@ public function testCacheTags() { ->getViewBuilder('entity_test') ->view($commented_entity); $renderer->renderRoot($build); - $expected_cache_tags = array( + $cache_context_tags = \Drupal::service('cache_contexts_manager')->convertTokensToKeys($build['#cache']['contexts'])->getCacheableMetadata()->getCacheTags(); + $expected_cache_tags = Cache::mergeTags($cache_context_tags, [ 'entity_test_view', 'entity_test:' . $commented_entity->id(), 'comment_list', @@ -78,9 +80,9 @@ public function testCacheTags() { 'config:field.field.entity_test.entity_test.comment', 'config:field.storage.comment.comment_body', 'config:user.settings', - ); + ]); sort($expected_cache_tags); - $this->assertEqual($build['#cache']['tags'], $expected_cache_tags, 'The test entity has the expected cache tags before it has comments.'); + $this->assertEqual($build['#cache']['tags'], $expected_cache_tags); // Create a comment on that entity. Comment loading requires that the uid // also exists in the {users} table. @@ -111,7 +113,8 @@ public function testCacheTags() { ->getViewBuilder('entity_test') ->view($commented_entity); $renderer->renderRoot($build); - $expected_cache_tags = array( + $cache_context_tags = \Drupal::service('cache_contexts_manager')->convertTokensToKeys($build['#cache']['contexts'])->getCacheableMetadata()->getCacheTags(); + $expected_cache_tags = Cache::mergeTags($cache_context_tags, [ 'entity_test_view', 'entity_test:' . $commented_entity->id(), 'comment_list', @@ -125,9 +128,9 @@ public function testCacheTags() { 'config:field.field.entity_test.entity_test.comment', 'config:field.storage.comment.comment_body', 'config:user.settings', - ); + ]); sort($expected_cache_tags); - $this->assertEqual($build['#cache']['tags'], $expected_cache_tags, 'The test entity has the expected cache tags when it has comments.'); + $this->assertEqual($build['#cache']['tags'], $expected_cache_tags); } } diff --git a/core/modules/comment/src/Tests/CommentRssTest.php b/core/modules/comment/src/Tests/CommentRssTest.php index b82da48..aed6be8 100644 --- a/core/modules/comment/src/Tests/CommentRssTest.php +++ b/core/modules/comment/src/Tests/CommentRssTest.php @@ -8,6 +8,7 @@ namespace Drupal\comment\Tests; use Drupal\comment\Plugin\Field\FieldType\CommentItemInterface; +use Drupal\Core\Cache\Cache; use Drupal\Core\Entity\Entity\EntityViewDisplay; use Drupal\system\Tests\Cache\AssertPageCacheContextsAndTagsTrait; @@ -52,16 +53,22 @@ function testCommentRss() { $this->postComment($this->node, $this->randomMachineName(), $this->randomMachineName()); $this->drupalGet('rss.xml'); - $this->assertCacheTags([ - 'config:views.view.frontpage', 'node:1', 'node_list', 'node_view', 'user:3', - ]); - $this->assertCacheContexts([ + $cache_contexts = [ 'languages:language_interface', 'theme', 'user.node_grants:view', 'user.permissions', 'timezone', - ]); + ]; + $this->assertCacheContexts($cache_contexts); + + $cache_context_tags = \Drupal::service('cache_contexts_manager')->convertTokensToKeys($cache_contexts)->getCacheableMetadata()->getCacheTags(); + $this->assertCacheTags(Cache::mergeTags($cache_context_tags, [ + 'config:views.view.frontpage', + 'node:1', 'node_list', + 'node_view', + 'user:3', + ])); $raw = '' . $this->node->url('canonical', array('fragment' => 'comments', 'absolute' => TRUE)) . ''; $this->assertRaw($raw, 'Comments as part of RSS feed.'); diff --git a/core/modules/node/src/Cache/NodeAccessGrantsCacheContext.php b/core/modules/node/src/Cache/NodeAccessGrantsCacheContext.php index 912309f..7232771 100644 --- a/core/modules/node/src/Cache/NodeAccessGrantsCacheContext.php +++ b/core/modules/node/src/Cache/NodeAccessGrantsCacheContext.php @@ -7,6 +7,7 @@ namespace Drupal\node\Cache; +use Drupal\Core\Cache\CacheableMetadata; use Drupal\Core\Cache\Context\CalculatedCacheContextInterface; use Drupal\Core\Cache\Context\UserCacheContext; @@ -82,4 +83,11 @@ protected function checkNodeGrants($operation) { return $operation . '.' . implode(';', $grants_context_parts); } + /** + * {@inheritdoc} + */ + public function getCacheableMetadata($operation = NULL) { + return new CacheableMetadata(); + } + } diff --git a/core/modules/node/src/Tests/Views/FrontPageTest.php b/core/modules/node/src/Tests/Views/FrontPageTest.php index b3ab9c9..5d36780 100644 --- a/core/modules/node/src/Tests/Views/FrontPageTest.php +++ b/core/modules/node/src/Tests/Views/FrontPageTest.php @@ -251,6 +251,8 @@ protected function assertFrontPageViewCacheTags($do_assert_views_caches) { 'url.query_args.pagers:0', ]; + $cache_context_tags = \Drupal::service('cache_contexts_manager')->convertTokensToKeys($cache_contexts)->getCacheableMetadata()->getCacheTags(); + // Test before there are any nodes. $empty_node_listing_cache_tags = [ 'config:views.view.frontpage', @@ -260,12 +262,12 @@ protected function assertFrontPageViewCacheTags($do_assert_views_caches) { $view, $empty_node_listing_cache_tags, $do_assert_views_caches, - $empty_node_listing_cache_tags + Cache::mergeTags($empty_node_listing_cache_tags, $cache_context_tags) ); $this->assertPageCacheContextsAndTags( Url::fromRoute('view.frontpage.page_1'), $cache_contexts, - Cache::mergeTags($empty_node_listing_cache_tags, ['rendered', 'config:user.role.anonymous']) + Cache::mergeTags($empty_node_listing_cache_tags, $cache_context_tags, ['rendered', 'config:user.role.anonymous']) ); // Create some nodes on the frontpage view. Add more than 10 nodes in order @@ -307,12 +309,17 @@ protected function assertFrontPageViewCacheTags($do_assert_views_caches) { 'node:14', 'node:15', ]; - $first_page_output_cache_tags = Cache::mergeTags($first_page_result_cache_tags, [ - 'config:filter.format.plain_text', - 'node_view', - 'user_view', - 'user:0', - ]); + $cache_context_tags = \Drupal::service('cache_contexts_manager')->convertTokensToKeys($cache_contexts)->getCacheableMetadata()->getCacheTags(); + $first_page_output_cache_tags = Cache::mergeTags( + $first_page_result_cache_tags, + $cache_context_tags, + [ + 'config:filter.format.plain_text', + 'node_view', + 'user_view', + 'user:0', + ] + ); $view->setDisplay('page_1'); $view->setCurrentPage(0); $this->assertViewsCacheTags( diff --git a/core/modules/system/src/Tests/Cache/CacheContextOptimizationTest.php b/core/modules/system/src/Tests/Cache/CacheContextOptimizationTest.php new file mode 100644 index 0000000..9c29913 --- /dev/null +++ b/core/modules/system/src/Tests/Cache/CacheContextOptimizationTest.php @@ -0,0 +1,83 @@ +installEntitySchema('user'); + $this->installConfig(['user']); + $this->installSchema('system', ['sequences']); + } + + /** + * Ensures that 'user.permissions' cache context is able to define cache tags. + */ + public function testUserPermissionCacheContextOptimization() { + $user1 = $this->createUser(); + $this->assertEqual($user1->id(), 1); + + $authenticated_user = $this->createUser(['administer permissions']); + $role = $authenticated_user->getRoles()[1]; + + $test_element = [ + '#cache' => [ + 'keys' => ['test'], + 'contexts' => ['user', 'user.permissions'], + ], + ]; + \Drupal::service('account_switcher')->switchTo($authenticated_user); + $element = $test_element; + $element['#markup'] = 'content for authenticated users'; + $output = \Drupal::service('renderer')->renderRoot($element); + $this->assertEqual($output, 'content for authenticated users'); + + // Verify that the render caching is working so that other tests can be + // trusted. + $element = $test_element; + $element['#markup'] = 'this should not be visible'; + $output = \Drupal::service('renderer')->renderRoot($element); + $this->assertEqual($output, 'content for authenticated users'); + + // Even though the cache contexts have been optimized to only include 'user' + // cache context, the element should have been changed because + // 'user.permissions' cache context defined a cache tags for permission + // change, which should have bubbled up for the element when it was optimized + // away. + Role::load($role) + ->revokePermission('administer permissions') + ->save(); + $element = $test_element; + $element['#markup'] = 'this should be visible'; + $output = \Drupal::service('renderer')->renderRoot($element); + $this->assertEqual($output, 'this should be visible'); + } + +} diff --git a/core/modules/system/src/Tests/Entity/EntityCacheTagsTestBase.php b/core/modules/system/src/Tests/Entity/EntityCacheTagsTestBase.php index 0b365cb..a43b7d2 100644 --- a/core/modules/system/src/Tests/Entity/EntityCacheTagsTestBase.php +++ b/core/modules/system/src/Tests/Entity/EntityCacheTagsTestBase.php @@ -349,6 +349,9 @@ public function testReferencedEntity() { ->getCacheTags(); } + $context_metadata = \Drupal::service('cache_contexts_manager')->convertTokensToKeys($entity_cache_contexts)->getCacheableMetadata(); + $cache_context_tags = $context_metadata->getCacheTags(); + // Generate the cache tags for the (non) referencing entities. $referencing_entity_cache_tags = Cache::mergeTags( $this->referencingEntity->getCacheTags(), @@ -357,6 +360,7 @@ public function testReferencedEntity() { $this->entity->getCacheTags(), $this->getAdditionalCacheTagsForEntity($this->entity), $view_cache_tag, + $cache_context_tags, ['rendered'] ); $non_referencing_entity_cache_tags = Cache::mergeTags( @@ -389,7 +393,10 @@ public function testReferencedEntity() { $access_cache_contexts = $this->getAccessCacheContextsForEntity($this->entity); $redirected_cid = NULL; if (count($access_cache_contexts)) { - $redirected_cid = $this->createCacheId($cache_keys, Cache::mergeContexts($entity_cache_contexts, $this->getAdditionalCacheContextsForEntity($this->referencingEntity), $access_cache_contexts)); + $cache_contexts = Cache::mergeContexts($entity_cache_contexts, $this->getAdditionalCacheContextsForEntity($this->referencingEntity), $access_cache_contexts); + $redirected_cid = $this->createCacheId($cache_keys, $cache_contexts); + $context_metadata = \Drupal::service('cache_contexts_manager')->convertTokensToKeys($cache_contexts)->getCacheableMetadata(); + $referencing_entity_cache_tags = Cache::mergeTags($referencing_entity_cache_tags, $context_metadata->getCacheTags()); } $this->verifyRenderCache($cid, $referencing_entity_cache_tags, $redirected_cid); @@ -653,7 +660,7 @@ protected function createCacheId(array $keys, array $contexts) { $cid_parts = $keys; $contexts = \Drupal::service('cache_contexts_manager')->convertTokensToKeys($contexts); - $cid_parts = array_merge($cid_parts, $contexts); + $cid_parts = array_merge($cid_parts, $contexts->getKeys()); return implode(':', $cid_parts); } diff --git a/core/modules/system/src/Tests/Entity/EntityViewBuilderTest.php b/core/modules/system/src/Tests/Entity/EntityViewBuilderTest.php index a7aa63d..b160081 100644 --- a/core/modules/system/src/Tests/Entity/EntityViewBuilderTest.php +++ b/core/modules/system/src/Tests/Entity/EntityViewBuilderTest.php @@ -65,7 +65,7 @@ public function testEntityViewBuilderCache() { // Get a fully built entity view render array. $entity_test->save(); $build = $this->container->get('entity.manager')->getViewBuilder('entity_test')->view($entity_test, 'full'); - $cid_parts = array_merge($build['#cache']['keys'], $cache_contexts_manager->convertTokensToKeys(['languages:' . LanguageInterface::TYPE_INTERFACE, 'theme'])); + $cid_parts = array_merge($build['#cache']['keys'], $cache_contexts_manager->convertTokensToKeys(['languages:' . LanguageInterface::TYPE_INTERFACE, 'theme'])->getKeys()); $cid = implode(':', $cid_parts); $bin = $build['#cache']['bin']; @@ -117,7 +117,7 @@ public function testEntityViewBuilderCacheWithReferences() { // Get a fully built entity view render array for the referenced entity. $build = $this->container->get('entity.manager')->getViewBuilder('entity_test')->view($entity_test_reference, 'full'); - $cid_parts = array_merge($build['#cache']['keys'], $cache_contexts_manager->convertTokensToKeys(['languages:' . LanguageInterface::TYPE_INTERFACE, 'theme'])); + $cid_parts = array_merge($build['#cache']['keys'], $cache_contexts_manager->convertTokensToKeys(['languages:' . LanguageInterface::TYPE_INTERFACE, 'theme'])->getKeys()); $cid_reference = implode(':', $cid_parts); $bin_reference = $build['#cache']['bin']; @@ -136,7 +136,7 @@ public function testEntityViewBuilderCacheWithReferences() { // Get a fully built entity view render array. $build = $this->container->get('entity.manager')->getViewBuilder('entity_test')->view($entity_test, 'full'); - $cid_parts = array_merge($build['#cache']['keys'], $cache_contexts_manager->convertTokensToKeys(['languages:' . LanguageInterface::TYPE_INTERFACE, 'theme'])); + $cid_parts = array_merge($build['#cache']['keys'], $cache_contexts_manager->convertTokensToKeys(['languages:' . LanguageInterface::TYPE_INTERFACE, 'theme'])->getKeys()); $cid = implode(':', $cid_parts); $bin = $build['#cache']['bin']; diff --git a/core/modules/system/tests/modules/entity_test/src/Cache/EntityTestViewGrantsCacheContext.php b/core/modules/system/tests/modules/entity_test/src/Cache/EntityTestViewGrantsCacheContext.php index 0b4b360..f2e3bb2 100644 --- a/core/modules/system/tests/modules/entity_test/src/Cache/EntityTestViewGrantsCacheContext.php +++ b/core/modules/system/tests/modules/entity_test/src/Cache/EntityTestViewGrantsCacheContext.php @@ -34,4 +34,11 @@ public function getContext() { return '299792458'; } + /** + * {@inheritdoc} + */ + public function getCacheableMetadata() { + return NULL; + } + } diff --git a/core/modules/system/tests/modules/pager_test/src/Controller/PagerTestController.php b/core/modules/system/tests/modules/pager_test/src/Controller/PagerTestController.php index 004603f..7f03636 100644 --- a/core/modules/system/tests/modules/pager_test/src/Controller/PagerTestController.php +++ b/core/modules/system/tests/modules/pager_test/src/Controller/PagerTestController.php @@ -69,7 +69,7 @@ public function queryParameters() { * #pre_render callback for #type => pager that shows the pager cache context. */ public static function showPagerCacheContext(array $pager) { - drupal_set_message(\Drupal::service('cache_contexts_manager')->convertTokensToKeys(['url.query_args.pagers:' . $pager['#element']])[0]); + drupal_set_message(\Drupal::service('cache_contexts_manager')->convertTokensToKeys(['url.query_args.pagers:' . $pager['#element']])->getKeys()[0]); return $pager; } diff --git a/core/modules/views/src/Plugin/views/cache/CachePluginBase.php b/core/modules/views/src/Plugin/views/cache/CachePluginBase.php index 06a18e3..73ba6dc 100644 --- a/core/modules/views/src/Plugin/views/cache/CachePluginBase.php +++ b/core/modules/views/src/Plugin/views/cache/CachePluginBase.php @@ -211,7 +211,7 @@ public function generateResultsKey() { 'items_per_page' => $this->view->getItemsPerPage(), 'offset' => $this->view->getOffset(), ]; - $key_data += \Drupal::service('cache_contexts_manager')->convertTokensToKeys($this->displayHandler->getCacheMetadata()['contexts']); + $key_data += \Drupal::service('cache_contexts_manager')->convertTokensToKeys($this->displayHandler->getCacheMetadata()['contexts'])->getKeys(); $this->resultsKey = $this->view->storage->id() . ':' . $this->displayHandler->display['id'] . ':results:' . hash('sha256', serialize($key_data)); } diff --git a/core/modules/views/tests/modules/views_test_data/src/Cache/ViewsTestCacheContext.php b/core/modules/views/tests/modules/views_test_data/src/Cache/ViewsTestCacheContext.php index 25be646..88d7559 100644 --- a/core/modules/views/tests/modules/views_test_data/src/Cache/ViewsTestCacheContext.php +++ b/core/modules/views/tests/modules/views_test_data/src/Cache/ViewsTestCacheContext.php @@ -30,4 +30,11 @@ public function getContext() { return \Drupal::state()->get('views_test_cache_context', 'George'); } + /** + * {@inheritdoc} + */ + public function getCacheableMetadata() { + return NULL; + } + } diff --git a/core/tests/Drupal/Tests/Core/Cache/Context/CacheContextsManagerTest.php b/core/tests/Drupal/Tests/Core/Cache/Context/CacheContextsManagerTest.php index 99a32b3..c6c06a5 100644 --- a/core/tests/Drupal/Tests/Core/Cache/Context/CacheContextsManagerTest.php +++ b/core/tests/Drupal/Tests/Core/Cache/Context/CacheContextsManagerTest.php @@ -95,7 +95,7 @@ public function testConvertTokensToKeys() { 'baz.cnenzrgreO', 'bar', ]; - $this->assertEquals($expected, $new_keys); + $this->assertEquals($expected, $new_keys->getKeys()); } /** @@ -234,6 +234,13 @@ public function getContext() { return 'bar'; } + /** + * {@inheritdoc} + */ + public function getCacheableMetadata() { + return NULL; + } + } /** @@ -258,4 +265,11 @@ public function getContext($parameter = NULL) { return 'baz.' . str_rot13($parameter); } + /** + * {@inheritdoc} + */ + public function getCacheableMetadata($parameter = NULL) { + return NULL; + } + } diff --git a/core/tests/Drupal/Tests/Core/Render/RendererTestBase.php b/core/tests/Drupal/Tests/Core/Render/RendererTestBase.php index 7388c61..d753c5d 100644 --- a/core/tests/Drupal/Tests/Core/Render/RendererTestBase.php +++ b/core/tests/Drupal/Tests/Core/Render/RendererTestBase.php @@ -8,6 +8,8 @@ namespace Drupal\Tests\Core\Render; use Drupal\Core\Cache\Cache; +use Drupal\Core\Cache\CacheableMetadata; +use Drupal\Core\Cache\Context\CacheContextKeys; use Drupal\Core\Cache\MemoryBackend; use Drupal\Core\Language\LanguageInterface; use Drupal\Core\Render\Element; @@ -124,7 +126,7 @@ protected function setUp() { $keys[] = $context_id; } } - return $keys; + return new CacheContextKeys($keys, new CacheableMetadata()); }); $this->renderCache = new RenderCache($this->requestStack, $this->cacheFactory, $this->cacheContextsManager); $this->renderer = new Renderer($this->controllerResolver, $this->themeManager, $this->elementInfo, $this->renderCache, $this->rendererConfig);