Skip to content

Commit f086259

Browse files
committed
bug symfony#19186 Fix for symfony#19183 to add support for new PHP MongoDB extension in sessions. (omanizer)
This PR was submitted for the master branch but it was merged into the 2.7 branch instead (closes symfony#19186). Discussion ---------- Fix for symfony#19183 to add support for new PHP MongoDB extension in sessions. | Q | A | ------------- | --- | Branch? | 3.0 | Bug fix? | yes | New feature? | yes (ish) | BC breaks? | no | Deprecations? | no | Tests pass? | no | Fixed tickets | symfony#19183 | License | MIT | Doc PR | Commits ------- ebbc706 Fix for symfony#19183 to add support for new PHP MongoDB extension in sessions.
2 parents 6cdb090 + ebbc706 commit f086259

File tree

2 files changed

+162
-38
lines changed

2 files changed

+162
-38
lines changed

src/Symfony/Component/HttpFoundation/Session/Storage/Handler/MongoDbSessionHandler.php

Lines changed: 55 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -61,15 +61,15 @@ class MongoDbSessionHandler implements \SessionHandlerInterface
6161
* If you use such an index, you can drop `gc_probability` to 0 since
6262
* no garbage-collection is required.
6363
*
64-
* @param \Mongo|\MongoClient $mongo A MongoClient or Mongo instance
65-
* @param array $options An associative array of field options
64+
* @param \Mongo|\MongoClient|\MongoDB\Client $mongo A MongoDB\Client, MongoClient or Mongo instance
65+
* @param array $options An associative array of field options
6666
*
6767
* @throws \InvalidArgumentException When MongoClient or Mongo instance not provided
6868
* @throws \InvalidArgumentException When "database" or "collection" not provided
6969
*/
7070
public function __construct($mongo, array $options)
7171
{
72-
if (!($mongo instanceof \MongoClient || $mongo instanceof \Mongo)) {
72+
if (!($mongo instanceof \MongoDB\Client || $mongo instanceof \MongoClient || $mongo instanceof \Mongo)) {
7373
throw new \InvalidArgumentException('MongoClient or Mongo instance required');
7474
}
7575

@@ -108,7 +108,9 @@ public function close()
108108
*/
109109
public function destroy($sessionId)
110110
{
111-
$this->getCollection()->remove(array(
111+
$methodName = ($this->mongo instanceof \MongoDB\Client) ? 'deleteOne' : 'remove';
112+
113+
$this->getCollection()->$methodName(array(
112114
$this->options['id_field'] => $sessionId,
113115
));
114116

@@ -120,8 +122,10 @@ public function destroy($sessionId)
120122
*/
121123
public function gc($maxlifetime)
122124
{
123-
$this->getCollection()->remove(array(
124-
$this->options['expiry_field'] => array('$lt' => new \MongoDate()),
125+
$methodName = ($this->mongo instanceof \MongoDB\Client) ? 'deleteOne' : 'remove';
126+
127+
$this->getCollection()->$methodName(array(
128+
$this->options['expiry_field'] => array('$lt' => $this->createDateTime()),
125129
));
126130

127131
return true;
@@ -132,18 +136,28 @@ public function gc($maxlifetime)
132136
*/
133137
public function write($sessionId, $data)
134138
{
135-
$expiry = new \MongoDate(time() + (int) ini_get('session.gc_maxlifetime'));
139+
$expiry = $this->createDateTime(time() + (int) ini_get('session.gc_maxlifetime'));
136140

137141
$fields = array(
138-
$this->options['data_field'] => new \MongoBinData($data, \MongoBinData::BYTE_ARRAY),
139-
$this->options['time_field'] => new \MongoDate(),
142+
$this->options['time_field'] => $this->createDateTime(),
140143
$this->options['expiry_field'] => $expiry,
141144
);
142145

143-
$this->getCollection()->update(
146+
$options = array('upsert' => true);
147+
148+
if ($this->mongo instanceof \MongoDB\Client) {
149+
$fields[$this->options['data_field']] = new \MongoDB\BSON\Binary($data, \MongoDB\BSON\Binary::TYPE_OLD_BINARY);
150+
} else {
151+
$fields[$this->options['data_field']] = new \MongoBinData($data, \MongoBinData::BYTE_ARRAY);
152+
$options['multiple'] = false;
153+
}
154+
155+
$methodName = ($this->mongo instanceof \MongoDB\Client) ? 'updateOne' : 'update';
156+
157+
$this->getCollection()->$methodName(
144158
array($this->options['id_field'] => $sessionId),
145159
array('$set' => $fields),
146-
array('upsert' => true, 'multiple' => false)
160+
$options
147161
);
148162

149163
return true;
@@ -156,10 +170,18 @@ public function read($sessionId)
156170
{
157171
$dbData = $this->getCollection()->findOne(array(
158172
$this->options['id_field'] => $sessionId,
159-
$this->options['expiry_field'] => array('$gte' => new \MongoDate()),
173+
$this->options['expiry_field'] => array('$gte' => $this->createDateTime()),
160174
));
161175

162-
return null === $dbData ? '' : $dbData[$this->options['data_field']]->bin;
176+
if (null === $dbData) {
177+
return '';
178+
}
179+
180+
if ($dbData[$this->options['data_field']] instanceof \MongoDB\BSON\Binary) {
181+
return $dbData[$this->options['data_field']]->getData();
182+
}
183+
184+
return $dbData[$this->options['data_field']]->bin;
163185
}
164186

165187
/**
@@ -185,4 +207,24 @@ protected function getMongo()
185207
{
186208
return $this->mongo;
187209
}
210+
211+
/**
212+
* Create a date object using the class appropriate for the current mongo connection.
213+
*
214+
* Return an instance of a MongoDate or \MongoDB\BSON\UTCDateTime
215+
*
216+
* @param int $seconds An integer representing UTC seconds since Jan 1 1970. Defaults to now.
217+
*/
218+
private function createDateTime($seconds = null)
219+
{
220+
if (is_null($seconds)) {
221+
$seconds = time();
222+
}
223+
224+
if ($this->mongo instanceof \MongoDB\Client) {
225+
return new \MongoDB\BSON\UTCDateTime($seconds * 1000);
226+
}
227+
228+
return new \MongoDate($seconds);
229+
}
188230
}

src/Symfony/Component/HttpFoundation/Tests/Session/Storage/Handler/MongoDbSessionHandlerTest.php

Lines changed: 107 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,6 @@
1515

1616
/**
1717
* @author Markus Bachmann <markus.bachmann@bachi.biz>
18-
* @requires extension mongo
1918
* @group time-sensitive
2019
*/
2120
class MongoDbSessionHandlerTest extends \PHPUnit_Framework_TestCase
@@ -31,7 +30,15 @@ protected function setUp()
3130
{
3231
parent::setUp();
3332

34-
$mongoClass = version_compare(phpversion('mongo'), '1.3.0', '<') ? 'Mongo' : 'MongoClient';
33+
if (!extension_loaded('mongo') && !extension_loaded('mongodb')) {
34+
$this->markTestSkipped('The Mongo or MongoDB extension is required.');
35+
}
36+
37+
if (phpversion('mongodb')) {
38+
$mongoClass = 'MongoDB\Client';
39+
} else {
40+
$mongoClass = version_compare(phpversion('mongo'), '1.3.0', '<') ? 'Mongo' : 'MongoClient';
41+
}
3542

3643
$this->mongo = $this->getMockBuilder($mongoClass)
3744
->disableOriginalConstructor()
@@ -98,14 +105,28 @@ public function testRead()
98105

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

104-
return array(
109+
if (phpversion('mongodb')) {
110+
$that->assertInstanceOf('MongoDB\BSON\UTCDateTime', $criteria[$that->options['expiry_field']]['$gte']);
111+
$that->assertGreaterThanOrEqual(round(intval((string) $criteria[$that->options['expiry_field']]['$gte']) / 1000), $testTimeout);
112+
} else {
113+
$that->assertInstanceOf('MongoDate', $criteria[$that->options['expiry_field']]['$gte']);
114+
$that->assertGreaterThanOrEqual($criteria[$that->options['expiry_field']]['$gte']->sec, $testTimeout);
115+
}
116+
117+
$fields = array(
105118
$that->options['id_field'] => 'foo',
106-
$that->options['data_field'] => new \MongoBinData('bar', \MongoBinData::BYTE_ARRAY),
107-
$that->options['id_field'] => new \MongoDate(),
108119
);
120+
121+
if (phpversion('mongodb')) {
122+
$fields[$that->options['data_field']] = new \MongoDB\BSON\Binary('bar', \MongoDB\BSON\Binary::TYPE_OLD_BINARY);
123+
$fields[$that->options['id_field']] = new \MongoDB\BSON\UTCDateTime(time() * 1000);
124+
} else {
125+
$fields[$that->options['data_field']] = new \MongoBinData('bar', \MongoBinData::BYTE_ARRAY);
126+
$fields[$that->options['id_field']] = new \MongoDate();
127+
}
128+
129+
return $fields;
109130
}));
110131

111132
$this->assertEquals('bar', $this->storage->read('foo'));
@@ -123,22 +144,36 @@ public function testWrite()
123144
$that = $this;
124145
$data = array();
125146

147+
$methodName = phpversion('mongodb') ? 'updateOne' : 'update';
148+
126149
$collection->expects($this->once())
127-
->method('update')
150+
->method($methodName)
128151
->will($this->returnCallback(function ($criteria, $updateData, $options) use ($that, &$data) {
129152
$that->assertEquals(array($that->options['id_field'] => 'foo'), $criteria);
130-
$that->assertEquals(array('upsert' => true, 'multiple' => false), $options);
153+
154+
if (phpversion('mongodb')) {
155+
$that->assertEquals(array('upsert' => true), $options);
156+
} else {
157+
$that->assertEquals(array('upsert' => true, 'multiple' => false), $options);
158+
}
131159

132160
$data = $updateData['$set'];
133161
}));
134162

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

138-
$this->assertEquals('bar', $data[$this->options['data_field']]->bin);
139-
$that->assertInstanceOf('MongoDate', $data[$this->options['time_field']]);
140-
$this->assertInstanceOf('MongoDate', $data[$this->options['expiry_field']]);
141-
$this->assertGreaterThanOrEqual($expectedExpiry, $data[$this->options['expiry_field']]->sec);
166+
if (phpversion('mongodb')) {
167+
$that->assertEquals('bar', $data[$that->options['data_field']]->getData());
168+
$that->assertInstanceOf('MongoDB\BSON\UTCDateTime', $data[$that->options['time_field']]);
169+
$that->assertInstanceOf('MongoDB\BSON\UTCDateTime', $data[$that->options['expiry_field']]);
170+
$that->assertGreaterThanOrEqual($expectedExpiry, round(intval((string) $data[$that->options['expiry_field']]) / 1000));
171+
} else {
172+
$that->assertEquals('bar', $data[$that->options['data_field']]->bin);
173+
$that->assertInstanceOf('MongoDate', $data[$that->options['time_field']]);
174+
$that->assertInstanceOf('MongoDate', $data[$that->options['expiry_field']]);
175+
$that->assertGreaterThanOrEqual($expectedExpiry, $data[$that->options['expiry_field']]->sec);
176+
}
142177
}
143178

144179
public function testWriteWhenUsingExpiresField()
@@ -164,20 +199,33 @@ public function testWriteWhenUsingExpiresField()
164199
$that = $this;
165200
$data = array();
166201

202+
$methodName = phpversion('mongodb') ? 'updateOne' : 'update';
203+
167204
$collection->expects($this->once())
168-
->method('update')
205+
->method($methodName)
169206
->will($this->returnCallback(function ($criteria, $updateData, $options) use ($that, &$data) {
170207
$that->assertEquals(array($that->options['id_field'] => 'foo'), $criteria);
171-
$that->assertEquals(array('upsert' => true, 'multiple' => false), $options);
208+
209+
if (phpversion('mongodb')) {
210+
$that->assertEquals(array('upsert' => true), $options);
211+
} else {
212+
$that->assertEquals(array('upsert' => true, 'multiple' => false), $options);
213+
}
172214

173215
$data = $updateData['$set'];
174216
}));
175217

176218
$this->assertTrue($this->storage->write('foo', 'bar'));
177219

178-
$this->assertEquals('bar', $data[$this->options['data_field']]->bin);
179-
$that->assertInstanceOf('MongoDate', $data[$this->options['time_field']]);
180-
$that->assertInstanceOf('MongoDate', $data[$this->options['expiry_field']]);
220+
if (phpversion('mongodb')) {
221+
$that->assertEquals('bar', $data[$that->options['data_field']]->getData());
222+
$that->assertInstanceOf('MongoDB\BSON\UTCDateTime', $data[$that->options['time_field']]);
223+
$that->assertInstanceOf('MongoDB\BSON\UTCDateTime', $data[$that->options['expiry_field']]);
224+
} else {
225+
$that->assertEquals('bar', $data[$that->options['data_field']]->bin);
226+
$that->assertInstanceOf('MongoDate', $data[$that->options['time_field']]);
227+
$that->assertInstanceOf('MongoDate', $data[$that->options['expiry_field']]);
228+
}
181229
}
182230

183231
public function testReplaceSessionData()
@@ -191,16 +239,22 @@ public function testReplaceSessionData()
191239

192240
$data = array();
193241

242+
$methodName = phpversion('mongodb') ? 'updateOne' : 'update';
243+
194244
$collection->expects($this->exactly(2))
195-
->method('update')
245+
->method($methodName)
196246
->will($this->returnCallback(function ($criteria, $updateData, $options) use (&$data) {
197247
$data = $updateData;
198248
}));
199249

200250
$this->storage->write('foo', 'bar');
201251
$this->storage->write('foo', 'foobar');
202252

203-
$this->assertEquals('foobar', $data['$set'][$this->options['data_field']]->bin);
253+
if (phpversion('mongodb')) {
254+
$this->assertEquals('foobar', $data['$set'][$this->options['data_field']]->getData());
255+
} else {
256+
$this->assertEquals('foobar', $data['$set'][$this->options['data_field']]->bin);
257+
}
204258
}
205259

206260
public function testDestroy()
@@ -212,8 +266,10 @@ public function testDestroy()
212266
->with($this->options['database'], $this->options['collection'])
213267
->will($this->returnValue($collection));
214268

269+
$methodName = phpversion('mongodb') ? 'deleteOne' : 'remove';
270+
215271
$collection->expects($this->once())
216-
->method('remove')
272+
->method($methodName)
217273
->with(array($this->options['id_field'] => 'foo'));
218274

219275
$this->assertTrue($this->storage->destroy('foo'));
@@ -230,19 +286,45 @@ public function testGc()
230286

231287
$that = $this;
232288

289+
$methodName = phpversion('mongodb') ? 'deleteOne' : 'remove';
290+
233291
$collection->expects($this->once())
234-
->method('remove')
292+
->method($methodName)
235293
->will($this->returnCallback(function ($criteria) use ($that) {
236-
$that->assertInstanceOf('MongoDate', $criteria[$that->options['expiry_field']]['$lt']);
237-
$that->assertGreaterThanOrEqual(time() - 1, $criteria[$that->options['expiry_field']]['$lt']->sec);
294+
if (phpversion('mongodb')) {
295+
$that->assertInstanceOf('MongoDB\BSON\UTCDateTime', $criteria[$that->options['expiry_field']]['$lt']);
296+
$that->assertGreaterThanOrEqual(time() - 1, round(intval((string) $criteria[$that->options['expiry_field']]['$lt']) / 1000));
297+
} else {
298+
$that->assertInstanceOf('MongoDate', $criteria[$that->options['expiry_field']]['$lt']);
299+
$that->assertGreaterThanOrEqual(time() - 1, $criteria[$that->options['expiry_field']]['$lt']->sec);
300+
}
238301
}));
239302

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

306+
public function testGetConnection()
307+
{
308+
$method = new \ReflectionMethod($this->storage, 'getMongo');
309+
$method->setAccessible(true);
310+
311+
if (phpversion('mongodb')) {
312+
$mongoClass = 'MongoDB\Client';
313+
} else {
314+
$mongoClass = version_compare(phpversion('mongo'), '1.3.0', '<') ? 'Mongo' : 'MongoClient';
315+
}
316+
317+
$this->assertInstanceOf($mongoClass, $method->invoke($this->storage));
318+
}
319+
243320
private function createMongoCollectionMock()
244321
{
245-
$collection = $this->getMockBuilder('MongoCollection')
322+
$collectionClass = 'MongoCollection';
323+
if (phpversion('mongodb')) {
324+
$collectionClass = 'MongoDB\Collection';
325+
}
326+
327+
$collection = $this->getMockBuilder($collectionClass)
246328
->disableOriginalConstructor()
247329
->getMock();
248330

0 commit comments

Comments
 (0)