Drupal 8.0.0 beta 12. More info: https://www.drupal.org/node/2514176

This commit is contained in:
Pantheon Automation 2015-08-17 17:00:26 -07:00 committed by Greg Anderson
commit 9921556621
13277 changed files with 1459781 additions and 0 deletions

View file

@ -0,0 +1,261 @@
<?php
/**
* @file
* Contains \Drupal\Core\Cache\ApcuBackend.
*/
namespace Drupal\Core\Cache;
/**
* Stores cache items in the Alternative PHP Cache User Cache (APCu).
*/
class ApcuBackend implements CacheBackendInterface {
/**
* The name of the cache bin to use.
*
* @var string
*/
protected $bin;
/**
* Prefix for all keys in the storage that belong to this site.
*
* @var string
*/
protected $sitePrefix;
/**
* Prefix for all keys in this cache bin.
*
* Includes the site-specific prefix in $sitePrefix.
*
* @var string
*/
protected $binPrefix;
/**
* The cache tags checksum provider.
*
* @var \Drupal\Core\Cache\CacheTagsChecksumInterface
*/
protected $checksumProvider;
/**
* Constructs a new ApcuBackend instance.
*
* @param string $bin
* The name of the cache bin.
* @param string $site_prefix
* The prefix to use for all keys in the storage that belong to this site.
* @param \Drupal\Core\Cache\CacheTagsChecksumInterface $checksum_provider
* The cache tags checksum provider.
*/
public function __construct($bin, $site_prefix, CacheTagsChecksumInterface $checksum_provider) {
$this->bin = $bin;
$this->sitePrefix = $site_prefix;
$this->checksumProvider = $checksum_provider;
$this->binPrefix = $this->sitePrefix . '::' . $this->bin . '::';
}
/**
* Prepends the APC user variable prefix for this bin to a cache item ID.
*
* @param string $cid
* The cache item ID to prefix.
*
* @return string
* The APCu key for the cache item ID.
*/
protected function getApcuKey($cid) {
return $this->binPrefix . $cid;
}
/**
* {@inheritdoc}
*/
public function get($cid, $allow_invalid = FALSE) {
$cache = apc_fetch($this->getApcuKey($cid));
return $this->prepareItem($cache, $allow_invalid);
}
/**
* {@inheritdoc}
*/
public function getMultiple(&$cids, $allow_invalid = FALSE) {
// Translate the requested cache item IDs to APCu keys.
$map = array();
foreach ($cids as $cid) {
$map[$this->getApcuKey($cid)] = $cid;
}
$result = apc_fetch(array_keys($map));
$cache = array();
if ($result) {
foreach ($result as $key => $item) {
$item = $this->prepareItem($item, $allow_invalid);
if ($item) {
$cache[$map[$key]] = $item;
}
}
}
unset($result);
$cids = array_diff($cids, array_keys($cache));
return $cache;
}
/**
* Returns all cached items, optionally limited by a cache ID prefix.
*
* APCu is a memory cache, shared across all server processes. To prevent
* cache item clashes with other applications/installations, every cache item
* is prefixed with a unique string for this site. Therefore, functions like
* apc_clear_cache() cannot be used, and instead, a list of all cache items
* belonging to this application need to be retrieved through this method
* instead.
*
* @param string $prefix
* (optional) A cache ID prefix to limit the result to.
*
* @return \APCIterator
* An APCIterator containing matched items.
*/
protected function getAll($prefix = '') {
return new \APCIterator('user', '/^' . preg_quote($this->getApcuKey($prefix), '/') . '/');
}
/**
* Prepares a cached item.
*
* Checks that the item is either permanent or did not expire.
*
* @param \stdClass $cache
* An item loaded from cache_get() or cache_get_multiple().
* @param bool $allow_invalid
* If TRUE, a cache item may be returned even if it is expired or has been
* invalidated. See ::get().
*
* @return mixed
* The cache item or FALSE if the item expired.
*/
protected function prepareItem($cache, $allow_invalid) {
if (!isset($cache->data)) {
return FALSE;
}
$cache->tags = $cache->tags ? explode(' ', $cache->tags) : array();
// Check expire time.
$cache->valid = $cache->expire == Cache::PERMANENT || $cache->expire >= REQUEST_TIME;
// Check if invalidateTags() has been called with any of the entry's tags.
if (!$this->checksumProvider->isValid($cache->checksum, $cache->tags)) {
$cache->valid = FALSE;
}
if (!$allow_invalid && !$cache->valid) {
return FALSE;
}
return $cache;
}
/**
* {@inheritdoc}
*/
public function set($cid, $data, $expire = CacheBackendInterface::CACHE_PERMANENT, array $tags = array()) {
Cache::validateTags($tags);
$tags = array_unique($tags);
$cache = new \stdClass();
$cache->cid = $cid;
$cache->created = round(microtime(TRUE), 3);
$cache->expire = $expire;
$cache->tags = implode(' ', $tags);
$cache->checksum = $this->checksumProvider->getCurrentChecksum($tags);
// APC serializes/unserializes any structure itself.
$cache->serialized = 0;
$cache->data = $data;
// apc_store()'s $ttl argument can be omitted but also set to 0 (zero),
// in which case the value will persist until it's removed from the cache or
// until the next cache clear, restart, etc. This is what we want to do
// when $expire equals CacheBackendInterface::CACHE_PERMANENT.
if ($expire === CacheBackendInterface::CACHE_PERMANENT) {
$expire = 0;
}
apc_store($this->getApcuKey($cid), $cache, $expire);
}
/**
* {@inheritdoc}
*/
public function setMultiple(array $items = array()) {
foreach ($items as $cid => $item) {
$this->set($cid, $item['data'], isset($item['expire']) ? $item['expire'] : CacheBackendInterface::CACHE_PERMANENT, isset($item['tags']) ? $item['tags'] : array());
}
}
/**
* {@inheritdoc}
*/
public function delete($cid) {
apc_delete($this->getApcuKey($cid));
}
/**
* {@inheritdoc}
*/
public function deleteMultiple(array $cids) {
apc_delete(array_map(array($this, 'getApcuKey'), $cids));
}
/**
* {@inheritdoc}
*/
public function deleteAll() {
apc_delete(new \APCIterator('user', '/^' . preg_quote($this->binPrefix, '/') . '/'));
}
/**
* {@inheritdoc}
*/
public function garbageCollection() {
// APC performs garbage collection automatically.
}
/**
* {@inheritdoc}
*/
public function removeBin() {
apc_delete(new \APCIterator('user', '/^' . preg_quote($this->binPrefix, '/') . '/'));
}
/**
* {@inheritdoc}
*/
public function invalidate($cid) {
$this->invalidateMultiple(array($cid));
}
/**
* {@inheritdoc}
*/
public function invalidateMultiple(array $cids) {
foreach ($this->getMultiple($cids) as $cache) {
$this->set($cache->cid, $cache, REQUEST_TIME - 1);
}
}
/**
* {@inheritdoc}
*/
public function invalidateAll() {
foreach ($this->getAll() as $data) {
$cid = str_replace($this->binPrefix, '', $data['key']);
$this->set($cid, $data['value'], REQUEST_TIME - 1);
}
}
}

View file

@ -0,0 +1,56 @@
<?php
/**
* @file
* Contains \Drupal\Core\Cache\ApcuBackendFactory.
*/
namespace Drupal\Core\Cache;
use Drupal\Core\Site\Settings;
class ApcuBackendFactory implements CacheFactoryInterface {
/**
* The site prefix string.
*
* @var string
*/
protected $sitePrefix;
/**
* The cache tags checksum provider.
*
* @var \Drupal\Core\Cache\CacheTagsChecksumInterface
*/
protected $checksumProvider;
/**
* Constructs an ApcuBackendFactory object.
*
* @param string $root
* The app root.
* @param string $site_path
* The site path.
* @param \Drupal\Core\Cache\CacheTagsChecksumInterface $checksum_provider
* The cache tags checksum provider.
*/
public function __construct($root, $site_path, CacheTagsChecksumInterface $checksum_provider) {
$this->sitePrefix = Settings::getApcuPrefix('apcu_backend', $root, $site_path);
$this->checksumProvider = $checksum_provider;
}
/**
* Gets ApcuBackend for the specified cache bin.
*
* @param $bin
* The cache bin for which the object is created.
*
* @return \Drupal\Core\Cache\ApcuBackend
* The cache backend object for the specified cache bin.
*/
public function get($bin) {
return new ApcuBackend($bin, $this->sitePrefix, $this->checksumProvider);
}
}

View file

@ -0,0 +1,226 @@
<?php
/**
* @file
* Contains \Drupal\Core\Cache\BackendChain.
*/
namespace Drupal\Core\Cache;
/**
* Defines a chained cache implementation for combining multiple cache backends.
*
* Can be used to combine two or more backends together to behave as if they
* were a single backend.
*
* For example a slower, persistent storage engine could be combined with a
* faster, volatile storage engine. When retrieving items from cache, they will
* be fetched from the volatile backend first, only falling back to the
* persistent backend if an item is not available. An item not present in the
* volatile backend but found in the persistent one will be propagated back up
* to ensure fast retrieval on the next request. On cache sets and deletes, both
* backends will be invoked to ensure consistency.
*
* @ingroup cache
*/
class BackendChain implements CacheBackendInterface, CacheTagsInvalidatorInterface {
/**
* Ordered list of CacheBackendInterface instances.
*
* @var array
*/
protected $backends = array();
/**
* Constructs a DatabaseBackend object.
*
* @param string $bin
* The cache bin for which the object is created.
*/
public function __construct($bin) {
}
/**
* Appends a cache backend to the cache chain.
*
* @param CacheBackendInterface $backend
* The cache backend to be appended to the cache chain.
*
* @return \Drupal\Core\Cache\BackendChain
* The called object.
*/
public function appendBackend(CacheBackendInterface $backend) {
$this->backends[] = $backend;
return $this;
}
/**
* Prepends a cache backend to the cache chain.
*
* @param CacheBackendInterface $backend
* The backend to be prepended to the cache chain.
*
* @return \Drupal\Core\Cache\BackendChain
* The called object.
*/
public function prependBackend(CacheBackendInterface $backend) {
array_unshift($this->backends, $backend);
return $this;
}
/**
* Implements Drupal\Core\Cache\CacheBackendInterface::get().
*/
public function get($cid, $allow_invalid = FALSE) {
foreach ($this->backends as $index => $backend) {
if (($return = $backend->get($cid, $allow_invalid)) !== FALSE) {
// We found a result, propagate it to all missed backends.
if ($index > 0) {
for ($i = ($index - 1); 0 <= $i; --$i) {
$this->backends[$i]->set($cid, $return->data, $return->expire, $return->tags);
}
}
return $return;
}
}
return FALSE;
}
/**
* Implements Drupal\Core\Cache\CacheBackendInterface::getMultiple().
*/
public function getMultiple(&$cids, $allow_invalid = FALSE) {
$return = array();
foreach ($this->backends as $index => $backend) {
$items = $backend->getMultiple($cids, $allow_invalid);
// Propagate the values that could be retrieved from the current cache
// backend to all missed backends.
if ($index > 0 && !empty($items)) {
for ($i = ($index - 1); 0 <= $i; --$i) {
foreach ($items as $cached) {
$this->backends[$i]->set($cached->cid, $cached->data, $cached->expire, $cached->tags);
}
}
}
// Append the values to the previously retrieved ones.
$return += $items;
if (empty($cids)) {
// No need to go further if we don't have any cid to fetch left.
break;
}
}
return $return;
}
/**
* Implements Drupal\Core\Cache\CacheBackendInterface::set().
*/
public function set($cid, $data, $expire = Cache::PERMANENT, array $tags = array()) {
foreach ($this->backends as $backend) {
$backend->set($cid, $data, $expire, $tags);
}
}
/**
* {@inheritdoc}
*/
public function setMultiple(array $items) {
foreach ($this->backends as $backend) {
$backend->setMultiple($items);
}
}
/**
* Implements Drupal\Core\Cache\CacheBackendInterface::delete().
*/
public function delete($cid) {
foreach ($this->backends as $backend) {
$backend->delete($cid);
}
}
/**
* Implements Drupal\Core\Cache\CacheBackendInterface::deleteMultiple().
*/
public function deleteMultiple(array $cids) {
foreach ($this->backends as $backend) {
$backend->deleteMultiple($cids);
}
}
/**
* Implements Drupal\Core\Cache\CacheBackendInterface::deleteAll().
*/
public function deleteAll() {
foreach ($this->backends as $backend) {
$backend->deleteAll();
}
}
/**
* Implements Drupal\Core\Cache\CacheBackendInterface::invalidate().
*/
public function invalidate($cid) {
foreach ($this->backends as $backend) {
$backend->invalidate($cid);
}
}
/**
* Implements Drupal\Core\Cache\CacheBackendInterface::invalidateMultiple().
*/
public function invalidateMultiple(array $cids) {
foreach ($this->backends as $backend) {
$backend->invalidateMultiple($cids);
}
}
/**
* Implements Drupal\Core\Cache\CacheBackendInterface::invalidateTags().
*/
public function invalidateTags(array $tags) {
foreach ($this->backends as $backend) {
if ($backend instanceof CacheTagsInvalidatorInterface) {
$backend->invalidateTags($tags);
}
}
}
/**
* Implements Drupal\Core\Cache\CacheBackendInterface::invalidateAll().
*/
public function invalidateAll() {
foreach ($this->backends as $backend) {
$backend->invalidateAll();
}
}
/**
* Implements Drupal\Core\Cache\CacheBackendInterface::garbageCollection().
*/
public function garbageCollection() {
foreach ($this->backends as $backend) {
$backend->garbageCollection();
}
}
/**
* {@inheritdoc}
*/
public function removeBin() {
foreach ($this->backends as $backend) {
$backend->removeBin();
}
}
}

View file

@ -0,0 +1,196 @@
<?php
/**
* @file
* Contains \Drupal\Core\Cache\Cache.
*/
namespace Drupal\Core\Cache;
use Drupal\Core\Database\Query\SelectInterface;
/**
* Helper methods for cache.
*
* @ingroup cache
*/
class Cache {
/**
* Indicates that the item should never be removed unless explicitly deleted.
*/
const PERMANENT = CacheBackendInterface::CACHE_PERMANENT;
/**
* Merges arrays of cache contexts and removes duplicates.
*
* @param string[]
* Arrays of cache contexts to merge.
*
* @return string[]
* The merged array of cache contexts.
*/
public static function mergeContexts() {
$cache_context_arrays = func_get_args();
$cache_contexts = [];
foreach ($cache_context_arrays as $contexts) {
$cache_contexts = array_merge($cache_contexts, $contexts);
}
$cache_contexts = array_unique($cache_contexts);
\Drupal::service('cache_contexts_manager')->validateTokens($cache_contexts);
sort($cache_contexts);
return $cache_contexts;
}
/**
* Merges arrays of cache tags and removes duplicates.
*
* The cache tags array is returned in a format that is valid for
* \Drupal\Core\Cache\CacheBackendInterface::set().
*
* When caching elements, it is necessary to collect all cache tags into a
* single array, from both the element itself and all child elements. This
* allows items to be invalidated based on all tags attached to the content
* they're constituted from.
*
* @param string[]
* Arrays of cache tags to merge.
*
* @return string[]
* The merged array of cache tags.
*/
public static function mergeTags() {
$cache_tag_arrays = func_get_args();
$cache_tags = [];
foreach ($cache_tag_arrays as $tags) {
$cache_tags = array_merge($cache_tags, $tags);
}
$cache_tags = array_unique($cache_tags);
static::validateTags($cache_tags);
sort($cache_tags);
return $cache_tags;
}
/**
* Merges max-age values (expressed in seconds), finds the lowest max-age.
*
* Ensures infinite max-age (Cache::PERMANENT) is taken into account.
*
* @param int
* Max-age values.
*
* @return int
* The minimum max-age value.
*/
public static function mergeMaxAges() {
$max_ages = func_get_args();
// Filter out all max-age values set to cache permanently.
if (in_array(Cache::PERMANENT, $max_ages)) {
$max_ages = array_filter($max_ages, function ($max_age) {
return $max_age !== Cache::PERMANENT;
});
// If nothing is left, then all max-age values were set to cache
// permanently, and then that is the result.
if (empty($max_ages)) {
return Cache::PERMANENT;
}
}
return min($max_ages);
}
/**
* Validates an array of cache tags.
*
* Can be called before using cache tags in operations, to ensure validity.
*
* @param string[] $tags
* An array of cache tags.
*
* @throws \LogicException
*/
public static function validateTags(array $tags) {
if (empty($tags)) {
return;
}
foreach ($tags as $value) {
if (!is_string($value)) {
throw new \LogicException('Cache tags must be strings, ' . gettype($value) . ' given.');
}
}
}
/**
* Build an array of cache tags from a given prefix and an array of suffixes.
*
* Each suffix will be converted to a cache tag by appending it to the prefix,
* with a colon between them.
*
* @param string $prefix
* 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, $glue = ':') {
$tags = [];
foreach ($suffixes as $suffix) {
$tags[] = $prefix . $glue . $suffix;
}
return $tags;
}
/**
* Marks cache items from all bins with any of the specified tags as invalid.
*
* @param string[] $tags
* The list of tags to invalidate cache items for.
*/
public static function invalidateTags(array $tags) {
\Drupal::service('cache_tags.invalidator')->invalidateTags($tags);
}
/**
* Gets all cache bin services.
*
* @return array
* An array of cache backend objects keyed by cache bins.
*/
public static function getBins() {
$bins = array();
$container = \Drupal::getContainer();
foreach ($container->getParameter('cache_bins') as $service_id => $bin) {
$bins[$bin] = $container->get($service_id);
}
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));
}
}

