Commit a6ffb282 authored by catch's avatar catch

Issue #2158003 by Wim Leers, msonnabaum, effulgentsia, moshe weitzman: Remove...

Issue #2158003 by Wim Leers, msonnabaum, effulgentsia, moshe weitzman: Remove Block Cache API in favor of blocks returning #cache with cache tags.
parent 99c739a0
......@@ -4,6 +4,24 @@ services:
arguments: ['@settings']
calls:
- [setContainer, ['@service_container']]
cache_contexts:
class: Drupal\Core\Cache\CacheContexts
arguments: ['@service_container', '%cache_contexts%' ]
cache_context.url:
class: Drupal\Core\Cache\UrlCacheContext
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']
......
......@@ -900,6 +900,13 @@ function drupal_serve_page_from_cache(stdClass $cache, Response $response, Reque
// max-age > 0, allowing the page to be cached by external proxies, when a
// session cookie is present unless the Vary header has been replaced.
$max_age = !$request->cookies->has(session_name()) || isset($boot_headers['vary']) ? $config->get('cache.page.max_age') : 0;
// RFC 2616, section 14.21 says: 'To mark a response as "never expires," an
// origin server sends an Expires date approximately one year from the time
// the response is sent. HTTP/1.1 servers SHOULD NOT send Expires dates more
// than one year in the future.'
if ($max_age > 31536000 || $max_age === \Drupal\Core\Cache\Cache::PERMANENT) {
$max_age = 31536000;
}
$response->headers->set('Cache-Control', 'public, max-age=' . $max_age);
// Entity tag should change if the output changes.
......
......@@ -131,78 +131,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.
*
......@@ -3718,16 +3646,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
......@@ -4510,101 +4434,11 @@ function drupal_cache_tags_page_get(Response $response) {
return array();
}
/**
* 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));
$query->preExecute();
$cache_keys[] = hash('sha256', serialize(array((string) $query, $query->getArguments())));
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,10 +4451,12 @@ function drupal_render_cid_create($elements) {
return $elements['#cache']['cid'];
}
elseif (isset($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($elements['#cache']['keys'], drupal_render_cid_parts($granularity));
return implode(':', $cid_parts);
// 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).
$cache_contexts = \Drupal::service("cache_contexts");
$keys = $cache_contexts->convertTokensToKeys($elements['#cache']['keys']);
return implode(':', $keys);
}
return FALSE;
}
......
......@@ -7,6 +7,8 @@
namespace Drupal\Core\Cache;
use Drupal\Core\Database\Query\SelectInterface;
/**
* Helper methods for cache.
*
......@@ -72,4 +74,26 @@ public static function getBins() {
return $bins;
}
/**
* 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.
*
* @return string
* A hash of the query arguments.
*/
public static function keyFromQuery(SelectInterface $query) {
$query->preExecute();
$keys = array((string) $query, $query->getArguments());
return hash('sha256', serialize($keys));
}
}
......@@ -21,7 +21,7 @@ interface CacheBackendInterface {
/**
* Indicates that the item should never be removed unless explicitly deleted.
*/
const CACHE_PERMANENT = 0;
const CACHE_PERMANENT = -1;
/**
* Returns data from the persistent cache.
......
<?php
/**
* @file
* Contains \Drupal\Core\Cache\CacheContextInterface.
*/
namespace Drupal\Core\Cache;
/**
* Provides an interface for defining a cache context service.
*/
interface CacheContextInterface {
/**
* Returns the label of the cache context.
*
* @return string
* The label of the cache context.
*/
public static function getLabel();
/**
* Returns the string representation of the cache context.
*
* A cache context service's name is used as a token (placeholder) cache key,
* and is then replaced with the string returned by this method.
*
* @return string
* The string representation of the cache context.
*/
public function getContext();
}
<?php
/**
* @file
* Contains \Drupal\Core\Cache\CacheContexts.
*/
namespace Drupal\Core\Cache;
use Drupal\Core\Database\Query\SelectInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* Defines the CacheContexts service.
*
* Converts string placeholders into their final string values, to be used as a
* cache key.
*/
class CacheContexts {
/**
* The service container.
*
* @var \Symfony\Component\DependencyInjection\ContainerInterface
*/
protected $container;
/**
* Available cache contexts and corresponding labels.
*
* @var array
*/
protected $contexts;
/**
* Constructs a CacheContexts object.
*
* @param \Symfony\Component\DependencyInjection\ContainerInterface $container
* The current service container.
* @param array $contexts
* An array of key-value pairs, where the keys are service names (which also
* serve as the corresponding cache context token) and the values are the
* cache context labels.
*/
public function __construct(ContainerInterface $container, array $contexts) {
$this->container = $container;
$this->contexts = $contexts;
}
/**
* Provides an array of available cache contexts.
*
* @return array
* An array of available cache contexts.
*/
public function getAll() {
return $this->contexts;
}
/**
* Provides an array of available cache context labels.
*
* To be used in cache configuration forms.
*
* @return array
* An array of available cache contexts and corresponding labels.
*/
public function getLabels() {
$with_labels = array();
foreach ($this->contexts as $context) {
$with_labels[$context] = $this->getService($context)->getLabel();
}
return $with_labels;
}
/**
* Converts cache context tokens to string representations of the context.
*
* Cache keys may either be static (just strings) or tokens (placeholders
* that are converted to static keys by the @cache_contexts service, depending
* depending on the request). This is the default cache contexts service.
*
* @param array $keys
* An array of cache keys that may or may not contain cache context tokens.
*
* @return array
* A copy of the input, with cache context tokens converted.
*/
public function convertTokensToKeys(array $keys) {
$context_keys = array_intersect($keys, $this->getAll());
$new_keys = $keys;
// Iterate over the indices instead of the values so that the order of the
// cache keys are preserved.
foreach (array_keys($context_keys) as $index) {
$new_keys[$index] = $this->getContext($keys[$index]);
}
return $new_keys;
}
/**
* Provides the string representaton of a cache context.
*
* @param string $context
* A cache context token of an available cache context service.
*
* @return string
* The string representaton of a cache context.
*/
protected function getContext($context) {
return $this->getService($context)->getContext();
}
/**
* Retrieves a service from the container.
*
* @param string $service
* The ID of the service to retrieve.
*
* @return mixed
* The specified service.
*/
protected function getService($service) {
return $this->container->get($service);
}
}
<?php
/**
* @file
* Contains \Drupal\Core\Cache\CacheContextsPass.
*/
namespace Drupal\Core\Cache;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
/**
* Adds cache_contexts parameter to the container.
*/
class CacheContextsPass implements CompilerPassInterface {
/**
* Implements CompilerPassInterface::process().
*
* Collects the cache contexts into the cache_contexts parameter.
*/
public function process(ContainerBuilder $container) {
$cache_contexts = array_keys($container->findTaggedServiceIds('cache.context'));
$container->setParameter('cache_contexts', $cache_contexts);
}
}
<?php
/**
* @file
* Contains \Drupal\Core\Cache\LanguageCacheContext.
*/
namespace Drupal\Core\Cache;
use Drupal\Core\Language\LanguageManagerInterface;
/**
* Defines the LanguageCacheContext service, for "per language" caching.
*/
class LanguageCacheContext implements CacheContextInterface {
/**
* The language manager.
*
* @param \Drupal\Core\Language\LanguageManagerInterface $language_manager
*/
protected $languageManager;
/**
* Constructs a new LanguageCacheContext service.
*
* @param \Drupal\Core\Language\LanguageManagerInterface $language_manager
* The language manager.
*/
public function __construct(LanguageManagerInterface $language_manager) {
$this->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);
}
}
<?php
/**
* @file
* Contains \Drupal\Core\Cache\LanguageCacheContext.
*/
namespace Drupal\Core\Cache;
use Symfony\Component\HttpFoundation\Request;
use Drupal\Core\Theme\ThemeNegotiatorInterface;
/**
* Defines the ThemeCacheContext service, for "per theme" caching.
*/
class ThemeCacheContext implements CacheContextInterface {
/**
* The current request.
*
* @var \Symfony\Component\HttpFoundation\Request
*/
protected $request;
/**
* The theme negotiator.
*
* @var \Drupal\Core\Theme\ThemeNegotiator
*/
protected $themeNegotiator;
/**
* Constructs a new ThemeCacheContext service.
*
* @param \Symfony\Component\HttpFoundation\Request $request
* The HTTP request object.
* @param \Drupal\Core\Theme\ThemeNegotiatorInterface $theme_negotiator
* The theme negotiator.
*/
public function __construct(Request $request, ThemeNegotiatorInterface $theme_negotiator) {
$this->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';
}
}
<?php
/**
* @file
* Contains \Drupal\Core\Cache\UrlCacheContext.
*/
namespace Drupal\Core\Cache;
use Symfony\Component\HttpFoundation\Request;
/**
* Defines the UrlCacheContext service, for "per page" caching.
*/
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
* The HTTP request object.
*/
public function __construct(Request $request) {
$this->request = $request;
}
/**
* {@inheritdoc}
*/
public static function getLabel() {
return t('URL');
}
/**
* {@inheritdoc}
*/
public function getContext() {
return $this->request->getUri();
}
}
......@@ -7,6 +7,7 @@
namespace Drupal\Core;
use Drupal\Core\Cache\CacheContextsPass;
use Drupal\Core\Cache\ListCacheBinsPass;
use Drupal\Core\Config\ConfigFactoryOverridePass;
use Drupal\Core\DependencyInjection\ServiceProviderInterface;
......@@ -72,6 +73,7 @@ public function register(ContainerBuilder $container) {
$container->addCompilerPass(new RegisterPathProcessorsPass());
$container->addCompilerPass(new RegisterRouteProcessorsPass());
$container->addCompilerPass(new ListCacheBinsPass());
$container->addCompilerPass(new CacheContextsPass());
// Add the compiler pass for appending string translators.
$container->addCompilerPass(new RegisterStringTranslatorsPass());
// Add the compiler pass that will process the tagged breadcrumb builder
......
......@@ -158,8 +158,14 @@ 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.theme',
'cache_context.user.roles',
),
'bin' => $this->cacheBin,
);
......
......@@ -169,52 +169,16 @@ function block_page_build(&$page) {
function block_get_blocks_by_region($region) {
$build = array();
if ($list = block_list($region)) {
$build = _block_get_renderable_region($list);
}
return $build;
}
/**
* Gets an array of blocks suitable for drupal_render().
*
* @param $list