123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382 |
- <?php
- /**
- * Copyright © Magento, Inc. All rights reserved.
- * See COPYING.txt for license details.
- */
- /**
- * MongoDb cache backend
- */
- namespace Magento\Framework\Cache\Backend;
- class MongoDb extends \Zend_Cache_Backend implements \Zend_Cache_Backend_ExtendedInterface
- {
- /**
- * Infinite expiration time
- */
- const EXPIRATION_TIME_INFINITE = 0;
- /**#@+
- * Available comparison modes. Used for composing queries to search by tags
- */
- const COMPARISON_MODE_MATCHING_TAG = \Zend_Cache::CLEANING_MODE_MATCHING_TAG;
- const COMPARISON_MODE_NOT_MATCHING_TAG = \Zend_Cache::CLEANING_MODE_NOT_MATCHING_TAG;
- const COMPARISON_MODE_MATCHING_ANY_TAG = \Zend_Cache::CLEANING_MODE_MATCHING_ANY_TAG;
- /**#@-*/
- /**#@-*/
- protected $_collection = null;
- /**
- * List of available options
- *
- * @var array
- */
- protected $_options = [
- 'connection_string' => 'mongodb://localhost:27017', // MongoDB connection string
- 'mongo_options' => [], // MongoDB connection options
- 'db' => '', // Name of a database to be used for cache storage
- 'collection' => 'cache', // Name of a collection to be used for cache storage
- ];
- /**
- * @param array $options
- */
- public function __construct(array $options = [])
- {
- if (!extension_loaded('mongo') || !version_compare(\Mongo::VERSION, '1.2.11', '>=')) {
- \Zend_Cache::throwException(
- "At least 1.2.11 version of 'mongo' extension is required for using MongoDb cache backend"
- );
- }
- if (empty($options['db'])) {
- \Zend_Cache::throwException("'db' option is not specified");
- }
- parent::__construct($options);
- }
- /**
- * Get collection
- *
- * @return \MongoCollection
- */
- protected function _getCollection()
- {
- if (null === $this->_collection) {
- $connection = new \Mongo($this->_options['connection_string'], $this->_options['mongo_options']);
- $database = $connection->selectDB($this->_options['db']);
- $this->_collection = $database->selectCollection($this->_options['collection']);
- }
- return $this->_collection;
- }
- /**
- * Return an array of stored cache ids
- *
- * @return string[] array of stored cache ids (string)
- */
- public function getIds()
- {
- return array_keys(iterator_to_array($this->_getCollection()->find([], ['_id'])));
- }
- /**
- * Return an array of stored tags
- *
- * @return string[] array of stored tags (string)
- */
- public function getTags()
- {
- $result = $this->_getCollection()->distinct('tags');
- return $result ?: [];
- }
- /**
- * Return an array of stored cache ids which match given tags
- *
- * In case of multiple tags, a logical AND is made between tags
- *
- * @param string[] $tags array of tags
- * @return string[] array of matching cache ids (string)
- */
- public function getIdsMatchingTags($tags = [])
- {
- $query = $this->_getQueryMatchingTags($tags, self::COMPARISON_MODE_MATCHING_TAG);
- if (empty($query)) {
- return [];
- }
- $result = $this->_getCollection()->find($query, ['_id']);
- return array_keys(iterator_to_array($result));
- }
- /**
- * Return an array of stored cache ids which don't match given tags
- *
- * In case of multiple tags, a logical OR is made between tags
- *
- * @param string[] $tags array of tags
- * @return string[] array of not matching cache ids (string)
- */
- public function getIdsNotMatchingTags($tags = [])
- {
- $query = $this->_getQueryMatchingTags($tags, self::COMPARISON_MODE_NOT_MATCHING_TAG);
- if (empty($query)) {
- return [];
- }
- $result = $this->_getCollection()->find($query, ['_id']);
- return array_keys(iterator_to_array($result));
- }
- /**
- * Return an array of stored cache ids which match any given tags
- *
- * In case of multiple tags, a logical AND is made between tags
- *
- * @param string[] $tags array of tags
- * @return string[] array of any matching cache ids (string)
- */
- public function getIdsMatchingAnyTags($tags = [])
- {
- $query = $this->_getQueryMatchingTags($tags, self::COMPARISON_MODE_MATCHING_ANY_TAG);
- if (empty($query)) {
- return [];
- }
- $result = $this->_getCollection()->find($query, ['_id']);
- return array_keys(iterator_to_array($result));
- }
- /**
- * Get query to filter by specified tags and comparison mode
- *
- * @param string[] $tags
- * @param string $comparisonMode
- * @return array
- */
- protected function _getQueryMatchingTags(array $tags, $comparisonMode)
- {
- $operators = [
- self::COMPARISON_MODE_MATCHING_TAG => '$and',
- self::COMPARISON_MODE_NOT_MATCHING_TAG => '$nor',
- self::COMPARISON_MODE_MATCHING_ANY_TAG => '$or',
- ];
- if (!isset($operators[$comparisonMode])) {
- \Zend_Cache::throwException("Incorrect comparison mode specified: {$comparisonMode}");
- }
- $operator = $operators[$comparisonMode];
- $query = [];
- foreach ($tags as $tag) {
- $query[$operator][] = ['tags' => $this->_quoteString($tag)];
- }
- return $query;
- }
- /**
- * Return the filling percentage of the backend storage
- *
- * @return int integer between 0 and 100
- * TODO: implement basing on info from MongoDB server
- */
- public function getFillingPercentage()
- {
- return 1;
- }
- /**
- * Return an array of metadatas for the given cache id
- *
- * The array must include these keys :
- * - expire : the expire timestamp
- * - tags : a string array of tags
- * - mtime : timestamp of last modification time
- *
- * @param string $cacheId cache id
- * @return array|false array of metadatas (false if the cache id is not found)
- */
- public function getMetadatas($cacheId)
- {
- $result = $this->_getCollection()->findOne(
- ['_id' => $this->_quoteString($cacheId)],
- ['expire', 'tags', 'mtime']
- );
- return $result === null ? false : $result;
- }
- /**
- * Give (if possible) an extra lifetime to the given cache id
- *
- * @param string $cacheId cache id
- * @param int $extraLifetime
- * @return boolean true if ok
- */
- public function touch($cacheId, $extraLifetime)
- {
- $time = time();
- $condition = ['_id' => $this->_quoteString($cacheId), 'expire' => ['$gt' => $time]];
- $update = ['$set' => ['mtime' => $time], '$inc' => ['expire' => (int)$extraLifetime]];
- return $this->_getCollection()->update($condition, $update);
- }
- /**
- * Return an associative array of capabilities (booleans) of the backend
- *
- * The array must include these keys :
- * - automatic_cleaning (is automating cleaning necessary)
- * - tags (are tags supported)
- * - expired_read (is it possible to read expired cache records
- * (for doNotTestCacheValidity option for example))
- * - priority does the backend deal with priority when saving
- * - infinite_lifetime (is infinite lifetime can work with this backend)
- * - get_list (is it possible to get the list of cache ids and the complete list of tags)
- *
- * @return array associative of with capabilities
- */
- public function getCapabilities()
- {
- return [
- 'automatic_cleaning' => true,
- 'tags' => true,
- 'expired_read' => true,
- 'priority' => false,
- 'infinite_lifetime' => true,
- 'get_list' => true
- ];
- }
- /**
- * Test if a cache is available for the given id and (if yes) return it (false else)
- *
- * Note : return value is always "string" (unserialization is done by the core not by the backend)
- *
- * @param string $cacheId Cache id
- * @param boolean $notTestCacheValidity If set to true, the cache validity won't be tested
- * @return string|bool cached data. Return false if nothing found
- */
- public function load($cacheId, $notTestCacheValidity = false)
- {
- $query = ['_id' => $this->_quoteString($cacheId)];
- if (!$notTestCacheValidity) {
- $query['$or'] = [
- ['expire' => self::EXPIRATION_TIME_INFINITE],
- ['expire' => ['$gt' => time()]],
- ];
- }
- $result = $this->_getCollection()->findOne($query, ['data']);
- return $result ? $result['data']->bin : false;
- }
- /**
- * Test if a cache is available or not (for the given id)
- *
- * @param string $cacheId cache id
- * @return int|bool "last modified" timestamp of the available cache record or false if cache is not available
- */
- public function test($cacheId)
- {
- $result = $this->_getCollection()->findOne(
- [
- '_id' => $this->_quoteString($cacheId),
- '$or' => [
- ['expire' => self::EXPIRATION_TIME_INFINITE],
- ['expire' => ['$gt' => time()]],
- ],
- ],
- ['mtime']
- );
- return $result ? $result['mtime'] : false;
- }
- /**
- * Save some string data into a cache record
- *
- * Note : $data is always "string" (serialization is done by the
- * core not by the backend)
- *
- * @param string $data Datas to cache
- * @param string $cacheId Cache id
- * @param string[] $tags Array of strings, the cache record will be tagged by each string entry
- * @param int|bool $specificLifetime If != false, set a specific lifetime (null => infinite lifetime)
- * @return boolean true if no problem
- */
- public function save($data, $cacheId, $tags = [], $specificLifetime = false)
- {
- $lifetime = $this->getLifetime($specificLifetime);
- $time = time();
- $expire = $lifetime === null ? self::EXPIRATION_TIME_INFINITE : $time + $lifetime;
- $tags = array_map([$this, '_quoteString'], $tags);
- $document = [
- '_id' => $this->_quoteString($cacheId),
- 'data' => new \MongoBinData($this->_quoteString($data), \MongoBinData::BYTE_ARRAY),
- 'tags' => $tags,
- 'mtime' => $time,
- 'expire' => $expire,
- ];
- return $this->_getCollection()->save($document);
- }
- /**
- * Remove a cache record
- *
- * @param string $cacheId Cache id
- * @return boolean True if no problem
- */
- public function remove($cacheId)
- {
- return $this->_getCollection()->remove(['_id' => $this->_quoteString($cacheId)]);
- }
- /**
- * Clean some cache records
- *
- * Available modes are :
- * \Zend_Cache::CLEANING_MODE_ALL (default) => remove all cache entries ($tags is not used)
- * \Zend_Cache::CLEANING_MODE_OLD => remove too old cache entries ($tags is not used)
- * \Zend_Cache::CLEANING_MODE_MATCHING_TAG => remove cache entries matching all given tags
- * ($tags can be an array of strings or a single string)
- * \Zend_Cache::CLEANING_MODE_NOT_MATCHING_TAG => remove cache entries not {matching one of the given tags}
- * ($tags can be an array of strings or a single string)
- * \Zend_Cache::CLEANING_MODE_MATCHING_ANY_TAG => remove cache entries matching any given tags
- * ($tags can be an array of strings or a single string)
- *
- * @param string $mode Clean mode
- * @param string[] $tags Array of tags
- * @return bool true if no problem
- */
- public function clean($mode = \Zend_Cache::CLEANING_MODE_ALL, $tags = [])
- {
- $result = false;
- switch ($mode) {
- case \Zend_Cache::CLEANING_MODE_ALL:
- $result = $this->_getCollection()->drop();
- $result = (bool)$result['ok'];
- break;
- case \Zend_Cache::CLEANING_MODE_OLD:
- $query = ['expire' => ['$ne' => self::EXPIRATION_TIME_INFINITE, '$lte' => time()]];
- break;
- case \Zend_Cache::CLEANING_MODE_MATCHING_TAG:
- case \Zend_Cache::CLEANING_MODE_NOT_MATCHING_TAG:
- case \Zend_Cache::CLEANING_MODE_MATCHING_ANY_TAG:
- $query = $this->_getQueryMatchingTags((array)$tags, $mode);
- break;
- default:
- \Zend_Cache::throwException('Unsupported cleaning mode: ' . $mode);
- }
- if (!empty($query)) {
- $result = $this->_getCollection()->remove($query);
- }
- return $result;
- }
- /**
- * Quote specified value to be used in query as string
- *
- * @param string $value
- * @return string
- */
- protected function _quoteString($value)
- {
- return (string)$value;
- }
- }
|