Commit a120045c authored by Dries's avatar Dries
Browse files

- Patch #81461 by catch, pillarsdotnet, beejeebus: clean up the cache API...

- Patch #81461 by catch, pillarsdotnet, beejeebus: clean up the cache API (stop overloading function arguments, remove procedural wrappers).
parent 40939063
......@@ -28,7 +28,7 @@
/**
* Indicates that the item should never be removed unless explicitly selected.
*
* The item may be removed using cache_clear_all() with a cache ID.
* The item may be removed using cache()->delete() with a cache ID.
*/
define('CACHE_PERMANENT', 0);
......@@ -283,12 +283,12 @@
abstract class DrupalCacheArray implements ArrayAccess {
/**
* A cid to pass to cache_set() and cache_get().
* A cid to pass to cache()->set() and cache()->get().
*/
private $cid;
/**
* A bin to pass to cache_set() and cache_get().
* A bin to pass to cache()->set() and cache()->get().
*/
private $bin;
......@@ -314,7 +314,7 @@ public function __construct($cid, $bin) {
$this->cid = $cid;
$this->bin = $bin;
if ($cached = cache_get($this->cid, $this->bin)) {
if ($cached = cache($bin)->get($this->cid)) {
$this->storage = $cached->data;
}
}
......@@ -391,10 +391,10 @@ protected function set($cid, $data, $bin, $lock = TRUE) {
// To implement locking for cache misses, override __construct().
$lock_name = $cid . ':' . $bin;
if (!$lock || lock_acquire($lock_name)) {
if ($cached = cache_get($cid, $bin)) {
if ($cached = cache($bin)->get($cid)) {
$data = $cached->data + $data;
}
cache_set($cid, $data, $bin);
cache($bin)->set($cid, $data);
if ($lock) {
lock_release($lock_name);
}
......@@ -899,7 +899,7 @@ function drupal_get_filename($type, $name, $filename = NULL) {
function variable_initialize($conf = array()) {
// NOTE: caching the variables improves performance by 20% when serving
// cached pages.
if ($cached = cache_get('variables', 'cache_bootstrap')) {
if ($cached = cache('bootstrap')->get('variables')) {
$variables = $cached->data;
}
else {
......@@ -914,7 +914,7 @@ function variable_initialize($conf = array()) {
else {
// Proceed with variable rebuild.
$variables = array_map('unserialize', db_query('SELECT name, value FROM {variable}')->fetchAllKeyed());
cache_set('variables', $variables, 'cache_bootstrap');
cache('bootstrap')->set('variables', $variables);
lock_release($name);
}
}
......@@ -971,7 +971,7 @@ function variable_set($name, $value) {
db_merge('variable')->key(array('name' => $name))->fields(array('value' => serialize($value)))->execute();
cache_clear_all('variables', 'cache_bootstrap');
cache('bootstrap')->delete('variables');
$conf[$name] = $value;
}
......@@ -995,7 +995,7 @@ function variable_del($name) {
db_delete('variable')
->condition('name', $name)
->execute();
cache_clear_all('variables', 'cache_bootstrap');
cache('bootstrap')->delete('variables');
unset($conf[$name]);
}
......@@ -1024,7 +1024,7 @@ function drupal_page_get_cache($check_only = FALSE) {
}
if (drupal_page_is_cacheable()) {
$cache = cache_get($base_root . request_uri(), 'cache_page');
$cache = cache('page')->get($base_root . request_uri());
if ($cache !== FALSE) {
$cache_hit = TRUE;
}
......@@ -2791,7 +2791,7 @@ function drupal_get_complete_schema($rebuild = FALSE) {
if (empty($schema) || $rebuild) {
// Try to load the schema from cache.
if (!$rebuild && $cached = cache_get('schema')) {
if (!$rebuild && $cached = cache()->get('schema')) {
$schema = $cached->data;
}
// Otherwise, rebuild the schema cache.
......@@ -2817,7 +2817,7 @@ function drupal_get_complete_schema($rebuild = FALSE) {
// That would break modules which use $schema further down the line.
$current = (array) module_invoke($module, 'schema');
// Set 'module' and 'name' keys for each table, and remove descriptions,
// as they needlessly slow down cache_get() for every single request.
// as they needlessly slow down cache()->get() for every single request.
_drupal_schema_initialize($current, $module);
$schema = array_merge($schema, $current);
}
......@@ -2826,10 +2826,10 @@ function drupal_get_complete_schema($rebuild = FALSE) {
// If the schema is empty, avoid saving it: some database engines require
// the schema to perform queries, and this could lead to infinite loops.
if (!empty($schema) && (drupal_get_bootstrap_phase() == DRUPAL_BOOTSTRAP_FULL)) {
cache_set('schema', $schema);
cache()->set('schema', $schema);
}
if ($rebuild) {
cache_clear_all('schema:', 'cache', TRUE);
cache()->deletePrefix('schema:');
}
}
}
......@@ -2900,7 +2900,7 @@ function _registry_check_code($type, $name = NULL) {
if (!isset($lookup_cache)) {
$lookup_cache = array();
if ($cache = cache_get('lookup_cache', 'cache_bootstrap')) {
if ($cache = cache('bootstrap')->get('lookup_cache')) {
$lookup_cache = $cache->data;
}
}
......@@ -2917,7 +2917,7 @@ function _registry_check_code($type, $name = NULL) {
// changes to the lookup cache for this request.
if ($type == REGISTRY_WRITE_LOOKUP_CACHE) {
if ($cache_update_needed) {
cache_set('lookup_cache', $lookup_cache, 'cache_bootstrap');
cache('bootstrap')->set('lookup_cache', $lookup_cache);
}
return;
}
......
......@@ -26,6 +26,16 @@ function getMultiple(&$cids) {
function set($cid, $data, $expire = CACHE_PERMANENT) {
}
function deletePrefix($cid) {
try {
if (class_exists('Database')) {
parent::deletePrefix($cid);
}
}
catch (Exception $e) {
}
}
function clear($cid = NULL, $wildcard = FALSE) {
// If there is a database cache, attempt to clear it whenever possible. The
// reason for doing this is that the database cache can accumulate data
......
<?php
/**
* Get the cache object for a cache bin.
* Factory for instantiating and statically caching the correct class for a cache bin.
*
* By default, this returns an instance of the DrupalDatabaseCache class.
* Classes implementing DrupalCacheInterface can register themselves both as a
......@@ -10,11 +10,16 @@
* @see DrupalCacheInterface
*
* @param $bin
* The cache bin for which the cache object should be returned.
* The cache bin for which the cache object should be returned, defaults to
* 'cache'.
* @return DrupalCacheInterface
* The cache object associated with the specified bin.
*/
function _cache_get_object($bin) {
function cache($bin = 'cache') {
// Temporary backwards compatibiltiy layer, allow old style prefixed cache
// bin names to be passed as arguments.
$bin = str_replace('cache_', '', $bin);
// We do not use drupal_static() here because we do not want to change the
// storage of a cache bin mid-request.
static $cache_objects;
......@@ -46,7 +51,7 @@ function _cache_get_object($bin) {
* The cache or FALSE on failure.
*/
function cache_get($cid, $bin = 'cache') {
return _cache_get_object($bin)->get($cid);
return cache($bin)->get($cid);
}
/**
......@@ -61,7 +66,7 @@ function cache_get($cid, $bin = 'cache') {
* An array of the items successfully returned from cache indexed by cid.
*/
function cache_get_multiple(array &$cids, $bin = 'cache') {
return _cache_get_object($bin)->getMultiple($cids);
return cache($bin)->getMultiple($cids);
}
/**
......@@ -134,7 +139,7 @@ function cache_get_multiple(array &$cids, $bin = 'cache') {
* the given time, after which it behaves like CACHE_TEMPORARY.
*/
function cache_set($cid, $data, $bin = 'cache', $expire = CACHE_PERMANENT) {
return _cache_get_object($bin)->set($cid, $data, $expire);
return cache($bin)->set($cid, $data, $expire);
}
/**
......@@ -161,12 +166,12 @@ function cache_clear_all($cid = NULL, $bin = NULL, $wildcard = FALSE) {
// Clear the block cache first, so stale data will
// not end up in the page cache.
if (module_exists('block')) {
cache_clear_all(NULL, 'cache_block');
cache('block')->expire();
}
cache_clear_all(NULL, 'cache_page');
cache('page')->expire();
return;
}
return _cache_get_object($bin)->clear($cid, $wildcard);
return cache($bin)->clear($cid, $wildcard);
}
/**
......@@ -181,7 +186,7 @@ function cache_clear_all($cid = NULL, $bin = NULL, $wildcard = FALSE) {
* TRUE if the cache bin specified is empty.
*/
function cache_is_empty($bin) {
return _cache_get_object($bin)->isEmpty();
return cache($bin)->isEmpty();
}
/**
......@@ -218,7 +223,7 @@ function cache_is_empty($bin) {
* cache_get($cid, 'custom_bin');
* @endcode
*
* @see _cache_get_object()
* @see cache()
* @see DrupalDatabaseCache
*/
interface DrupalCacheInterface {
......@@ -274,6 +279,44 @@ function getMultiple(&$cids);
*/
function set($cid, $data, $expire = CACHE_PERMANENT);
/**
* Delete an item from the cache.
*
* @param $cid
* The cache ID to delete.
*/
function delete($cid);
/**
* Delete multiple items from the cache.
*
* @param $cids
* An array of $cids to delete.
*/
function deleteMultiple(Array $cids);
/**
* Delete items from the cache using a wildcard prefix.
*
* @param $prefix
* A wildcard prefix.
*/
function deletePrefix($prefix);
/**
* Flush all cache items in a bin.
*/
function flush();
/**
* Expire temporary items from cache.
*/
function expire();
/**
* Perform garbage collection on a cache bin.
*/
function garbageCollection();
/**
* Expire data from the cache. If called without arguments, expirable
......@@ -286,6 +329,9 @@ function set($cid, $data, $expire = CACHE_PERMANENT);
* If set to TRUE, the $cid is treated as a substring
* to match rather than a complete ID. The match is a right hand
* match. If '*' is given as $cid, the bin $bin will be emptied.
*
* @todo: this method is deprecated, as it's functionality is covered by
* more targetted methods in the interface.
*/
function clear($cid = NULL, $wildcard = FALSE);
......@@ -311,6 +357,11 @@ class DrupalDatabaseCache implements DrupalCacheInterface {
protected $bin;
function __construct($bin) {
// All cache tables should be prefixed with 'cache_', apart from the
// default 'cache' bin, which would look silly.
if ($bin != 'cache') {
$bin = 'cache_' . $bin;
}
$this->bin = $bin;
}
......@@ -350,28 +401,6 @@ function getMultiple(&$cids) {
}
}
/**
* Garbage collection for get() and getMultiple().
*
* @param $bin
* The bin being requested.
*/
protected function garbageCollection() {
global $user;
// Garbage collection necessary when enforcing a minimum cache lifetime.
$cache_flush = variable_get('cache_flush_' . $this->bin, 0);
if ($cache_flush && ($cache_flush + variable_get('cache_lifetime', 0) <= REQUEST_TIME)) {
// Reset the variable immediately to prevent a meltdown in heavy load situations.
variable_set('cache_flush_' . $this->bin, 0);
// Time to flush old cache data
db_delete($this->bin)
->condition('expire', CACHE_PERMANENT, '<>')
->condition('expire', $cache_flush, '<=')
->execute();
}
}
/**
* Prepare a cached item.
*
......@@ -434,64 +463,101 @@ function set($cid, $data, $expire = CACHE_PERMANENT) {
}
}
function clear($cid = NULL, $wildcard = FALSE) {
global $user;
function delete($cid) {
db_delete($this->bin)
->condition('cid', $cid)
->execute();
}
if (empty($cid)) {
if (variable_get('cache_lifetime', 0)) {
// We store the time in the current user's $user->cache variable which
// will be saved into the sessions bin by _drupal_session_write(). We then
// simulate that the cache was flushed for this user by not returning
// cached data that was cached before the timestamp.
$user->cache = REQUEST_TIME;
$cache_flush = variable_get('cache_flush_' . $this->bin, 0);
if ($cache_flush == 0) {
// This is the first request to clear the cache, start a timer.
variable_set('cache_flush_' . $this->bin, REQUEST_TIME);
}
elseif (REQUEST_TIME > ($cache_flush + variable_get('cache_lifetime', 0))) {
// Clear the cache for everyone, cache_lifetime seconds have
// passed since the first request to clear the cache.
db_delete($this->bin)
->condition('expire', CACHE_PERMANENT, '<>')
->condition('expire', REQUEST_TIME, '<')
->execute();
variable_set('cache_flush_' . $this->bin, 0);
}
function deleteMultiple(Array $cids) {
// Delete in chunks when a large array is passed.
do {
db_delete($this->bin)
->condition('cid', array_splice($cids, 0, 1000), 'IN')
->execute();
}
while (count($cids));
}
function deletePrefix($prefix) {
db_delete($this->bin)
->condition('cid', db_like($prefix) . '%', 'LIKE')
->execute();
}
function flush() {
db_truncate($this->bin)->execute();
}
function expire() {
if (variable_get('cache_lifetime', 0)) {
// We store the time in the current user's $user->cache variable which
// will be saved into the sessions bin by _drupal_session_write(). We then
// simulate that the cache was flushed for this user by not returning
// cached data that was cached before the timestamp.
$GLOBALS['user']->cache = REQUEST_TIME;
$cache_flush = variable_get('cache_flush_' . $this->bin, 0);
if ($cache_flush == 0) {
// This is the first request to clear the cache, start a timer.
variable_set('cache_flush_' . $this->bin, REQUEST_TIME);
}
else {
// No minimum cache lifetime, flush all temporary cache entries now.
elseif (REQUEST_TIME > ($cache_flush + variable_get('cache_lifetime', 0))) {
// Clear the cache for everyone, cache_lifetime seconds have
// passed since the first request to clear the cache.
db_delete($this->bin)
->condition('expire', CACHE_PERMANENT, '<>')
->condition('expire', REQUEST_TIME, '<')
->execute();
variable_set('cache_flush_' . $this->bin, 0);
}
}
else {
// No minimum cache lifetime, flush all temporary cache entries now.
db_delete($this->bin)
->condition('expire', CACHE_PERMANENT, '<>')
->condition('expire', REQUEST_TIME, '<')
->execute();
}
}
function garbageCollection() {
global $user;
// When cache lifetime is in force, avoid running garbage collection too
// often since this will remove temporary cache items indiscriminately.
$cache_flush = variable_get('cache_flush_' . $this->bin, 0);
if ($cache_flush && ($cache_flush + variable_get('cache_lifetime', 0) <= REQUEST_TIME)) {
// Reset the variable immediately to prevent a meltdown in heavy load situations.
variable_set('cache_flush_' . $this->bin, 0);
// Time to flush old cache data
db_delete($this->bin)
->condition('expire', CACHE_PERMANENT, '<>')
->condition('expire', $cache_flush, '<=')
->execute();
}
}
function clear($cid = NULL, $wildcard = FALSE) {
global $user;
if (empty($cid)) {
$this->expire();
}
else {
if ($wildcard) {
if ($cid == '*') {
db_truncate($this->bin)->execute();
$this->flush();
}
else {
db_delete($this->bin)
->condition('cid', db_like($cid) . '%', 'LIKE')
->execute();
$this->deletePrefix($cid);
}
}
elseif (is_array($cid)) {
// Delete in chunks when a large array is passed.
do {
db_delete($this->bin)
->condition('cid', array_splice($cid, 0, 1000), 'IN')
->execute();
}
while (count($cid));
$this->deleteMultiple($cid);
}
else {
db_delete($this->bin)
->condition('cid', $cid)
->execute();
$this->delete($cid);
}
}
}
......
......@@ -1238,6 +1238,10 @@ protected function setUp() {
->condition('test_id', $this->testId)
->execute();
// Reset all statics and variables to perform tests in a clean environment.
$conf = array();
drupal_static_reset();
// Clone the current connection and replace the current prefix.
$connection_info = Database::getConnectionInfo('default');
Database::renameConnection('default', 'simpletest_original_default');
......@@ -1285,10 +1289,6 @@ protected function setUp() {
ini_set('log_errors', 1);
ini_set('error_log', $public_files_directory . '/error.log');
// Reset all statics and variables to perform tests in a clean environment.
$conf = array();
drupal_static_reset();
// Set the test information for use in other parts of Drupal.
$test_info = &$GLOBALS['drupal_test_info'];
$test_info['test_run_id'] = $this->databasePrefix;
......
<?php
class CacheTestCase extends DrupalWebTestCase {
protected $default_bin = 'cache';
protected $default_bin = 'cache_page';
protected $default_cid = 'test_temporary';
protected $default_value = 'CacheTest';
......@@ -22,9 +22,9 @@ class CacheTestCase extends DrupalWebTestCase {
$bin = $this->default_bin;
}
$cache = cache_get($cid, $bin);
$cached = cache($bin)->get($cid);
return isset($cache->data) && $cache->data == $var;
return isset($cached->data) && $cached->data == $var;
}
/**
......@@ -71,8 +71,8 @@ class CacheTestCase extends DrupalWebTestCase {
$cid = $this->default_cid;
}
$cache = cache_get($cid, $bin);
$this->assertFalse($cache, $message);
$cached = cache($bin)->get($cid);
$this->assertFalse($cached, $message);
}
/**
......@@ -85,7 +85,7 @@ class CacheTestCase extends DrupalWebTestCase {
$bin = $this->default_bin;
}
cache_clear_all(NULL, $bin);
cache($bin)->expire();
}
/**
......@@ -146,23 +146,24 @@ class CacheSavingCase extends CacheTestCase {
$test_object->test2 = 100;
$test_object->test3 = array('drupal1', 'drupal2' => 'drupal3', 'drupal4' => array('drupal5', 'drupal6'));
cache_set('test_object', $test_object, 'cache');
$cache = cache_get('test_object', 'cache');
$this->assertTrue(isset($cache->data) && $cache->data == $test_object, t('Object is saved and restored properly.'));
cache()->set('test_object', $test_object);
$cached = cache()->get('test_object');
$this->assertTrue(isset($cached->data) && $cached->data == $test_object, t('Object is saved and restored properly.'));
}
/*
* Check or a variable is stored and restored properly.
**/
function checkVariable($var) {
cache_set('test_var', $var, 'cache');
$cache = cache_get('test_var', 'cache');
$this->assertTrue(isset($cache->data) && $cache->data === $var, t('@type is saved and restored properly.', array('@type' => ucfirst(gettype($var)))));
cache()->set('test_var', $var);
$cached = cache()->get('test_var');
$this->assertTrue(isset($cached->data) && $cached->data === $var, t('@type is saved and restored properly.', array('@type' => ucfirst(gettype($var)))));
}
}
/**
* Test cache_get_multiple().
* Test getMultiple().
*/
class CacheGetMultipleUnitTest extends CacheTestCase {
......@@ -175,33 +176,34 @@ class CacheGetMultipleUnitTest extends CacheTestCase {
}
function setUp() {
$this->default_bin = 'cache_page';
$this->default_bin = 'page';
parent::setUp();
}
/**
* Test cache_get_multiple().
* Test getMultiple().
*/
function testCacheMultiple() {
$item1 = $this->randomName(10);
$item2 = $this->randomName(10);
cache_set('item1', $item1, $this->default_bin);
cache_set('item2', $item2, $this->default_bin);
$cache = cache($this->default_bin);
$cache->set('item1', $item1);
$cache->set('item2', $item2);
$this->assertTrue($this->checkCacheExists('item1', $item1), t('Item 1 is cached.'));
$this->assertTrue($this->checkCacheExists('item2', $item2), t('Item 2 is cached.'));
// Fetch both records from the database with cache_get_multiple().
// Fetch both records from the database with getMultiple().
$item_ids = array('item1', 'item2');
$items = cache_get_multiple($item_ids, $this->default_bin);
$items = $cache->getMultiple($item_ids);
$this->assertEqual($items['item1']->data, $item1, t('Item was returned from cache successfully.'));
$this->assertEqual($items['item2']->data, $item2, t('Item was returned from cache successfully.'));
// Remove one item from the cache.
cache_clear_all('item2', $this->default_bin);
$cache->delete('item2');
// Confirm that only one item is returned by cache_get_multiple().
// Confirm that only one item is returned by getMultiple().
$item_ids = array('item1', 'item2');
$items = cache_get_multiple($item_ids, $this->default_bin);
$items = $cache->getMultiple($item_ids);
$this->assertEqual($items['item1']->data, $item1, t('Item was returned from cache successfully.'));
$this->assertFalse(isset($items['item2']), t('Item was not returned from the cache.'));
$this->assertTrue(count($items) == 1, t('Only valid cache entries returned.'));
......@@ -221,7 +223,7 @@ class CacheClearCase extends CacheTestCase {
}
function setUp() {
$this->default_bin = 'cache_page';
$this->default_bin = 'page';
$this->default_value = $this->randomName(10);
parent::setUp();
......@@ -231,44 +233,36 @@ class CacheClearCase extends CacheTestCase {
* Test clearing using a cid.
*/
function testClearCid() {
cache_set('test_cid_clear', $this->default_value, $this->default_bin);
$cache = cache($this->default_bin);
$cache->set('test_cid_clear', $this->default_value);
$this->assertCacheExists(t(