Chris@0: bin = $bin; Chris@0: $this->sitePrefix = $site_prefix; Chris@0: $this->checksumProvider = $checksum_provider; Chris@0: $this->binPrefix = $this->sitePrefix . '::' . $this->bin . '::'; Chris@0: } Chris@0: Chris@0: /** Chris@0: * Prepends the APCu user variable prefix for this bin to a cache item ID. Chris@0: * Chris@0: * @param string $cid Chris@0: * The cache item ID to prefix. Chris@0: * Chris@0: * @return string Chris@0: * The APCu key for the cache item ID. Chris@0: */ Chris@0: public function getApcuKey($cid) { Chris@0: return $this->binPrefix . $cid; Chris@0: } Chris@0: Chris@0: /** Chris@0: * {@inheritdoc} Chris@0: */ Chris@0: public function get($cid, $allow_invalid = FALSE) { Chris@0: $cache = apcu_fetch($this->getApcuKey($cid)); Chris@0: return $this->prepareItem($cache, $allow_invalid); Chris@0: } Chris@0: Chris@0: /** Chris@0: * {@inheritdoc} Chris@0: */ Chris@0: public function getMultiple(&$cids, $allow_invalid = FALSE) { Chris@0: // Translate the requested cache item IDs to APCu keys. Chris@0: $map = []; Chris@0: foreach ($cids as $cid) { Chris@0: $map[$this->getApcuKey($cid)] = $cid; Chris@0: } Chris@0: Chris@0: $result = apcu_fetch(array_keys($map)); Chris@0: $cache = []; Chris@0: if ($result) { Chris@0: foreach ($result as $key => $item) { Chris@0: $item = $this->prepareItem($item, $allow_invalid); Chris@0: if ($item) { Chris@0: $cache[$map[$key]] = $item; Chris@0: } Chris@0: } Chris@0: } Chris@0: unset($result); Chris@0: Chris@0: $cids = array_diff($cids, array_keys($cache)); Chris@0: return $cache; Chris@0: } Chris@0: Chris@0: /** Chris@0: * Returns all cached items, optionally limited by a cache ID prefix. Chris@0: * Chris@0: * APCu is a memory cache, shared across all server processes. To prevent Chris@0: * cache item clashes with other applications/installations, every cache item Chris@0: * is prefixed with a unique string for this site. Therefore, functions like Chris@0: * apcu_clear_cache() cannot be used, and instead, a list of all cache items Chris@0: * belonging to this application need to be retrieved through this method Chris@0: * instead. Chris@0: * Chris@0: * @param string $prefix Chris@0: * (optional) A cache ID prefix to limit the result to. Chris@0: * Chris@0: * @return \APCUIterator Chris@0: * An APCUIterator containing matched items. Chris@0: */ Chris@0: protected function getAll($prefix = '') { Chris@0: return $this->getIterator('/^' . preg_quote($this->getApcuKey($prefix), '/') . '/'); Chris@0: } Chris@0: Chris@0: /** Chris@0: * Prepares a cached item. Chris@0: * Chris@0: * Checks that the item is either permanent or did not expire. Chris@0: * Chris@0: * @param \stdClass $cache Chris@18: * An item loaded from self::get() or self::getMultiple(). Chris@0: * @param bool $allow_invalid Chris@0: * If TRUE, a cache item may be returned even if it is expired or has been Chris@0: * invalidated. See ::get(). Chris@0: * Chris@0: * @return mixed Chris@0: * The cache item or FALSE if the item expired. Chris@0: */ Chris@0: protected function prepareItem($cache, $allow_invalid) { Chris@0: if (!isset($cache->data)) { Chris@0: return FALSE; Chris@0: } Chris@0: Chris@0: $cache->tags = $cache->tags ? explode(' ', $cache->tags) : []; Chris@0: Chris@0: // Check expire time. Chris@0: $cache->valid = $cache->expire == Cache::PERMANENT || $cache->expire >= REQUEST_TIME; Chris@0: Chris@0: // Check if invalidateTags() has been called with any of the entry's tags. Chris@0: if (!$this->checksumProvider->isValid($cache->checksum, $cache->tags)) { Chris@0: $cache->valid = FALSE; Chris@0: } Chris@0: Chris@0: if (!$allow_invalid && !$cache->valid) { Chris@0: return FALSE; Chris@0: } Chris@0: Chris@0: return $cache; Chris@0: } Chris@0: Chris@0: /** Chris@0: * {@inheritdoc} Chris@0: */ Chris@0: public function set($cid, $data, $expire = CacheBackendInterface::CACHE_PERMANENT, array $tags = []) { Chris@14: assert(Inspector::assertAllStrings($tags), 'Cache tags must be strings.'); Chris@0: $tags = array_unique($tags); Chris@0: $cache = new \stdClass(); Chris@0: $cache->cid = $cid; Chris@0: $cache->created = round(microtime(TRUE), 3); Chris@0: $cache->expire = $expire; Chris@0: $cache->tags = implode(' ', $tags); Chris@0: $cache->checksum = $this->checksumProvider->getCurrentChecksum($tags); Chris@0: // APCu serializes/unserializes any structure itself. Chris@0: $cache->serialized = 0; Chris@0: $cache->data = $data; Chris@0: Chris@0: // Expiration is handled by our own prepareItem(), not APCu. Chris@0: apcu_store($this->getApcuKey($cid), $cache); Chris@0: } Chris@0: Chris@0: /** Chris@0: * {@inheritdoc} Chris@0: */ Chris@0: public function setMultiple(array $items = []) { Chris@0: foreach ($items as $cid => $item) { Chris@0: $this->set($cid, $item['data'], isset($item['expire']) ? $item['expire'] : CacheBackendInterface::CACHE_PERMANENT, isset($item['tags']) ? $item['tags'] : []); Chris@0: } Chris@0: } Chris@0: Chris@0: /** Chris@0: * {@inheritdoc} Chris@0: */ Chris@0: public function delete($cid) { Chris@0: apcu_delete($this->getApcuKey($cid)); Chris@0: } Chris@0: Chris@0: /** Chris@0: * {@inheritdoc} Chris@0: */ Chris@0: public function deleteMultiple(array $cids) { Chris@0: apcu_delete(array_map([$this, 'getApcuKey'], $cids)); Chris@0: } Chris@0: Chris@0: /** Chris@0: * {@inheritdoc} Chris@0: */ Chris@0: public function deleteAll() { Chris@0: apcu_delete($this->getIterator('/^' . preg_quote($this->binPrefix, '/') . '/')); Chris@0: } Chris@0: Chris@0: /** Chris@0: * {@inheritdoc} Chris@0: */ Chris@0: public function garbageCollection() { Chris@0: // APCu performs garbage collection automatically. Chris@0: } Chris@0: Chris@0: /** Chris@0: * {@inheritdoc} Chris@0: */ Chris@0: public function removeBin() { Chris@0: apcu_delete($this->getIterator('/^' . preg_quote($this->binPrefix, '/') . '/')); Chris@0: } Chris@0: Chris@0: /** Chris@0: * {@inheritdoc} Chris@0: */ Chris@0: public function invalidate($cid) { Chris@0: $this->invalidateMultiple([$cid]); Chris@0: } Chris@0: Chris@0: /** Chris@0: * {@inheritdoc} Chris@0: */ Chris@0: public function invalidateMultiple(array $cids) { Chris@0: foreach ($this->getMultiple($cids) as $cache) { Chris@0: $this->set($cache->cid, $cache, REQUEST_TIME - 1); Chris@0: } Chris@0: } Chris@0: Chris@0: /** Chris@0: * {@inheritdoc} Chris@0: */ Chris@0: public function invalidateAll() { Chris@0: foreach ($this->getAll() as $data) { Chris@0: $cid = str_replace($this->binPrefix, '', $data['key']); Chris@0: $this->set($cid, $data['value'], REQUEST_TIME - 1); Chris@0: } Chris@0: } Chris@0: Chris@0: /** Chris@0: * Instantiates and returns the APCUIterator class. Chris@0: * Chris@0: * @param mixed $search Chris@0: * A PCRE regular expression that matches against APC key names, either as a Chris@0: * string for a single regular expression, or as an array of regular Chris@0: * expressions. Or, optionally pass in NULL to skip the search. Chris@0: * @param int $format Chris@0: * The desired format, as configured with one or more of the APC_ITER_* Chris@0: * constants. Chris@0: * @param int $chunk_size Chris@0: * The chunk size. Must be a value greater than 0. The default value is 100. Chris@0: * @param int $list Chris@0: * The type to list. Either pass in APC_LIST_ACTIVE or APC_LIST_DELETED. Chris@0: * Chris@0: * @return \APCUIterator Chris@0: */ Chris@0: protected function getIterator($search = NULL, $format = APC_ITER_ALL, $chunk_size = 100, $list = APC_LIST_ACTIVE) { Chris@0: return new \APCUIterator($search, $format, $chunk_size, $list); Chris@0: } Chris@0: Chris@0: }