View file

@ -0,0 +1,223 @@
<?php
/**
* @file
* Contains \Drupal\Core\Cache\CacheBackendInterface.
*/
namespace Drupal\Core\Cache;
/**
* Defines an interface for cache implementations.
*
* All cache implementations have to implement this interface.
* Drupal\Core\Cache\DatabaseBackend provides the default implementation, which
* can be consulted as an example.
*
* The cache indentifiers are case sensitive.
*
* @ingroup cache
*/
interface CacheBackendInterface {
/**
* Indicates that the item should never be removed unless explicitly deleted.
*/
const CACHE_PERMANENT = -1;
/**
* Returns data from the persistent cache.
*
* @param string $cid
* The cache ID of the data to retrieve.
* @param bool $allow_invalid
* (optional) If TRUE, a cache item may be returned even if it is expired or
* has been invalidated. Such items may sometimes be preferred, if the
* alternative is recalculating the value stored in the cache, especially
* if another concurrent request is already recalculating the same value.
* The "valid" property of the returned object indicates whether the item is
* valid or not. Defaults to FALSE.
*
* @return object|false
* The cache item or FALSE on failure.
*
* @see \Drupal\Core\Cache\CacheBackendInterface::getMultiple()
*/
public function get($cid, $allow_invalid = FALSE);
/**
* Returns data from the persistent cache when given an array of cache IDs.
*
* @param array $cids
* An array of cache IDs for the data to retrieve. This is passed by
* reference, and will have the IDs successfully returned from cache
* removed.
* @param bool $allow_invalid
* (optional) If TRUE, cache items may be returned even if they have expired
* or been invalidated. Such items may sometimes be preferred, if the
* alternative is recalculating the value stored in the cache, especially
* if another concurrent thread is already recalculating the same value. The
* "valid" property of the returned objects indicates whether the items are
* valid or not. Defaults to FALSE.
*
* @return array
* An array of cache item objects indexed by cache ID.
*
* @see \Drupal\Core\Cache\CacheBackendInterface::get()
*/
public function getMultiple(&$cids, $allow_invalid = FALSE);
/**
* Stores data in the persistent cache.
*
* Core cache implementations set the created time on cache item with
* microtime(TRUE) rather than REQUEST_TIME_FLOAT, because the created time
* of cache items should match when they are created, not when the request
* started. Apart from being more accurate, this increases the chance an
* item will legitimately be considered valid.
*
* @param string $cid
* The cache ID of the data to store.
* @param mixed $data
* The data to store in the cache.
* Some storage engines only allow objects up to a maximum of 1MB in size to
* be stored by default. When caching large arrays or similar, take care to
* ensure $data does not exceed this size.
* @param int $expire
* One of the following values:
* - CacheBackendInterface::CACHE_PERMANENT: Indicates that the item should
* not be removed unless it is deleted explicitly.
* - A Unix timestamp: Indicates that the item will be considered invalid
* after this time, i.e. it will not be returned by get() unless
* $allow_invalid has been set to TRUE. When the item has expired, it may
* be permanently deleted by the garbage collector at any time.
* @param array $tags
* An array of tags to be stored with the cache item. These should normally
* identify objects used to build the cache item, which should trigger
* cache invalidation when updated. For example if a cached item represents
* a node, both the node ID and the author's user ID might be passed in as
* tags. For example array('node' => array(123), 'user' => array(92)).
*
* @see \Drupal\Core\Cache\CacheBackendInterface::get()
* @see \Drupal\Core\Cache\CacheBackendInterface::getMultiple()
*/
public function set($cid, $data, $expire = Cache::PERMANENT, array $tags = array());
/**
* Store multiple items in the persistent cache.
*
* @param array $items
* An array of cache items, keyed by cid. In the form:
* @code
* $items = array(
* $cid => array(
* // Required, will be automatically serialized if not a string.
* 'data' => $data,
* // Optional, defaults to CacheBackendInterface::CACHE_PERMANENT.
* 'expire' => CacheBackendInterface::CACHE_PERMANENT,
* // (optional) The cache tags for this item, see CacheBackendInterface::set().
* 'tags' => array(),
* ),
* );
* @endcode
*/
public function setMultiple(array $items);
/**
* Deletes an item from the cache.
*
* If the cache item is being deleted because it is no longer "fresh", you may
* consider using invalidate() instead. This allows callers to retrieve the
* invalid item by calling get() with $allow_invalid set to TRUE. In some cases
* an invalid item may be acceptable rather than having to rebuild the cache.
*
* @param string $cid
* The cache ID to delete.
*
* @see \Drupal\Core\Cache\CacheBackendInterface::invalidate()
* @see \Drupal\Core\Cache\CacheBackendInterface::deleteMultiple()
* @see \Drupal\Core\Cache\CacheBackendInterface::deleteAll()
*/
public function delete($cid);
/**
* Deletes multiple items from the cache.
*
* If the cache items are being deleted because they are no longer "fresh",
* you may consider using invalidateMultiple() instead. This allows callers to
* retrieve the invalid items by calling get() with $allow_invalid set to TRUE.
* In some cases an invalid item may be acceptable rather than having to
* rebuild the cache.
*
* @param array $cids
* An array of cache IDs to delete.
*
* @see \Drupal\Core\Cache\CacheBackendInterface::invalidateMultiple()
* @see \Drupal\Core\Cache\CacheBackendInterface::delete()
* @see \Drupal\Core\Cache\CacheBackendInterface::deleteAll()
*/
public function deleteMultiple(array $cids);
/**
* Deletes all cache items in a bin.
*
* @see \Drupal\Core\Cache\CacheBackendInterface::invalidateAll()
* @see \Drupal\Core\Cache\CacheBackendInterface::delete()
* @see \Drupal\Core\Cache\CacheBackendInterface::deleteMultiple()
*/
public function deleteAll();
/**
* Marks a cache item as invalid.
*
* Invalid items may be returned in later calls to get(), if the $allow_invalid
* argument is TRUE.
*
* @param string $cid
* The cache ID to invalidate.
*
* @see \Drupal\Core\Cache\CacheBackendInterface::delete()
* @see \Drupal\Core\Cache\CacheBackendInterface::invalidateMultiple()
* @see \Drupal\Core\Cache\CacheBackendInterface::invalidateAll()
*/
public function invalidate($cid);
/**
* Marks cache items as invalid.
*
* Invalid items may be returned in later calls to get(), if the $allow_invalid
* argument is TRUE.
*
* @param string[] $cids
* An array of cache IDs to invalidate.
*
* @see \Drupal\Core\Cache\CacheBackendInterface::deleteMultiple()
* @see \Drupal\Core\Cache\CacheBackendInterface::invalidate()
* @see \Drupal\Core\Cache\CacheBackendInterface::invalidateAll()
*/
public function invalidateMultiple(array $cids);
/**
* Marks all cache items as invalid.
*
* Invalid items may be returned in later calls to get(), if the $allow_invalid
* argument is TRUE.
*
* @see \Drupal\Core\Cache\CacheBackendInterface::deleteAll()
* @see \Drupal\Core\Cache\CacheBackendInterface::invalidate()
* @see \Drupal\Core\Cache\CacheBackendInterface::invalidateMultiple()
*/
public function invalidateAll();
/**
* Performs garbage collection on a cache bin.
*
* The backend may choose to delete expired or invalidated items.
*/
public function garbageCollection();
/**
* Remove a cache bin.
*/
public function removeBin();
}

View file

@ -0,0 +1,323 @@
<?php
/**
* @file
* Contains \Drupal\Core\Cache\CacheCollector.
*/
namespace Drupal\Core\Cache;
use Drupal\Core\DestructableInterface;
use Drupal\Core\Lock\LockBackendInterface;
/**
* Default implementation for CacheCollectorInterface.
*
* By default, the class accounts for caches where calling functions might
* request keys that won't exist even after a cache rebuild. This prevents
* situations where a cache rebuild would be triggered over and over due to a
* 'missing' item. These cases are stored internally as a value of NULL. This
* means that the CacheCollector::get() method must be overridden if caching
* data where the values can legitimately be NULL, and where
* CacheCollector->has() needs to correctly return (equivalent to
* array_key_exists() vs. isset()). This should not be necessary in the majority
* of cases.
*
* @ingroup cache
*/
abstract class CacheCollector implements CacheCollectorInterface, DestructableInterface {
/**
* The cache id that is used for the cache entry.
*
* @var string
*/
protected $cid;
/**
* A list of tags that are used for the cache entry.
*
* @var array
*/
protected $tags;
/**
* The cache backend that should be used.
*
* @var \Drupal\Core\Cache\CacheBackendInterface
*/
protected $cache;
/**
* The lock backend that should be used.
*
* @var \Drupal\Core\Lock\LockBackendInterface
*/
protected $lock;
/**
* An array of keys to add to the cache on service termination.
*
* @var array
*/
protected $keysToPersist = array();
/**
* An array of keys to remove from the cache on service termination.
*
* @var array
*/
protected $keysToRemove = array();
/**
* Storage for the data itself.
*
* @var array
*/
protected $storage = array();
/**
* Stores the cache creation time.
*
* This is used to check if an invalidated cache item has been overwritten in
* the meantime.
*
* @var int
*/
protected $cacheCreated;
/**
* Flag that indicates of the cache has been invalidated.
*
* @var bool
*/
protected $cacheInvalidated = FALSE;
/**
* Indicates if the collected cache was already loaded.
*
* The collected cache is lazy loaded when an entry is set, get or deleted.
*
* @var bool
*/
protected $cacheLoaded = FALSE;
/**
* Constructs a CacheCollector object.
*
* @param string $cid
* The cid for the array being cached.
* @param \Drupal\Core\Cache\CacheBackendInterface $cache
* The cache backend.
* @param \Drupal\Core\Lock\LockBackendInterface $lock
* The lock backend.
* @param array $tags
* (optional) The tags to specify for the cache item.
*/
public function __construct($cid, CacheBackendInterface $cache, LockBackendInterface $lock, array $tags = array()) {
Cache::validateTags($tags);
$this->cid = $cid;
$this->cache = $cache;
$this->tags = $tags;
$this->lock = $lock;
}
/**
* Gets the cache ID.
*
* @return string
*/
protected function getCid() {
return $this->cid;
}
/**
* {@inheritdoc}
*/
public function has($key) {
// Make sure the value is loaded.
$this->get($key);
return isset($this->storage[$key]) || array_key_exists($key, $this->storage);
}
/**
* {@inheritdoc}
*/
public function get($key) {
$this->lazyLoadCache();
if (isset($this->storage[$key]) || array_key_exists($key, $this->storage)) {
return $this->storage[$key];
}
else {
return $this->resolveCacheMiss($key);
}
}
/**
* Implements \Drupal\Core\Cache\CacheCollectorInterface::set().
*
* This is not persisted by default. In practice this means that setting a
* value will only apply while the object is in scope and will not be written
* back to the persistent cache. This follows a similar pattern to static vs.
* persistent caching in procedural code. Extending classes may wish to alter
* this behavior, for example by adding a call to persist().
*/
public function set($key, $value) {
$this->lazyLoadCache();
$this->storage[$key] = $value;
// The key might have been marked for deletion.
unset($this->keysToRemove[$key]);
$this->invalidateCache();
}
/**
* {@inheritdoc}
*/
public function delete($key) {
$this->lazyLoadCache();
unset($this->storage[$key]);
$this->keysToRemove[$key] = $key;
// The key might have been marked for persisting.
unset($this->keysToPersist[$key]);
$this->invalidateCache();
}
/**
* Flags an offset value to be written to the persistent cache.
*
* @param string $key
* The key that was requested.
* @param bool $persist
* (optional) Whether the offset should be persisted or not, defaults to
* TRUE. When called with $persist = FALSE the offset will be unflagged so
* that it will not be written at the end of the request.
*/
protected function persist($key, $persist = TRUE) {
$this->keysToPersist[$key] = $persist;
}
/**
* Resolves a cache miss.
*
* When an offset is not found in the object, this is treated as a cache
* miss. This method allows classes using this implementation to look up the
* actual value and allow it to be cached.
*
* @param string $key
* The offset that was requested.
*
* @return mixed
* The value of the offset, or NULL if no value was found.
*/
abstract protected function resolveCacheMiss($key);
/**
* Writes a value to the persistent cache immediately.
*
* @param bool $lock
* (optional) Whether to acquire a lock before writing to cache. Defaults to
* TRUE.
*/
protected function updateCache($lock = TRUE) {
$data = array();
foreach ($this->keysToPersist as $offset => $persist) {
if ($persist) {
$data[$offset] = $this->storage[$offset];
}
}
if (empty($data) && empty($this->keysToRemove)) {
return;
}
// Lock cache writes to help avoid stampedes.
$cid = $this->getCid();
$lock_name = $cid . ':' . __CLASS__;
if (!$lock || $this->lock->acquire($lock_name)) {
// Set and delete operations invalidate the cache item. Try to also load
// an eventually invalidated cache entry, only update an invalidated cache
// entry if the creation date did not change as this could result in an
// inconsistent cache.
if ($cache = $this->cache->get($cid, $this->cacheInvalidated)) {
if ($this->cacheInvalidated && $cache->created != $this->cacheCreated) {
// We have invalidated the cache in this request and got a different
// cache entry. Do not attempt to overwrite data that might have been
// changed in a different request. We'll let the cache rebuild in
// later requests.
$this->cache->delete($cid);
$this->lock->release($lock_name);
return;
}
$data = array_merge($cache->data, $data);
}
// Remove keys marked for deletion.
foreach ($this->keysToRemove as $delete_key) {
unset($data[$delete_key]);
}
$this->cache->set($cid, $data, Cache::PERMANENT, $this->tags);
if ($lock) {
$this->lock->release($lock_name);
}
}
$this->keysToPersist = array();
$this->keysToRemove = array();
}
/**
* {@inheritdoc}
*/
public function reset() {
$this->storage = array();
$this->keysToPersist = array();
$this->keysToRemove = array();
$this->cacheLoaded = FALSE;
}
/**
* {@inheritdoc}
*/
public function clear() {
$this->reset();
if ($this->tags) {
Cache::invalidateTags($this->tags);
}
else {
$this->cache->delete($this->getCid());
}
}
/**
* {@inheritdoc}
*/
public function destruct() {
$this->updateCache();
}
/**
* Loads the cache if not already done.
*/
protected function lazyLoadCache() {
if ($this->cacheLoaded) {
return;
}
// The cache was not yet loaded, set flag to TRUE.
$this->cacheLoaded = TRUE;
if ($cache = $this->cache->get($this->getCid())) {
$this->cacheCreated = $cache->created;
$this->storage = $cache->data;
}
}
/**
* Invalidate the cache.
*/
protected function invalidateCache() {
// Invalidate the cache to make sure that other requests immediately see the
// deletion before this request is terminated.
$this->cache->invalidate($this->getCid());
$this->cacheInvalidated = TRUE;
}
}

