Commit fc7c38bd authored by catch's avatar catch

Issue #1764474 by Berdir, chx, alexpott, pounard, msonnabaum: Make Cache...

Issue #1764474 by Berdir, chx, alexpott, pounard, msonnabaum: Make Cache interface and backends use the DIC.
parent 59d72a2c
......@@ -5,7 +5,7 @@
* Functions and interfaces for cache handling.
*/
use Drupal\Core\Cache\CacheFactory;
use Drupal\Core\Cache\Cache;
/**
* Instantiates and statically caches the correct class for a cache bin.
......@@ -26,40 +26,7 @@
* @see Drupal\Core\Cache\CacheBackendInterface
*/
function cache($bin = 'cache') {
// Use the advanced drupal_static() pattern, since this is called very often.
static $drupal_static_fast;
if (!isset($drupal_static_fast)) {
$drupal_static_fast['cache'] = &drupal_static(__FUNCTION__, array());
}
$cache_objects = &$drupal_static_fast['cache'];
// Temporary backwards compatibiltiy layer, allow old style prefixed cache
// bin names to be passed as arguments.
$bin = str_replace('cache_', '', $bin);
if (!isset($cache_objects[$bin])) {
$cache_objects[$bin] = CacheFactory::get($bin);
}
return $cache_objects[$bin];
}
/**
* Deletes items from all bins with any of the specified tags.
*
* Many sites have more than one active cache backend, and each backend may use
* a different strategy for storing tags against cache items, and deleting cache
* items associated with a given tag.
*
* When deleting a given list of tags, we iterate over each cache backend, and
* and call deleteTags() on each.
*
* @param array $tags
* The list of tags to delete cache items for.
*/
function cache_delete_tags(array $tags) {
foreach (CacheFactory::getBackends() as $bin => $class) {
cache($bin)->deleteTags($tags);
}
return Drupal::cache($bin);
}
/**
......@@ -74,9 +41,10 @@ function cache_delete_tags(array $tags) {
*
* @param array $tags
* The list of tags to invalidate cache items for.
*
* @deprecated 8.x
* Use \Drupal\Core\Cache\Cache::invalidateTags().
*/
function cache_invalidate_tags(array $tags) {
foreach (CacheFactory::getBackends() as $bin => $class) {
cache($bin)->invalidateTags($tags);
}
Cache::invalidateTags($tags);
}
<?php
use Drupal\Core\Cache\Cache;
use Symfony\Component\DependencyInjection\Container;
use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
......@@ -6311,8 +6312,7 @@ function drupal_implode_tags($tags) {
*
* At times, it is necessary to re-initialize the entire system to account for
* changed or new code. This function:
* - Clears all persistent caches (invoking hook_cache_flush()), which always
* includes:
* - Clears all persistent caches:
* - The bootstrap cache bin containing base system, module system, and theme
* system information.
* - The common 'cache' cache bin containing arbitrary caches.
......@@ -6342,6 +6342,7 @@ function drupal_implode_tags($tags) {
* longer exist. All following hook_rebuild() operations must be based on fresh
* and current system data. All modules must be able to rely on this contract.
*
* @see \Drupal\Core\Cache\CacheHelper::getBins()
* @see hook_cache_flush()
* @see hook_rebuild()
*
......@@ -6364,8 +6365,12 @@ function drupal_flush_all_caches() {
// Flush all persistent caches.
// This is executed based on old/previously known information, which is
// sufficient, since new extensions cannot have any primed caches yet.
foreach (module_invoke_all('cache_flush') as $bin) {
cache($bin)->deleteAll();
module_invoke_all('cache_flush');
foreach (Cache::getBins() as $service_id => $cache_backend) {
// @todo remove form after http://drupal.org/node/512026 is in.
if ($service_id != 'cache.form' && $service_id != 'cache.menu') {
$cache_backend->deleteAll();
}
}
// Flush asset file caches.
......
......@@ -350,6 +350,11 @@ function install_begin_request(&$install_state) {
// Register the 'language_manager' service.
$container->register('language_manager', 'Drupal\Core\Language\LanguageManager');
foreach (array('bootstrap', 'config', 'cache', 'form', 'menu', 'page', 'path') as $bin) {
$container
->register("cache.$bin", 'Drupal\Core\Cache\MemoryBackend')
->addArgument($bin);
}
// The install process cannot use the database lock backend since the database
// is not fully up, so we use a null backend implementation during the
......@@ -390,7 +395,6 @@ function install_begin_request(&$install_state) {
$module_handler->load('system');
require_once DRUPAL_ROOT . '/core/includes/cache.inc';
$conf['cache_classes'] = array('cache' => 'Drupal\Core\Cache\MemoryBackend');
// Prepare for themed output. We need to run this at the beginning of the
// page request to avoid a different theme accidentally getting set. (We also
......@@ -1622,13 +1626,7 @@ function install_load_profile(&$install_state) {
* @param $install_state
* An array of information about the current installation state.
*/
function install_bootstrap_full(&$install_state) {
// The early stages of the installer override the cache backend since Drupal
// isn't fully set up yet. Here the override is removed so that the standard
// cache backend will be used again.
unset($GLOBALS['conf']['cache_classes']['cache']);
drupal_static_reset('cache');
function install_bootstrap_full() {
drupal_bootstrap(DRUPAL_BOOTSTRAP_FULL);
}
......
......@@ -9,6 +9,7 @@
*/
use Drupal\Component\Graph\Graph;
use Drupal\Component\Utility\Settings;
use Drupal\Core\Config\FileStorage;
use Drupal\Core\Config\ConfigException;
use Drupal\Core\DrupalKernel;
......@@ -97,7 +98,9 @@ function update_prepare_d8_bootstrap() {
// During the bootstrap to DRUPAL_BOOTSTRAP_PAGE_CACHE, code will try to read
// the cache but the cache tables are not compatible yet. Use the Null backend
// by default to avoid exceptions.
$GLOBALS['conf']['cache_classes'] = array('cache' => 'Drupal\Core\Cache\NullBackend');
$settings = settings()->getAll();
$settings['cache']['default'] = 'cache.backend.memory';
new Settings($settings);
// Enable UpdateBundle service overrides.
$GLOBALS['conf']['container_bundles'][] = 'Drupal\Core\DependencyInjection\UpdateBundle';
......@@ -420,8 +423,11 @@ function update_prepare_d8_bootstrap() {
}
}
// Now remove the cache override.
unset($GLOBALS['conf']['cache_classes']['cache']);
drupal_static_reset('cache');
$settings = settings()->getAll();
unset($settings['cache']['default']);
new Settings($settings);
$kernel = new DrupalKernel('update', FALSE, drupal_classloader(), FALSE);
$kernel->boot();
}
/**
......
......@@ -131,6 +131,20 @@ public static function database() {
return static::$container->get('database');
}
/**
* Returns the requested cache bin.
*
* @param string $bin
* (optional) The cache bin for which the cache object should be returned,
* defaults to 'cache'.
*
* @return Drupal\Core\Cache\CacheBackendInterface
* The cache object associated with the specified bin.
*/
public static function cache($bin = 'cache') {
return static::$container->get('cache.' . $bin);
}
/**
* Returns an expirable key value store collection.
*
......
<?php
/**
* @file
* Contains \Drupal\Core\Cache\Cache.
*/
namespace Drupal\Core\Cache;
/**
* Helper methods for cache.
*/
class Cache {
/**
* Deletes items from all bins with any of the specified tags.
*
* Many sites have more than one active cache backend, and each backend may
* use a different strategy for storing tags against cache items, and
* deleting cache items associated with a given tag.
*
* When deleting a given list of tags, we iterate over each cache backend, and
* and call deleteTags() on each.
*
* @param array $tags
* The list of tags to delete cache items for.
*/
public static function deleteTags(array $tags) {
foreach (static::getBins() as $cache_backend) {
$cache_backend->deleteTags($tags);
}
}
/**
* Marks cache items from all bins with any of the specified tags as invalid.
*
* Many sites have more than one active cache backend, and each backend my use
* a different strategy for storing tags against cache items, and invalidating
* cache items associated with a given tag.
*
* When invalidating a given list of tags, we iterate over each cache backend,
* and call invalidateTags() on each.
*
* @param array $tags
* The list of tags to invalidate cache items for.
*/
public static function invalidateTags(array $tags) {
foreach (static::getBins() as $cache_backend) {
$cache_backend->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;
}
}
......@@ -2,7 +2,7 @@
/**
* @file
* Contains Drupal\Core\Cache\CacheFactory.
* Contains \Drupal\Core\Cache\CacheFactory.
*/
namespace Drupal\Core\Cache;
......@@ -10,7 +10,28 @@
/**
* Defines the cache backend factory.
*/
class CacheFactory {
use Drupal\Component\Utility\Settings;
use Symfony\Component\DependencyInjection\ContainerAware;
use Symfony\Component\DependencyInjection\ContainerBuilder;
class CacheFactory extends ContainerAware {
/**
* The settings array.
*
* @var \Drupal\Component\Utility\Settings
*/
protected $settings;
/**
* Constructs CacheFactory object.
*
* @param \Drupal\Component\Utility\Settings $settings
* The settings array.
*/
function __construct(Settings $settings) {
$this->settings = $settings;
}
/**
* Instantiates a cache backend class for a given cache bin.
......@@ -24,33 +45,37 @@ class CacheFactory {
* @param string $bin
* The cache bin for which a cache backend object should be returned.
*
* @return Drupal\Core\Cache\CacheBackendInterface
* @return \Drupal\Core\Cache\CacheBackendInterface
* The cache backend object associated with the specified bin.
*/
public static function get($bin) {
// Check whether there is a custom class defined for the requested bin or
// use the default 'cache' definition otherwise.
$cache_backends = self::getBackends();
$class = isset($cache_backends[$bin]) ? $cache_backends[$bin] : $cache_backends['cache'];
return new $class($bin);
public function get($bin) {
$cache_settings = $this->settings->get('cache');
if (isset($cache_settings[$bin])) {
$service_name = $cache_settings[$bin];
}
elseif (isset($cache_settings['default'])) {
$service_name = $cache_settings['default'];
}
else {
$service_name = 'cache.backend.database';
}
return $this->container->get($service_name)->get($bin);
}
/**
* Returns a list of cache backends for this site.
* Helper to register a cache bin to the container.
*
* @return array
* An associative array with cache bins as keys, and backend class names as
* value.
* @param \Symfony\Component\DependencyInjection\ContainerBuilder $container
* The container to register the cache bin on.
* @param $bin
* The cache bin to add. Do not add the cache_ prefix.
*/
public static function getBackends() {
// @todo Improve how cache backend classes are defined. Cannot be
// configuration, since e.g. the CachedStorage config storage controller
// requires the definition in its constructor already.
global $conf;
$cache_backends = isset($conf['cache_classes']) ? $conf['cache_classes'] : array();
// Ensure there is a default 'cache' bin definition.
$cache_backends += array('cache' => 'Drupal\Core\Cache\DatabaseBackend');
return $cache_backends;
public static function registerBin(ContainerBuilder $container, $bin) {
$container
->register("cache.$bin", 'Drupal\Core\Cache\CacheBackendInterface')
->setFactoryService('cache_factory')
->setFactoryMethod('get')
->addArgument($bin)
->addTag('cache.bin');
}
}
......@@ -7,8 +7,8 @@
namespace Drupal\Core\Cache;
use Drupal\Core\Database\Connection;
use Drupal\Core\Database\Database;
use Drupal\Core\Database\DatabaseException;
/**
* Defines a default cache implementation.
......@@ -23,19 +23,30 @@ class DatabaseBackend implements CacheBackendInterface {
*/
protected $bin;
/**
* The database connection.
*
* @var \Drupal\Core\Database\Connection
*/
protected $connection;
/**
* Constructs a DatabaseBackend object.
*
* @param \Drupal\Core\Database\Connection $connection
* The database connection.
* @param string $bin
* The cache bin for which the object is created.
*/
public function __construct($bin) {
public function __construct(Connection $connection, $bin) {
// All cache tables should be prefixed with 'cache_', except for the
// default 'cache' bin.
if ($bin != 'cache') {
$bin = 'cache_' . $bin;
}
$this->bin = $bin;
$this->connection = $connection;
}
/**
......@@ -58,7 +69,7 @@ public function getMultiple(&$cids, $allow_invalid = FALSE) {
// 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 = Database::getConnection()->query('SELECT cid, data, created, expire, serialized, tags, checksum_invalidations, checksum_deletions FROM {' . Database::getConnection()->escapeTable($this->bin) . '} WHERE cid IN (:cids)', array(':cids' => $cids));
$result = $this->connection->query('SELECT cid, data, created, expire, serialized, tags, checksum_invalidations, checksum_deletions FROM {' . $this->connection->escapeTable($this->bin) . '} WHERE cid IN (:cids)', array(':cids' => $cids));
$cache = array();
foreach ($result as $item) {
$item = $this->prepareItem($item, $allow_invalid);
......@@ -142,7 +153,7 @@ public function set($cid, $data, $expire = CacheBackendInterface::CACHE_PERMANEN
$fields['serialized'] = 0;
}
Database::getConnection()->merge($this->bin)
$this->connection->merge($this->bin)
->key(array('cid' => $cid))
->fields($fields)
->execute();
......@@ -152,7 +163,7 @@ public function set($cid, $data, $expire = CacheBackendInterface::CACHE_PERMANEN
* Implements Drupal\Core\Cache\CacheBackendInterface::delete().
*/
public function delete($cid) {
Database::getConnection()->delete($this->bin)
$this->connection->delete($this->bin)
->condition('cid', $cid)
->execute();
}
......@@ -163,7 +174,7 @@ public function delete($cid) {
public function deleteMultiple(array $cids) {
// Delete in chunks when a large array is passed.
do {
Database::getConnection()->delete($this->bin)
$this->connection->delete($this->bin)
->condition('cid', array_splice($cids, 0, 1000), 'IN')
->execute();
}
......@@ -177,7 +188,7 @@ public function deleteTags(array $tags) {
$tag_cache = &drupal_static('Drupal\Core\Cache\CacheBackendInterface::tagCache');
foreach ($this->flattenTags($tags) as $tag) {
unset($tag_cache[$tag]);
Database::getConnection()->merge('cache_tags')
$this->connection->merge('cache_tags')
->insertFields(array('deletions' => 1))
->expression('deletions', 'deletions + 1')
->key(array('tag' => $tag))
......@@ -189,7 +200,7 @@ public function deleteTags(array $tags) {
* Implements Drupal\Core\Cache\CacheBackendInterface::deleteAll().
*/
public function deleteAll() {
Database::getConnection()->truncate($this->bin)->execute();
$this->connection->truncate($this->bin)->execute();
}
/**
......@@ -205,7 +216,7 @@ public function invalidate($cid) {
public function invalidateMultiple(array $cids) {
// Update in chunks when a large array is passed.
do {
Database::getConnection()->update($this->bin)
$this->connection->update($this->bin)
->fields(array('expire' => REQUEST_TIME - 1))
->condition('cid', array_splice($cids, 0, 1000), 'IN')
->execute();
......@@ -220,7 +231,7 @@ public function invalidateTags(array $tags) {
$tag_cache = &drupal_static('Drupal\Core\Cache\CacheBackendInterface::tagCache');
foreach ($this->flattenTags($tags) as $tag) {
unset($tag_cache[$tag]);
Database::getConnection()->merge('cache_tags')
$this->connection->merge('cache_tags')
->insertFields(array('invalidations' => 1))
->expression('invalidations', 'invalidations + 1')
->key(array('tag' => $tag))
......@@ -232,7 +243,7 @@ public function invalidateTags(array $tags) {
* Implements Drupal\Core\Cache\CacheBackendInterface::invalidateAll().
*/
public function invalidateAll() {
Database::getConnection()->update($this->bin)
$this->connection->update($this->bin)
->fields(array('expire' => REQUEST_TIME - 1))
->execute();
}
......@@ -296,7 +307,7 @@ protected function checksumTags($flat_tags) {
$query_tags = array_diff($flat_tags, array_keys($tag_cache));
if ($query_tags) {
$db_tags = Database::getConnection()->query('SELECT tag, invalidations, deletions FROM {cache_tags} WHERE tag IN (:tags)', array(':tags' => $query_tags))->fetchAllAssoc('tag', \PDO::FETCH_ASSOC);
$db_tags = $this->connection->query('SELECT tag, invalidations, deletions FROM {cache_tags} WHERE tag IN (:tags)', array(':tags' => $query_tags))->fetchAllAssoc('tag', \PDO::FETCH_ASSOC);
$tag_cache += $db_tags;
// Fill static cache with empty objects for tags not found in the database.
......@@ -316,7 +327,7 @@ protected function checksumTags($flat_tags) {
*/
public function isEmpty() {
$this->garbageCollection();
$query = Database::getConnection()->select($this->bin);
$query = $this->connection->select($this->bin);
$query->addExpression('1');
$result = $query->range(0, 1)
->execute()
......
<?php
/**
* @file
* Contains \Drupal\Core\Cache\DatabaseBackendFactory.
*/
namespace Drupal\Core\Cache;
use Drupal\Core\Database\Connection;
class DatabaseBackendFactory {
/**
* The database connection.
*
* @var \Drupal\Core\Database\Connection
*/
protected $connection;
/**
* Constructs the DatabaseBackendFactory object.
*
* @param \Drupal\Core\Database\Connection $connection
*/
function __construct(Connection $connection) {
$this->connection = $connection;
}
/**
* 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, $bin);
}
}
<?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();
foreach ($container->findTaggedServiceIds('cache.bin') as $id => $attributes) {
$cache_bins[$id] = substr($id, strpos($id, '.') + 1);
}
$container->setParameter('cache_bins', $cache_bins);
}
}
<?php
/**
* @file
* Contains \Drupal\Core\Cache\MemoryBackendFactory.
*/
namespace Drupal\Core\Cache;
class MemoryBackendFactory {
/**
* Gets MemoryBackend for the specified cache bin.
*
* @param $bin
* The cache bin for which the object is created.
*
* @return \Drupal\Core\Cache\MemoryBackend
* The cache backend object for the specified cache bin.
*/
function get($bin) {
return new MemoryBackend($bin);
}
}
......@@ -7,6 +7,8 @@
namespace Drupal\Core;
use Drupal\Core\Cache\CacheFactory;
use Drupal\Core\Cache\ListCacheBinsPass;
use Drupal\Core\DependencyInjection\Compiler\RegisterKernelListenersPass;
use Drupal\Core\DependencyInjection\Compiler\RegisterAccessChecksPass;
use Drupal\Core\DependencyInjection\Compiler\RegisterMatchersPass;
......@@ -36,18 +38,11 @@ class CoreBundle extends Bundle {
* Implements \Symfony\Component\HttpKernel\Bundle\BundleInterface::build().
*/
public function build(ContainerBuilder $container) {
$this->registerCache($container);
// Register active configuration storage.
$container
->register('config.cachedstorage.storage', 'Drupal\Core\Config\FileStorage')
->addArgument(config_get_config_directory(CONFIG_ACTIVE_DIRECTORY));
// @todo Replace this with a cache.factory service plus 'config' argument.
$container
->register('cache.config', 'Drupal\Core\Cache\CacheBackendInterface')
->setFactoryClass('Drupal\Core\Cache\CacheFactory')
->setFactoryMethod('get')
->addArgument('config');
$container
->register('config.storage', 'Drupal\Core\Config\CachedStorage')
->addArgument(new Reference('config.cachedstorage.storage'))
......@@ -170,17 +165,6 @@ public function build(ContainerBuilder $container) {
$container->register('controller_resolver', 'Drupal\Core\ControllerResolver')
->addArgument(new Reference('service_container'));
$container
->register('cache.cache', 'Drupal\Core\Cache\CacheBackendInterface')
->setFactoryClass('Drupal\Core\Cache\CacheFactory')
->setFactoryMethod('get')
->addArgument('cache');
$container
->register('cache.bootstrap', 'Drupal\Core\Cache\CacheBackendInterface')
->setFactoryClass('Drupal\Core\Cache\CacheFactory')
->setFactoryMethod('get')
->addArgument('bootstrap');
$this->registerModuleHandler($container);
$container->register('http_kernel', 'Drupal\Core\HttpKernel')
......@@ -224,12 +208,6 @@ public function build(ContainerBuilder $container) {
->addArgument(new Reference('event_dispatcher'))
->addArgument(new Reference('module_handler'));
$container
->register('cache.path', 'Drupal\Core\Cache\CacheBackendInterface')
->setFactoryClass('Drupal\Core\Cache\CacheFactory')
->setFactoryMethod('get')
->addArgument('path');
$container->register('path.alias_manager.cached', 'Drupal\Core\CacheDecorator\AliasManagerCacheDecorator')
->addArgument(new Reference('path.alias_manager'))
->addArgument(new Reference('cache.path'));
......@@ -447,4 +425,27 @@ protected function registerPathProcessors(ContainerBuilder $container) {
$container->addCompilerPass(new RegisterPathProcessorsPass());
}
/**
* Register services related to cache.
*/
protected function registerCache(ContainerBuilder $container) {
// This factory chooses the backend service for a given bin.
$container
->register('cache_factory', 'Drupal\Core\Cache\CacheFactory')
->addArgument(new Reference('settings'))
->addMethodCall('setContainer', array(new Reference('service_container')));
// These are the core provided backend services.
$container
->register('cache.backend.database', 'Drupal\Core\Cache\DatabaseBackendFactory')
->addArgument(new Reference('database'));
$container
->register('cache.backend.memory', 'Drupal\Core\Cache\MemoryBackendFactory');
// Register a service for each bin for injecting purposes.
foreach (array('bootstrap', 'config', 'cache', 'form', 'menu', 'page', 'path') as $bin) {
CacheFactory::registerBin($container, $bin);
}
$container->addCompilerPass(new ListCacheBinsPass());
}
}
......@@ -496,13 +496,6 @@ function _block_get_renderable_block($element) {
return $element;
}
/**
* Implements hook_cache_flush().
*/
function block_cache_flush() {
return array('block');
}
/**
* Implements hook_rebuild().
*/
......
......@@ -7,6 +7,7 @@
namespace Drupal\Block;
use Drupal\Core\Cache\CacheFactory;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\HttpKernel\Bundle\Bundle;
......@@ -22,6 +23,7 @@ public function build(ContainerBuilder $container) {
// Register the BlockManager class with the dependency injection container.
$container->register('plugin.manager.block', 'Drupal\block\Plugin\Type\BlockManager')
->addArgument('%container.namespaces%');
CacheFactory::registerBin($container, 'block');
}
}
......@@ -34,7 +34,7 @@ public function __construct(array $namespaces) {
$this->discovery = new AnnotatedClassDiscovery('block', 'block', $namespaces);
$this->discovery = new DerivativeDiscoveryDecorator($this->discovery);
$this->discovery = new AlterDecorator($this->discovery, 'block');
$this->discovery = new CacheDecorator($this->discovery, 'block_plugins:' . language(LANGUAGE_TYPE_INTERFACE)->langcode, 'cache_block', CacheBackendInterface::CACHE_PERMANENT, array('block'));
$this->discovery = new CacheDecorator($this->discovery, 'block_plugins:' . language(LANGUAGE_TYPE_INTERFACE)->langcode, 'block', CacheBackendInterface::CACHE_PERMANENT, array('block'));
}
/**
......