Skip to content

Commit

Permalink
Merge branch '2.3' into 2.6
Browse files Browse the repository at this point in the history
* 2.3:
  [HttpFoundation] MongoDbSessionHandler::read() now checks for valid session age
  [WebProfilerBundle] Set debug+charset on the ExceptionHandler fallback
  used HTML5 meta charset tag and removed hardcoded ones
  Revert "bug #13715 Enforce UTF-8 charset for core controllers (WouterJ)"

Conflicts:
	src/Symfony/Bundle/TwigBundle/Controller/ExceptionController.php
	src/Symfony/Bundle/WebProfilerBundle/Controller/ProfilerController.php
	src/Symfony/Component/HttpFoundation/Session/Storage/Handler/MongoDbSessionHandler.php
	src/Symfony/Component/HttpFoundation/Tests/Session/Storage/Handler/MongoDbSessionHandlerTest.php
  • Loading branch information
fabpot committed Mar 12, 2015
2 parents 84aef45 + eb335a7 commit 7b43935
Show file tree
Hide file tree
Showing 2 changed files with 67 additions and 67 deletions.
54 changes: 25 additions & 29 deletions Session/Storage/Handler/MongoDbSessionHandler.php
Expand Up @@ -42,6 +42,24 @@ class MongoDbSessionHandler implements \SessionHandlerInterface
* * id_field: The field name for storing the session id [default: _id]
* * data_field: The field name for storing the session data [default: data]
* * time_field: The field name for storing the timestamp [default: time]
* * expiry_field: The field name for storing the expiry-timestamp [default: expires_at]
*
* It is strongly recommended to put an index on the `expiry_field` for
* garbage-collection. Alternatively it's possible to automatically expire
* the sessions in the database as described below:
*
* A TTL collections can be used on MongoDB 2.2+ to cleanup expired sessions
* automatically. Such an index can for example look like this:
*
* db.<session-collection>.ensureIndex(
* { "<expiry-field>": 1 },
* { "expireAfterSeconds": 0 }
* )
*
* More details on: http://docs.mongodb.org/manual/tutorial/expire-data/
*
* If you use such an index, you can drop `gc_probability` to 0 since
* no garbage-collection is required.
*
* @param \Mongo|\MongoClient $mongo A MongoClient or Mongo instance
* @param array $options An associative array of field options
Expand All @@ -65,7 +83,7 @@ public function __construct($mongo, array $options)
'id_field' => '_id',
'data_field' => 'data',
'time_field' => 'time',
'expiry_field' => false,
'expiry_field' => 'expires_at',
), $options);
}