View file

@ -0,0 +1,81 @@
<?php
/**
* @file
* Contains \Drupal\Core\Cache\CacheCollectorInterface.
*/
namespace Drupal\Core\Cache;
/**
* Provides a caching wrapper to be used in place of large structures.
*
* This should be extended by systems that need to cache large amounts of data
* to calling functions. These structures can become very large, so this
* class is used to allow different strategies to be used for caching internally
* (lazy loading, building caches over time etc.). This can dramatically reduce
* the amount of data that needs to be loaded from cache backends on each
* request, and memory usage from static caches of that same data.
*
* The default implementation is \Drupal\Core\Cache\CacheCollector.
*
* @ingroup cache
*/
interface CacheCollectorInterface {
/**
* Gets value from the cache.
*
* @param string $key
* Key that identifies the data.
*
* @return mixed
* The corresponding cache data.
*/
public function get($key);
/**
* Sets cache data.
*
* It depends on the specific case and implementation whether this has a
* permanent effect or if it just affects the current request.
*
* @param string $key
* Key that identifies the data.
* @param mixed $value
* The data to be set.
*/
public function set($key, $value);
/**
* Deletes the element.
*
* It depends on the specific case and implementation whether this has a
* permanent effect or if it just affects the current request.
*
* @param string $key
* Key that identifies the data.
*/
public function delete($key);
/**
* Returns whether data exists for this key.
*
* @param string $key
* Key that identifies the data.
*/
public function has($key);
/**
* Resets the local cache.
*
* Does not clear the persistent cache.
*/
public function reset();
/**
* Clears the collected cache entry.
*/
public function clear();
}

View file

@ -0,0 +1,88 @@
<?php
/**
* @file
* Contains \Drupal\Core\Cache\CacheFactory.
*/
namespace Drupal\Core\Cache;
/**
* Defines the cache backend factory.
*/
use Drupal\Core\Site\Settings;
use Symfony\Component\DependencyInjection\ContainerAwareInterface;
use Symfony\Component\DependencyInjection\ContainerAwareTrait;
use Symfony\Component\DependencyInjection\ContainerBuilder;
class CacheFactory implements CacheFactoryInterface, ContainerAwareInterface {
use ContainerAwareTrait;
/**
* The settings array.
*
* @var \Drupal\Core\Site\Settings
*/
protected $settings;
/**
* A map of cache bin to default cache backend service name.
*
* All mappings in $settings takes precedence over this, but this can be used
* to optimize cache storage for a Drupal installation without cache
* customizations in settings.php. For example, this can be used to map the
* 'bootstrap' bin to 'cache.backend.chainedfast', while allowing other bins
* to fall back to the global default of 'cache.backend.database'.
*
* @var array
*/
protected $defaultBinBackends;
/**
* Constructs CacheFactory object.
*
* @param \Drupal\Core\Site\Settings $settings
* The settings array.
* @param array $default_bin_backends
* (optional) A mapping of bin to backend service name. Mappings in
* $settings take precedence over this.
*/
public function __construct(Settings $settings, array $default_bin_backends = array()) {
$this->settings = $settings;
$this->defaultBinBackends = $default_bin_backends;
}
/**
* Instantiates a cache backend class for a given cache bin.
*
* By default, this returns an instance of the
* Drupal\Core\Cache\DatabaseBackend class.
*
* Classes implementing Drupal\Core\Cache\CacheBackendInterface can register
* themselves both as a default implementation and for specific bins.
*
* @param string $bin
* The cache bin for which a cache backend object should be returned.
*
* @return \Drupal\Core\Cache\CacheBackendInterface
* The cache backend object associated with the specified bin.
*/
public function get($bin) {
$cache_settings = $this->settings->get('cache');
if (isset($cache_settings['bins'][$bin])) {
$service_name = $cache_settings['bins'][$bin];
}
elseif (isset($cache_settings['default'])) {
$service_name = $cache_settings['default'];
}
elseif (isset($this->defaultBinBackends[$bin])) {
$service_name = $this->defaultBinBackends[$bin];
}
else {
$service_name = 'cache.backend.database';
}
return $this->container->get($service_name)->get($bin);
}
}

View file

@ -0,0 +1,26 @@
<?php
/**
* @file
* Contains \Drupal\Core\Cache\CacheFactoryInterface.
*/
namespace Drupal\Core\Cache;
/**
* An interface defining cache factory classes.
*/
interface CacheFactoryInterface {
/**
* Gets a cache backend class for a given cache bin.
*
* @param string $bin
* The cache bin for which a cache backend object should be returned.
*
* @return \Drupal\Core\Cache\CacheBackendInterface
* The cache backend object associated with the specified bin.
*/
public function get($bin);
}

View file

@ -0,0 +1,62 @@
<?php
/**
* @file
* Contains \Drupal\Core\Cache\CacheTagsChecksumInterface.
*/
namespace Drupal\Core\Cache;
/**
* Provides checksums for cache tag invalidations.
*
* Cache backends can use this to check if any cache tag invalidations happened
* for a stored cache item.
*
* To do so, they can inject the cache_tags.invalidator.checksum service, and
* when a cache item is written, store cache tags together with the current
* checksum, calculated by getCurrentChecksum(). When a cache item is fetched,
* the checksum can be validated with isValid(). The service will return FALSE
* if any of those cache tags were invalidated in the meantime.
*
* @ingroup cache
*/
interface CacheTagsChecksumInterface {
/**
* Returns the sum total of validations for a given set of tags.
*
* Called by a backend when storing a cache item.
*
* @param string[] $tags
* Array of cache tags.
*
* @return string
* Cache tag invalidations checksum.
*/
public function getCurrentChecksum(array $tags);
/**
* Returns whether the checksum is valid for the given cache tags.
*
* Used when retrieving a cache item in a cache backend, to verify that no
* cache tag based invalidation happened.
*
* @param int $checksum
* The checksum that was stored together with the cache item.
* @param string[] $tags
* The cache tags that were stored together with the cache item.
*
* @return bool
* FALSE if cache tag invalidations happened for the passed in tags since
* the cache item was stored, TRUE otherwise.
*/
public function isValid($checksum, array $tags);
/**
* Reset statically cached tags.
*
* This is only used by tests.
*/
public function reset();
}

View file

@ -0,0 +1,85 @@
<?php
/**
* @file
* Contains \Drupal\Core\Cache\CacheTagsInvalidator.
*/
namespace Drupal\Core\Cache;
use Symfony\Component\DependencyInjection\ContainerAwareTrait;
/**
* Passes cache tag events to classes that wish to respond to them.
*/
class CacheTagsInvalidator implements CacheTagsInvalidatorInterface {
use ContainerAwareTrait;
/**
* Holds an array of cache tags invalidators.
*
* @var \Drupal\Core\Cache\CacheTagsInvalidatorInterface[]
*/
protected $invalidators = array();
/**
* {@inheritdoc}
*/
public function invalidateTags(array $tags) {
// Validate the tags.
Cache::validateTags($tags);
// Notify all added cache tags invalidators.
foreach ($this->invalidators as $invalidator) {
$invalidator->invalidateTags($tags);
}
// Additionally, notify each cache bin if it implements the service.
foreach ($this->getInvalidatorCacheBins() as $bin) {
$bin->invalidateTags($tags);
}
}
/**
* Reset statically cached tags in all cache tag checksum services.
*
* This is only used by tests.
*/
public function resetChecksums() {
foreach ($this->invalidators as $invalidator) {
if ($invalidator instanceof CacheTagsChecksumInterface) {
$invalidator->reset();
}
}
}
/**
* Adds a cache tags invalidator.
*
* @param \Drupal\Core\Cache\CacheTagsInvalidatorInterface $invalidator
* A cache invalidator.
*/
public function addInvalidator(CacheTagsInvalidatorInterface $invalidator) {
$this->invalidators[] = $invalidator;
}
/**
* Returns all cache bins that need to be notified about invalidations.
*
* @return \Drupal\Core\Cache\CacheTagsInvalidatorInterface[]
* An array of cache backend objects that implement the invalidator
* interface, keyed by their cache bin.
*/
protected function getInvalidatorCacheBins() {
$bins = array();
foreach ($this->container->getParameter('cache_bins') as $service_id => $bin) {
$service = $this->container->get($service_id);
if ($service instanceof CacheTagsInvalidatorInterface) {
$bins[$bin] = $service;
}
}
return $bins;
}
}

View file

@ -0,0 +1,29 @@
<?php
/**
* @file
* Contains \Drupal\Core\Cache\CacheTagsInvalidatorInterface.
*/
namespace Drupal\Core\Cache;
/**
* Defines required methods for classes wanting to handle cache tag changes.
*
* Services that implement this interface must add the cache_tags_invalidator
* tag to be notified. Cache backends may implement this interface as well, they
* will be notified automatically.
*
* @ingroup cache
*/
interface CacheTagsInvalidatorInterface {
/**
* Marks cache items with any of the specified tags as invalid.
*
* @param string[] $tags
* The list of tags for which to invalidate cache items.
*/
public function invalidateTags(array $tags);
}

View file

@ -0,0 +1,56 @@
<?php
/**
* @file
* Contains \Drupal\Core\Cache\CacheableDependencyInterface.
*/
namespace Drupal\Core\Cache;
/**
* Defines an interface for objects which may be used by other cached objects.
*
* All cacheability metadata exposed in this interface is bubbled to parent
* objects when they are cached: if a child object needs to be varied by certain
* cache contexts, invalidated by certain cache tags, expire after a certain
* maximum age, then so should any parent object.
*
* @ingroup cache
*/
interface CacheableDependencyInterface {
/**
* The cache contexts associated with this object.
*
* These identify a specific variation/representation of the object.
*
* Cache contexts are tokens: placeholders that are converted to cache keys by
* the @cache_contexts_manager service. The replacement value depends on the
* request context (the current URL, language, and so on). They're converted
* before storing an object in cache.
*
* @return string[]
* An array of cache context tokens, used to generate a cache ID.
*
* @see \Drupal\Core\Cache\Context\CacheContextsManager::convertTokensToKeys()
*/
public function getCacheContexts();
/**
* The cache tags associated with this object.
*
* When this object is modified, these cache tags will be invalidated.
*
* @return string[]
* A set of cache tags.
*/
public function getCacheTags();
/**
* The maximum age for which this object may be cached.
*
* @return int
* The maximum time in seconds that this object may be cached.
*/
public function getCacheMaxAge();
}

View file

@ -0,0 +1,233 @@
<?php
/**
* @file
* Contains \Drupal\Core\Cache\CacheableMetadata.
*/
namespace Drupal\Core\Cache;
/**
* Defines a generic class for passing cacheability metadata.
*
* @ingroup cache
*/
class CacheableMetadata implements CacheableDependencyInterface {
/**
* Cache contexts.
*
* @var string[]
*/
protected $contexts = [];
/**
* Cache tags.
*
* @var string[]
*/
protected $tags = [];
/**
* Cache max-age.
*
* @var int
*/
protected $maxAge = Cache::PERMANENT;
/**
* {@inheritdoc}
*/
public function getCacheTags() {
return $this->tags;
}
/**
* Adds cache tags.
*
* @param string[] $cache_tags
* The cache tags to be added.
*
* @return $this
*/
public function addCacheTags(array $cache_tags) {
$this->tags = Cache::mergeTags($this->tags, $cache_tags);
return $this;
}
/**
* Sets cache tags.
*
* @param string[] $cache_tags
* The cache tags to be associated.
*
* @return $this
*/
public function setCacheTags(array $cache_tags) {
$this->tags = $cache_tags;
return $this;
}
/**
* {@inheritdoc}
*/
public function getCacheContexts() {
return $this->contexts;
}
/**
* Adds cache contexts.
*
* @param string[] $cache_contexts
* The cache contexts to be added.
*
* @return $this
*/
public function addCacheContexts(array $cache_contexts) {
$this->contexts = Cache::mergeContexts($this->contexts, $cache_contexts);
return $this;
}
/**
* Sets cache contexts.
*
* @param string[] $cache_contexts
* The cache contexts to be associated.
*
* @return $this
*/
public function setCacheContexts(array $cache_contexts) {
$this->contexts = $cache_contexts;
return $this;
}
/**
* {@inheritdoc}
*/
public function getCacheMaxAge() {
return $this->maxAge;
}
/**
* Sets the maximum age (in seconds).
*
* Defaults to Cache::PERMANENT
*
* @param int $max_age
* The max age to associate.
*
* @return $this
*
* @throws \InvalidArgumentException
* If a non-integer value is supplied.
*/
public function setCacheMaxAge($max_age) {
if (!is_int($max_age)) {
throw new \InvalidArgumentException('$max_age must be an integer');
}
$this->maxAge = $max_age;
return $this;
}
/**
* Merges the values of another CacheableMetadata object with this one.
*
* @param \Drupal\Core\Cache\CacheableMetadata $other
* The other CacheableMetadata object.
*
* @return static
* A new CacheableMetadata object, with the merged data.
*/
public function merge(CacheableMetadata $other) {
$result = new static();
// This is called many times per request, so avoid merging unless absolutely
// necessary.
if (empty($this->contexts)) {
$result->contexts = $other->contexts;
}
elseif (empty($other->contexts)) {
$result->contexts = $this->contexts;
}
else {
$result->contexts = Cache::mergeContexts($this->contexts, $other->contexts);
}
if (empty($this->tags)) {
$result->tags = $other->tags;
}
elseif (empty($other->tags)) {
$result->tags = $this->tags;
}
else {
$result->tags = Cache::mergeTags($this->tags, $other->tags);
}
if ($this->maxAge === Cache::PERMANENT) {
$result->maxAge = $other->maxAge;
}
elseif ($other->maxAge === Cache::PERMANENT) {
$result->maxAge = $this->maxAge;
}
else {
$result->maxAge = Cache::mergeMaxAges($this->maxAge, $other->maxAge);
}
return $result;
}
/**
* Applies the values of this CacheableMetadata object to a render array.
*
* @param array &$build
* A render array.
*/
public function applyTo(array &$build) {
$build['#cache']['contexts'] = $this->contexts;
$build['#cache']['tags'] = $this->tags;
$build['#cache']['max-age'] = $this->maxAge;
}
/**
* Creates a CacheableMetadata object with values taken from a render array.
*
* @param array $build
* A render array.
*
* @return static
*/
public static function createFromRenderArray(array $build) {
$meta = new static();
$meta->contexts = (isset($build['#cache']['contexts'])) ? $build['#cache']['contexts'] : [];
$meta->tags = (isset($build['#cache']['tags'])) ? $build['#cache']['tags'] : [];
$meta->maxAge = (isset($build['#cache']['max-age'])) ? $build['#cache']['max-age'] : Cache::PERMANENT;
return $meta;
}
/**
* Creates a CacheableMetadata object from a depended object.
*
* @param \Drupal\Core\Cache\CacheableDependencyInterface|mixed $object
* The object whose cacheability metadata to retrieve. If it implements
* CacheableDependencyInterface, its cacheability metadata will be used,
* otherwise, the passed in object must be assumed to be uncacheable, so
* max-age 0 is set.
*
* @return static
*/
public static function createFromObject($object) {
if ($object instanceof CacheableDependencyInterface) {
$meta = new static();
$meta->contexts = $object->getCacheContexts();
$meta->tags = $object->getCacheTags();
$meta->maxAge = $object->getCacheMaxAge();
return $meta;
}
// Objects that don't implement CacheableDependencyInterface must be assumed
// to be uncacheable, so set max-age 0.
$meta = new static();
$meta->maxAge = 0;
return $meta;
}
}

View file

@ -0,0 +1,26 @@
<?php
/**
* @file
* Contains \Drupal\Core\Cache\CacheableResponse.
*/
namespace Drupal\Core\Cache;
use Symfony\Component\HttpFoundation\Response;
/**
* A response that contains and can expose cacheability metadata.
*
* Supports Drupal's caching concepts: cache tags for invalidation and cache
* contexts for variations.
*
* @see \Drupal\Core\Cache\Cache
* @see \Drupal\Core\Cache\CacheableMetadata
* @see \Drupal\Core\Cache\CacheableResponseTrait
*/
class CacheableResponse extends Response implements CacheableResponseInterface {
use CacheableResponseTrait;
}

