Commit 3d94f559 authored by catch's avatar catch

Issue #1637478 by alexpott, pounard, catch: Add a PHP array cache backend.

parent 6e8a671f
<?php
/**
* @file
* Definition of Drupal\Core\Cache\ArrayBackend.
*/
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.
*
*/
class MemoryBackend implements CacheBackendInterface {
/**
* Array to store cache objects.
*/
protected $cache;
/**
* All tags invalidated during the request.
*/
protected $invalidatedTags = array();
/**
* Implements Drupal\Core\Cache\CacheBackendInterface::__construct().
*/
public function __construct($bin) {
}
/**
* Implements Drupal\Core\Cache\CacheBackendInterface::get().
*/
public function get($cid) {
if (isset($this->cache[$cid])) {
return $this->prepareItem($this->cache[$cid]);
}
else {
return FALSE;
}
}
/**
* Implements Drupal\Core\Cache\CacheBackendInterface::getMultiple().
*/
public function getMultiple(&$cids) {
$ret = array();
$items = array_intersect_key($this->cache, array_flip($cids));
foreach ($items as $item) {
$item = $this->prepareItem($item);
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 stdClass $cache
* An item loaded from cache_get() or cache_get_multiple().
*
* @return mixed
* The item with data as appropriate or FALSE if there is no
* valid item to load.
*/
protected function prepareItem($cache) {
if (!isset($cache->data)) {
return FALSE;
}
// The cache data is invalid if any of its tags have been cleared since.
if (count($cache->tags) && $this->hasInvalidatedTags($cache)) {
return FALSE;
}
return $cache;
}
/**
* Implements Drupal\Core\Cache\CacheBackendInterface::set().
*/
public function set($cid, $data, $expire = CACHE_PERMANENT, array $tags = array()) {
$this->cache[$cid] = (object) array(
'cid' => $cid,
'data' => $data,
'expire' => $expire,
'tags' => $tags,
'checksum' => $this->checksum($this->flattenTags($tags)),
);
}
/*
* Calculates a checksum so data can be invalidated using tags.
*/
function checksum($tags) {
$checksum = "";
foreach($tags as $tag) {
// Has the tag already been invalidated.
if (isset($this->invalidatedTags[$tag])) {
$checksum = $checksum . $tag . ':' . $this->invalidatedTags[$tag];
}
}
return $checksum;
}
/**
* 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::deletePrefix().
*/
public function deletePrefix($prefix) {
foreach ($this->cache as $cid => $item) {
if (strpos($cid, $prefix) === 0) {
unset($this->cache[$cid]);
}
}
}
/**
* Implements Drupal\Core\Cache\CacheBackendInterface::flush().
*/
public function flush() {
$this->cache = array();
}
/**
* Implements Drupal\Core\Cache\CacheBackendInterface::expire().
*
* Cache expiration is not implemented for PHP ArrayBackend as this backend
* only persists during a single request and expiration are done using
* REQUEST_TIME.
*/
public function expire() {
}
/**
* Checks to see if any of the tags associated with a cache object have been
* invalidated.
*
* @param object @cache
* An cache object to calculate and compare it's original checksum for.
*
* @return boolean
* TRUE if the a tag has been invalidated, FALSE otherwise.
*/
protected function hasInvalidatedTags($cache) {
if ($cache->checksum != $this->checksum($this->flattenTags($cache->tags))) {
return TRUE;
}
return FALSE;
}
/**
* Flattens a tags array into a numeric array suitable for string storage.
*
* @param array $tags
* Associative array of tags to flatten.
*
* @return
* An array of flattened tag identifiers.
*/
protected function flattenTags(array $tags) {
if (isset($tags[0])) {
return $tags;
}
$flat_tags = array();
foreach ($tags as $namespace => $values) {
if (is_array($values)) {
foreach ($values as $value) {
$flat_tags["$namespace:$value"] = "$namespace:$value";
}
}
else {
$flat_tags["$namespace:$value"] = "$namespace:$values";
}
}
return $flat_tags;
}
/**
* Implements Drupal\Core\Cache\CacheBackendInterface::invalidateTags().
*/
public function invalidateTags(array $tags) {
$flat_tags = $this->flattenTags($tags);
foreach($flat_tags as $tag) {
if (isset($this->invalidatedTags[$tag])) {
$this->invalidatedTags[$tag] = $this->invalidatedTags[$tag] + 1;
}
else {
$this->invalidatedTags[$tag] = 1;
}
}
}
/**
* Implements Drupal\Core\Cache\CacheBackendInterface::isEmpty().
*/
public function isEmpty() {
return empty($this->cache);
}
/**
* Implements Drupal\Core\Cache\CacheBackendInterface::garbageCollection()
*/
public function garbageCollection() {
}
}
......@@ -13,6 +13,7 @@
* Provides helper methods for cache tests.
*/
abstract class CacheTestBase extends WebTestBase {
protected $default_bin = 'page';
protected $default_cid = 'test_temporary';
protected $default_value = 'CacheTest';
......
......@@ -11,6 +11,7 @@
* Tests cache clearing methods.
*/
class ClearTest extends CacheTestBase {
public static function getInfo() {
return array(
'name' => 'Cache clear test',
......@@ -26,83 +27,6 @@ function setUp() {
parent::setUp();
}
/**
* Test clearing using a cid.
*/
function testClearCid() {
$cache = cache($this->default_bin);
$cache->set('test_cid_clear', $this->default_value);
$this->assertCacheExists(t('Cache was set for clearing cid.'), $this->default_value, 'test_cid_clear');
$cache->delete('test_cid_clear');
$this->assertCacheRemoved(t('Cache was removed after clearing cid.'), 'test_cid_clear');
}
/**
* Test clearing using wildcard.
*/
function testClearWildcard() {
$cache = cache($this->default_bin);
$cache->set('test_cid_clear1', $this->default_value);
$cache->set('test_cid_clear2', $this->default_value);
$this->assertTrue($this->checkCacheExists('test_cid_clear1', $this->default_value)
&& $this->checkCacheExists('test_cid_clear2', $this->default_value),
t('Two caches were created for checking cid "*" with wildcard true.'));
$cache->flush();
$this->assertFalse($this->checkCacheExists('test_cid_clear1', $this->default_value)
|| $this->checkCacheExists('test_cid_clear2', $this->default_value),
t('Two caches removed after clearing cid "*" with wildcard true.'));
$cache->set('test_cid_clear1', $this->default_value);
$cache->set('test_cid_clear2', $this->default_value);
$this->assertTrue($this->checkCacheExists('test_cid_clear1', $this->default_value)
&& $this->checkCacheExists('test_cid_clear2', $this->default_value),
t('Two caches were created for checking cid substring with wildcard true.'));
$cache->deletePrefix('test_');
$this->assertFalse($this->checkCacheExists('test_cid_clear1', $this->default_value)
|| $this->checkCacheExists('test_cid_clear2', $this->default_value),
t('Two caches removed after clearing cid substring with wildcard true.'));
}
/**
* Test clearing using an array.
*/
function testClearArray() {
// Create three cache entries.
$cache = cache($this->default_bin);
$cache->set('test_cid_clear1', $this->default_value);
$cache->set('test_cid_clear2', $this->default_value);
$cache->set('test_cid_clear3', $this->default_value);
$this->assertTrue($this->checkCacheExists('test_cid_clear1', $this->default_value)
&& $this->checkCacheExists('test_cid_clear2', $this->default_value)
&& $this->checkCacheExists('test_cid_clear3', $this->default_value),
t('Three cache entries were created.'));
// Clear two entries using an array.
$cache->deleteMultiple(array('test_cid_clear1', 'test_cid_clear2'));
$this->assertFalse($this->checkCacheExists('test_cid_clear1', $this->default_value)
|| $this->checkCacheExists('test_cid_clear2', $this->default_value),
t('Two cache entries removed after clearing with an array.'));
$this->assertTrue($this->checkCacheExists('test_cid_clear3', $this->default_value),
t('Entry was not cleared from the cache'));
// Set the cache clear threshold to 2 to confirm that the full bin is cleared
// when the threshold is exceeded.
variable_set('cache_clear_threshold', 2);
$cache->set('test_cid_clear1', $this->default_value);
$cache->set('test_cid_clear2', $this->default_value);
$this->assertTrue($this->checkCacheExists('test_cid_clear1', $this->default_value)
&& $this->checkCacheExists('test_cid_clear2', $this->default_value),
t('Two cache entries were created.'));
$cache->deleteMultiple(array('test_cid_clear1', 'test_cid_clear2', 'test_cid_clear3'));
$this->assertFalse($this->checkCacheExists('test_cid_clear1', $this->default_value)
|| $this->checkCacheExists('test_cid_clear2', $this->default_value)
|| $this->checkCacheExists('test_cid_clear3', $this->default_value),
t('All cache entries removed when the array exceeded the cache clear threshold.'));
}
/**
* Test drupal_flush_all_caches().
*/
......@@ -124,49 +48,4 @@ function testFlushAllCaches() {
$this->assertFalse($this->checkCacheExists($cid, $this->default_value, $bin), t('All cache entries removed from @bin.', array('@bin' => $bin)));
}
}
/**
* Test clearing using cache tags.
*/
function testClearTags() {
$cache = cache($this->default_bin);
$cache->set('test_cid_clear1', $this->default_value, CACHE_PERMANENT, array('test_tag' => array(1)));
$cache->set('test_cid_clear2', $this->default_value, CACHE_PERMANENT, array('test_tag' => array(1)));
$this->assertTrue($this->checkCacheExists('test_cid_clear1', $this->default_value)
&& $this->checkCacheExists('test_cid_clear2', $this->default_value),
t('Two cache items were created.'));
cache_invalidate(array('test_tag' => array(1)));
$this->assertFalse($this->checkCacheExists('test_cid_clear1', $this->default_value)
|| $this->checkCacheExists('test_cid_clear2', $this->default_value),
t('Two caches removed after clearing a cache tag.'));
$cache->set('test_cid_clear1', $this->default_value, CACHE_PERMANENT, array('test_tag' => array(1)));
$cache->set('test_cid_clear2', $this->default_value, CACHE_PERMANENT, array('test_tag' => array(2)));
$cache->set('test_cid_clear3', $this->default_value, CACHE_PERMANENT, array('test_tag_foo' => array(3)));
$this->assertTrue($this->checkCacheExists('test_cid_clear1', $this->default_value)
&& $this->checkCacheExists('test_cid_clear2', $this->default_value)
&& $this->checkCacheExists('test_cid_clear3', $this->default_value),
t('Two cached items were created.'));
cache_invalidate(array('test_tag_foo' => array(3)));
$this->assertTrue($this->checkCacheExists('test_cid_clear1', $this->default_value)
&& $this->checkCacheExists('test_cid_clear2', $this->default_value),
t('Cached items not matching the tag were not cleared.'));
$this->assertFalse($this->checkCacheExists('test_cid_clear3', $this->default_value),
t('Cached item matching the tag was removed.'));
// For our next trick, we will attempt to clear data in multiple bins.
$tags = array('test_tag' => array(1, 2, 3));
$bins = array('cache', 'cache_page', 'cache_bootstrap');
foreach ($bins as $bin) {
cache($bin)->set('test', $this->default_value, CACHE_PERMANENT, $tags);
$this->assertTrue($this->checkCacheExists('test', $this->default_value, $bin), 'Cache item was set in bin.');
}
cache_invalidate(array('test_tag' => array(2)));
foreach ($bins as $bin) {
$this->assertFalse($this->checkCacheExists('test', $this->default_value, $bin), 'Tag expire affected item in bin.');
}
$this->assertFalse($this->checkCacheExists('test_cid_clear2', $this->default_value), 'Cached items matching tag were cleared.');
$this->assertTrue($this->checkCacheExists('test_cid_clear1', $this->default_value), 'Cached items not matching tag were not cleared.');
}
}
<?php
/**
* @file
* Definition of Drupal\system\Tests\Cache\DatabaseBackendUnitTest.
*/
namespace Drupal\system\Tests\Cache;
use Drupal\Core\Cache\DatabaseBackend;
/**
* Tests DatabaseBackend using GenericCacheBackendUnitTestBase.
*/
class DatabaseBackendUnitTest extends GenericCacheBackendUnitTestBase {
public static function getInfo() {
return array(
'name' => 'Database backend',
'description' => 'Unit test of the database backend using the generic cache unit test base.',
'group' => 'Cache',
);
}
protected function createCacheBackend($bin) {
return new DatabaseBackend($bin);
}
public function setUpCacheBackend() {
drupal_install_schema('system');
}
public function tearDownCacheBackend() {
drupal_uninstall_schema('system');
}
}
<?php
/**
* @file
* Definition of Drupal\system\Tests\Cache\GetMultipleTest.
*/
namespace Drupal\system\Tests\Cache;
/**
* Tests getMultiple().
*/
class GetMultipleTest extends CacheTestBase {
public static function getInfo() {
return array(
'name' => 'Fetching multiple cache items',
'description' => 'Confirm that multiple records are fetched correctly.',
'group' => 'Cache',
);
}
function setUp() {
$this->default_bin = 'page';
parent::setUp();
}
/**
* Test getMultiple().
*/
function testCacheMultiple() {
$item1 = $this->randomName(10);
$item2 = $this->randomName(10);
$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 getMultiple().
$item_ids = array('item1', 'item2');
$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->delete('item2');
// Confirm that only one item is returned by getMultiple().
$item_ids = array('item1', 'item2');
$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.'));
}
}
<?php
/**
* @file
* Definition of Drupal\system\Tests\Cache\IsEmptyTest.
*/
namespace Drupal\system\Tests\Cache;
/**
* Tests the isEmpty() method.
*/
class IsEmptyTest extends CacheTestBase {
public static function getInfo() {
return array(
'name' => 'Cache emptiness test',
'description' => 'Check if a cache bin is empty after performing clear operations.',
'group' => 'Cache'
);
}
function setUp() {
$this->default_bin = 'page';
$this->default_value = $this->randomName(10);
parent::setUp();
}
/**
* Test clearing using a cid.
*/
function testIsEmpty() {
// Clear the cache bin.
$cache = cache($this->default_bin);
$cache->flush();
$this->assertTrue($cache->isEmpty(), t('The cache bin is empty'));
// Add some data to the cache bin.
$cache->set($this->default_cid, $this->default_value);
$this->assertCacheExists(t('Cache was set.'), $this->default_value, $this->default_cid);
$this->assertFalse($cache->isEmpty(), t('The cache bin is not empty'));
// Remove the cached data.
$cache->delete($this->default_cid);
$this->assertCacheRemoved(t('Cache was removed.'), $this->default_cid);
$this->assertTrue($cache->isEmpty(), t('The cache bin is empty'));
}
}
<?php
/**
* @file
* Definition of Drupal\system\Tests\Cache\ArrayBackendUnitTest.
*/
namespace Drupal\system\Tests\Cache;
use Drupal\Core\Cache\MemoryBackend;
/**
* Tests MemoryBackend using GenericCacheBackendUnitTestBase.
*/
class MemoryBackendUnitTest extends GenericCacheBackendUnitTestBase {
public static function getInfo() {
return array(
'name' => 'Memory cache backend',
'description' => 'Unit test of the memory cache backend using the generic cache unit test base.',
'group' => 'Cache',
);
}
protected function createCacheBackend($bin) {
return new MemoryBackend($bin);
}
}
......@@ -14,6 +14,7 @@
* Tests the cache NullBackend.
*/
class NullBackendTest extends UnitTestBase {
public static function getInfo() {
return array(
'name' => 'Cache NullBackend test',
......
<?php
/**
* @file
* Definition of Drupal\system\Tests\Cache\SavingTest.
*/
namespace Drupal\system\Tests\Cache;
use stdClass;
/**
* Tests that variables are saved and restored in the right way.
*/
class SavingTest extends CacheTestBase {
public static function getInfo() {
return array(
'name' => 'Cache saving test',
'description' => 'Check our variables are saved and restored the right way.',
'group' => 'Cache'
);
}
/**
* Test the saving and restoring of a string.
*/
function testString() {
$this->checkVariable($this->randomName(100));
}
/**
* Test the saving and restoring of an integer.
*/
function testInteger() {
$this->checkVariable(100);
}
/**
* Test the saving and restoring of a double.
*/
function testDouble() {
$this->checkVariable(1.29);
}
/**
* Test the saving and restoring of an array.
*/
function testArray() {
$this->checkVariable(array('drupal1', 'drupal2' => 'drupal3', 'drupal4' => array('drupal5', 'drupal6')));
}
/**
* Test the saving and restoring of an object.
*/
function testObject() {
$test_object = new stdClass();
$test_object->test1 = $this->randomName(100);
$test_object->test2 = 100;
$test_object->test3 = array('drupal1', 'drupal2' => 'drupal3', 'drupal4' => array('drupal5', 'drupal6'));
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);
$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 no empty cids are written in cache table.
*/
function testNoEmptyCids() {
$this->drupalGet('user/register');
$this->assertFalse(cache()->get(''), t('No cache entry is written with an empty cid.'));
}
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment