diff --git a/README.md b/README.md index 6ea86f6..87b15a9 100644 --- a/README.md +++ b/README.md @@ -146,6 +146,54 @@ $cache = new Cache($adapter); ``` +### Mongo + +Use it to store the cache in a Mongo database. Requires the +[(legacy) mongo extension](http://php.net/mongo) or the +[mongodb/mongodb](https://github.com/mongodb/mongo-php-library) library. + +You may pass either a database or collection object to the constructor. If a +database object is passed, the `items` collection within that DB is used. + +``` php +selectDatabase($dbname); + +$adapter = new Mongo($database); +$adapter->setOption('ttl', 3600); +$cache = new Cache($adapter); + +``` + +``` php +selectDatabase($dbName); +$collection = $database->selectCollection($collectionName); + +$adapter = new Mongo($collection); +$adapter->setOption('ttl', 3600); +$cache = new Cache($adapter); + +``` + +_Note that expired cache items aren't automatically deleted. To keep your +database clean, you should create a +[ttl index](https://docs.mongodb.org/manual/core/index-ttl/)._ + + +``` +db.items.createIndex( { "ttl": 1 }, { expireAfterSeconds: 30 } ) +``` ### Mysqli diff --git a/src/Adapter/Mongo.php b/src/Adapter/Mongo.php new file mode 100644 index 0000000..6ee96b3 --- /dev/null +++ b/src/Adapter/Mongo.php @@ -0,0 +1,113 @@ + + */ + +namespace Desarrolla2\Cache\Adapter; + +use Desarrolla2\Cache\Exception\CacheException; + +/** + * Mongo + */ +class Mongo extends AbstractAdapter implements AdapterInterface +{ + /** + * @var MongoCollection|MongoDB\Collection + */ + protected $collection; + + /** + * @param MongoDB|MongoDB\Database|MongoCollection|MongoDB\Collection $backend + */ + public function __construct($backend = null) + { + if (!isset($backend)) { + $client = class_exist('MongoCollection') ? new \MongoClient() : new \MongoDB\Client(); + $backend = $client->selectDatabase('cache'); + } + + if ($backend instanceof \MongoCollection || $backend instanceof \MongoDB\Collection) { + $this->collection = $backend; + } elseif ($backend instanceof \MongoDB || $backend instanceof \MongoDB\Database) { + $this->collection = $backend->selectCollection('items'); + } else { + $type = (is_object($database) ? get_class($database) . ' ' : '') . gettype($database); + throw new CacheException("Database should be a database (MongoDB or MongoDB\Database) or " . + " collection (MongoCollection or MongoDB\Collection) object, not a $type"); + } + } + + /** + * {@inheritdoc} + */ + public function del($key) + { + $tKey = $this->getKey($key); + $this->collection->remove(array('_id' => $tKey)); + } + + /** + * {@inheritdoc } + */ + public function get($key) + { + $tKey = $this->getKey($key); + $tNow = $this->getTtl(); + $data = $this->collection->findOne(array('_id' => $tKey, 'ttl' => array('$gte' => $tNow))); + if (isset($data)) { + return $this->unPack($data['value']); + } + + return false; + } + + /** + * {@inheritdoc } + */ + public function has($key) + { + $tKey = $this->getKey($key); + $tNow = $this->getTtl(); + return $this->collection->count(array('_id' => $tKey, 'ttl' => array('$gte' => $tNow))) > 0; + } + + /** + * {@inheritdoc } + */ + public function set($key, $value, $ttl = null) + { + $tKey = $this->getKey($key); + $tValue = $this->pack($value); + if (!$ttl) { + $ttl = $this->ttl; + } + $item = array( + '_id' => $tKey, + 'value' => $tValue, + 'ttl' => $this->getTtl($ttl), + ); + $this->collection->update(array('_id' => $tKey), $item, array('upsert' => true)); + } + + /** + * Get TTL as Date type BSON object + * + * @param int $ttl + * @return MongoDate|MongoDB\BSON\UTCDatetime + */ + protected function getTtl($ttl = 0) + { + return $this->collection instanceof \MongoCollection ? + new \MongoDate((int) $ttl + time()) : + new \MongoDB\BSON\UTCDatetime(((int) $ttl + time() * 1000)); + } +} diff --git a/tests/Adapter/MongoTest.php b/tests/Adapter/MongoTest.php new file mode 100644 index 0000000..75eccb1 --- /dev/null +++ b/tests/Adapter/MongoTest.php @@ -0,0 +1,61 @@ + + */ + +namespace Desarrolla2\Test\Cache\Adapter; + +use Desarrolla2\Cache\Cache; +use Desarrolla2\Cache\Adapter\Mongo; + +/** + * MongoTest + */ +class MongoTest extends AbstractCacheTest +{ + public function setUp() + { + parent::setup(); + if (!extension_loaded('mongo')) { + $this->markTestSkipped( + 'The mongo extension is not available.' + ); + } + + $client = new \MongoClient($this->config['mongo']['dsn']); + $database = $client->selectDB($this->config['mongo']['database']); + + $this->cache = new Cache( + new Mongo($database) + ); + } + + /** + * @return array + */ + public function dataProviderForOptions() + { + return [ + ['ttl', 100], + ]; + } + + /** + * @return array + */ + public function dataProviderForOptionsException() + { + return [ + ['ttl', 0, '\Desarrolla2\Cache\Exception\CacheException'], + ['file', 100, '\Desarrolla2\Cache\Exception\CacheException'], + ]; + } +} diff --git a/tests/config.json.dist b/tests/config.json.dist index 07a9c90..320ac06 100644 --- a/tests/config.json.dist +++ b/tests/config.json.dist @@ -7,7 +7,8 @@ "port": "11211" }, "mongo": { - "dns": "mongodb://localhost:27017" + "dsn": "mongodb://localhost:27017", + "database": "cache" }, "mysql": { "user": "root", @@ -16,4 +17,4 @@ "port": "3306", "database": "cache" } -} \ No newline at end of file +}