View file

@ -0,0 +1,42 @@
<?php
/**
* @file
* Contains \Drupal\Core\Cache\CacheableResponseInterface.
*/
namespace Drupal\Core\Cache;
/**
* Defines an interface for responses that can expose cacheability metadata.
*
* @see \Drupal\Core\Cache\CacheableResponseTrait
*/
interface CacheableResponseInterface {
/**
* Adds a dependency on an object: merges its cacheability metadata.
*
* E.g. when a response depends on some configuration, an entity, or an access
* result, we must make sure their cacheability metadata is present on the
* response. This method makes doing that simple.
*
* @param \Drupal\Core\Cache\CacheableDependencyInterface|mixed $dependency
* The dependency. If the object implements CacheableDependencyInterface,
* then its cacheability metadata will be used. Otherwise, the passed in
* object must be assumed to be uncacheable, so max-age 0 is set.
*
* @return $this
*
* @see \Drupal\Core\Cache\CacheableMetadata::createFromObject()
*/
public function addCacheableDependency($dependency);
/**
* Returns the cacheability metadata for this response.
*
* @return \Drupal\Core\Cache\CacheableMetadata
*/
public function getCacheableMetadata();
}

View file

@ -0,0 +1,52 @@
<?php
/**
* @file
* Contains \Drupal\Core\Cache\CacheableResponseTrait.
*/
namespace Drupal\Core\Cache;
/**
* Provides an implementation of CacheableResponseInterface.
*
* @see \Drupal\Core\Cache\CacheableResponseInterface
*/
trait CacheableResponseTrait {
/**
* The cacheability metadata.
*
* @var \Drupal\Core\Cache\CacheableMetadata
*/
protected $cacheabilityMetadata;
/**
* {@inheritdoc}
*/
public function addCacheableDependency($dependency) {
// A trait doesn't have a constructor, so initialize the cacheability
// metadata if that hasn't happened yet.
if (!isset($this->cacheabilityMetadata)) {
$this->cacheabilityMetadata = new CacheableMetadata();
}
$this->cacheabilityMetadata = $this->cacheabilityMetadata->merge(CacheableMetadata::createFromObject($dependency));
return $this;
}
/**
* {@inheritdoc}
*/
public function getCacheableMetadata() {
// A trait doesn't have a constructor, so initialize the cacheability
// metadata if that hasn't happened yet.
if (!isset($this->cacheabilityMetadata)) {
$this->cacheabilityMetadata = new CacheableMetadata();
}
return $this->cacheabilityMetadata;
}
}

View file

@ -0,0 +1,306 @@
<?php
/**
* @file
* Contains \Drupal\Core\Cache\ChainedFastBackend.
*/
namespace Drupal\Core\Cache;
/**
* Defines a backend with a fast and a consistent backend chain.
*
* In order to mitigate a network roundtrip for each cache get operation, this
* cache allows a fast backend to be put in front of a slow(er) backend.
* Typically the fast backend will be something like APCu, and be bound to a
* single web node, and will not require a network round trip to fetch a cache
* item. The fast backend will also typically be inconsistent (will only see
* changes from one web node). The slower backend will be something like Mysql,
* Memcached or Redis, and will be used by all web nodes, thus making it
* consistent, but also require a network round trip for each cache get.
*
* In addition to being useful for sites running on multiple web nodes, this
* backend can also be useful for sites running on a single web node where the
* fast backend (e.g., APCu) isn't shareable between the web and CLI processes.
* Single-node configurations that don't have that limitation can just use the
* fast cache backend directly.
*
* We always use the fast backend when reading (get()) entries from cache, but
* check whether they were created before the last write (set()) to this
* (chained) cache backend. Those cache entries that were created before the
* last write are discarded, but we use their cache IDs to then read them from
* the consistent (slower) cache backend instead; at the same time we update
* the fast cache backend so that the next read will hit the faster backend
* again. Hence we can guarantee that the cache entries we return are all
* up-to-date, and maximally exploit the faster cache backend. This cache
* backend uses and maintains a "last write timestamp" to determine which cache
* entries should be discarded.
*
* Because this backend will mark all the cache entries in a bin as out-dated
* for each write to a bin, it is best suited to bins with fewer changes.
*
* @ingroup cache
*/
class ChainedFastBackend implements CacheBackendInterface, CacheTagsInvalidatorInterface {
/**
* Cache key prefix for the bin-specific entry to track the last write.
*/
const LAST_WRITE_TIMESTAMP_PREFIX = 'last_write_timestamp_';
/**
* @var string
*/
protected $bin;
/**
* The consistent cache backend.
*
* @var \Drupal\Core\Cache\CacheBackendInterface
*/
protected $consistentBackend;
/**
* The fast cache backend.
*
* @var \Drupal\Core\Cache\CacheBackendInterface
*/
protected $fastBackend;
/**
* The time at which the last write to this cache bin happened.
*
* @var float
*/
protected $lastWriteTimestamp;
/**
* Constructs a ChainedFastBackend object.
*
* @param \Drupal\Core\Cache\CacheBackendInterface $consistent_backend
* The consistent cache backend.
* @param \Drupal\Core\Cache\CacheBackendInterface $fast_backend
* The fast cache backend.
* @param string $bin
* The cache bin for which the object is created.
*/
public function __construct(CacheBackendInterface $consistent_backend, CacheBackendInterface $fast_backend, $bin) {
$this->consistentBackend = $consistent_backend;
$this->fastBackend = $fast_backend;
$this->bin = 'cache_' . $bin;
$this->lastWriteTimestamp = NULL;
}
/**
* {@inheritdoc}
*/
public function get($cid, $allow_invalid = FALSE) {
$cids = array($cid);
$cache = $this->getMultiple($cids, $allow_invalid);
return reset($cache);
}
/**
* {@inheritdoc}
*/
public function getMultiple(&$cids, $allow_invalid = FALSE) {
$cids_copy = $cids;
$cache = array();
// If we can determine the time at which the last write to the consistent
// backend occurred (we might not be able to if it has been recently
// flushed/restarted), then we can use that to validate items from the fast
// backend, so try to get those first. Otherwise, we can't assume that
// anything in the fast backend is valid, so don't even bother fetching
// from there.
$last_write_timestamp = $this->getLastWriteTimestamp();
if ($last_write_timestamp) {
// Items in the fast backend might be invalid based on their timestamp,
// but we can't check the timestamp prior to getting the item, which
// includes unserializing it. However, unserializing an invalid item can
// throw an exception. For example, a __wakeup() implementation that
// receives object properties containing references to code or data that
// no longer exists in the application's current state.
//
// Unserializing invalid data, whether it throws an exception or not, is
// a waste of time, but we only incur it while a cache invalidation has
// not yet finished propagating to all the fast backend instances.
//
// Most cache backend implementations should not wrap their internal
// get() implementations with a try/catch, because they have no reason to
// assume that their data is invalid, and doing so would mask
// unserialization errors of valid data. We do so here, only because the
// fast backend is non-authoritative, and after discarding its
// exceptions, we proceed to check the consistent (authoritative) backend
// and allow exceptions from that to bubble up.
try {
$items = $this->fastBackend->getMultiple($cids, $allow_invalid);
}
catch (\Exception $e) {
$cids = $cids_copy;
$items = array();
}
// Even if items were successfully fetched from the fast backend, they
// are potentially invalid if older than the last time the bin was
// written to in the consistent backend, so only keep ones that aren't.
foreach ($items as $item) {
if ($item->created < $last_write_timestamp) {
$cids[array_search($item->cid, $cids_copy)] = $item->cid;
}
else {
$cache[$item->cid] = $item;
}
}
}
// If there were any cache entries that were not available in the fast
// backend, retrieve them from the consistent backend and store them in the
// fast one.
if ($cids) {
foreach ($this->consistentBackend->getMultiple($cids, $allow_invalid) as $item) {
$cache[$item->cid] = $item;
// Don't write the cache tags to the fast backend as any cache tag
// invalidation results in an invalidation of the whole fast backend.
$this->fastBackend->set($item->cid, $item->data, $item->expire);
}
}
return $cache;
}
/**
* {@inheritdoc}
*/
public function set($cid, $data, $expire = Cache::PERMANENT, array $tags = array()) {
$this->consistentBackend->set($cid, $data, $expire, $tags);
$this->markAsOutdated();
// Don't write the cache tags to the fast backend as any cache tag
// invalidation results in an invalidation of the whole fast backend.
$this->fastBackend->set($cid, $data, $expire);
}
/**
* {@inheritdoc}
*/
public function setMultiple(array $items) {
$this->consistentBackend->setMultiple($items);
$this->markAsOutdated();
// Don't write the cache tags to the fast backend as any cache tag
// invalidation results in an invalidation of the whole fast backend.
foreach ($items as &$item) {
unset($item['tags']);
}
$this->fastBackend->setMultiple($items);
}
/**
* {@inheritdoc}
*/
public function delete($cid) {
$this->consistentBackend->deleteMultiple(array($cid));
$this->markAsOutdated();
}
/**
* {@inheritdoc}
*/
public function deleteMultiple(array $cids) {
$this->consistentBackend->deleteMultiple($cids);
$this->markAsOutdated();
}
/**
* {@inheritdoc}
*/
public function deleteAll() {
$this->consistentBackend->deleteAll();
$this->markAsOutdated();
}
/**
* {@inheritdoc}
*/
public function invalidate($cid) {
$this->invalidateMultiple(array($cid));
}
/**
* {@inheritdoc}
*/
public function invalidateMultiple(array $cids) {
$this->consistentBackend->invalidateMultiple($cids);
$this->markAsOutdated();
}
/**
* {@inheritdoc}
*/
public function invalidateTags(array $tags) {
if ($this->consistentBackend instanceof CacheTagsInvalidatorInterface) {
$this->consistentBackend->invalidateTags($tags);
}
$this->markAsOutdated();
}
/**
* {@inheritdoc}
*/
public function invalidateAll() {
$this->consistentBackend->invalidateAll();
$this->markAsOutdated();
}
/**
* {@inheritdoc}
*/
public function garbageCollection() {
$this->consistentBackend->garbageCollection();
$this->fastBackend->garbageCollection();
}
/**
* {@inheritdoc}
*/
public function removeBin() {
$this->consistentBackend->removeBin();
$this->fastBackend->removeBin();
}
/**
* @todo Document in https://www.drupal.org/node/2311945.
*/
public function reset() {
$this->lastWriteTimestamp = NULL;
}
/**
* Gets the last write timestamp.
*/
protected function getLastWriteTimestamp() {
if ($this->lastWriteTimestamp === NULL) {
$cache = $this->consistentBackend->get(self::LAST_WRITE_TIMESTAMP_PREFIX . $this->bin);
$this->lastWriteTimestamp = $cache ? $cache->data : 0;
}
return $this->lastWriteTimestamp;
}
/**
* Marks the fast cache bin as outdated because of a write.
*/
protected function markAsOutdated() {
// Clocks on a single server can drift. Multiple servers may have slightly
// differing opinions about the current time. Given that, do not assume
// 'now' on this server is always later than our stored timestamp.
// Also add 1 millisecond, to ensure that caches written earlier in the same
// millisecond are invalidated. It is possible that caches will be later in
// the same millisecond and are then incorrectly invalidated, but that only
// costs one additional roundtrip to the persistent cache.
$now = round(microtime(TRUE) + .001, 3);
if ($now > $this->getLastWriteTimestamp()) {
$this->lastWriteTimestamp = $now;
$this->consistentBackend->set(self::LAST_WRITE_TIMESTAMP_PREFIX . $this->bin, $this->lastWriteTimestamp);
}
}
}

View file

@ -0,0 +1,94 @@
<?php
/**
* @file
* Contains \Drupal\Core\Cache\ChainedFastBackendFactory.
*/
namespace Drupal\Core\Cache;
use Drupal\Core\Site\Settings;
use Symfony\Component\DependencyInjection\ContainerAwareTrait;
/**
* Defines the chained fast cache backend factory.
*/
class ChainedFastBackendFactory implements CacheFactoryInterface {
use ContainerAwareTrait;
/**
* The service name of the consistent backend factory.
*
* @var string
*/
protected $consistentServiceName;
/**
* The service name of the fast backend factory.
*
* @var string
*/
protected $fastServiceName;
/**
* Constructs ChainedFastBackendFactory object.
*
* @param \Drupal\Core\Site\Settings|NULL $settings
* (optional) The settings object.
* @param string|NULL $consistent_service_name
* (optional) The service name of the consistent backend factory. Defaults
* to:
* - $settings->get('cache')['default'] (if specified)
* - 'cache.backend.database' (if the above isn't specified)
* @param string|NULL $fast_service_name
* (optional) The service name of the fast backend factory. Defaults to:
* - 'cache.backend.apcu' (if the PHP process has APCu enabled)
* - NULL (if the PHP process doesn't have APCu enabled)
*/
public function __construct(Settings $settings = NULL, $consistent_service_name = NULL, $fast_service_name = NULL) {
// Default the consistent backend to the site's default backend.
if (!isset($consistent_service_name)) {
$cache_settings = isset($settings) ? $settings->get('cache') : array();
$consistent_service_name = isset($cache_settings['default']) ? $cache_settings['default'] : 'cache.backend.database';
}
// Default the fast backend to APCu if it's available.
if (!isset($fast_service_name) && function_exists('apc_fetch')) {
$fast_service_name = 'cache.backend.apcu';
}
$this->consistentServiceName = $consistent_service_name;
// Do not use the fast chained backend during installation. In those cases,
// we expect many cache invalidations and writes, the fast chained cache
// backend performs badly in such a scenario.
if (!drupal_installation_attempted()) {
$this->fastServiceName = $fast_service_name;
}
}
/**
* Instantiates a chained, fast cache backend class for a given cache bin.
*
* @param string $bin
* The cache bin for which a cache backend object should be returned.
*
* @return \Drupal\Core\Cache\CacheBackendInterface
* The cache backend object associated with the specified bin.
*/
public function get($bin) {
// Use the chained backend only if there is a fast backend available;
// otherwise, just return the consistent backend directly.
if (isset($this->fastServiceName)) {
return new ChainedFastBackend(
$this->container->get($this->consistentServiceName)->get($bin),
$this->container->get($this->fastServiceName)->get($bin),
$bin
);
}
else {
return $this->container->get($this->consistentServiceName)->get($bin);
}
}
}

View file

@ -0,0 +1,52 @@
<?php
/**
* @file
* Contains \Drupal\Core\Cache\Context\AccountPermissionsCacheContext.
*/
namespace Drupal\Core\Cache\Context;
use Drupal\Core\Session\AccountInterface;
use Drupal\Core\Session\PermissionsHashGeneratorInterface;
/**
* Defines the AccountPermissionsCacheContext service, for "per permission" caching.
*/
class AccountPermissionsCacheContext extends UserCacheContext {
/**
* The permissions hash generator.
*
* @var \Drupal\user\PermissionsHashInterface
*/
protected $permissionsHashGenerator;
/**
* Constructs a new UserCacheContext service.
*
* @param \Drupal\Core\Session\AccountInterface $user
* The current user.
* @param \Drupal\user\PermissionsHashInterface $permissions_hash_generator
* The permissions hash generator.
*/
public function __construct(AccountInterface $user, PermissionsHashGeneratorInterface $permissions_hash_generator) {
$this->user = $user;
$this->permissionsHashGenerator = $permissions_hash_generator;
}
/**
* {@inheritdoc}
*/
public static function getLabel() {
return t("Account's permissions");
}
/**
* {@inheritdoc}
*/
public function getContext() {
return 'ph.' . $this->permissionsHashGenerator->generate($this->user);
}
}

View file