Expand Down Expand Up @@ -102,21 +120,8 @@ public function destroy($sessionId)
*/
public function gc($maxlifetime)
{
/* Note: MongoDB 2.2+ supports TTL collections, which may be used in
* place of this method by indexing the "time_field" field with an
* "expireAfterSeconds" option. Regardless of whether TTL collections
* are used, consider indexing this field to make the remove query more
* efficient.
*
* See: http://docs.mongodb.org/manual/tutorial/expire-data/
*/
if (false !== $this->options['expiry_field']) {
return true;
}
$time = new \MongoDate(time() - $maxlifetime);

$this->getCollection()->remove(array(
$this->options['time_field'] => array('$lt' => $time),
$this->options['expiry_field'] => array('$lt' => new \MongoDate()),
));

return true;
Expand All @@ -127,24 +132,14 @@ public function gc($maxlifetime)
*/
public function write($sessionId, $data)
{
$expiry = new \MongoDate(time() + (int) ini_get('session.gc_maxlifetime'));

$fields = array(
$this->options['data_field'] => new \MongoBinData($data, \MongoBinData::BYTE_ARRAY),
$this->options['time_field'] => new \MongoDate(),
$this->options['expiry_field'] => $expiry,
);

/* Note: As discussed in the gc method of this class. You can utilise
* TTL collections in MongoDB 2.2+
* We are setting the "expiry_field" as part of the write operation here
* You will need to create the index on your collection that expires documents
* at that time
* e.g.
* db.MySessionCollection.ensureIndex( { "expireAt": 1 }, { expireAfterSeconds: 0 } )
*/
if (false !== $this->options['expiry_field']) {
$expiry = new \MongoDate(time() + (int) ini_get('session.gc_maxlifetime'));
$fields[$this->options['expiry_field']] = $expiry;
}

$this->getCollection()->update(
array($this->options['id_field'] => $sessionId),
array('$set' => $fields),
Expand All @@ -160,7 +155,8 @@ public function write($sessionId, $data)
public function read($sessionId)
{
$dbData = $this->getCollection()->findOne(array(
$this->options['id_field'] => $sessionId,
$this->options['id_field'] => $sessionId,
$this->options['expiry_field'] => array('$gte' => new \MongoDate()),
));

return null === $dbData ? '' : $dbData[$this->options['data_field']]->bin;
Expand Down
80 changes: 42 additions & 38 deletions Tests/Session/Storage/Handler/MongoDbSessionHandlerTest.php
Expand Up @@ -40,6 +40,7 @@ protected function setUp()
'id_field' => '_id',
'data_field' => 'data',
'time_field' => 'time',
'expiry_field' => 'expires_at',
'database' => 'sf2-test',
'collection' => 'session-test',
);
Expand Down Expand Up @@ -73,6 +74,42 @@ public function testCloseMethodAlwaysReturnTrue()
$this->assertTrue($this->storage->close(), 'The "close" method should always return true');
}

public function testRead()
{
$collection = $this->createMongoCollectionMock();

$this->mongo->expects($this->once())
->method('selectCollection')
->with($this->options['database'], $this->options['collection'])
->will($this->returnValue($collection));

$that = $this;

// defining the timeout before the actual method call
// allows to test for "greater than" values in the $criteria
$testTimeout = time();

$collection->expects($this->once())
->method('findOne')
->will($this->returnCallback(function ($criteria) use ($that, $testTimeout) {
$that->assertArrayHasKey($that->options['id_field'], $criteria);
$that->assertEquals($criteria[$that->options['id_field']], 'foo');

$that->assertArrayHasKey($that->options['expiry_field'], $criteria);
$that->assertArrayHasKey('$gte', $criteria[$that->options['expiry_field']]);
$that->assertInstanceOf('MongoDate', $criteria[$that->options['expiry_field']]['$gte']);
$that->assertGreaterThanOrEqual($criteria[$that->options['expiry_field']]['$gte']->sec, $testTimeout);

return array(
$that->options['id_field'] => 'foo',
$that->options['data_field'] => new \MongoBinData('bar', \MongoBinData::BYTE_ARRAY),
$that->options['id_field'] => new \MongoDate(),
);
}));

$this->assertEquals('bar', $this->storage->read('foo'));
}

public function testWrite()
{
$collection = $this->createMongoCollectionMock();
Expand All @@ -94,10 +131,13 @@ public function testWrite()
$data = $updateData['$set'];
}));

$expectedExpiry = time() + (int) ini_get('session.gc_maxlifetime');
$this->assertTrue($this->storage->write('foo', 'bar'));

$this->assertEquals('bar', $data[$this->options['data_field']]->bin);
$that->assertInstanceOf('MongoDate', $data[$this->options['time_field']]);
$this->assertInstanceOf('MongoDate', $data[$this->options['expiry_field']]);
$this->assertGreaterThanOrEqual($expectedExpiry, $data[$this->options['expiry_field']]->sec);
}

public function testWriteWhenUsingExpiresField()
Expand Down Expand Up @@ -192,49 +232,13 @@ public function testGc()
$collection->expects($this->once())
->method('remove')
->will($this->returnCallback(function ($criteria) use ($that) {
$that->assertInstanceOf('MongoDate', $criteria[$that->options['time_field']]['$lt']);
$that->assertGreaterThanOrEqual(time() - 1, $criteria[$that->options['time_field']]['$lt']->sec);
$that->assertInstanceOf('MongoDate', $criteria[$that->options['expiry_field']]['$lt']);
$that->assertGreaterThanOrEqual(time() - 1, $criteria[$that->options['expiry_field']]['$lt']->sec);
}));

$this->assertTrue($this->storage->gc(1));
}

public function testGcWhenUsingExpiresField()
{
$this->options = array(
'id_field' => '_id',
'data_field' => 'data',
'time_field' => 'time',
'database' => 'sf2-test',
'collection' => 'session-test',
'expiry_field' => 'expiresAt',
);

$this->storage = new MongoDbSessionHandler($this->mongo, $this->options);

$collection = $this->createMongoCollectionMock();

$this->mongo->expects($this->never())
->method('selectCollection');

$that = $this;

$collection->expects($this->never())
->method('remove');

$this->assertTrue($this->storage->gc(1));
}

public function testGetConnection()
{
$method = new \ReflectionMethod($this->storage, 'getMongo');
$method->setAccessible(true);

$mongoClass = (version_compare(phpversion('mongo'), '1.3.0', '<')) ? '\Mongo' : '\MongoClient';

$this->assertInstanceOf($mongoClass, $method->invoke($this->storage));
}

private function createMongoCollectionMock()
{
$mongoClient = $this->getMockBuilder('MongoClient')
Expand Down

0 comments on commit 7b43935

Please sign in to comment.