From bb336a5b022d504aaa6d2914aa39fed6ba86006d Mon Sep 17 00:00:00 2001 From: Chris Sedlmayr Date: Tue, 29 Jul 2014 14:36:07 +0100 Subject: [PATCH 01/11] [HttpFOundation] Added support for auto expiry in MongoSbSessionHandler This adds a config option that disables the PHP GC method call from doing anything, and also means that the write method sets up the auto expiring index --- .../Storage/Handler/MongoDbSessionHandler.php | 38 ++++++--- .../Handler/MongoDbSessionHandlerTest.php | 78 ++++++++++++++++++- 2 files changed, 104 insertions(+), 12 deletions(-) diff --git a/src/Symfony/Component/HttpFoundation/Session/Storage/Handler/MongoDbSessionHandler.php b/src/Symfony/Component/HttpFoundation/Session/Storage/Handler/MongoDbSessionHandler.php index 87aa7fb4f77a9..f0efdc6484a9e 100644 --- a/src/Symfony/Component/HttpFoundation/Session/Storage/Handler/MongoDbSessionHandler.php +++ b/src/Symfony/Component/HttpFoundation/Session/Storage/Handler/MongoDbSessionHandler.php @@ -62,9 +62,10 @@ public function __construct($mongo, array $options) $this->mongo = $mongo; $this->options = array_merge(array( - 'id_field' => '_id', - 'data_field' => 'data', - 'time_field' => 'time', + 'id_field' => '_id', + 'data_field' => 'data', + 'time_field' => 'time', + 'expiry_field' => false, ), $options); } @@ -109,11 +110,14 @@ public function gc($maxlifetime) * * See: http://docs.mongodb.org/manual/tutorial/expire-data/ */ - $time = new \MongoDate(time() - $maxlifetime); + if ($this->options['expiry_field'] == false) { + $time = new \MongoDate(time() - $maxlifetime); - $this->getCollection()->remove(array( - $this->options['time_field'] => array('$lt' => $time), - )); + $this->getCollection()->remove(array( + $this->options['time_field'] => array('$lt' => $time), + )); + + } return true; } @@ -123,12 +127,24 @@ public function gc($maxlifetime) */ public function write($sessionId, $data) { + $fields = array( + $this->options['data_field'] => new \MongoBinData($data, \MongoBinData::BYTE_ARRAY), + $this->options['time_field'] => new \MongoDate(), + ); + + if ($this->options['expiry_field'] != false) { + $expiry = new \MongoDate(time() + (int) ini_get('session.gc_maxlifetime')); + $fields[$this->options['expiry_field']] = $expiry; + + $this->getCollection()->createIndex( + [$this->options['time_field'] => 1], + ['expireAfterSeconds' => 0] + ); + } + $this->getCollection()->update( array($this->options['id_field'] => $sessionId), - array('$set' => array( - $this->options['data_field'] => new \MongoBinData($data, \MongoBinData::BYTE_ARRAY), - $this->options['time_field'] => new \MongoDate(), - )), + array('$set' => $fields), array('upsert' => true, 'multiple' => false) ); diff --git a/src/Symfony/Component/HttpFoundation/Tests/Session/Storage/Handler/MongoDbSessionHandlerTest.php b/src/Symfony/Component/HttpFoundation/Tests/Session/Storage/Handler/MongoDbSessionHandlerTest.php index 9577d5247886a..4dcf2aec16d71 100644 --- a/src/Symfony/Component/HttpFoundation/Tests/Session/Storage/Handler/MongoDbSessionHandlerTest.php +++ b/src/Symfony/Component/HttpFoundation/Tests/Session/Storage/Handler/MongoDbSessionHandlerTest.php @@ -41,7 +41,7 @@ protected function setUp() 'data_field' => 'data', 'time_field' => 'time', 'database' => 'sf2-test', - 'collection' => 'session-test' + 'collection' => 'session-test', ); $this->storage = new MongoDbSessionHandler($this->mongo, $this->options); @@ -100,6 +100,50 @@ public function testWrite() $that->assertInstanceOf('MongoDate', $data[$this->options['time_field']]); } + public function testWriteWhenUsingExpiresField() + { + $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->once()) + ->method('selectCollection') + ->with($this->options['database'], $this->options['collection']) + ->will($this->returnValue($collection)); + + $that = $this; + $data = array(); + + $collection->expects($this->once()) + ->method('update') + ->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); + + $data = $updateData['$set']; + })); + + $collection->expects($this->once()) + ->method('createIndex') + ->with([$this->options['time_field'] => 1], ['expireAfterSeconds' => 0]) + ->willReturn(true); + + $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']]); + } + public function testReplaceSessionData() { $collection = $this->createMongoCollectionMock(); @@ -160,6 +204,38 @@ public function testGc() $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') + ->with($this->options['database'], $this->options['collection']) + ->will($this->returnValue($collection)); + + $that = $this; + + $collection->expects($this->never()) + ->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); + })); + + $this->assertTrue($this->storage->gc(-1)); + } + private function createMongoCollectionMock() { From 796b18a50d2c91ab42d3e3a966f1f201a0beb47d Mon Sep 17 00:00:00 2001 From: Chris Sedlmayr Date: Tue, 29 Jul 2014 15:34:34 +0100 Subject: [PATCH 02/11] Fixes an issue in the mongo handler where the wrong option was used for expiry checks --- .../Session/Storage/Handler/MongoDbSessionHandler.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Symfony/Component/HttpFoundation/Session/Storage/Handler/MongoDbSessionHandler.php b/src/Symfony/Component/HttpFoundation/Session/Storage/Handler/MongoDbSessionHandler.php index f0efdc6484a9e..ebae0b6fc82f6 100644 --- a/src/Symfony/Component/HttpFoundation/Session/Storage/Handler/MongoDbSessionHandler.php +++ b/src/Symfony/Component/HttpFoundation/Session/Storage/Handler/MongoDbSessionHandler.php @@ -137,7 +137,7 @@ public function write($sessionId, $data) $fields[$this->options['expiry_field']] = $expiry; $this->getCollection()->createIndex( - [$this->options['time_field'] => 1], + [$this->options['expiry_field'] => 1], ['expireAfterSeconds' => 0] ); } From 2d8b2bd07537fcfee158198854896586b4623f98 Mon Sep 17 00:00:00 2001 From: Chris Sedlmayr Date: Tue, 29 Jul 2014 16:42:34 +0100 Subject: [PATCH 03/11] [HttpFoundation] Fixes unit test following the recent update --- .../Tests/Session/Storage/Handler/MongoDbSessionHandlerTest.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Symfony/Component/HttpFoundation/Tests/Session/Storage/Handler/MongoDbSessionHandlerTest.php b/src/Symfony/Component/HttpFoundation/Tests/Session/Storage/Handler/MongoDbSessionHandlerTest.php index 4dcf2aec16d71..17aac9294eb2c 100644 --- a/src/Symfony/Component/HttpFoundation/Tests/Session/Storage/Handler/MongoDbSessionHandlerTest.php +++ b/src/Symfony/Component/HttpFoundation/Tests/Session/Storage/Handler/MongoDbSessionHandlerTest.php @@ -134,7 +134,7 @@ public function testWriteWhenUsingExpiresField() $collection->expects($this->once()) ->method('createIndex') - ->with([$this->options['time_field'] => 1], ['expireAfterSeconds' => 0]) + ->with([$this->options['expiry_field'] => 1], ['expireAfterSeconds' => 0]) ->willReturn(true); $this->assertTrue($this->storage->write('foo', 'bar')); From 4d42f32c4247dacb687350349f38bf5b7a658295 Mon Sep 17 00:00:00 2001 From: Chris Sedlmayr Date: Tue, 29 Jul 2014 17:17:30 +0100 Subject: [PATCH 04/11] Fixes usage of short array syntax to support 5.3 --- .../Session/Storage/Handler/MongoDbSessionHandler.php | 4 ++-- .../Session/Storage/Handler/MongoDbSessionHandlerTest.php | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Symfony/Component/HttpFoundation/Session/Storage/Handler/MongoDbSessionHandler.php b/src/Symfony/Component/HttpFoundation/Session/Storage/Handler/MongoDbSessionHandler.php index ebae0b6fc82f6..77a651d8e215a 100644 --- a/src/Symfony/Component/HttpFoundation/Session/Storage/Handler/MongoDbSessionHandler.php +++ b/src/Symfony/Component/HttpFoundation/Session/Storage/Handler/MongoDbSessionHandler.php @@ -137,8 +137,8 @@ public function write($sessionId, $data) $fields[$this->options['expiry_field']] = $expiry; $this->getCollection()->createIndex( - [$this->options['expiry_field'] => 1], - ['expireAfterSeconds' => 0] + array($this->options['expiry_field'] => 1), + array('expireAfterSeconds' => 0) ); } diff --git a/src/Symfony/Component/HttpFoundation/Tests/Session/Storage/Handler/MongoDbSessionHandlerTest.php b/src/Symfony/Component/HttpFoundation/Tests/Session/Storage/Handler/MongoDbSessionHandlerTest.php index 17aac9294eb2c..7699d5c5b23ec 100644 --- a/src/Symfony/Component/HttpFoundation/Tests/Session/Storage/Handler/MongoDbSessionHandlerTest.php +++ b/src/Symfony/Component/HttpFoundation/Tests/Session/Storage/Handler/MongoDbSessionHandlerTest.php @@ -134,7 +134,7 @@ public function testWriteWhenUsingExpiresField() $collection->expects($this->once()) ->method('createIndex') - ->with([$this->options['expiry_field'] => 1], ['expireAfterSeconds' => 0]) + ->with(array($this->options['expiry_field'] => 1), array('expireAfterSeconds' => 0)) ->willReturn(true); $this->assertTrue($this->storage->write('foo', 'bar')); From cb5e5625dfddbddb2050ed3b6c06a425e6b441e8 Mon Sep 17 00:00:00 2001 From: Chris Sedlmayr Date: Tue, 29 Jul 2014 21:42:28 +0100 Subject: [PATCH 05/11] Use strict comparisons on boolean checks --- .../Session/Storage/Handler/MongoDbSessionHandler.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Symfony/Component/HttpFoundation/Session/Storage/Handler/MongoDbSessionHandler.php b/src/Symfony/Component/HttpFoundation/Session/Storage/Handler/MongoDbSessionHandler.php index 77a651d8e215a..37fa27d093937 100644 --- a/src/Symfony/Component/HttpFoundation/Session/Storage/Handler/MongoDbSessionHandler.php +++ b/src/Symfony/Component/HttpFoundation/Session/Storage/Handler/MongoDbSessionHandler.php @@ -110,7 +110,7 @@ public function gc($maxlifetime) * * See: http://docs.mongodb.org/manual/tutorial/expire-data/ */ - if ($this->options['expiry_field'] == false) { + if ($this->options['expiry_field'] === false) { $time = new \MongoDate(time() - $maxlifetime); $this->getCollection()->remove(array( @@ -132,7 +132,7 @@ public function write($sessionId, $data) $this->options['time_field'] => new \MongoDate(), ); - if ($this->options['expiry_field'] != false) { + if ($this->options['expiry_field'] !== false) { $expiry = new \MongoDate(time() + (int) ini_get('session.gc_maxlifetime')); $fields[$this->options['expiry_field']] = $expiry; From d748743847d5ed0970eb9905a6929fc375011a56 Mon Sep 17 00:00:00 2001 From: Chris Sedlmayr Date: Wed, 30 Jul 2014 11:47:26 +0100 Subject: [PATCH 06/11] Switched to using yoda expressions --- .../Session/Storage/Handler/MongoDbSessionHandler.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Symfony/Component/HttpFoundation/Session/Storage/Handler/MongoDbSessionHandler.php b/src/Symfony/Component/HttpFoundation/Session/Storage/Handler/MongoDbSessionHandler.php index 37fa27d093937..a86283f8424a5 100644 --- a/src/Symfony/Component/HttpFoundation/Session/Storage/Handler/MongoDbSessionHandler.php +++ b/src/Symfony/Component/HttpFoundation/Session/Storage/Handler/MongoDbSessionHandler.php @@ -110,7 +110,7 @@ public function gc($maxlifetime) * * See: http://docs.mongodb.org/manual/tutorial/expire-data/ */ - if ($this->options['expiry_field'] === false) { + if (false === $this->options['expiry_field']) { $time = new \MongoDate(time() - $maxlifetime); $this->getCollection()->remove(array( @@ -132,7 +132,7 @@ public function write($sessionId, $data) $this->options['time_field'] => new \MongoDate(), ); - if ($this->options['expiry_field'] !== false) { + if (false !== $this->options['expiry_field']) { $expiry = new \MongoDate(time() + (int) ini_get('session.gc_maxlifetime')); $fields[$this->options['expiry_field']] = $expiry; From 2f8742ea2393f3557407318ac197b9f350aede59 Mon Sep 17 00:00:00 2001 From: Chris Sedlmayr Date: Fri, 1 Aug 2014 09:00:03 +0100 Subject: [PATCH 07/11] Return early from the gc method when expiry indexes are used --- .../Storage/Handler/MongoDbSessionHandler.php | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/Symfony/Component/HttpFoundation/Session/Storage/Handler/MongoDbSessionHandler.php b/src/Symfony/Component/HttpFoundation/Session/Storage/Handler/MongoDbSessionHandler.php index a86283f8424a5..cd8f621ca0a2d 100644 --- a/src/Symfony/Component/HttpFoundation/Session/Storage/Handler/MongoDbSessionHandler.php +++ b/src/Symfony/Component/HttpFoundation/Session/Storage/Handler/MongoDbSessionHandler.php @@ -110,14 +110,14 @@ public function gc($maxlifetime) * * See: http://docs.mongodb.org/manual/tutorial/expire-data/ */ - if (false === $this->options['expiry_field']) { - $time = new \MongoDate(time() - $maxlifetime); - - $this->getCollection()->remove(array( - $this->options['time_field'] => array('$lt' => $time), - )); - + if (false !== $this->options['expiry_field']) { + return true; } + $time = new \MongoDate(time() - $maxlifetime); + + $this->getCollection()->remove(array( + $this->options['time_field'] => array('$lt' => $time), + )); return true; } From fc962db8aa906039383caf1ea1fd6683ed147d60 Mon Sep 17 00:00:00 2001 From: Chris Sedlmayr Date: Fri, 1 Aug 2014 09:10:30 +0100 Subject: [PATCH 08/11] Modifies the gc tests to use positive values --- .../Session/Storage/Handler/MongoDbSessionHandlerTest.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Symfony/Component/HttpFoundation/Tests/Session/Storage/Handler/MongoDbSessionHandlerTest.php b/src/Symfony/Component/HttpFoundation/Tests/Session/Storage/Handler/MongoDbSessionHandlerTest.php index 7699d5c5b23ec..0014a18a0f22d 100644 --- a/src/Symfony/Component/HttpFoundation/Tests/Session/Storage/Handler/MongoDbSessionHandlerTest.php +++ b/src/Symfony/Component/HttpFoundation/Tests/Session/Storage/Handler/MongoDbSessionHandlerTest.php @@ -198,10 +198,10 @@ public function testGc() ->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->assertGreaterThanOrEqual(time() - 1, $criteria[$that->options['time_field']]['$lt']->sec); })); - $this->assertTrue($this->storage->gc(-1)); + $this->assertTrue($this->storage->gc(1)); } public function testGcWhenUsingExpiresField() @@ -230,10 +230,10 @@ public function testGcWhenUsingExpiresField() ->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->assertGreaterThanOrEqual(time() - 1, $criteria[$that->options['time_field']]['$lt']->sec); })); - $this->assertTrue($this->storage->gc(-1)); + $this->assertTrue($this->storage->gc(1)); } private function createMongoCollectionMock() From b55c63b4922b5093d9fda78c4e5b9593e27bba26 Mon Sep 17 00:00:00 2001 From: Chris Sedlmayr Date: Mon, 4 Aug 2014 16:26:46 +0100 Subject: [PATCH 09/11] Now longer creating the index during write, but have documented it --- .../Storage/Handler/MongoDbSessionHandler.php | 14 +++++++++----- .../Storage/Handler/MongoDbSessionHandlerTest.php | 5 ----- 2 files changed, 9 insertions(+), 10 deletions(-) diff --git a/src/Symfony/Component/HttpFoundation/Session/Storage/Handler/MongoDbSessionHandler.php b/src/Symfony/Component/HttpFoundation/Session/Storage/Handler/MongoDbSessionHandler.php index cd8f621ca0a2d..81b02a0a49c70 100644 --- a/src/Symfony/Component/HttpFoundation/Session/Storage/Handler/MongoDbSessionHandler.php +++ b/src/Symfony/Component/HttpFoundation/Session/Storage/Handler/MongoDbSessionHandler.php @@ -132,14 +132,18 @@ public function write($sessionId, $data) $this->options['time_field'] => new \MongoDate(), ); + /* 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()->createIndex( - array($this->options['expiry_field'] => 1), - array('expireAfterSeconds' => 0) - ); } $this->getCollection()->update( diff --git a/src/Symfony/Component/HttpFoundation/Tests/Session/Storage/Handler/MongoDbSessionHandlerTest.php b/src/Symfony/Component/HttpFoundation/Tests/Session/Storage/Handler/MongoDbSessionHandlerTest.php index 0014a18a0f22d..012baa06d2bf7 100644 --- a/src/Symfony/Component/HttpFoundation/Tests/Session/Storage/Handler/MongoDbSessionHandlerTest.php +++ b/src/Symfony/Component/HttpFoundation/Tests/Session/Storage/Handler/MongoDbSessionHandlerTest.php @@ -132,11 +132,6 @@ public function testWriteWhenUsingExpiresField() $data = $updateData['$set']; })); - $collection->expects($this->once()) - ->method('createIndex') - ->with(array($this->options['expiry_field'] => 1), array('expireAfterSeconds' => 0)) - ->willReturn(true); - $this->assertTrue($this->storage->write('foo', 'bar')); $this->assertEquals('bar', $data[$this->options['data_field']]->bin); From 88b256c9cb17197e8f0a94afe795ffa7b8e3a8d4 Mon Sep 17 00:00:00 2001 From: Chris Sedlmayr Date: Mon, 4 Aug 2014 16:41:29 +0100 Subject: [PATCH 10/11] Corrects error in comment --- .../Session/Storage/Handler/MongoDbSessionHandler.php | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/Symfony/Component/HttpFoundation/Session/Storage/Handler/MongoDbSessionHandler.php b/src/Symfony/Component/HttpFoundation/Session/Storage/Handler/MongoDbSessionHandler.php index 81b02a0a49c70..b151c8e33d4e3 100644 --- a/src/Symfony/Component/HttpFoundation/Session/Storage/Handler/MongoDbSessionHandler.php +++ b/src/Symfony/Component/HttpFoundation/Session/Storage/Handler/MongoDbSessionHandler.php @@ -134,12 +134,11 @@ public function write($sessionId, $data) /* 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 + * 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')); From 9744f618bea52e7452dc9942de90052ef82d9a7b Mon Sep 17 00:00:00 2001 From: Chris Sedlmayr Date: Mon, 11 Aug 2014 21:37:52 +0100 Subject: [PATCH 11/11] Removes will and with expectations from method tests that are never called --- .../Storage/Handler/MongoDbSessionHandlerTest.php | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/src/Symfony/Component/HttpFoundation/Tests/Session/Storage/Handler/MongoDbSessionHandlerTest.php b/src/Symfony/Component/HttpFoundation/Tests/Session/Storage/Handler/MongoDbSessionHandlerTest.php index 012baa06d2bf7..19170bbbf02ec 100644 --- a/src/Symfony/Component/HttpFoundation/Tests/Session/Storage/Handler/MongoDbSessionHandlerTest.php +++ b/src/Symfony/Component/HttpFoundation/Tests/Session/Storage/Handler/MongoDbSessionHandlerTest.php @@ -215,18 +215,12 @@ public function testGcWhenUsingExpiresField() $collection = $this->createMongoCollectionMock(); $this->mongo->expects($this->never()) - ->method('selectCollection') - ->with($this->options['database'], $this->options['collection']) - ->will($this->returnValue($collection)); + ->method('selectCollection'); $that = $this; $collection->expects($this->never()) - ->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); - })); + ->method('remove'); $this->assertTrue($this->storage->gc(1)); }