@ -0,0 +1,34 @@
<?php
/**
* @file
* Contains \Drupal\Core\Cache\Context\CacheContextInterface.
*/
namespace Drupal\Core\Cache\Context;
/**
* 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();
}

View file

@ -0,0 +1,274 @@
<?php
/**
* @file
* Contains \Drupal\Core\Cache\Context\CacheContextsManager.
*/
namespace Drupal\Core\Cache\Context;
use Drupal\Component\Utility\SafeMarkup;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* Converts cache context tokens into cache keys.
*
* Uses cache context services (services tagged with 'cache.context', and whose
* service ID has the 'cache_context.' prefix) to dynamically generate cache
* keys based on the request context, thus allowing developers to express the
* state by which should varied (the current URL, language, and so on).
*
* Note that this maps exactly to HTTP's Vary header semantics:
* @link http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.44
*
* @see \Drupal\Core\Cache\Context\CacheContextInterface
* @see \Drupal\Core\Cache\Context\CalculatedCacheContextInterface
* @see \Drupal\Core\Cache\Context\CacheContextsPass
*/
class CacheContextsManager {
/**
* The service container.
*
* @var \Symfony\Component\DependencyInjection\ContainerInterface
*/
protected $container;
/**
* Available cache context IDs and corresponding labels.
*
* @var string[]
*/
protected $contexts;
/**
* Constructs a CacheContextsManager object.
*
* @param \Symfony\Component\DependencyInjection\ContainerInterface $container
* The current service container.
* @param string[] $contexts
* An array of the available cache context IDs.
*/
public function __construct(ContainerInterface $container, array $contexts) {
$this->container = $container;
$this->contexts = $contexts;
}
/**
* Provides an array of available cache contexts.
*
* @return string[]
* An array of available cache context IDs.
*/
public function getAll() {
return $this->contexts;
}
/**
* Provides an array of available cache context labels.
*
* To be used in cache configuration forms.
*
* @param bool $include_calculated_cache_contexts
* Whether to also return calculated cache contexts. Default to FALSE.
*
* @return array
* An array of available cache contexts and corresponding labels.
*/
public function getLabels($include_calculated_cache_contexts = FALSE) {
$with_labels = array();
foreach ($this->contexts as $context) {
$service = $this->getService($context);
if (!$include_calculated_cache_contexts && $service instanceof CalculatedCacheContextInterface) {
continue;
}
$with_labels[$context] = $service->getLabel();
}
return $with_labels;
}
/**
* Converts cache context tokens to cache keys.
*
* A cache context token is either:
* - a cache context ID (if the service ID is 'cache_context.foo', then 'foo'
* is a cache context ID), e.g. 'foo'
* - a calculated cache context ID, followed by a double colon, followed by
* the parameter for the calculated cache context, e.g. 'bar:some_parameter'
*
* @param string[] $context_tokens
* An array of cache context tokens.
*
* @return string[]
* The array of corresponding cache keys.
*
* @throws \InvalidArgumentException
*/
public function convertTokensToKeys(array $context_tokens) {
$context_tokens = $this->optimizeTokens($context_tokens);
sort($context_tokens);
$keys = [];
foreach (static::parseTokens($context_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;
}
/**
* Optimizes cache context tokens (the minimal representative subset).
*
* A minimal representative subset means that any cache context token in the
* given set of cache context tokens that is a property of another cache
* context cache context token in the set, is removed.
*
* Hence a minimal representative subset is the most compact representation
* possible of a set of cache context tokens, that still captures the entire
* universe of variations.
*
* E.g. when caching per user ('user'), also caching per role ('user.roles')
* is meaningless because "per role" is implied by "per user".
*
* Examples remember that the period indicates hierarchy and the colon can
* be used to get a specific value of a calculated cache context:
* - ['a', 'a.b'] -> ['a']
* - ['a', 'a.b.c'] -> ['a']
* - ['a.b', 'a.b.c'] -> ['a.b']
* - ['a', 'a.b', 'a.b.c'] -> ['a']
* - ['x', 'x:foo'] -> ['x']
* - ['a', 'a.b.c:bar'] -> ['a']
*
* @param string[] $context_tokens
* A set of cache context tokens.
*
* @return string[]
* A representative subset of the given set of cache context tokens..
*/
public function optimizeTokens(array $context_tokens) {
$optimized_content_tokens = [];
foreach ($context_tokens as $context_token) {
// Context tokens without:
// - a period means they don't have a parent
// - a colon means they're not a specific value of a cache context
// hence no optimizations are possible.
if (strpos($context_token, '.') === FALSE && strpos($context_token, ':') === FALSE) {
$optimized_content_tokens[] = $context_token;
}
// The context token has a period or a colon. Iterate over all ancestor
// cache contexts. If one exists, omit the context token.
else {
$ancestor_found = FALSE;
// Treat a colon like a period, that allows us to consider 'a' the
// ancestor of 'a:foo', without any additional code for the colon.
$ancestor = str_replace(':', '.', $context_token);
do {
$ancestor = substr($ancestor, 0, strrpos($ancestor, '.'));
if (in_array($ancestor, $context_tokens)) {
// An ancestor cache context is in $context_tokens, hence this cache
// context is implied.
$ancestor_found = TRUE;
}
} while(!$ancestor_found && strpos($ancestor, '.') !== FALSE);
if (!$ancestor_found) {
$optimized_content_tokens[] = $context_token;
}
}
}
return $optimized_content_tokens;
}
/**
* Retrieves a cache context service from the container.
*
* @param string $context_id
* The context ID, which together with the service ID prefix allows the
* corresponding cache context service to be retrieved.
*
* @return \Drupal\Core\Cache\Context\CacheContextInterface
* The requested cache context service.
*/
protected function getService($context_id) {
return $this->container->get('cache_context.' . $context_id);
}
/**
* Parses cache context tokens into context IDs and optional parameters.
*
* @param string[] $context_tokens
* An array of cache context tokens.
*
* @return array
* An array with the parsed results, with each result being an array
* containing:
* - The cache context ID.
* - The associated parameter (for a calculated cache context), or NULL if
* there is no parameter.
*/
public static function parseTokens(array $context_tokens) {
$contexts_with_parameters = [];
foreach ($context_tokens as $context) {
$context_id = $context;
$parameter = NULL;
if (strpos($context, ':') !== FALSE) {
list($context_id, $parameter) = explode(':', $context, 2);
}
$contexts_with_parameters[] = [$context_id, $parameter];
}
return $contexts_with_parameters;
}
/**
* Validates an array of cache context tokens.
*
* Can be called before using cache contexts in operations, to check validity.
*
* @param string[] $context_tokens
* An array of cache context tokens.
*
* @throws \LogicException
*
* @see \Drupal\Core\Cache\Context\CacheContextsManager::parseTokens()
*/
public function validateTokens(array $context_tokens = []) {
if (empty($context_tokens)) {
return;
}
// Initialize the set of valid context tokens with the container's contexts.
if (!isset($this->validContextTokens)) {
$this->validContextTokens = array_flip($this->contexts);
}
foreach ($context_tokens as $context_token) {
if (!is_string($context_token)) {
throw new \LogicException(sprintf('Cache contexts must be strings, %s given.', gettype($context_token)));
}
if (isset($this->validContextTokens[$context_token])) {
continue;
}
// If it's a valid context token, then the ID must be stored in the set
// of valid context tokens (since we initialized it with the list of cache
// context IDs using the container). In case of an invalid context token,
// throw an exception, otherwise cache it, including the parameter, to
// minimize the amount of work in future ::validateContexts() calls.
$context_id = $context_token;
$colon_pos = strpos($context_id, ':');
if ($colon_pos !== FALSE) {
$context_id = substr($context_id, 0, $colon_pos);
}
if (isset($this->validContextTokens[$context_id])) {
$this->validContextTokens[$context_token] = TRUE;
}
else {
throw new \LogicException(sprintf('"%s" is not a valid cache context ID.', $context_id));
}
}
}
}

View file

@ -0,0 +1,48 @@
<?php
/**
* @file
* Contains \Drupal\Core\Cache\Context\CacheContextsPass.
*/
namespace Drupal\Core\Cache\Context;
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 = [];
foreach (array_keys($container->findTaggedServiceIds('cache.context')) as $id) {
if (strpos($id, 'cache_context.') !== 0) {
throw new \InvalidArgumentException(sprintf('The service "%s" has an invalid service ID: cache context service IDs must use the "cache_context." prefix. (The suffix is the cache context ID developers may use.)', $id));
}
$cache_contexts[] = substr($id, 14);
}
// Validate.
sort($cache_contexts);
foreach ($cache_contexts as $id) {
// Validate the hierarchy of non-root-level cache contexts.
if (strpos($id, '.') !== FALSE) {
$parent = substr($id, 0, strrpos($id, '.'));
if (!in_array($parent, $cache_contexts)) {
throw new \InvalidArgumentException(sprintf('The service "%s" has an invalid service ID: the period indicates the hierarchy of cache contexts, therefore "%s" is considered the parent cache context, but no cache context service with that name was found.', $id, $parent));
}
}
}
$container->setParameter('cache_contexts', $cache_contexts);
}
}

View file

@ -0,0 +1,40 @@
<?php
/**
* @file
* Contains \Drupal\Core\Cache\Context\CalculatedCacheContextInterface.
*/
namespace Drupal\Core\Cache\Context;
/**
* Provides an interface for defining a calculated cache context service.
*/
interface CalculatedCacheContextInterface {
/**
* Returns the label of the cache context.
*
* @return string
* The label of the cache context.
*
* @see Cache
*/
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.
*
* @param string|null $parameter
* The parameter, or NULL to indicate all possible parameter values.
*
* @return string
* The string representation of the cache context. When $parameter is NULL,
* a value representing all possible parameters must be generated.
*/
public function getContext($parameter = NULL);
}

View file

@ -0,0 +1,34 @@
<?php
/**
* @file
* Contains \Drupal\Core\Cache\Context\CookiesCacheContext.
*/
namespace Drupal\Core\Cache\Context;
/**
* Defines the CookiesCacheContext service, for "per cookie" caching.
*/
class CookiesCacheContext extends RequestStackCacheContextBase implements CalculatedCacheContextInterface {
/**
* {@inheritdoc}
*/
public static function getLabel() {
return t('HTTP cookies');
}
/**
* {@inheritdoc}
*/
public function getContext($cookie = NULL) {
if ($cookie === NULL) {
return $this->requestStack->getCurrentRequest()->cookies->all();
}
else {
return $this->requestStack->getCurrentRequest()->cookies->get($cookie);
}
}
}

View file

@ -0,0 +1,34 @@
<?php
/**
* @file
* Contains \Drupal\Core\Cache\Context\HeadersCacheContext.
*/
namespace Drupal\Core\Cache\Context;
/**
* Defines the HeadersCacheContext service, for "per header" caching.
*/
class HeadersCacheContext extends RequestStackCacheContextBase implements CalculatedCacheContextInterface {
/**
* {@inheritdoc}
*/
public static function getLabel() {
return t('HTTP headers');
}
/**
* {@inheritdoc}
*/
public function getContext($header = NULL) {
if ($header === NULL) {
return $this->requestStack->getCurrentRequest()->headers->all();
}
else {
return $this->requestStack->getCurrentRequest()->headers->get($header);
}
}
}

View file

@ -0,0 +1,29 @@
<?php
/**
* @file
* Contains \Drupal\Core\Cache\Context\IpCacheContext.
*/
namespace Drupal\Core\Cache\Context;
/**
* Defines the IpCacheContext service, for "per IP address" caching.
*/
class IpCacheContext extends RequestStackCacheContextBase {
/**
* {@inheritdoc}
*/
public static function getLabel() {
return t('IP address');
}
/**
* {@inheritdoc}
*/
public function getContext() {
return $this->requestStack->getCurrentRequest()->getClientIp();
}
}

View file

@ -0,0 +1,29 @@
<?php
/**
* @file
* Contains \Drupal\Core\Cache\Context\IsSuperUserCacheContext.
*/
namespace Drupal\Core\Cache\Context;
/**
* Defines the IsSuperUserCacheContext service, for "super user or not" caching.
*/
class IsSuperUserCacheContext extends UserCacheContext {
/**
* {@inheritdoc}
*/
public static function getLabel() {
return t('Is super user');
}
/**
* {@inheritdoc}
*/
public function getContext() {
return ((int) $this->user->id()) === 1 ? '1' : '0';
}
}

View file

@ -0,0 +1,77 @@
<?php
/**
* @file
* Contains \Drupal\Core\Cache\Context\LanguagesCacheContext.
*/
namespace Drupal\Core\Cache\Context;
use Drupal\Core\Language\LanguageManagerInterface;
/**
* Defines the LanguagesCacheContext service, for "per language" caching.
*/
class LanguagesCacheContext implements CalculatedCacheContextInterface {
/**
* The language manager.
*
* @param \Drupal\Core\Language\LanguageManagerInterface $language_manager
*/
protected $languageManager;
/**
* Constructs a new LanguagesCacheContext 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}
*
* $type can be NULL, or one of the language types supported by the language
* manager, typically:
* - LanguageInterface::TYPE_INTERFACE
* - LanguageInterface::TYPE_CONTENT
* - LanguageInterface::TYPE_URL
*
* @see \Drupal\Core\Language\LanguageManagerInterface::getLanguageTypes()
*
* @throws \RuntimeException
* In case an invalid language type is specified.
*/
public function getContext($type = NULL) {
if ($type === NULL) {
$context_parts = array();
if ($this->languageManager->isMultilingual()) {
foreach ($this->languageManager->getLanguageTypes() as $type) {
$context_parts[] = $this->languageManager->getCurrentLanguage($type)->getId();
}
}
else {
$context_parts[] = $this->languageManager->getCurrentLanguage()->getId();
}
return implode(',', $context_parts);
}
else {
$language_types = $this->languageManager->getDefinedLanguageTypesInfo();
if (!isset($language_types[$type])) {
throw new \RuntimeException(sprintf('The language type "%s" is invalid.', $type));
}
return $this->languageManager->getCurrentLanguage($type)->getId();
}
}
}

View file

@ -0,0 +1,36 @@
<?php
/**
* @file
* Contains \Drupal\Core\Cache\Context\MenuActiveTrailsCacheContext.
*/
namespace Drupal\Core\Cache\Context;
use Symfony\Component\DependencyInjection\ContainerAware;
/**
* Defines the MenuActiveTrailsCacheContext service.
*
* This class is container-aware to avoid initializing the 'menu.active_trail'
* service (and its dependencies) when it is not necessary.
*/
class MenuActiveTrailsCacheContext extends ContainerAware implements CalculatedCacheContextInterface {
/**
* {@inheritdoc}
*/
public static function getLabel() {
return t("Active menu trail");
}
/**
* {@inheritdoc}
*/
public function getContext($menu_name = NULL) {
$active_trail = $this->container->get('menu.active_trail')
->getActiveTrailIds($menu_name);
return 'menu_trail.' . $menu_name . '|' . implode('|', $active_trail);
}
}

View file

@ -0,0 +1,37 @@
<?php
/**
* @file
* Contains \Drupal\Core\Cache\Context\PagersCacheContext.
*/
namespace Drupal\Core\Cache\Context;
/**
* Defines a cache context for "per page in a pager" caching.
*/
class PagersCacheContext extends RequestStackCacheContextBase implements CalculatedCacheContextInterface {
/**
* {@inheritdoc}
*/
public static function getLabel() {
return t('Pager');
}
/**
* {@inheritdoc}
*
* @see pager_find_page()
*/
public function getContext($pager_id = NULL) {
// The value of the 'page' query argument contains the information that
// controls *all* pagers.
if ($pager_id === NULL) {
return 'pager' . $this->requestStack->getCurrentRequest()->query->get('page', '');
}
return 'pager.' . $pager_id . '.' . pager_find_page($pager_id);
}
}

View file

@ -0,0 +1,38 @@
<?php
/**
* @file
* Contains \Drupal\Core\Cache\Context\QueryArgsCacheContext.
*/
namespace Drupal\Core\Cache\Context;
/**
* Defines the QueryArgsCacheContext service, for "per query args" caching.
*
* A "host" is defined as the combination of URI scheme, domain name and port.
*
* @see Symfony\Component\HttpFoundation::getSchemeAndHttpHost()
*/
class QueryArgsCacheContext extends RequestStackCacheContextBase implements CalculatedCacheContextInterface {
/**
* {@inheritdoc}
*/
public static function getLabel() {
return t('Query arguments');
}
/**
* {@inheritdoc}
*/
public function getContext($query_arg = NULL) {
if ($query_arg === NULL) {
return $this->requestStack->getCurrentRequest()->getQueryString();
}
else {
return $this->requestStack->getCurrentRequest()->query->get($query_arg);
}
}
}

