diff --git a/core/lib/Drupal/Core/Cache/MultiBackend.php b/core/lib/Drupal/Core/Cache/MultiBackend.php index 7e8a5d1..f5c5d92 100644 --- a/core/lib/Drupal/Core/Cache/MultiBackend.php +++ b/core/lib/Drupal/Core/Cache/MultiBackend.php @@ -8,7 +8,7 @@ namespace Drupal\Core\Cache; /** - * @todo + * A proxy cache backend to preload multiple items. */ class MultiBackend implements CacheBackendInterface, CacheTagsInvalidatorInterface { @@ -27,6 +27,13 @@ class MultiBackend implements CacheBackendInterface, CacheTagsInvalidatorInterfa protected $bin; /** + * The cache key used to store the multi get keys. + * + * @var string + */ + protected $multiCidKey; + + /** * An array of multiple cache IDs this backend is using. * * @var array @@ -50,7 +57,8 @@ class MultiBackend implements CacheBackendInterface, CacheTagsInvalidatorInterfa */ public function __construct(CacheBackendInterface $cache_backend, $bin) { $this->cacheBackend = $cache_backend; - $this->bin = 'cache_multi_' . $bin; + $this->bin = $bin; + $this->multiCidKey = 'cache_multi_' . $bin; // @todo Lazy load this? $this->preloadMultiple(); @@ -60,43 +68,52 @@ public function __construct(CacheBackendInterface $cache_backend, $bin) { * {@inheritdoc} */ public function get($cid, $allow_invalid = FALSE) { - return isset($this->cacheItems[$cid]) ? $this->cacheItems[$cid] : FALSE; + return isset($this->cacheItems[$cid]) ? $this->prepareItem($this->cacheItems[$cid], $allow_invalid) : FALSE; } /** * {@inheritdoc} */ public function getMultiple(&$cids, $allow_invalid = FALSE) { - return array_intersect_key($this->cacheItems, array_flip($cids)); + $ret = []; + $items = array_intersect_key($this->cacheItems, 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; } /** * {@inheritdoc} */ public function set($cid, $data, $expire = Cache::PERMANENT, array $tags = array()) { + $this->cacheItems[$cid] = $this->createCacheObject($cid, $data, $expire, $tags); + $return = $this->cacheBackend->set($cid, $data, $expire, $tags); $this->addMultiKeys([$cid]); - $this->cacheItems[$cid] = (object) [ - 'cid' => $cid, - 'data' => $data, - 'expire' => $expire, - 'created' => REQUEST_TIME, - 'tags' => $tags, - 'valid' => TRUE, - ]; + return $return; } /** * {@inheritdoc} */ - public function setMultiple(array $items = array()) { + 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'] : []); + } + $return = $this->cacheBackend->setMultiple($items); // Map keys and store them. - $this->addMultiKeys(array_map(function($item) { - return $item['cid']; - }, $items)); + $this->addMultiKeys(array_keys($items)); return $return; } @@ -105,6 +122,7 @@ public function setMultiple(array $items = array()) { * {@inheritdoc} */ public function delete($cid) { + unset($this->cacheItems[$cid]); $this->cacheBackend->delete($cid); $this->removeMultiKeys([$cid]); } @@ -113,6 +131,10 @@ public function delete($cid) { * {@inheritdoc} */ public function deleteMultiple(array $cids) { + foreach ($cids as $cid) { + unset($this->cacheItems[$cid]); + } + $this->cacheBackend->deleteMultiple($cids); $this->removeMultiKeys($cids); } @@ -121,14 +143,19 @@ public function deleteMultiple(array $cids) { * {@inheritdoc} */ public function deleteAll() { - $keys = array_merge($this->multiCids, [$this->bin]); - $this->cacheBackend->getMultiple($keys); + $this->cacheItems = []; + $keys = array_merge($this->multiCids, [$this->multiCidKey]); + $this->cacheBackend->deleteMultiple($keys); } /** * {@inheritdoc} */ public function invalidate($cid) { + if (isset($this->cacheItems[$cid])) { + $this->cacheItems[$cid]->expire = $this->getRequestTime() - 1; + } + $this->cacheBackend->invalidate($cid); } @@ -136,6 +163,12 @@ public function invalidate($cid) { * {@inheritdoc} */ public function invalidateMultiple(array $cids) { + foreach ($cids as $cid) { + if (isset($this->cacheItems[$cid])) { + $this->cacheItems[$cid]->expire = $this->getRequestTime() - 1; + } + } + $this->cacheBackend->invalidateMultiple($cids); } @@ -143,7 +176,13 @@ public function invalidateMultiple(array $cids) { * {@inheritdoc} */ public function invalidateAll() { - $keys = array_merge($this->multiCids, [$this->bin]); + // Invalidate all static items. + foreach ($this->cacheItems as $cid => $item) { + $this->cacheItems[$cid]->expire = $this->getRequestTime() - 1; + } + + // Invalidate all items for the storeage backend, including the mutli list. + $keys = array_merge($this->multiCids, [$this->multiCidKey]); $this->cacheBackend->invalidateMultiple($keys); } @@ -151,6 +190,12 @@ public function invalidateAll() { * Implements Drupal\Core\Cache\CacheBackendInterface::invalidateTags(). */ public function invalidateTags(array $tags) { + foreach ($this->cacheItems as $cid => $item) { + if (array_intersect($tags, $item->tags)) { + $this->cacheItems[$cid]->expire = $this->getRequestTime() - 1; + } + } + $this->cacheBackend->invalidateTags($tags); } @@ -165,21 +210,23 @@ public function garbageCollection() { * {@inheritdoc} */ public function removeBin() { - return FALSE; + $this->cacheItems = []; + $this->resetMultiKeys(); + // @todo Call anything on the storage backend? } /** * Pre load Multiple items. */ protected function preloadMultiple() { - // The bin name will be the multi cid key. - // @todo Maybe bin is a bad property name for that. - $multi_cid_cache = $this->cacheBackend->get($this->bin); + $multi_cid_cache = $this->cacheBackend->get($this->multiCidKey); - if (isset($multi_cid_cache->data)) { - $this->multiCids = $multi_cid_cache->data; + // Return now if there are no cids to load. + if (empty($multi_cid_cache->data)) { + return; } + $this->multiCids = $multi_cid_cache->data; $this->cacheItems = $this->cacheBackend->getMultiple($this->multiCids); // If the loaded items and the multi cids are different reset them. @@ -190,6 +237,46 @@ protected function preloadMultiple() { } /** + * 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 manipulations of the returned object. + $prepared = clone $cache; + + if (is_object($prepared->data)) { + $prepared->data = clone $cache->data; + } + + // Check expire time. + $prepared->valid = $prepared->expire == Cache::PERMANENT || $prepared->expire >= $this->getRequestTime(); + + if (!$allow_invalid && !$prepared->valid) { + return FALSE; + } + + return $prepared; + } + + /** * Adds a mutli cache key. * * @param array $keys @@ -199,12 +286,12 @@ protected function addMultiKeys(array $keys) { $this->multiCids[$key] = $key; } - // @todo Move this to set on desctruct. - $this->cacheBackend->set($this->bin, $this->multiCids); + // @todo Move this to set on destruct? + $this->cacheBackend->set($this->multiCidKey, $this->multiCids); } /** - * Removes a mutli cache key. + * Removes a multi cache key. * * @param array $keys */ @@ -213,15 +300,45 @@ protected function removeMultiKeys(array $keys) { unset($this->multiCids[$key]); } - $this->cacheBackend->set($this->bin, $this->multiCids); + $this->cacheBackend->set($this->multiCidKey, $this->multiCids); } /** * Resets multi cache keys. */ protected function resetMultiKeys() { - $this->cacheBackend->delete($this->bin); + $this->cacheBackend->delete($this->multiCidKey); $this->multiCids = []; } + /** + * Creates a cache object. + * + * @param $cid + * @param $data + * @param $expire + * @param array $tags + * + * @return \stdClass + */ + protected function createCacheObject($cid, $data, $expire = Cache::PERMANENT, array $tags = []) { + return (object) [ + 'cid' => $cid, + 'data' => is_object($data) ? clone $data : $data, + 'expire' => $expire, + 'created' => $this->getRequestTime(), + 'tags' => $tags, + 'valid' => $expire == Cache::PERMANENT || $expire >= $this->getRequestTime(), + ]; + } + + /** + * Wrapper method for REQUEST_TIME constant. + * + * @return int + */ + protected function getRequestTime() { + return defined('REQUEST_TIME') ? REQUEST_TIME : (int) $_SERVER['REQUEST_TIME']; + } + } diff --git a/core/modules/system/src/Tests/Cache/MultiBackendUnitTest.php b/core/modules/system/src/Tests/Cache/MultiBackendUnitTest.php index 03b9b1e..793ec83 100644 --- a/core/modules/system/src/Tests/Cache/MultiBackendUnitTest.php +++ b/core/modules/system/src/Tests/Cache/MultiBackendUnitTest.php @@ -7,7 +7,6 @@ namespace Drupal\system\Tests\Cache; -use Drupal\Core\Cache\BackendChain; use Drupal\Core\Cache\MemoryBackend; use Drupal\Core\Cache\MultiBackend; @@ -18,6 +17,12 @@ */ class MultiBackendUnitTest extends GenericCacheBackendUnitTestBase { + /** + * Creates a new MultiBackend instance. + * + * @return \Drupal\Core\Cache\MultiBackend + * A new MultiBackend object. + */ protected function createCacheBackend($bin) { $multi = new MultiBackend(new MemoryBackend('foo'), $bin); \Drupal::service('cache_tags.invalidator')->addInvalidator($multi);