Skip to content

Commit

Permalink
Merge 2c14a41 into 81bd29b
Browse files Browse the repository at this point in the history
  • Loading branch information
frqnck committed Aug 19, 2016
2 parents 81bd29b + 2c14a41 commit 6ca1e1d
Show file tree
Hide file tree
Showing 8 changed files with 242 additions and 45 deletions.
3 changes: 2 additions & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,8 @@
"ext-apc": "Allows to cache into APC data store ~ up to PHP 5.4.",
"ext-apcu": "Allows to use APCu userland caching ~ any PHP version.",
"ext-redis": "So you can cache into Redis servers.",
"ext-mongo": "Allows to cache into MongoDB instances.",
"ext-mongo": "Allows to cache into MongoDB instances using http://php.net/mongo ~ up to PHP 5.6.",
"ext-mongodb": "Allows to cache into MongoDB instances using https://php.net/mongodb ~ from PHP 7 and above and HHVM.",
"ext-memcached": "Allows to use Memcached distributed memory caching system",
"ext-pdo": "Allows to cache into PDO supported DBs such as Oracle, MS SQL server, IBM DB2.",
"ext-pdo_sqlite": "Allows to cache into SQLite.",
Expand Down
93 changes: 60 additions & 33 deletions src/Mongo.php
Original file line number Diff line number Diff line change
Expand Up @@ -28,24 +28,36 @@ class Mongo extends AbstractCache

/**
* Holds the MongoDB object
* @var \MongoDB
* @var \MongoDB|Mongo\DatabaseAdapter
*/
public $db;

/**
* Holds the MongoCollection object
* @var \MongoCollection
* @var \MongoCollection|Mongo\CollectionAdapter
*/
public $collection;

/**
* Indicates the use of the legacy \MongoClient.
* @var bool
*/
private $is_legacy = false;

/**
* Constructor. Sets the Mongo DB adapter.
*
* @param \MongoClient $Mongo A \MongoClient instance.
* @param \MongoClient|\MongoDB\Client $Mongo A Mongo client instance.
* @param array $options Array of options.
*/
public function __construct(\MongoClient $Mongo, array $options=null)
public function __construct($Mongo, array $options=null)
{
if (!is_a($Mongo, '\MongoDB\Client') && !is_a($Mongo, '\MongoClient')) {
throw new \InvalidArgumentException(
'Expected instance of "\MongoDB\Client" or "\MongoClient"'
);
}

// default options
$this->options['db_name'] = 'apix';
$this->options['collection_name'] = 'cache';
Expand All @@ -54,26 +66,35 @@ public function __construct(\MongoClient $Mongo, array $options=null)
// Set the adapter and merge the user+default options
parent::__construct($Mongo, $options);

$this->db = $this->adapter->selectDB($this->options['db_name']);
if (is_a($Mongo, '\MongoDB\Client')) {
$this->is_legacy = false;
$this->db = new Mongo\DatabaseAdapter(
$this->adapter->{$this->options['db_name']}
);
} else {
$this->is_legacy = true;
$this->db = $this->adapter->selectDB($this->options['db_name']);
}

$this->collection = $this->db->createCollection(
$this->options['collection_name'],
false
);
$this->options['collection_name'],
array()
);

$this->collection->ensureIndex(
array('key' => 1),
array(
'unique' => true,
'dropDups' => true,
// 'sparse' => true
)
);
array('key' => 1),
array(
'unique' => true,
'dropDups' => true,
// 'sparse' => true
)
);

// Using MongoDB TTL collections (MongoDB 2.2+)
$this->collection->ensureIndex(
array('expire' => 1),
array('expireAfterSeconds' => 1)
);
array('expire' => 1),
array('expireAfterSeconds' => 1)
);

$this->setSerializer($this->options['object_serializer']);
}
Expand All @@ -87,15 +108,16 @@ public function loadKey($key)
$cache = $this->get($mKey);