View file

@ -0,0 +1,29 @@
<?php
/**
* @file
* Contains \Drupal\Core\Cache\Context\RequestFormatCacheContext.
*/
namespace Drupal\Core\Cache\Context;
/**
* Defines the RequestFormatCacheContext service, for "per format" caching.
*/
class RequestFormatCacheContext extends RequestStackCacheContextBase {
/**
* {@inheritdoc}
*/
public static function getLabel() {
return t('Request format');
}
/**
* {@inheritdoc}
*/
public function getContext() {
return $this->requestStack->getCurrentRequest()->getRequestFormat();
}
}

View file

@ -0,0 +1,34 @@
<?php
/**
* @file
* Contains \Drupal\Core\Cache\Context\RequestStackCacheContextBase.
*/
namespace Drupal\Core\Cache\Context;
use Symfony\Component\HttpFoundation\RequestStack;
/**
* Defines a base class for cache contexts depending only on the request stack.
*/
abstract class RequestStackCacheContextBase implements CacheContextInterface {
/**
* The request stack.
*
* @var \Symfony\Component\HttpFoundation\RequestStack
*/
protected $requestStack;
/**
* Constructs a new RequestStackCacheContextBase class.
*
* @param \Symfony\Component\HttpFoundation\RequestStack $request_stack
* The request stack.
*/
public function __construct(RequestStack $request_stack) {
$this->requestStack = $request_stack;
}
}

View file

@ -0,0 +1,48 @@
<?php
/**
* @file
* Contains \Drupal\Core\Cache\Context\RouteCacheContext.
*/
namespace Drupal\Core\Cache\Context;
use Drupal\Core\Routing\RouteMatchInterface;
/**
* Defines the RouteCacheContext service, for "per route" caching.
*/
class RouteCacheContext implements CacheContextInterface {
/**
* The route match.
*
* @var \Drupal\Core\Routing\RouteMatchInterface
*/
protected $routeMatch;
/**
* Constructs a new RouteCacheContext class.
*
* @param \Drupal\Core\Routing\RouteMatchInterface $route_match
* The route match.
*/
public function __construct(RouteMatchInterface $route_match) {
$this->routeMatch = $route_match;
}
/**
* {@inheritdoc}
*/
public static function getLabel() {
return t('Route');
}
/**
* {@inheritdoc}
*/
public function getContext() {
return $this->routeMatch->getRouteName() . hash('sha256', serialize($this->routeMatch->getRawParameters()->all()));
}
}

View file

@ -0,0 +1,29 @@
<?php
/**
* @file
* Contains \Drupal\Core\Cache\Context\RouteNameCacheContext.
*/
namespace Drupal\Core\Cache\Context;
/**
* Defines the RouteCacheContext service, for "per route name" caching.
*/
class RouteNameCacheContext extends RouteCacheContext {
/**
* {@inheritdoc}
*/
public static function getLabel() {
return t('Route name');
}
/**
* {@inheritdoc}
*/
public function getContext() {
return $this->routeMatch->getRouteName();
}
}

View file

@ -0,0 +1,38 @@
<?php
/**
* @file
* Contains \Drupal\Core\Cache\Context\SiteCacheContext.
*/
namespace Drupal\Core\Cache\Context;
/**
* Defines the SiteCacheContext service, for "per site" caching.
*
* A "site" is defined as the combination of URI scheme, domain name, port and
* base path. It allows for varying between the *same* site being accessed via
* different entry points. (Different sites in a multisite setup have separate
* databases.) For example: http://example.com and http://www.example.com.
*
* @see \Symfony\Component\HttpFoundation\Request::getSchemeAndHttpHost()
* @see \Symfony\Component\HttpFoundation\Request::getBaseUrl()
*/
class SiteCacheContext extends RequestStackCacheContextBase {
/**
* {@inheritdoc}
*/
public static function getLabel() {
return t('Site');
}
/**
* {@inheritdoc}
*/
public function getContext() {
$request = $this->requestStack->getCurrentRequest();
return $request->getSchemeAndHttpHost() . $request->getBaseUrl();
}
}

View file

@ -0,0 +1,49 @@
<?php
/**
* @file
* Contains \Drupal\Core\Cache\Context\ThemeCacheContext.
*/
namespace Drupal\Core\Cache\Context;
use Drupal\Core\Routing\RouteMatchInterface;
use Drupal\Core\Theme\ThemeManagerInterface;
/**
* Defines the ThemeCacheContext service, for "per theme" caching.
*/
class ThemeCacheContext implements CacheContextInterface {
/**
* The theme manager.
*
* @var \Drupal\Core\Theme\ThemeManagerInterface
*/
protected $themeManager;
/**
* Constructs a new ThemeCacheContext service.
*
* @param \Drupal\Core\Theme\ThemeManagerInterface $theme_manager
* The theme manager.
*/
public function __construct(ThemeManagerInterface $theme_manager) {
$this->themeManager = $theme_manager;
}
/**
* {@inheritdoc}
*/
public static function getLabel() {
return t('Theme');
}
/**
* {@inheritdoc}
*/
public function getContext() {
return $this->themeManager->getActiveTheme()->getName() ?: 'stark';
}
}

View file

@ -0,0 +1,33 @@
<?php
/**
* @file
* Contains \Drupal\Core\Cache\Context\TimeZoneCacheContext.
*/
namespace Drupal\Core\Cache\Context;
/**
* Defines the TimeZoneCacheContext service, for "per time zone" caching.
*
* @see \Drupal\Core\Session\AccountProxy::setAccount()
*/
class TimeZoneCacheContext implements CacheContextInterface {
/**
* {@inheritdoc}
*/
public static function getLabel() {
return t("Time zone");
}
/**
* {@inheritdoc}
*/
public function getContext() {
// date_default_timezone_set() is called in AccountProxy::setAccount(), so
// we can safely retrieve the timezone.
return date_default_timezone_get();
}
}

View file

@ -0,0 +1,29 @@
<?php
/**
* @file
* Contains \Drupal\Core\Cache\Context\UrlCacheContext.
*/
namespace Drupal\Core\Cache\Context;
/**
* Defines the UrlCacheContext service, for "per page" caching.
*/
class UrlCacheContext extends RequestStackCacheContextBase {
/**
* {@inheritdoc}
*/
public static function getLabel() {
return t('URL');
}
/**
* {@inheritdoc}
*/
public function getContext() {
return $this->requestStack->getCurrentRequest()->getUri();
}
}

View file

@ -0,0 +1,41 @@
<?php
/**
* @file
* Contains \Drupal\Core\Cache\Context\UserCacheContext.
*/
namespace Drupal\Core\Cache\Context;
use Drupal\Core\Session\AccountInterface;
/**
* Defines the UserCacheContext service, for "per user" caching.
*/
class UserCacheContext implements CacheContextInterface {
/**
* Constructs a new UserCacheContext service.
*
* @param \Drupal\Core\Session\AccountInterface $user
* The current user.
*/
public function __construct(AccountInterface $user) {
$this->user = $user;
}
/**
* {@inheritdoc}
*/
public static function getLabel() {
return t('User');
}
/**
* {@inheritdoc}
*/
public function getContext() {
return "u." . $this->user->id();
}
}

View file

@ -0,0 +1,44 @@
<?php
/**
* @file
* Contains \Drupal\Core\Cache\Context\UserRolesCacheContext.
*/
namespace Drupal\Core\Cache\Context;
/**
* Defines the UserRolesCacheContext service, for "per role" caching.
*
* Only use this cache context when checking explicitly for certain roles. Use
* user.permissions for anything that checks permissions.
*/
class UserRolesCacheContext extends UserCacheContext implements CalculatedCacheContextInterface{
/**
* {@inheritdoc}
*/
public static function getLabel() {
return t("User's roles");
}
/**
* {@inheritdoc}
*/
public function getContext($role = NULL) {
// User 1 does not actually have any special behavior for roles; this is
// added as additional security and backwards compatibility protection for
// SA-CORE-2015-002.
// @todo Remove in Drupal 9.0.0.
if ($this->user->id() == 1) {
return 'is-super-user';
}
if ($role === NULL) {
return 'r.' . implode(',', $this->user->getRoles());
}
else {
return 'r.' . $role . '.' . (in_array($role, $this->user->getRoles()) ? '0' : '1');
}
}
}

View file

@ -0,0 +1,510 @@
<?php
/**
* @file
* Contains \Drupal\Core\Cache\DatabaseBackend.
*/
namespace Drupal\Core\Cache;
use Drupal\Component\Utility\Crypt;
use Drupal\Core\Database\Connection;
use Drupal\Core\Database\SchemaObjectExistsException;
/**
* Defines a default cache implementation.
*
* This is Drupal's default cache implementation. It uses the database to store
* cached data. Each cache bin corresponds to a database table by the same name.
*
* @ingroup cache
*/
class DatabaseBackend implements CacheBackendInterface {
/**
* @var string
*/
protected $bin;
/**
* The database connection.
*
* @var \Drupal\Core\Database\Connection
*/
protected $connection;
/**
* The cache tags checksum provider.
*
* @var \Drupal\Core\Cache\CacheTagsChecksumInterface
*/
protected $checksumProvider;
/**
* Constructs a DatabaseBackend object.
*
* @param \Drupal\Core\Database\Connection $connection
* The database connection.
* @param \Drupal\Core\Cache\CacheTagsChecksumInterface $checksum_provider
* The cache tags checksum provider.
* @param string $bin
* The cache bin for which the object is created.
*/
public function __construct(Connection $connection, CacheTagsChecksumInterface $checksum_provider, $bin) {
// All cache tables should be prefixed with 'cache_'.
$bin = 'cache_' . $bin;
$this->bin = $bin;
$this->connection = $connection;
$this->checksumProvider = $checksum_provider;
}
/**
* Implements Drupal\Core\Cache\CacheBackendInterface::get().
*/
public function get($cid, $allow_invalid = FALSE) {
$cids = array($cid);
$cache = $this->getMultiple($cids, $allow_invalid);
return reset($cache);
}
/**
* Implements Drupal\Core\Cache\CacheBackendInterface::getMultiple().
*/
public function getMultiple(&$cids, $allow_invalid = FALSE) {
$cid_mapping = array();
foreach ($cids as $cid) {
$cid_mapping[$this->normalizeCid($cid)] = $cid;
}
// When serving cached pages, the overhead of using ::select() was found
// to add around 30% overhead to the request. Since $this->bin is a
// variable, this means the call to ::query() here uses a concatenated
// string. This is highly discouraged under any other circumstances, and
// is used here only due to the performance overhead we would incur
// otherwise. When serving an uncached page, the overhead of using
// ::select() is a much smaller proportion of the request.
$result = array();
try {
$result = $this->connection->query('SELECT cid, data, created, expire, serialized, tags, checksum FROM {' . $this->connection->escapeTable($this->bin) . '} WHERE cid IN ( :cids[] ) ORDER BY cid', array(':cids[]' => array_keys($cid_mapping)));
}
catch (\Exception $e) {
// Nothing to do.
}
$cache = array();
foreach ($result as $item) {
// Map the cache ID back to the original.
$item->cid = $cid_mapping[$item->cid];
$item = $this->prepareItem($item, $allow_invalid);
if ($item) {
$cache[$item->cid] = $item;
}
}
$cids = array_diff($cids, array_keys($cache));
return $cache;
}
/**
* Prepares a cached item.
*
* Checks that items are either permanent or did not expire, and unserializes
* data as appropriate.
*
* @param object $cache
* An item loaded from cache_get() or cache_get_multiple().
* @param bool $allow_invalid
* If FALSE, the method returns FALSE if the cache item is not valid.
*
* @return mixed|false
* The item with data unserialized as appropriate and a property indicating
* whether the item is valid, or FALSE if there is no valid item to load.
*/
protected function prepareItem($cache, $allow_invalid) {
if (!isset($cache->data)) {
return FALSE;
}
$cache->tags = $cache->tags ? explode(' ', $cache->tags) : array();
// Check expire time.
$cache->valid = $cache->expire == Cache::PERMANENT || $cache->expire >= REQUEST_TIME;
// Check if invalidateTags() has been called with any of the items's tags.
if (!$this->checksumProvider->isValid($cache->checksum, $cache->tags)) {
$cache->valid = FALSE;
}
if (!$allow_invalid && !$cache->valid) {
return FALSE;
}
// Unserialize and return the cached data.
if ($cache->serialized) {
$cache->data = unserialize($cache->data);
}
return $cache;
}
/**
* Implements Drupal\Core\Cache\CacheBackendInterface::set().
*/
public function set($cid, $data, $expire = Cache::PERMANENT, array $tags = array()) {
Cache::validateTags($tags);
$tags = array_unique($tags);
// Sort the cache tags so that they are stored consistently in the database.
sort($tags);
$try_again = FALSE;
try {
// The bin might not yet exist.
$this->doSet($cid, $data, $expire, $tags);
}
catch (\Exception $e) {
// If there was an exception, try to create the bins.
if (!$try_again = $this->ensureBinExists()) {
// If the exception happened for other reason than the missing bin
// table, propagate the exception.
throw $e;
}
}
// Now that the bin has been created, try again if necessary.
if ($try_again) {
$this->doSet($cid, $data, $expire, $tags);
}
}
/**
* Actually set the cache.
*/
protected function doSet($cid, $data, $expire, $tags) {
$fields = array(
'created' => round(microtime(TRUE), 3),
'expire' => $expire,
'tags' => implode(' ', $tags),
'checksum' => $this->checksumProvider->getCurrentChecksum($tags),
);
if (!is_string($data)) {
$fields['data'] = serialize($data);
$fields['serialized'] = 1;
}
else {
$fields['data'] = $data;
$fields['serialized'] = 0;
}
$this->connection->merge($this->bin)
->key('cid', $this->normalizeCid($cid))
->fields($fields)
->execute();
}
/**
* {@inheritdoc}
*/
public function setMultiple(array $items) {
$values = array();
foreach ($items as $cid => $item) {
$item += array(
'expire' => CacheBackendInterface::CACHE_PERMANENT,
'tags' => array(),
);
Cache::validateTags($item['tags']);
$item['tags'] = array_unique($item['tags']);
// Sort the cache tags so that they are stored consistently in the DB.
sort($item['tags']);
$fields = array(
'cid' => $cid,
'expire' => $item['expire'],
'created' => round(microtime(TRUE), 3),
'tags' => implode(' ', $item['tags']),
'checksum' => $this->checksumProvider->getCurrentChecksum($item['tags']),
);
if (!is_string($item['data'])) {
$fields['data'] = serialize($item['data']);
$fields['serialized'] = 1;
}
else {
$fields['data'] = $item['data'];
$fields['serialized'] = 0;
}
$values[] = $fields;
}
// Use a transaction so that the database can write the changes in a single
// commit. The transaction is started after calculating the tag checksums
// since that can create a table and this causes an exception when using
// PostgreSQL.
$transaction = $this->connection->startTransaction();
try {
// Delete all items first so we can do one insert. Rather than multiple
// merge queries.
$this->deleteMultiple(array_keys($items));
$query = $this->connection
->insert($this->bin)
->fields(array('cid', 'expire', 'created', 'tags', 'checksum', 'data', 'serialized'));
foreach ($values as $fields) {
// Only pass the values since the order of $fields matches the order of
// the insert fields. This is a performance optimization to avoid
// unnecessary loops within the method.
$query->values(array_values($fields));
}
$query->execute();
}
catch (\Exception $e) {
$transaction->rollback();
// @todo Log something here or just re throw?
throw $e;
}
}
/**
* Implements Drupal\Core\Cache\CacheBackendInterface::delete().
*/
public function delete($cid) {
$this->deleteMultiple(array($cid));
}
/**
* Implements Drupal\Core\Cache\CacheBackendInterface::deleteMultiple().
*/
public function deleteMultiple(array $cids) {
$cids = array_values(array_map(array($this, 'normalizeCid'), $cids));
try {
// Delete in chunks when a large array is passed.
foreach (array_chunk($cids, 1000) as $cids_chunk) {
$this->connection->delete($this->bin)
->condition('cid', $cids_chunk, 'IN')
->execute();
}
}
catch (\Exception $e) {
// Create the cache table, which will be empty. This fixes cases during
// core install where a cache table is cleared before it is set
// with {cache_render} and {cache_data}.
if (!$this->ensureBinExists()) {
$this->catchException($e);
}
}
}
/**
* Implements Drupal\Core\Cache\CacheBackendInterface::deleteAll().
*/
public function deleteAll() {
try {
$this->connection->truncate($this->bin)->execute();
}
catch (\Exception $e) {
// Create the cache table, which will be empty. This fixes cases during
// core install where a cache table is cleared before it is set
// with {cache_render} and {cache_data}.
if (!$this->ensureBinExists()) {
$this->catchException($e);
}
}
}
/**
* Implements Drupal\Core\Cache\CacheBackendInterface::invalidate().
*/
public function invalidate($cid) {
$this->invalidateMultiple(array($cid));
}
/**
* Implements Drupal\Core\Cache\CacheBackendInterface::invalidateMultiple().
*/
public function invalidateMultiple(array $cids) {
$cids = array_values(array_map(array($this, 'normalizeCid'), $cids));
try {
// Update in chunks when a large array is passed.
foreach (array_chunk($cids, 1000) as $cids_chunk) {
$this->connection->update($this->bin)
->fields(array('expire' => REQUEST_TIME - 1))
->condition('cid', $cids_chunk, 'IN')
->execute();
}
}
catch (\Exception $e) {
$this->catchException($e);
}
}
/**
* Implements Drupal\Core\Cache\CacheBackendInterface::invalidateAll().
*/
public function invalidateAll() {
try {
$this->connection->update($this->bin)
->fields(array('expire' => REQUEST_TIME - 1))
->execute();
}
catch (\Exception $e) {
$this->catchException($e);
}
}
/**
* Implements Drupal\Core\Cache\CacheBackendInterface::garbageCollection().
*/
public function garbageCollection() {
try {
$this->connection->delete($this->bin)
->condition('expire', Cache::PERMANENT, '<>')
->condition('expire', REQUEST_TIME, '<')
->execute();
}
catch (\Exception $e) {
// If the table does not exist, it surely does not have garbage in it.
// If the table exists, the next garbage collection will clean up.
// There is nothing to do.
}
}
/**
* {@inheritdoc}
*/
public function removeBin() {
try {
$this->connection->schema()->dropTable($this->bin);
}
catch (\Exception $e) {
$this->catchException($e);
}
}
/**
* Check if the cache bin exists and create it if not.
*/
protected function ensureBinExists() {
try {
$database_schema = $this->connection->schema();
if (!$database_schema->tableExists($this->bin)) {
$schema_definition = $this->schemaDefinition();
$database_schema->createTable($this->bin, $schema_definition);
return TRUE;
}
}
// If another process has already created the cache table, attempting to
// recreate it will throw an exception. In this case just catch the
// exception and do nothing.
catch (SchemaObjectExistsException $e) {
return TRUE;
}
return FALSE;
}
/**
* Act on an exception when cache might be stale.
*
* If the table does not yet exist, that's fine, but if the table exists and
* yet the query failed, then the cache is stale and the exception needs to
* propagate.
*
* @param $e
* The exception.
* @param string|null $table_name
* The table name. Defaults to $this->bin.
*
* @throws \Exception
*/
protected function catchException(\Exception $e, $table_name = NULL) {
if ($this->connection->schema()->tableExists($table_name ?: $this->bin)) {
throw $e;
}
}
/**
* Normalizes a cache ID in order to comply with database limitations.
*
* @param string $cid
* The passed in cache ID.
*
* @return string
* An ASCII-encoded cache ID that is at most 255 characters long.
*/
protected function normalizeCid($cid) {
// Nothing to do if the ID is a US ASCII string of 255 characters or less.
$cid_is_ascii = mb_check_encoding($cid, 'ASCII');
if (strlen($cid) <= 255 && $cid_is_ascii) {
return $cid;
}
// Return a string that uses as much as possible of the original cache ID
// with the hash appended.
$hash = Crypt::hashBase64($cid);
if (!$cid_is_ascii) {
return $hash;
}
return substr($cid, 0, 255 - strlen($hash)) . $hash;
}
/**
* Defines the schema for the {cache_*} bin tables.
*/
public function schemaDefinition() {
$schema = array(
'description' => 'Storage for the cache API.',
'fields' => array(
'cid' => array(
'description' => 'Primary Key: Unique cache ID.',
'type' => 'varchar_ascii',
'length' => 255,
'not null' => TRUE,
'default' => '',
'binary' => TRUE,
),
'data' => array(
'description' => 'A collection of data to cache.',
'type' => 'blob',
'not null' => FALSE,
'size' => 'big',
),
'expire' => array(
'description' => 'A Unix timestamp indicating when the cache entry should expire, or ' . Cache::PERMANENT . ' for never.',
'type' => 'int',
'not null' => TRUE,
'default' => 0,
),
'created' => array(
'description' => 'A timestamp with millisecond precision indicating when the cache entry was created.',
'type' => 'numeric',
'precision' => 14,
'scale' => 3,
'not null' => TRUE,
'default' => 0,
),
'serialized' => array(
'description' => 'A flag to indicate whether content is serialized (1) or not (0).',
'type' => 'int',
'size' => 'small',
'not null' => TRUE,
'default' => 0,
),
'tags' => array(
'description' => 'Space-separated list of cache tags for this entry.',
'type' => 'text',
'size' => 'big',
'not null' => FALSE,
),
'checksum' => array(
'description' => 'The tag invalidation checksum when this entry was saved.',
'type' => 'varchar_ascii',
'length' => 255,
'not null' => TRUE,
),
),
'indexes' => array(
'expire' => array('expire'),
),
'primary key' => array('cid'),
);
return $schema;
}
}

