Fix for #19183 to add support for new PHP MongoDB extension in sessions.

This commit is contained in:
Ben Oman 2016-06-25 17:51:43 -06:00 committed by Fabien Potencier
parent 6cdb090dd0
commit ebbc7068f9
2 changed files with 162 additions and 38 deletions

View File

@ -61,15 +61,15 @@ class MongoDbSessionHandler implements \SessionHandlerInterface
* 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
* @param \Mongo|\MongoClient|\MongoDB\Client $mongo A MongoDB\Client, MongoClient or Mongo instance
* @param array $options An associative array of field options
*
* @throws \InvalidArgumentException When MongoClient or Mongo instance not provided
* @throws \InvalidArgumentException When "database" or "collection" not provided
*/
public function __construct($mongo, array $options)
{
if (!($mongo instanceof \MongoClient || $mongo instanceof \Mongo)) {
if (!($mongo instanceof \MongoDB\Client || $mongo instanceof \MongoClient || $mongo instanceof \Mongo)) {
throw new \InvalidArgumentException('MongoClient or Mongo instance required');
}
@ -108,7 +108,9 @@ class MongoDbSessionHandler implements \SessionHandlerInterface
*/
public function destroy($sessionId)
{
$this->getCollection()->remove(array(
$methodName = ($this->mongo instanceof \MongoDB\Client) ? 'deleteOne' : 'remove';
$this->getCollection()->$methodName(array(
$this->options['id_field'] => $sessionId,
));
@ -120,8 +122,10 @@ class MongoDbSessionHandler implements \SessionHandlerInterface
*/
public function gc($maxlifetime)
{
$this->getCollection()->remove(array(
$this->options['expiry_field'] => array('$lt' => new \MongoDate()),
$methodName = ($this->mongo instanceof \MongoDB\Client) ? 'deleteOne' : 'remove';
$this->getCollection()->$methodName(array(
$this->options['expiry_field'] => array('$lt' => $this->createDateTime()),
));
return true;
@ -132,18 +136,28 @@ class MongoDbSessionHandler implements \SessionHandlerInterface
*/
public function write($sessionId, $data)
{
$expiry = new \MongoDate(time() + (int) ini_get('session.gc_maxlifetime'));
$expiry = $this->createDateTime(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['time_field'] => $this->createDateTime(),
$this->options['expiry_field'] => $expiry,
);
$this->getCollection()->update(
$options = array('upsert' => true);
if ($this->mongo instanceof \MongoDB\Client) {
$fields[$this->options['data_field']] = new \MongoDB\BSON\Binary($data, \MongoDB\BSON\Binary::TYPE_OLD_BINARY);
} else {
$fields[$this->options['data_field']] = new \MongoBinData($data, \MongoBinData::BYTE_ARRAY);
$options['multiple'] = false;
}
$methodName = ($this->mongo instanceof \MongoDB\Client) ? 'updateOne' : 'update';
$this->getCollection()->$methodName(
array($this->options['id_field'] => $sessionId),
array('$set' => $fields),
array('upsert' => true, 'multiple' => false)
$options
);
return true;
@ -156,10 +170,18 @@ class MongoDbSessionHandler implements \SessionHandlerInterface
{
$dbData = $this->getCollection()->findOne(array(
$this->options['id_field'] => $sessionId,
$this->options['expiry_field'] => array('$gte' => new \MongoDate()),
$this->options['expiry_field'] => array('$gte' => $this->createDateTime()),
));
return null === $dbData ? '' : $dbData[$this->options['data_field']]->bin;
if (null === $dbData) {
return '';
}
if ($dbData[$this->options['data_field']] instanceof \MongoDB\BSON\Binary) {
return $dbData[$this->options['data_field']]->getData();
}
return $dbData[$this->options['data_field']]->bin;
}
/**
@ -185,4 +207,24 @@ class MongoDbSessionHandler implements \SessionHandlerInterface
{
return $this->mongo;
}
/**
* Create a date object using the class appropriate for the current mongo connection.
*
* Return an instance of a MongoDate or \MongoDB\BSON\UTCDateTime
*
* @param int $seconds An integer representing UTC seconds since Jan 1 1970. Defaults to now.
*/
private function createDateTime($seconds = null)
{
if (is_null($seconds)) {
$seconds = time();
}
if ($this->mongo instanceof \MongoDB\Client) {
return new \MongoDB\BSON\UTCDateTime($seconds * 1000);
}
return new \MongoDate($seconds);
}
}

View File

@ -15,7 +15,6 @@ use Symfony\Component\HttpFoundation\Session\Storage\Handler\MongoDbSessionHandl
/**
* @author Markus Bachmann <markus.bachmann@bachi.biz>
* @requires extension mongo
* @group time-sensitive
*/
class MongoDbSessionHandlerTest extends \PHPUnit_Framework_TestCase
@ -31,7 +30,15 @@ class MongoDbSessionHandlerTest extends \PHPUnit_Framework_TestCase
{
parent::setUp();
$mongoClass = version_compare(phpversion('mongo'), '1.3.0', '<') ? 'Mongo' : 'MongoClient';
if (!extension_loaded('mongo') && !extension_loaded('mongodb')) {
$this->markTestSkipped('The Mongo or MongoDB extension is required.');
}
if (phpversion('mongodb')) {
$mongoClass = 'MongoDB\Client';
} else {
$mongoClass = version_compare(phpversion('mongo'), '1.3.0', '<') ? 'Mongo' : 'MongoClient';
}
$this->mongo = $this->getMockBuilder($mongoClass)
->disableOriginalConstructor()
@ -98,14 +105,28 @@ class MongoDbSessionHandlerTest extends \PHPUnit_Framework_TestCase
$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(
if (phpversion('mongodb')) {
$that->assertInstanceOf('MongoDB\BSON\UTCDateTime', $criteria[$that->options['expiry_field']]['$gte']);
$that->assertGreaterThanOrEqual(round(intval((string) $criteria[$that->options['expiry_field']]['$gte']) / 1000), $testTimeout);
} else {
$that->assertInstanceOf('MongoDate', $criteria[$that->options['expiry_field']]['$gte']);
$that->assertGreaterThanOrEqual($criteria[$that->options['expiry_field']]['$gte']->sec, $testTimeout);
}
$fields = array(
$that->options['id_field'] => 'foo',
$that->options['data_field'] => new \MongoBinData('bar', \MongoBinData::BYTE_ARRAY),
$that->options['id_field'] => new \MongoDate(),
);
if (phpversion('mongodb')) {
$fields[$that->options['data_field']] = new \MongoDB\BSON\Binary('bar', \MongoDB\BSON\Binary::TYPE_OLD_BINARY);
$fields[$that->options['id_field']] = new \MongoDB\BSON\UTCDateTime(time() * 1000);
} else {
$fields[$that->options['data_field']] = new \MongoBinData('bar', \MongoBinData::BYTE_ARRAY);
$fields[$that->options['id_field']] = new \MongoDate();
}
return $fields;
}));
$this->assertEquals('bar', $this->storage->read('foo'));
@ -123,11 +144,18 @@ class MongoDbSessionHandlerTest extends \PHPUnit_Framework_TestCase
$that = $this;
$data = array();
$methodName = phpversion('mongodb') ? 'updateOne' : 'update';
$collection->expects($this->once())
->method('update')
->method($methodName)
->will($this->returnCallback(function ($criteria, $updateData, $options) use ($that, &$data) {
$that->assertEquals(array($that->options['id_field'] => 'foo'), $criteria);
$that->assertEquals(array('upsert' => true, 'multiple' => false), $options);
if (phpversion('mongodb')) {
$that->assertEquals(array('upsert' => true), $options);
} else {
$that->assertEquals(array('upsert' => true, 'multiple' => false), $options);
}
$data = $updateData['$set'];
}));
@ -135,10 +163,17 @@ class MongoDbSessionHandlerTest extends \PHPUnit_Framework_TestCase
$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);
if (phpversion('mongodb')) {
$that->assertEquals('bar', $data[$that->options['data_field']]->getData());
$that->assertInstanceOf('MongoDB\BSON\UTCDateTime', $data[$that->options['time_field']]);
$that->assertInstanceOf('MongoDB\BSON\UTCDateTime', $data[$that->options['expiry_field']]);
$that->assertGreaterThanOrEqual($expectedExpiry, round(intval((string) $data[$that->options['expiry_field']]) / 1000));
} else {
$that->assertEquals('bar', $data[$that->options['data_field']]->bin);
$that->assertInstanceOf('MongoDate', $data[$that->options['time_field']]);
$that->assertInstanceOf('MongoDate', $data[$that->options['expiry_field']]);
$that->assertGreaterThanOrEqual($expectedExpiry, $data[$that->options['expiry_field']]->sec);
}
}
public function testWriteWhenUsingExpiresField()
@ -164,20 +199,33 @@ class MongoDbSessionHandlerTest extends \PHPUnit_Framework_TestCase
$that = $this;
$data = array();
$methodName = phpversion('mongodb') ? 'updateOne' : 'update';
$collection->expects($this->once())
->method('update')
->method($methodName)
->will($this->returnCallback(function ($criteria, $updateData, $options) use ($that, &$data) {
$that->assertEquals(array($that->options['id_field'] => 'foo'), $criteria);
$that->assertEquals(array('upsert' => true, 'multiple' => false), $options);
if (phpversion('mongodb')) {
$that->assertEquals(array('upsert' => true), $options);
} else {
$that->assertEquals(array('upsert' => true, 'multiple' => false), $options);
}
$data = $updateData['$set'];
}));
$this->assertTrue($this->storage->write('foo', 'bar'));
$this->assertEquals('bar', $data[$this->options['data_field']]->bin);
$that->assertInstanceOf('MongoDate', $data[$this->options['time_field']]);
$that->assertInstanceOf('MongoDate', $data[$this->options['expiry_field']]);
if (phpversion('mongodb')) {
$that->assertEquals('bar', $data[$that->options['data_field']]->getData());
$that->assertInstanceOf('MongoDB\BSON\UTCDateTime', $data[$that->options['time_field']]);
$that->assertInstanceOf('MongoDB\BSON\UTCDateTime', $data[$that->options['expiry_field']]);
} else {
$that->assertEquals('bar', $data[$that->options['data_field']]->bin);
$that->assertInstanceOf('MongoDate', $data[$that->options['time_field']]);
$that->assertInstanceOf('MongoDate', $data[$that->options['expiry_field']]);
}
}
public function testReplaceSessionData()
@ -191,8 +239,10 @@ class MongoDbSessionHandlerTest extends \PHPUnit_Framework_TestCase
$data = array();
$methodName = phpversion('mongodb') ? 'updateOne' : 'update';
$collection->expects($this->exactly(2))
->method('update')
->method($methodName)
->will($this->returnCallback(function ($criteria, $updateData, $options) use (&$data) {
$data = $updateData;
}));
@ -200,7 +250,11 @@ class MongoDbSessionHandlerTest extends \PHPUnit_Framework_TestCase
$this->storage->write('foo', 'bar');
$this->storage->write('foo', 'foobar');
$this->assertEquals('foobar', $data['$set'][$this->options['data_field']]->bin);
if (phpversion('mongodb')) {
$this->assertEquals('foobar', $data['$set'][$this->options['data_field']]->getData());
} else {
$this->assertEquals('foobar', $data['$set'][$this->options['data_field']]->bin);
}
}
public function testDestroy()
@ -212,8 +266,10 @@ class MongoDbSessionHandlerTest extends \PHPUnit_Framework_TestCase
->with($this->options['database'], $this->options['collection'])
->will($this->returnValue($collection));
$methodName = phpversion('mongodb') ? 'deleteOne' : 'remove';
$collection->expects($this->once())
->method('remove')
->method($methodName)
->with(array($this->options['id_field'] => 'foo'));
$this->assertTrue($this->storage->destroy('foo'));
@ -230,19 +286,45 @@ class MongoDbSessionHandlerTest extends \PHPUnit_Framework_TestCase
$that = $this;
$methodName = phpversion('mongodb') ? 'deleteOne' : 'remove';
$collection->expects($this->once())
->method('remove')
->method($methodName)
->will($this->returnCallback(function ($criteria) use ($that) {
$that->assertInstanceOf('MongoDate', $criteria[$that->options['expiry_field']]['$lt']);
$that->assertGreaterThanOrEqual(time() - 1, $criteria[$that->options['expiry_field']]['$lt']->sec);
if (phpversion('mongodb')) {
$that->assertInstanceOf('MongoDB\BSON\UTCDateTime', $criteria[$that->options['expiry_field']]['$lt']);
$that->assertGreaterThanOrEqual(time() - 1, round(intval((string) $criteria[$that->options['expiry_field']]['$lt']) / 1000));
} else {
$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 testGetConnection()
{
$method = new \ReflectionMethod($this->storage, 'getMongo');
$method->setAccessible(true);
if (phpversion('mongodb')) {
$mongoClass = 'MongoDB\Client';
} else {
$mongoClass = version_compare(phpversion('mongo'), '1.3.0', '<') ? 'Mongo' : 'MongoClient';
}
$this->assertInstanceOf($mongoClass, $method->invoke($this->storage));
}
private function createMongoCollectionMock()
{
$collection = $this->getMockBuilder('MongoCollection')
$collectionClass = 'MongoCollection';
if (phpversion('mongodb')) {
$collectionClass = 'MongoDB\Collection';
}
$collection = $this->getMockBuilder($collectionClass)
->disableOriginalConstructor()
->getMock();