Skip to content
Snippets Groups Projects
Commit 2ea43def authored by catch's avatar catch
Browse files

Issue #2228261 by beejeebus, kim.pepper, alexpott, Wim Leers: Add a local,...

Issue #2228261 by beejeebus, kim.pepper, alexpott, Wim Leers: Add a local, PhpStorage-based cache backend.
parent dda140f9
No related branches found
No related tags found
2 merge requests!7452Issue #1797438. HTML5 validation is preventing form submit and not fully...,!789Issue #3210310: Adjust Database API to remove deprecated Drupal 9 code in Drupal 10
Showing with 433 additions and 7 deletions
......@@ -25,6 +25,8 @@ services:
cache.backend.database:
class: Drupal\Core\Cache\DatabaseBackendFactory
arguments: ['@database']
cache.backend.php:
class: Drupal\Core\Cache\PhpBackendFactory
cache.bootstrap:
class: Drupal\Core\Cache\CacheBackendInterface
tags:
......
......@@ -64,9 +64,9 @@ public function delete($name) {
}
/**
* Returns the full path where the file is or should be stored.
* {@inheritdoc}
*/
protected function getFullPath($name) {
public function getFullPath($name) {
return $this->directory . '/' . $name;
}
......@@ -83,4 +83,23 @@ function writeable() {
public function deleteAll() {
return FALSE;
}
/**
* {@inheritdoc}
*/
public function listAll() {
$names = array();
if (file_exists($this->directory)) {
foreach (new \DirectoryIterator($this->directory) as $fileinfo) {
if (!$fileinfo->isDot()) {
$name = $fileinfo->getFilename();
if ($name != '.htaccess') {
$names[] = $name;
}
}
}
}
return $names;
}
}
......@@ -195,9 +195,9 @@ public function delete($name) {
}
/**
* Returns the full path where the file is or should be stored.
* {@inheritdoc}
*/
protected function getFullPath($name) {
public function getFullPath($name) {
return $this->directory . '/' . $name;
}
......@@ -248,4 +248,23 @@ protected function unlink($path) {
// If there's nothing to delete return TRUE anyway.
return TRUE;
}
/**
* {@inheritdoc}
*/
public function listAll() {
$names = array();
if (file_exists($this->directory)) {
foreach (new \DirectoryIterator($this->directory) as $fileinfo) {
if (!$fileinfo->isDot()) {
$name = $fileinfo->getFilename();
if ($name != '.htaccess') {
$names[] = $name;
}
}
}
}
return $names;
}
}
......@@ -140,7 +140,7 @@ public function save($name, $data) {
* @return string
* The full path where the file is or should be stored.
*/
protected function getFullPath($name, &$directory = NULL, &$directory_mtime = NULL) {
public function getFullPath($name, &$directory = NULL, &$directory_mtime = NULL) {
if (!isset($directory)) {
$directory = $this->getContainingDirectoryFullPath($name);
}
......@@ -150,6 +150,17 @@ protected function getFullPath($name, &$directory = NULL, &$directory_mtime = NU
return $directory . '/' . hash_hmac('sha256', $name, $this->secret . $directory_mtime) . '.php';
}
/**
* {@inheritdoc}
*/
public function delete($name) {
$path = $this->getContainingDirectoryFullPath($name);
if (file_exists($path)) {
return $this->unlink($path);
}
return FALSE;
}
/**
* Returns the full path of the containing directory where the file is or should be stored.
*/
......
......@@ -65,4 +65,12 @@ protected function checkFile($name) {
$filename = $this->getFullPath($name, $directory, $directory_mtime);
return file_exists($filename) && filemtime($filename) <= $directory_mtime ? $filename : FALSE;
}
/**
* {@inheritdoc}
*/
public function getPath($name) {
return $this->checkFile($name);
}
}
......@@ -78,4 +78,25 @@ public function delete($name);
* Removes all files in this bin.
*/
public function deleteAll();
/**
* Gets the full file path.
*
* @param string $name
* The virtual file name. Can be a relative path.
*
* @return string|FALSE
* The full file path for the provided name. Return FALSE if the
* implementation needs to prevent access to the file.
*/
public function getFullPath($name);
/**
* Lists all the files in the storage.
*
* @return array
* Array of filenames.
*/
public function listAll();
}
<?php
/**
* @file
* Contains \Drupal\Core\Cache\PhpBackend.
*/
namespace Drupal\Core\Cache;
use Drupal\Core\PhpStorage\PhpStorageFactory;
use Drupal\Component\Utility\Variable;
/**
* 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();
/**
* Constructs a PhpBackend object.
*
* @param string $bin
* The cache bin for which the object is created.
*/
public function __construct($bin) {
$this->bin = 'cache_' . $bin;
}
/**
* {@inheritdoc}
*/
public function get($cid, $allow_invalid = FALSE) {
if ($file = $this->storage()->getFullPath($cid)) {
$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;
if (!$allow_invalid && !$cache->valid) {
return FALSE;
}
return $cache;
}
/**
* {@inheritdoc}
*/
public function set($cid, $data, $expire = Cache::PERMANENT, array $tags = array()) {
$item = (object) array(
'cid' => $cid,
'data' => $data,
'created' => REQUEST_TIME,
'expire' => $expire,
);
$item->tags = $this->flattenTags($tags);
$this->writeItem($cid, $item);
}
/**
* {@inheritdoc}
*/
public function delete($cid) {
$this->storage()->delete($cid);
}
/**
* {@inheritdoc}
*/
public function deleteMultiple(array $cids) {
foreach ($cids as $cid) {
$this->delete($cid);
}
}
/**
* {@inheritdoc}
*/
public function deleteTags(array $tags) {
$flat_tags = $this->flattenTags($tags);
foreach ($this->storage()->listAll() as $cid) {
$item = $this->get($cid);
if (is_object($item) && array_intersect($flat_tags, $item->tags)) {
$this->delete($cid);
}
}
}
/**
* {@inheritdoc}
*/
public function deleteAll() {
$this->storage()->deleteAll();
}
/**
* {@inheritdoc}
*/
public function invalidate($cid) {
if ($item = $this->get($cid)) {
$item->expire = REQUEST_TIME - 1;
$this->writeItem($cid, $item);
}
}
/**
* {@inheritdoc}
*/
public function invalidateMultiple(array $cids) {
foreach ($cids as $cid) {
$this->invalidate($cid);
}
}
/**
* {@inheritdoc}
*/
public function invalidateTags(array $tags) {
$flat_tags = $this->flattenTags($tags);
foreach ($this->storage()->listAll() as $cid) {
$item = $this->get($cid);
if ($item && array_intersect($flat_tags, $item->tags)) {
$this->invalidate($cid);
}
}
}
/**
* {@inheritdoc}
*/
public function invalidateAll() {
$this->invalidateMultiple($this->storage()->listAll());
}
/**
* 'Flattens' a tags array into an array of strings.
*
* @param array $tags
* Associative array of tags to flatten.
*
* @return array
* An indexed array of strings.
*/
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";
}
}
else {
$flat_tags[] = "$namespace:$values";
}
}
return $flat_tags;
}
/**
* {@inheritdoc}
*/
public function isEmpty() {
$names = $this->storage()->listAll();
return empty($names);
}
/**
* {@inheritdoc}
*/
public function garbageCollection() {
}
/**
* {@inheritdoc}
*/
public function removeBin() {
$this->cache = array();
$this->storage()->delete($this->bin);
}
/**
* Writes a cache item to PhpStorage.
*
* @param string $cid
* The cache ID of the data to store.
* @param \stdClass $item
* The cache item to store.
*/
protected function writeItem($cid, \stdClass $item) {
$data = str_replace('\\', '\\\\', serialize($item));
$content = "<?php return unserialize(<<<EOF
$data
EOF
);";
$this->storage()->save($cid, $content);
}
/**
* Gets the PHP code storage object to use.
*
* @return \Drupal\Core\PhpStorage\PhpStorageInterface
*/
protected function storage() {
if (!isset($this->storage)) {
$this->storage = PhpStorageFactory::get($this->bin);
}
return $this->storage;
}
}
<?php
/**
* @file
* Contains \Drupal\Core\Cache\PhpBackendFactory.
*/
namespace Drupal\Core\Cache;
class PhpBackendFactory implements CacheFactoryInterface {
/**
* 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);
}
}
......@@ -136,10 +136,11 @@ public function testSetGet() {
$backend = $this->getCacheBackend();
$this->assertIdentical(FALSE, $backend->get('test1'), "Backend does not contain data for cache id test1.");
$backend->set('test1', 7);
$with_backslash = array('foo' => '\Drupal\foo\Bar');
$backend->set('test1', $with_backslash);
$cached = $backend->get('test1');
$this->assert(is_object($cached), "Backend returned an object for cache id test1.");
$this->assertIdentical(7, $cached->data);
$this->assertIdentical($with_backslash, $cached->data);
$this->assertTrue($cached->valid, 'Item is marked as valid.');
$this->assertEqual($cached->created, REQUEST_TIME, 'Created time is correct.');
$this->assertEqual($cached->expire, Cache::PERMANENT, 'Expire time is correct.');
......
<?php
/**
* @file
* Contains \Drupal\system\Tests\Cache\PhpBackendUnitTest.
*/
namespace Drupal\system\Tests\Cache;
use Drupal\Core\Cache\PhpBackend;
/**
* Tests PhpBackendUnitTest using GenericCacheBackendUnitTestBase.
*/
class PhpBackendUnitTest extends GenericCacheBackendUnitTestBase {
public static function getInfo() {
return array(
'name' => 'Php cache backend',
'description' => 'Unit test of the PHP cache backend using the generic cache unit test base.',
'group' => 'Cache',
);
}
/**
* Creates a new instance of MemoryBackend.
*
* @return
* A new MemoryBackend object.
*/
protected function createCacheBackend($bin) {
return new PhpBackend($bin);
}
}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment