[HttpFoundation][Session] Add mongodb session storage #4013

Merged
merged 1 commit into from Apr 23, 2012
@@ -0,0 +1,145 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * 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 <markus.bachmann@bachi.biz>
+ */
+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;
+ }
+}
@@ -0,0 +1,97 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * 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 <markus.bachmann@bachi.biz>
+ */
+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()
@ghost
ghost Apr 22, 2012

Why is there no specific testRead case or at least assertion reading an non-existing id.

+ {
+ $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());
+
+ }
+}