core/core.services.yml | 10 ++ core/includes/common.inc | 184 +------------------- core/lib/Drupal/Core/Cache/Cache.php | 7 + .../lib/Drupal/Core/Cache/LanguageCacheContext.php | 54 ++++++ core/lib/Drupal/Core/Cache/ThemeCacheContext.php | 59 +++++++ core/lib/Drupal/Core/Cache/UrlCacheContext.php | 11 +- core/lib/Drupal/Core/Entity/EntityViewBuilder.php | 9 +- core/modules/block/lib/Drupal/block/BlockBase.php | 6 +- .../block/lib/Drupal/block/BlockViewBuilder.php | 11 +- .../lib/Drupal/block/Tests/BlockInterfaceTest.php | 4 +- 10 files changed, 170 insertions(+), 185 deletions(-) diff --git a/core/core.services.yml b/core/core.services.yml index 2169617..7058955 100644 --- a/core/core.services.yml +++ b/core/core.services.yml @@ -12,6 +12,16 @@ services: arguments: ['@request'] tags: - { name: cache.context} + cache_context.language: + class: Drupal\Core\Cache\LanguageCacheContext + arguments: ['@language_manager'] + tags: + - { name: cache.context} + cache_context.theme: + class: Drupal\Core\Cache\ThemeCacheContext + arguments: ['@request', '@theme.negotiator'] + tags: + - { name: cache.context} cache.backend.database: class: Drupal\Core\Cache\DatabaseBackendFactory arguments: ['@database'] diff --git a/core/includes/common.inc b/core/includes/common.inc index 73edc78..885f88c 100644 --- a/core/includes/common.inc +++ b/core/includes/common.inc @@ -132,78 +132,6 @@ const JS_THEME = 100; /** - * @defgroup block_caching Block Caching - * @{ - * Constants that define each block's caching state. - * - * Modules specify how their blocks can be cached in their hook_block_info() - * implementations. Caching can be turned off (DRUPAL_NO_CACHE), managed by the - * module declaring the block (DRUPAL_CACHE_CUSTOM), or managed by the core - * Block module. If the Block module is managing the cache, you can specify that - * the block is the same for every page and user (DRUPAL_CACHE_GLOBAL), or that - * it can change depending on the page (DRUPAL_CACHE_PER_PAGE) or by user - * (DRUPAL_CACHE_PER_ROLE or DRUPAL_CACHE_PER_USER). Page and user settings can - * be combined with a bitwise-binary or operator; for example, - * DRUPAL_CACHE_PER_ROLE | DRUPAL_CACHE_PER_PAGE means that the block can change - * depending on the user role or page it is on. - * - * The block cache is cleared when the 'content' cache tag is invalidated, - * following the same pattern as the page cache (node, comment, user, taxonomy - * added or updated...). - * - * Note that user 1 is excluded from block caching. - */ - -/** - * The block should not get cached. - * - * This setting should be used: - * - For simple blocks (notably those that do not perform any db query), where - * querying the db cache would be more expensive than directly generating the - * content. - * - For blocks that change too frequently. - */ -const DRUPAL_NO_CACHE = -1; - -/** - * The block is handling its own caching in its hook_block_view(). - * - * This setting is useful when time based expiration is needed or a site uses a - * node access which invalidates standard block cache. - */ -const DRUPAL_CACHE_CUSTOM = -2; - -/** - * The block or element can change depending on the user's roles. - * - * This is the default setting for blocks, used when the block does not specify - * anything. - */ -const DRUPAL_CACHE_PER_ROLE = 0x0001; - -/** - * The block or element can change depending on the user. - * - * This setting can be resource-consuming for sites with large number of users, - * and thus should only be used when DRUPAL_CACHE_PER_ROLE is not sufficient. - */ -const DRUPAL_CACHE_PER_USER = 0x0002; - -/** - * The block or element can change depending on the page being viewed. - */ -const DRUPAL_CACHE_PER_PAGE = 0x0004; - -/** - * The block or element is the same for every user and page that it is visible. - */ -const DRUPAL_CACHE_GLOBAL = 0x0008; - -/** - * @} End of "defgroup block_caching". - */ - -/** * The delimiter used to split plural strings. * * This is the ETX (End of text) character and is used as a minimal means to @@ -3724,16 +3652,12 @@ function drupal_render_page($page) { * associative array with one or several of the following keys: * - 'keys': An array of one or more keys that identify the element. If * 'keys' is set, the cache ID is created automatically from these keys. - * See drupal_render_cid_create(). - * - 'granularity' (optional): Define the cache granularity using binary - * combinations of the cache granularity constants, e.g. - * DRUPAL_CACHE_PER_USER to cache for each user separately or - * DRUPAL_CACHE_PER_PAGE | DRUPAL_CACHE_PER_ROLE to cache separately for - * each page and role. If not specified the element is cached globally for - * each theme and language. + * Cache keys may either be static (just strings) or tokens (placeholders + * that are converted to static keys by the @cache_contexts service, + * depending on the request). See drupal_render_cid_create(). * - 'cid': Specify the cache ID directly. Either 'keys' or 'cid' is - * required. If 'cid' is set, 'keys' and 'granularity' are ignored. Use - * only if you have special requirements. + * required. If 'cid' is set, 'keys' is ignored. Use only if you have + * special requirements. * - 'expire': Set to one of the cache lifetime constants. * - 'bin': Specify a cache bin to cache the element in. Default is 'cache'. * - If this element has #type defined and the default attributes for this @@ -4507,99 +4431,10 @@ function drupal_cache_tags_page_get(Response $response) { } /** - * Prepares an element for caching based on a query. - * - * This smart caching strategy saves Drupal from querying and rendering to HTML - * when the underlying query is unchanged. - * - * Expensive queries should use the query builder to create the query and then - * call this function. Executing the query and formatting results should happen - * in a #pre_render callback. - * - * @param $query - * A select query object as returned by db_select(). - * @param $function - * The name of the function doing this caching. A _pre_render suffix will be - * added to this string and is also part of the cache key in - * drupal_render_cache_set() and drupal_render_cache_get(). - * @param $expire - * The cache expire time, passed eventually to \Drupal::cache()->set(). - * @param $granularity - * One or more granularity constants passed to drupal_render_cid_parts(). - * - * @return - * A renderable array with the following keys and values: - * - #query: The passed-in $query. - * - #pre_render: $function with a _pre_render suffix. - * - #cache: An associative array prepared for drupal_render_cache_set(). - */ -function drupal_render_cache_by_query($query, $function, $expire = Cache::PERMANENT, $granularity = NULL) { - $cache_keys = array_merge(array($function), drupal_render_cid_parts($granularity)); - $cache_keys[] = Cache::keyFromQuery($query); - return array( - '#query' => $query, - '#pre_render' => array($function . '_pre_render'), - '#cache' => array( - 'keys' => $cache_keys, - 'expire' => $expire, - ), - ); -} - -/** - * Returns cache ID parts for building a cache ID. - * - * @param $granularity - * One or more cache granularity constants. For example, to cache separately - * for each user, use DRUPAL_CACHE_PER_USER. To cache separately for each - * page and role, use the expression: - * @code - * DRUPAL_CACHE_PER_PAGE | DRUPAL_CACHE_PER_ROLE - * @endcode - * - * @return - * An array of cache ID parts, always containing the active theme. If the - * locale module is enabled it also contains the active language. If - * $granularity was passed in, more parts are added. - */ -function drupal_render_cid_parts($granularity = NULL) { - global $theme, $base_root; - - $cid_parts[] = $theme; - - // If we have only one language enabled we do not need it as cid part. - $language_manager = \Drupal::languageManager(); - if ($language_manager->isMultilingual()) { - foreach ($language_manager->getLanguageTypes() as $type) { - $cid_parts[] = $language_manager->getCurrentLanguage($type)->id; - } - } - - if (!empty($granularity)) { - // 'PER_ROLE' and 'PER_USER' are mutually exclusive. 'PER_USER' can be a - // resource drag for sites with many users, so when a module is being - // equivocal, we favor the less expensive 'PER_ROLE' pattern. - if ($granularity & DRUPAL_CACHE_PER_ROLE) { - $cid_parts[] = 'r.' . implode(',', \Drupal::currentUser()->getRoles()); - } - elseif ($granularity & DRUPAL_CACHE_PER_USER) { - $cid_parts[] = 'u.' . \Drupal::currentUser()->id(); - } - - if ($granularity & DRUPAL_CACHE_PER_PAGE) { - $cid_parts[] = $base_root . request_uri(); - } - } - - return $cid_parts; -} - -/** * Creates the cache ID for a renderable element. * * This creates the cache ID string, either by returning the #cache['cid'] - * property if present or by building the cache ID out of the #cache['keys'] - * and, optionally, the #cache['granularity'] properties. + * property if present or by building the cache ID out of the #cache['keys']. * * @param $elements * A renderable array. @@ -4617,12 +4452,7 @@ function drupal_render_cid_create($elements) { // depending on the request). $cache_contexts = \Drupal::service("cache_contexts"); $keys = $cache_contexts->convertTokensToKeys($elements['#cache']['keys']); - - $granularity = isset($elements['#cache']['granularity']) ? $elements['#cache']['granularity'] : NULL; - // Merge in additional cache ID parts based provided by drupal_render_cid_parts(). - $cid_parts = array_merge($keys, drupal_render_cid_parts($granularity)); - - return implode(':', $cid_parts); + return implode(':', $keys); } return FALSE; } diff --git a/core/lib/Drupal/Core/Cache/Cache.php b/core/lib/Drupal/Core/Cache/Cache.php index 38ca1b1..a9c18ae 100644 --- a/core/lib/Drupal/Core/Cache/Cache.php +++ b/core/lib/Drupal/Core/Cache/Cache.php @@ -75,6 +75,13 @@ public static function getBins() { /** * Generates a hash from a query object, to be used as part of the cache key. * + * This smart caching strategy saves Drupal from querying and rendering to + * HTML when the underlying query is unchanged. + * + * Expensive queries should use the query builder to create the query and then + * call this function. Executing the query and formatting results should + * happen in a #pre_render callback. + * * @param \Drupal\Core\Database\Query\SelectInterface $query * A select query object. * diff --git a/core/lib/Drupal/Core/Cache/LanguageCacheContext.php b/core/lib/Drupal/Core/Cache/LanguageCacheContext.php new file mode 100644 index 0000000..b73652f --- /dev/null +++ b/core/lib/Drupal/Core/Cache/LanguageCacheContext.php @@ -0,0 +1,54 @@ +languageManager = $language_manager; + } + + /** + * {@inheritdoc} + */ + public static function getLabel() { + return t('Language'); + } + + /** + * {@inheritdoc} + */ + public function getContext() { + $context_parts = array(); + if ($this->language_manager->isMultilingual()) { + foreach ($this->language_manager->getLanguageTypes() as $type) { + $context_parts[] = $this->language_manager->getCurrentLanguage($type)->id; + } + } + return implode(':', $context_parts); + } + +} diff --git a/core/lib/Drupal/Core/Cache/ThemeCacheContext.php b/core/lib/Drupal/Core/Cache/ThemeCacheContext.php new file mode 100644 index 0000000..d777471 --- /dev/null +++ b/core/lib/Drupal/Core/Cache/ThemeCacheContext.php @@ -0,0 +1,59 @@ +request = $request; + $this->themeNegotiator = $theme_negotiator; + } + + /** + * {@inheritdoc} + */ + public static function getLabel() { + return t('Theme'); + } + + /** + * {@inheritdoc} + */ + public function getContext() { + return $this->themeNegotiator->determineActiveTheme($this->request) ?: 'stark'; + } + +} diff --git a/core/lib/Drupal/Core/Cache/UrlCacheContext.php b/core/lib/Drupal/Core/Cache/UrlCacheContext.php index 8d9141e..596e134 100644 --- a/core/lib/Drupal/Core/Cache/UrlCacheContext.php +++ b/core/lib/Drupal/Core/Cache/UrlCacheContext.php @@ -2,7 +2,7 @@ /** * @file - * Contains Drupal\Core\Cache\UrlCacheContext. + * Contains \Drupal\Core\Cache\UrlCacheContext. */ namespace Drupal\Core\Cache; @@ -15,9 +15,16 @@ class UrlCacheContext implements CacheContextInterface { /** + * The current request. + * + * @var \Symfony\Component\HttpFoundation\Request + */ + protected $request; + + /** * Constructs a new UrlCacheContext service. * - * @param Symfony\Component\HttpFoundation\Request $request + * @param \Symfony\Component\HttpFoundation\Request $request * The HTTP request object. */ public function __construct(Request $request) { diff --git a/core/lib/Drupal/Core/Entity/EntityViewBuilder.php b/core/lib/Drupal/Core/Entity/EntityViewBuilder.php index c069ded..88a6a5a 100644 --- a/core/lib/Drupal/Core/Entity/EntityViewBuilder.php +++ b/core/lib/Drupal/Core/Entity/EntityViewBuilder.php @@ -152,8 +152,13 @@ protected function getBuildDefaults(EntityInterface $entity, $view_mode, $langco // type configuration. if ($this->isViewModeCacheable($view_mode) && !$entity->isNew() && $entity->isDefaultRevision() && $this->entityType->isRenderCacheable()) { $return['#cache'] = array( - 'keys' => array('entity_view', $this->entityTypeId, $entity->id(), $view_mode), - 'granularity' => DRUPAL_CACHE_PER_ROLE, + 'keys' => array( + 'entity_view', + $this->entityTypeId, + $entity->id(), + $view_mode, + 'cache_context.user.roles', + ), 'bin' => $this->cacheBin, 'tags' => array( $this->entityTypeId . '_view' => TRUE, diff --git a/core/modules/block/lib/Drupal/block/BlockBase.php b/core/modules/block/lib/Drupal/block/BlockBase.php index de7916b..37da15a 100644 --- a/core/modules/block/lib/Drupal/block/BlockBase.php +++ b/core/modules/block/lib/Drupal/block/BlockBase.php @@ -145,13 +145,17 @@ public function buildConfigurationForm(array $form, array &$form_state) { '#default_value' => $this->configuration['cache']['max_age'], '#options' => $period, ); + $contexts = \Drupal::service("cache_contexts")->getLabels(); + // Blocks are always rendered in a "per theme" cache context. No need to + // show that option to the end user. + unset($contexts['cache_context.theme']); $form['cache']['contexts'] = array( '#type' => 'select', '#multiple' => TRUE, '#title' => t('Vary by context'), '#description' => t('The contexts this cached block must be varied by.'), '#default_value' => $this->configuration['cache']['contexts'], - '#options' => \Drupal::service("cache_contexts")->getLabels(), + '#options' => $contexts, '#states' => array( 'disabled' => array( ':input[name="settings[cache][max_age]"]' => array('value' => (string) 0), diff --git a/core/modules/block/lib/Drupal/block/BlockViewBuilder.php b/core/modules/block/lib/Drupal/block/BlockViewBuilder.php index 6fe8abc..4539902 100644 --- a/core/modules/block/lib/Drupal/block/BlockViewBuilder.php +++ b/core/modules/block/lib/Drupal/block/BlockViewBuilder.php @@ -81,8 +81,15 @@ public function viewMultiple(array $entities = array(), $view_mode = 'full', $la if ($plugin->isCacheable()) { $build[$entity_id]['#pre_render'][] = array($this, 'buildBlock'); // Generic cache keys, with the block plugin's custom keys appended - // (usually cache context keys like DRUPAL_CACHE_PER_ROLE). - $default_cache_keys = array('entity_view', 'block', $entity->id(), $entity->langcode); + // (usually cache context keys like 'cache_context.user.roles'). + $default_cache_keys = array( + 'entity_view', + 'block', + $entity->id(), + $entity->langcode, + // Blocks are always rendered in a "per theme" cache context. + 'cache_context.theme', + ); $max_age = $plugin->getCacheMaxAge(); $build[$entity_id]['#cache'] += array( 'keys' => array_merge($default_cache_keys, $plugin->getCacheKeys()), diff --git a/core/modules/block/lib/Drupal/block/Tests/BlockInterfaceTest.php b/core/modules/block/lib/Drupal/block/Tests/BlockInterfaceTest.php index 240fc39..aba46c5 100644 --- a/core/modules/block/lib/Drupal/block/Tests/BlockInterfaceTest.php +++ b/core/modules/block/lib/Drupal/block/Tests/BlockInterfaceTest.php @@ -67,6 +67,8 @@ public function testBlockInterface() { $period = array_map('format_interval', array_combine($period, $period)); $period[0] = '<' . t('no caching') . '>'; $period[\Drupal\Core\Cache\Cache::PERMANENT] = t('Forever'); + $contexts = \Drupal::service("cache_contexts")->getLabels(); + unset($contexts['cache_context.theme']); $expected_form = array( 'module' => array( '#type' => 'value', @@ -106,7 +108,7 @@ public function testBlockInterface() { '#title' => t('Vary by context'), '#description' => t('The contexts this cached block must be varied by.'), '#default_value' => array(), - '#options' => \Drupal::service("cache_contexts")->getLabels(), + '#options' => $contexts, '#states' => array( 'disabled' => array( ':input[name="settings[cache][max_age]"]' => array('value' => (string) 0),