Skip to content

Commit f086259

Browse files
committed
bug #19186 Fix for #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 #19186). Discussion ---------- Fix for #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 | #19183 | License | MIT | Doc PR | Commits ------- ebbc706 Fix for #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

+55-13
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

+107-25
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)