// check expiration
if (
null === $cache
or ( isset($cache['expire']) && (string) $cache['expire']->sec < time() )
) {
if ( null === $cache or (
isset($cache['expire']) && (string) $cache['expire']->sec < time()
)) {
unset($this->ttls[$mKey]);

return null;
}

// Serializer\Adapter::isSerialized() has been deprecated
// Only mongo needs it...
return null !== $this->serializer
&& $this->serializer->isSerialized($cache['data'])
? $this->serializer->unserialize($cache['data'])
Expand All @@ -117,8 +139,8 @@ public function get($key)

if ($cache !== null) {
$this->ttls[$key] = isset($cache['expire'])
? $cache['expire']->sec - time()
: 0;
? $cache['expire']->sec - time()
: 0;
}

return $cache;
Expand Down Expand Up @@ -162,14 +184,17 @@ public function save($data, $key, array $tags=null, $ttl=null)
}
}

$this->ttls[$key] = 0;

if (null !== $ttl && 0 !== $ttl) {
$expire = time()+$ttl;
$cache['expire'] = new \MongoDate($expire);
}

$this->ttls[$key] = isset($cache['expire'])
? $cache['expire']->sec - time()
: 0;
$cache['expire'] = $this->is_legacy
? new \MongoDate($expire)
: new \MongoDB\BSON\UTCDateTime($expire * 1000);

$this->ttls[$key] = $ttl;
}