View file

@ -0,0 +1,54 @@
<?php
/**
* @file
* Contains \Drupal\Core\Cache\DatabaseBackendFactory.
*/
namespace Drupal\Core\Cache;
use Drupal\Core\Database\Connection;
class DatabaseBackendFactory implements CacheFactoryInterface {
/**
* The database connection.
*
* @var \Drupal\Core\Database\Connection
*/
protected $connection;
/**
* The cache tags checksum provider.
*
* @var \Drupal\Core\Cache\CacheTagsChecksumInterface
*/
protected $checksumProvider;
/**
* Constructs the DatabaseBackendFactory object.
*
* @param \Drupal\Core\Database\Connection $connection
* Database connection
* @param \Drupal\Core\Cache\CacheTagsChecksumInterface $checksum_provider
* The cache tags checksum provider.
*/
function __construct(Connection $connection, CacheTagsChecksumInterface $checksum_provider) {
$this->connection = $connection;
$this->checksumProvider = $checksum_provider;
}
/**
* Gets DatabaseBackend for the specified cache bin.
*
* @param $bin
* The cache bin for which the object is created.
*
* @return \Drupal\Core\Cache\DatabaseBackend
* The cache backend object for the specified cache bin.
*/
function get($bin) {
return new DatabaseBackend($this->connection, $this->checksumProvider, $bin);
}
}

View file

@ -0,0 +1,213 @@
<?php
/**
* @file
* Contains \Drupal\Core\Cache\DatabaseCacheTagsChecksum.
*/
namespace Drupal\Core\Cache;
use Drupal\Core\Database\Connection;
use Drupal\Core\Database\SchemaObjectExistsException;
/**
* Cache tags invalidations checksum implementation that uses the database.
*/
class DatabaseCacheTagsChecksum implements CacheTagsChecksumInterface, CacheTagsInvalidatorInterface {
/**
* The database connection.
*
* @var \Drupal\Core\Database\Connection
*/
protected $connection;
/**
* Contains already loaded cache invalidations from the database.
*
* @var array
*/
protected $tagCache = array();
/**
* A list of tags that have already been invalidated in this request.
*
* Used to prevent the invalidation of the same cache tag multiple times.
*
* @var array
*/
protected $invalidatedTags = array();
/**
* Constructs a DatabaseCacheTagsChecksum object.
*
* @param \Drupal\Core\Database\Connection $connection
* The database connection.
*/
public function __construct(Connection $connection) {
$this->connection = $connection;
}
/**
* {@inheritdoc}
*/
public function invalidateTags(array $tags) {
try {
foreach ($tags as $tag) {
// Only invalidate tags once per request unless they are written again.
if (isset($this->invalidatedTags[$tag])) {
continue;
}
$this->invalidatedTags[$tag] = TRUE;
unset($this->tagCache[$tag]);
$this->connection->merge('cachetags')
->insertFields(array('invalidations' => 1))
->expression('invalidations', 'invalidations + 1')
->key('tag', $tag)
->execute();
}
}
catch (\Exception $e) {
// Create the cache table, which will be empty. This fixes cases during
// core install where cache tags are invalidated before the table is
// created.
if (!$this->ensureTableExists()) {
$this->catchException($e);
}
}
}
/**
* {@inheritdoc}
*/
public function getCurrentChecksum(array $tags) {
// Remove tags that were already invalidated during this request from the
// static caches so that another invalidation can occur later in the same
// request. Without that, written cache items would not be invalidated
// correctly.
foreach ($tags as $tag) {
unset($this->invalidatedTags[$tag]);
}
return $this->calculateChecksum($tags);
}
/**
* {@inheritdoc}
*/
public function isValid($checksum, array $tags) {
return $checksum == $this->calculateChecksum($tags);
}
/**
* Calculates the current checksum for a given set of tags.
*
* @param array $tags
* The array of tags to calculate the checksum for.
*
* @return int
* The calculated checksum.
*/
protected function calculateChecksum(array $tags) {
$checksum = 0;
$query_tags = array_diff($tags, array_keys($this->tagCache));
if ($query_tags) {
$db_tags = array();
try {
$db_tags = $this->connection->query('SELECT tag, invalidations FROM {cachetags} WHERE tag IN ( :tags[] )', array(':tags[]' => $query_tags))
->fetchAllKeyed();
$this->tagCache += $db_tags;
}
catch (\Exception $e) {
// If the table does not exist yet, create.
if (!$this->ensureTableExists()) {
$this->catchException($e);
}
}
// Fill static cache with empty objects for tags not found in the database.
$this->tagCache += array_fill_keys(array_diff($query_tags, array_keys($db_tags)), 0);
}
foreach ($tags as $tag) {
$checksum += $this->tagCache[$tag];
}
return $checksum;
}
/**
* {@inheritdoc}
*/
public function reset() {
$this->tagCache = array();
$this->invalidatedTags = array();
}
/**
* Check if the cache tags table exists and create it if not.
*/
protected function ensureTableExists() {
try {
$database_schema = $this->connection->schema();
// Create the cache tags table if it does not exist.
if (!$database_schema->tableExists('cachetags')) {
$schema_definition = $this->schemaDefinition();
$database_schema->createTable('cachetags', $schema_definition);
return TRUE;
}
}
// If another process has already created the cachetags table, attempting to
// recreate it will throw an exception. In this case just catch the
// exception and do nothing.
catch (SchemaObjectExistsException $e) {
return TRUE;
}
return FALSE;
}
/**
* Defines the schema for the {cachetags} table.
*/
public function schemaDefinition() {
$schema = array(
'description' => 'Cache table for tracking cache tag invalidations.',
'fields' => array(
'tag' => array(
'description' => 'Namespace-prefixed tag string.',
'type' => 'varchar_ascii',
'length' => 255,
'not null' => TRUE,
'default' => '',
),
'invalidations' => array(
'description' => 'Number incremented when the tag is invalidated.',
'type' => 'int',
'not null' => TRUE,
'default' => 0,
),
),
'primary key' => array('tag'),
);
return $schema;
}
/**
* Act on an exception when cache might be stale.
*
* If the {cachetags} table does not yet exist, that's fine but if the table
* exists and yet the query failed, then the cache is stale and the
* exception needs to propagate.
*
* @param \Exception $e
* The exception.
*
* @throws \Exception
*/
protected function catchException(\Exception $e) {
if ($this->connection->schema()->tableExists('cachetags')) {
throw $e;
}
}
}

View file

@ -0,0 +1,36 @@
<?php
/**
* @file
* Contains \Drupal\Core\Cache\ListCacheBinsPass.
*/
namespace Drupal\Core\Cache;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
/**
* Adds cache_bins parameter to the container.
*/
class ListCacheBinsPass implements CompilerPassInterface {
/**
* Implements CompilerPassInterface::process().
*
* Collects the cache bins into the cache_bins parameter.
*/
public function process(ContainerBuilder $container) {
$cache_bins = array();
$cache_default_bin_backends = array();
foreach ($container->findTaggedServiceIds('cache.bin') as $id => $attributes) {
$bin = substr($id, strpos($id, '.') + 1);
$cache_bins[$id] = $bin;
if (isset($attributes[0]['default_backend'])) {
$cache_default_bin_backends[$bin] = $attributes[0]['default_backend'];
}
}
$container->setParameter('cache_bins', $cache_bins);
$container->setParameter('cache_default_bin_backends', $cache_default_bin_backends);
}
}

View file

@ -0,0 +1,220 @@
<?php
/**
* @file
* Contains \Drupal\Core\Cache\MemoryBackend.
*/
namespace Drupal\Core\Cache;
/**
* Defines a memory cache implementation.
*
* Stores cache items in memory using a PHP array.
*
* Should be used for unit tests and specialist use-cases only, does not
* store cached items between requests.
*
* @ingroup cache
*/
class MemoryBackend implements CacheBackendInterface, CacheTagsInvalidatorInterface {
/**
* Array to store cache objects.
*/
protected $cache = array();
/**
* Constructs a MemoryBackend object.
*
* @param string $bin
* The cache bin for which the object is created.
*/
public function __construct($bin) {
}
/**
* Implements Drupal\Core\Cache\CacheBackendInterface::get().
*/
public function get($cid, $allow_invalid = FALSE) {
if (isset($this->cache[$cid])) {
return $this->prepareItem($this->cache[$cid], $allow_invalid);
}
else {
return FALSE;
}
}
/**
* Implements Drupal\Core\Cache\CacheBackendInterface::getMultiple().
*/
public function getMultiple(&$cids, $allow_invalid = FALSE) {
$ret = array();
$items = array_intersect_key($this->cache, array_flip($cids));
foreach ($items as $item) {
$item = $this->prepareItem($item, $allow_invalid);
if ($item) {
$ret[$item->cid] = $item;
}
}
$cids = array_diff($cids, array_keys($ret));
return $ret;
}
/**
* Prepares a cached item.
*
* Checks that items are either permanent or did not expire, and returns data
* as appropriate.
*
* @param object $cache
* An item loaded from cache_get() or cache_get_multiple().
* @param bool $allow_invalid
* (optional) If TRUE, cache items may be returned even if they have expired
* or been invalidated.
*
* @return mixed
* The item with data as appropriate or FALSE if there is no
* valid item to load.
*/
protected function prepareItem($cache, $allow_invalid) {
if (!isset($cache->data)) {
return FALSE;
}
// The object passed into this function is the one stored in $this->cache.
// We must clone it as part of the preparation step so that the actual
// cache object is not affected by the unserialize() call or other
// manipulations of the returned object.
$prepared = clone $cache;
$prepared->data = unserialize($prepared->data);
// Check expire time.
$prepared->valid = $prepared->expire == Cache::PERMANENT || $prepared->expire >= $this->getRequestTime();
if (!$allow_invalid && !$prepared->valid) {
return FALSE;
}
return $prepared;
}
/**
* Implements Drupal\Core\Cache\CacheBackendInterface::set().
*/
public function set($cid, $data, $expire = Cache::PERMANENT, array $tags = array()) {
Cache::validateTags($tags);
$tags = array_unique($tags);
// Sort the cache tags so that they are stored consistently in the database.
sort($tags);
$this->cache[$cid] = (object) array(
'cid' => $cid,
'data' => serialize($data),
'created' => $this->getRequestTime(),
'expire' => $expire,
'tags' => $tags,
);
}
/**
* {@inheritdoc}
*/
public function setMultiple(array $items = array()) {
foreach ($items as $cid => $item) {
$this->set($cid, $item['data'], isset($item['expire']) ? $item['expire'] : CacheBackendInterface::CACHE_PERMANENT, isset($item['tags']) ? $item['tags'] : array());
}
}
/**
* Implements Drupal\Core\Cache\CacheBackendInterface::delete().
*/
public function delete($cid) {
unset($this->cache[$cid]);
}
/**
* Implements Drupal\Core\Cache\CacheBackendInterface::deleteMultiple().
*/
public function deleteMultiple(array $cids) {
$this->cache = array_diff_key($this->cache, array_flip($cids));
}
/**
* Implements Drupal\Core\Cache\CacheBackendInterface::deleteAll().
*/
public function deleteAll() {
$this->cache = array();
}
/**
* Implements Drupal\Core\Cache\CacheBackendInterface::invalidate().
*/
public function invalidate($cid) {
if (isset($this->cache[$cid])) {
$this->cache[$cid]->expire = $this->getRequestTime() - 1;
}
}
/**
* Implements Drupal\Core\Cache\CacheBackendInterface::invalidateMultiple().
*/
public function invalidateMultiple(array $cids) {
foreach ($cids as $cid) {
$this->cache[$cid]->expire = $this->getRequestTime() - 1;
}
}
/**
* {@inheritdoc}
*/
public function invalidateTags(array $tags) {
foreach ($this->cache as $cid => $item) {
if (array_intersect($tags, $item->tags)) {
$this->cache[$cid]->expire = $this->getRequestTime() - 1;
}
}
}
/**
* Implements Drupal\Core\Cache\CacheBackendInterface::invalidateAll().
*/
public function invalidateAll() {
foreach ($this->cache as $cid => $item) {
$this->cache[$cid]->expire = $this->getRequestTime() - 1;
}
}
/**
* Implements Drupal\Core\Cache\CacheBackendInterface::garbageCollection()
*/
public function garbageCollection() {
}
/**
* {@inheritdoc}
*/
public function removeBin() {
$this->cache = [];
}
/**
* Wrapper method for REQUEST_TIME constant.
*
* @return int
*/
protected function getRequestTime() {
return defined('REQUEST_TIME') ? REQUEST_TIME : (int) $_SERVER['REQUEST_TIME'];
}
/**
* Prevents data stored in memory backends from being serialized.
*/
public function __sleep() {
return [];
}
}

