From 40df3bf86f4223132158ca1dfec60c0916ad7107 Mon Sep 17 00:00:00 2001 From: Markus Bachmann Date: Thu, 19 Apr 2012 20:34:49 +0200 Subject: [PATCH] Add mongodb session storage Some changes based on @stof and @stloyd suggestions Some changes based on @vicb suggestions Some changes based on @vicb suggestions Add changes --- .../Storage/Handler/MongoDbSessionHandler.php | 145 ++++++++++++++++++ .../Handler/MongoDbSessionHandlerTest.php | 97 ++++++++++++ 2 files changed, 242 insertions(+) create mode 100644 src/Symfony/Component/HttpFoundation/Session/Storage/Handler/MongoDbSessionHandler.php create mode 100644 src/Symfony/Component/HttpFoundation/Tests/Session/Storage/Handler/MongoDbSessionHandlerTest.php diff --git a/src/Symfony/Component/HttpFoundation/Session/Storage/Handler/MongoDbSessionHandler.php b/src/Symfony/Component/HttpFoundation/Session/Storage/Handler/MongoDbSessionHandler.php new file mode 100644 index 0000000000..59ea5d7288 --- /dev/null +++ b/src/Symfony/Component/HttpFoundation/Session/Storage/Handler/MongoDbSessionHandler.php @@ -0,0 +1,145 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\Session\Storage\Handler; + +/** + * MongoDB session handler + * + * @author Markus Bachmann + */ +class MongoDbSessionHandler implements \SessionHandlerInterface +{ + /** + * @var \Mongo + */ + private $mongo; + + /** + * @var \MongoCollection + */ + private $collection; + + /** + * @var array + */ + private $options; + + /** + * Constructor. + * + * @param \Mongo $mongo A "Mongo" instance + * @param array $options An associative array of field options + * + * @throws \InvalidArgumentException When "database" or "collection" not provided + */ + public function __construct(\Mongo $mongo, array $options) + { + if (!isset($options['database']) || !isset($options['collection'])) { + throw new \InvalidArgumentException('You must provide the "database" and "collection" option for MongoDBSessionHandler'); + } + + $this->mongo = $mongo; + + $this->options = array_merge(array( + 'id_field' => 'sess_id', + 'data_field' => 'sess_data', + 'time_field' => 'sess_time', + ), $options); + } + + /** + * {@inheritDoc} + */ + public function open($savePath, $sessionName) + { + return true; + } + + /** + * {@inheritDoc} + */ + public function close() + { + return true; + } + + /** + * {@inheritDoc} + */ + public function destroy($sessionId) + { + $this->getCollection()->remove( + array($this->options['id_field'] => $sessionId), + array('justOne' => true) + ); + + return true; + } + + /** + * {@inheritDoc} + */ + public function gc($lifetime) + { + $time = new \MongoTimestamp(time() - $lifetime); + + $this->getCollection()->remove(array( + $this->options['time_field'] => array('$lt' => $time), + )); + } + + /** + * {@inheritDoc] + */ + public function write($sessionId, $data) + { + $data = array( + $this->options['id_field'] => $sessionId, + $this->options['data_field'] => new \MongoBinData($data), + $this->options['time_field'] => new \MongoTimestamp() + ); + + $this->getCollection()->update( + array($this->options['id_field'] => $sessionId), + array('$set' => $data), + array('upsert' => true) + ); + + return true; + } + + /** + * {@inheritDoc} + */ + public function read($sessionId) + { + $dbData = $this->getCollection()->findOne(array( + $this->options['id_field'] => $sessionId, + )); + + return null === $dbData ? '' : $dbData[$this->options['data_field']]->bin; + } + + /** + * Return a "MongoCollection" instance + * + * @return \MongoCollection + */ + private function getCollection() + { + if (null === $this->collection) { + $this->collection = $this->mongo->selectDB($this->options['database'])->selectCollection($this->options['collection']); + } + + return $this->collection; + } +} \ No newline at end of file diff --git a/src/Symfony/Component/HttpFoundation/Tests/Session/Storage/Handler/MongoDbSessionHandlerTest.php b/src/Symfony/Component/HttpFoundation/Tests/Session/Storage/Handler/MongoDbSessionHandlerTest.php new file mode 100644 index 0000000000..f317b47649 --- /dev/null +++ b/src/Symfony/Component/HttpFoundation/Tests/Session/Storage/Handler/MongoDbSessionHandlerTest.php @@ -0,0 +1,97 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\Tests\Session\Storage\Handler; + +use Symfony\Component\HttpFoundation\Session\Storage\Handler\MongoDbSessionHandler; + +/** + * @author Markus Bachmann + */ +class MongoDbSessionHandlerTest extends \PHPUnit_Framework_TestCase +{ + private static $mongo; + + public static function setUpBeforeClass() + { + if (class_exists('\Mongo')) { + try { + self::$mongo = new \Mongo(); + } catch (\Exception $e) { + } + } + } + + protected function setUp() + { + if (null === self::$mongo) { + $this->markTestSkipped('MongoDbSessionHandler requires the php "mongo" extension and a mongodb server on localhost'); + } + + $this->options = array('database' => 'sf2-test', 'collection' => 'session-test'); + $this->options = array('database' => 'sf2-test', 'collection' => 'session-test'); + + $this->storage = new MongoDbSessionHandler(self::$mongo, $this->options); + } + + protected function tearDown() + { + self::$mongo->dropDB($this->options['database']); + } + + public function testOpenMethodAlwaysReturnTrue() + { + $this->assertTrue($this->storage->open('test', 'test'), 'The "open" method should always return true'); + } + + public function testCloseMethodAlwaysReturnTrue() + { + $this->assertTrue($this->storage->close(), 'The "close" method should always return true'); + } + + public function testWrite() + { + $this->assertTrue($this->storage->write('foo', 'bar')); + $this->assertEquals('bar', $this->storage->read('foo')); + } + + public function testReplaceSessionData() + { + $this->storage->write('foo', 'bar'); + $this->storage->write('foo', 'foobar'); + + $coll = self::$mongo->selectDB($this->options['database'])->selectCollection($this->options['collection']); + + $this->assertEquals('foobar', $this->storage->read('foo')); + $this->assertEquals(1, $coll->find(array('sess_id' => 'foo'))->count()); + } + + public function testDestroy() + { + $this->storage->write('foo', 'bar'); + $this->storage->destroy('foo'); + + $this->assertEquals('', $this->storage->read('foo')); + } + + public function testGc() + { + $this->storage->write('foo', 'bar'); + $this->storage->write('bar', 'foo'); + + $coll = self::$mongo->selectDB($this->options['database'])->selectCollection($this->options['collection']); + + $this->assertEquals(2, $coll->count()); + $this->storage->gc(-1); + $this->assertEquals(0, $coll->count()); + + } +} \ No newline at end of file