$res = $this->collection->update(
array('key' => $key), $cache, array('upsert' => true)
Expand Down Expand Up @@ -212,12 +237,14 @@ public function delete($key)
public function flush($all=false)
{
if (true === $all) {
$res = $this->db->drop();

$res = $this->collection->drop();
return (boolean) $res['ok'];
}
// $res = $this->collection->drop();
$regex = new \MongoRegex('/^' . $this->mapKey('') . '/');

$regex = $this->is_legacy
? new \MongoRegex('/^' . $this->mapKey('') . '/')
: array('$regex' => '^' . $this->mapKey(''));

$res = $this->collection->remove( array('key' => $regex) );

return (boolean) $res['ok'];
Expand Down
94 changes: 94 additions & 0 deletions src/Mongo/CollectionAdapter.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
<?php

namespace Apix\Cache\Mongo;

use MongoDB\Collection;

/**
* allows to use \MongoDB\Collection in the code as \MongoCollection
*/
class CollectionAdapter /*extends \MongoCollection*/
{
/**
* @var Collection
*/
private $collection;

/**
* CollectionAdapter constructor.
* @param Collection $collection
*/
public function __construct(Collection $collection)
{
$this->collection = $collection;
}

public function ensureIndex(array $keys, array $options = array())
{
$this->collection->createIndex($keys, $options);
}

public function insert($a, array $options = array())
{
try {
$this->collection->insertOne($a, $options);
return ['ok' => 1];
} catch (\Exception $e) {
return ['ok' => 0, 'error' => $e->getMessage()];
}
}

public function update(array $criteria, array $newobj, array $options = array())
{
try {
$this->collection->updateOne($criteria, array('$set' => $newobj), $options);
return ['ok' => 1];
} catch (\Exception $e) {
return ['ok' => 0, 'error' => $e->getMessage()];
}
}

public function remove(array $criteria = array(), array $options = array())
{
try {
$result = $this->collection->deleteMany($criteria, $options);
return ['ok' => 1, 'n' => $result->getDeletedCount()];
} catch (\Exception $e) {
return ['ok' => 0, 'error' => $e->getMessage()];
}
}

public function find(array $query = array(), array $fields = array())
{
$options = ['projection' => array_fill_keys($fields, 1) + ['_id' => 0]];

return $this->collection->find($query, $options);
}

public function findOne(array $query = array(), array $fields = array())
{
$options = ['projection' => array_fill_keys($fields, 1) + ['_id' => 0]];

$result = $this->collection->findOne($query, $options);

// mimic \MongoDate->sec
if (!empty($result['expire'])) {
$sec = $result['expire']->toDateTime()->getTimestamp();
$result['expire'] = new \stdClass();
$result['expire']->sec = $sec;
}

return $result;
}

public function drop()
{
$this->collection->drop();
return ['ok' => 1];
}

public function count($query = array())
{
return $this->collection->count($query);
}
}
34 changes: 34 additions & 0 deletions src/Mongo/DatabaseAdapter.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
<?php

namespace Apix\Cache\Mongo;

use MongoDB\Database;

/**
* allows to use \MongoDB\Database in the code as \MongoDB
*/
class DatabaseAdapter /*extends \MongoDB*/
{
/**
* @var Database
*/
private $db;

/**
* @param Database $db
*/
public function __construct(Database $db)
{
$this->db = $db;
}

/**
* @param string $name
* @param array $options
* @return CollectionAdapter
*/
public function createCollection($name, $options)
{
return new CollectionAdapter($this->db->selectCollection($name, $options));
}
}
10 changes: 8 additions & 2 deletions tests/GenericTestCase.php
Original file line number Diff line number Diff line change
Expand Up @@ -36,8 +36,14 @@ public function testSaveAndLoadWithArray()
{
$data = array('foo' => 'bar');
$this->assertTrue($this->cache->save($data, 'id'));
$this->assertEquals($data, $this->cache->loadKey('id'));
$this->assertEquals($data, $this->cache->load('id'));

$check = $this->cache->loadKey('id');
if (is_a($check, 'ArrayObject')) $check = $check->getArrayCopy();
$this->assertEquals($data, $check);

$check = $this->cache->load('id');
if (is_a($check, 'ArrayObject')) $check = $check->getArrayCopy();
$this->assertEquals($data, $check);
}

public function testSaveAndLoadWithObject()
Expand Down
27 changes: 21 additions & 6 deletions tests/MongoTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -26,10 +26,16 @@ class MongoTest extends GenericTestCase

public function setUp()
{
$this->skipIfMissing('mongo');
if (phpversion() >= '7.0.0' || defined('HHVM_VERSION')) {
$this->skipIfMissing('mongodb');
$class = '\MongoDB\Client';
} else {
$this->skipIfMissing('mongo');
$class = '\MongoClient';
}

try {
$this->mongo = new \MongoClient();
$this->mongo = new $class();
} catch (\Exception $e) {
$this->markTestSkipped( $e->getMessage() );
}
Expand All @@ -41,7 +47,8 @@ public function tearDown()
{
if (null !== $this->cache) {
$this->cache->flush();
$this->mongo->close();
if (method_exists($this->mongo, 'close'))
$this->mongo->close();
unset($this->cache, $this->mongo);
}
}
Expand All @@ -67,9 +74,9 @@ public function testFlushCacheOnly()

$this->assertTrue($this->cache->flush());

$this->assertEquals(
$foo, $this->cache->collection->findOne(array('foo'=>'bar'))
);
$check = $this->cache->collection->findOne(array('foo'=>'bar'));
if (is_a($check, 'ArrayObject')) $check = $check->getArrayCopy();
$this->assertEquals($foo, $check);

$this->assertNull($this->cache->loadKey('id3'));
$this->assertNull($this->cache->loadTag('tag1'));
Expand Down Expand Up @@ -104,4 +111,12 @@ public function testShortTtlDoesExpunge()
$this->assertNull( $this->cache->loadKey('ttlId') );
}

/**
* @expectedException InvalidArgumentException
*/
public function testThrowAnInvalidArgumentException()
{
new Cache\Mongo(new \stdClass);
}

}
2 changes: 1 addition & 1 deletion tests/PdoTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ function () {
return $dbs[$DB];
}

$this->markTestSkipped("Unsupported DB ($DB) environment.");
$this->markTestSkipped("Unsupported PDO DB ($DB) environment.");
}

public function setUp()
Expand Down
Loading

0 comments on commit 6ca1e1d

Please sign in to comment.