View file

@ -0,0 +1,29 @@
<?php
/**
* @file
* Contains \Drupal\Core\Cache\MemoryBackendFactory.
*/
namespace Drupal\Core\Cache;
class MemoryBackendFactory implements CacheFactoryInterface {
/**
* Instantiated memory cache bins.
*
* @var \Drupal\Core\Cache\MemoryBackend[]
*/
protected $bins = array();
/**
* {@inheritdoc}
*/
function get($bin) {
if (!isset($this->bins[$bin])) {
$this->bins[$bin] = new MemoryBackend($bin);
}
return $this->bins[$bin];
}
}

View file

@ -0,0 +1,100 @@
<?php
/**
* @file
* Contains \Drupal\Core\Cache\MemoryCounterBackend.
*/
namespace Drupal\Core\Cache;
/**
* Defines a memory cache implementation that counts set and get calls.
*
* This can be used to mock a cache backend where one needs to know how
* many times a cache entry was set or requested.
*
* @todo On the longrun this backend should be replaced by phpunit mock objects.
*
*/
class MemoryCounterBackend extends MemoryBackend {
/**
* Stores a list of cache cid calls keyed by function name.
*
* @var array
*/
protected $counter = array();
/**
* Implements \Drupal\Core\Cache\CacheBackendInterface::get().
*/
public function get($cid, $allow_invalid = FALSE) {
$this->increaseCounter(__FUNCTION__, $cid);
return parent::get($cid, $allow_invalid);
}
/**
* Implements \Drupal\Core\Cache\CacheBackendInterface::set().
*/
public function set($cid, $data, $expire = Cache::PERMANENT, array $tags = array()) {
$this->increaseCounter(__FUNCTION__, $cid);
parent::set($cid, $data, $expire, $tags);
}
/**
* Implements \Drupal\Core\Cache\CacheBackendInterface::delete().
*/
public function delete($cid) {
$this->increaseCounter(__FUNCTION__, $cid);
parent::delete($cid);
}
/**
* Increase the counter for a function with a certain cid.
*
* @param string $function
* The called function.
*
* @param string $cid
* The cache ID of the cache entry to increase the counter.
*/
protected function increaseCounter($function, $cid) {
if (!isset($this->counter[$function][$cid])) {
$this->counter[$function][$cid] = 1;
}
else {
$this->counter[$function][$cid]++;
}
}
/**
* Returns the call counter for the get, set and delete methods.
*
* @param string $method
* (optional) The name of the method to return the call counter for.
* @param string $cid
* (optional) The name of the cache id to return the call counter for.
*
* @return int|array
* An integer if both method and cid is given, an array otherwise.
*/
public function getCounter($method = NULL, $cid = NULL) {
if ($method && $cid) {
return isset($this->counter[$method][$cid]) ? $this->counter[$method][$cid] : 0;
}
elseif ($method) {
return isset($this->counter[$method]) ? $this->counter[$method] : array();
}
else {
return $this->counter;
}
}
/**
* Resets the call counter.
*/
public function resetCounter() {
$this->counter = array();
}
}

View file

@ -0,0 +1,96 @@
<?php
/**
* @file
* Contains \Drupal\Core\Cache\NullBackend.
*/
namespace Drupal\Core\Cache;
/**
* Defines a stub cache implementation.
*
* The stub implementation is needed when database access is not yet available.
* Because Drupal's caching system never requires that cached data be present,
* these stub functions can short-circuit the process and sidestep the need for
* any persistent storage. Using this cache implementation during normal
* operations would have a negative impact on performance.
*
* This also can be used for testing purposes.
*
* @ingroup cache
*/
class NullBackend implements CacheBackendInterface {
/**
* Constructs a NullBackend object.
*
* @param string $bin
* The cache bin for which the object is created.
*/
public function __construct($bin) {}
/**
* Implements Drupal\Core\Cache\CacheBackendInterface::get().
*/
public function get($cid, $allow_invalid = FALSE) {
return FALSE;
}
/**
* Implements Drupal\Core\Cache\CacheBackendInterface::getMultiple().
*/
public function getMultiple(&$cids, $allow_invalid = FALSE) {
return array();
}
/**
* Implements Drupal\Core\Cache\CacheBackendInterface::set().
*/
public function set($cid, $data, $expire = Cache::PERMANENT, array $tags = array()) {}
/**
* {@inheritdoc}
*/
public function setMultiple(array $items = array()) {}
/**
* Implements Drupal\Core\Cache\CacheBackendInterface::delete().
*/
public function delete($cid) {}
/**
* Implements Drupal\Core\Cache\CacheBackendInterface::deleteMultiple().
*/
public function deleteMultiple(array $cids) {}
/**
* Implements Drupal\Core\Cache\CacheBackendInterface::deleteAll().
*/
public function deleteAll() {}
/**
* Implements Drupal\Core\Cache\CacheBackendInterface::invalidate().
*/
public function invalidate($cid) {}
/**
* Implements Drupal\Core\Cache\CacheBackendInterface::invalidateMultiple().
*/
public function invalidateMultiple(array $cids) {}
/**
* Implements Drupal\Core\Cache\CacheBackendInterface::invalidateAll().
*/
public function invalidateAll() {}
/**
* Implements Drupal\Core\Cache\CacheBackendInterface::garbageCollection().
*/
public function garbageCollection() {}
/**
* {@inheritdoc}
*/
public function removeBin() {}
}

View file

@ -0,0 +1,19 @@
<?php
/**
* @file
* Contains \Drupal\Core\Cache\NullBackendFactory.
*/
namespace Drupal\Core\Cache;
class NullBackendFactory implements CacheFactoryInterface {
/**
* {@inheritdoc}
*/
function get($bin) {
return new NullBackend($bin);
}
}

View file

@ -0,0 +1,276 @@
<?php
/**
* @file
* Contains \Drupal\Core\Cache\PhpBackend.
*/
namespace Drupal\Core\Cache;
use Drupal\Core\PhpStorage\PhpStorageFactory;
use Drupal\Component\Utility\Crypt;
/**
* Defines a PHP cache implementation.
*
* Stores cache items in a PHP file using a storage that implements
* Drupal\Component\PhpStorage\PhpStorageInterface.
*
* This is fast because of PHP's opcode caching mechanism. Once a file's
* content is stored in PHP's opcode cache, including it doesn't require
* reading the contents from a filesystem. Instead, PHP will use the already
* compiled opcodes stored in memory.
*
* @ingroup cache
*/
class PhpBackend implements CacheBackendInterface {
/**
* @var string
*/
protected $bin;
/**
* Array to store cache objects.
*/
protected $cache = array();
/**
* The cache tags checksum provider.
*
* @var \Drupal\Core\Cache\CacheTagsChecksumInterface
*/
protected $checksumProvider;
/**
* Constructs a PhpBackend object.
*
* @param string $bin
* The cache bin for which the object is created.
* @param \Drupal\Core\Cache\CacheTagsChecksumInterface $checksum_provider
* The cache tags checksum provider.
*/
public function __construct($bin, CacheTagsChecksumInterface $checksum_provider) {
$this->bin = 'cache_' . $bin;
$this->checksumProvider = $checksum_provider;
}
/**
* {@inheritdoc}
*/
public function get($cid, $allow_invalid = FALSE) {
return $this->getByHash($this->normalizeCid($cid), $allow_invalid);
}
/**
* Fetch a cache item using a hashed cache ID.
*
* @param string $cidhash
* The hashed version of the original cache ID after being normalized.
* @param bool $allow_invalid
* (optional) If TRUE, a cache item may be returned even if it is expired or
* has been invalidated.
*
* @return bool|mixed
*/
protected function getByHash($cidhash, $allow_invalid = FALSE) {
if ($file = $this->storage()->getFullPath($cidhash)) {
$cache = @include $file;
}
if (isset($cache)) {
return $this->prepareItem($cache, $allow_invalid);
}
return FALSE;
}
/**
* {@inheritdoc}
*/
public function setMultiple(array $items) {
foreach ($items as $cid => $item) {
$this->set($cid, $item['data'], isset($item['expire']) ? $item['expire'] : CacheBackendInterface::CACHE_PERMANENT, isset($item['tags']) ? $item['tags'] : array());
}
}
/**
* {@inheritdoc}
*/
public function getMultiple(&$cids, $allow_invalid = FALSE) {
$ret = array();
foreach ($cids as $cid) {
if ($item = $this->get($cid, $allow_invalid)) {
$ret[$item->cid] = $item;
}
}
$cids = array_diff($cids, array_keys($ret));
return $ret;
}
/**
* Prepares a cached item.
*
* Checks that items are either permanent or did not expire, and returns data
* as appropriate.
*
* @param object $cache
* An item loaded from cache_get() or cache_get_multiple().
* @param bool $allow_invalid
* If FALSE, the method returns FALSE if the cache item is not valid.
*
* @return mixed
* The item with data as appropriate or FALSE if there is no
* valid item to load.
*/
protected function prepareItem($cache, $allow_invalid) {
if (!isset($cache->data)) {
return FALSE;
}
// Check expire time.
$cache->valid = $cache->expire == Cache::PERMANENT || $cache->expire >= REQUEST_TIME;
// Check if invalidateTags() has been called with any of the item's tags.
if (!$this->checksumProvider->isValid($cache->checksum, $cache->tags)) {
$cache->valid = FALSE;
}
if (!$allow_invalid && !$cache->valid) {
return FALSE;
}
return $cache;
}
/**
* {@inheritdoc}
*/
public function set($cid, $data, $expire = Cache::PERMANENT, array $tags = array()) {
Cache::validateTags($tags);
$item = (object) array(
'cid' => $cid,
'data' => $data,
'created' => round(microtime(TRUE), 3),
'expire' => $expire,
'tags' => array_unique($tags),
'checksum' => $this->checksumProvider->getCurrentChecksum($tags),
);
$this->writeItem($this->normalizeCid($cid), $item);
}
/**
* {@inheritdoc}
*/
public function delete($cid) {
$this->storage()->delete($this->normalizeCid($cid));
}
/**
* {@inheritdoc}
*/
public function deleteMultiple(array $cids) {
foreach ($cids as $cid) {
$this->delete($cid);
}
}
/**
* {@inheritdoc}
*/
public function deleteAll() {
$this->storage()->deleteAll();
}
/**
* {@inheritdoc}
*/
public function invalidate($cid) {
$this->invalidatebyHash($this->normalizeCid($cid));
}
/**
* Invalidate one cache item.
*
* @param string $cidhash
* The hashed version of the original cache ID after being normalized.
*/
protected function invalidatebyHash($cidhash) {
if ($item = $this->getByHash($cidhash)) {
$item->expire = REQUEST_TIME - 1;
$this->writeItem($cidhash, $item);
}
}
/**
* {@inheritdoc}
*/
public function invalidateMultiple(array $cids) {
foreach ($cids as $cid) {
$this->invalidate($cid);
}
}
/**
* {@inheritdoc}
*/
public function invalidateAll() {
foreach($this->storage()->listAll() as $cidhash) {
$this->invalidatebyHash($cidhash);
}
}
/**
* {@inheritdoc}
*/
public function garbageCollection() {
}
/**
* {@inheritdoc}
*/
public function removeBin() {
$this->cache = array();
$this->storage()->deleteAll();
}
/**
* Writes a cache item to PhpStorage.
*
* @param string $cidhash
* The hashed version of the original cache ID after being normalized.
* @param \stdClass $item
* The cache item to store.
*/
protected function writeItem($cidhash, \stdClass $item) {
$content = '<?php return unserialize(' . var_export(serialize($item), TRUE) . ');';
$this->storage()->save($cidhash, $content);
}
/**
* Gets the PHP code storage object to use.
*
* @return \Drupal\Component\PhpStorage\PhpStorageInterface
*/
protected function storage() {
if (!isset($this->storage)) {
$this->storage = PhpStorageFactory::get($this->bin);
}
return $this->storage;
}
/**
* Ensures a normalized cache ID.
*
* @param string $cid
* The passed in cache ID.
*
* @return string
* A normalized cache ID.
*/
protected function normalizeCid($cid) {
return Crypt::hashBase64($cid);
}
}

View file

@ -0,0 +1,42 @@
<?php
/**
* @file
* Contains \Drupal\Core\Cache\PhpBackendFactory.
*/
namespace Drupal\Core\Cache;
class PhpBackendFactory implements CacheFactoryInterface {
/**
* The cache tags checksum provider.
*
* @var \Drupal\Core\Cache\CacheTagsChecksumInterface
*/
protected $checksumProvider;
/**
* Constructs a PhpBackendFactory object.
*
* @param \Drupal\Core\Cache\CacheTagsChecksumInterface $checksum_provider
* The cache tags checksum provider.
*/
public function __construct(CacheTagsChecksumInterface $checksum_provider) {
$this->checksumProvider = $checksum_provider;
}
/**
* Gets PhpBackend for the specified cache bin.
*
* @param $bin
* The cache bin for which the object is created.
*
* @return \Drupal\Core\Cache\PhpBackend
* The cache backend object for the specified cache bin.
*/
function get($bin) {
return new PhpBackend($bin, $this->checksumProvider);
}
}

View file

@ -0,0 +1,38 @@
<?php
/**
* @file
* Contains \Drupal\Core\Cache\UnchangingCacheableDependencyTrait.
*/
namespace Drupal\Core\Cache;
/**
* Trait to implement CacheableDependencyInterface for unchanging objects.
*
* @see \Drupal\Core\Cache\CacheableDependencyInterface
*/
trait UnchangingCacheableDependencyTrait {
/**
* {@inheritdoc}
*/
public function getCacheContexts() {
return [];
}
/**
* {@inheritdoc}
*/
public function getCacheTags() {
return [];
}
/**
* {@inheritdoc}
*/
public function getCacheMaxAge() {
return Cache::PERMANENT;
}
}