Skip to content

Commit 2c792bc

Browse files
Merge branch '6.4' into 7.0
* 6.4: (28 commits) [Serializer] Fix `@method` annotation fix compatibility with Doctrine DBAL 4 ensure string type with mbstring func overloading enabled [HttpKernel] Fix quotes expectations in tests [Validator] updated Greek translation [Cache][HttpFoundation][Lock] Fix empty username/password for PDO PostgreSQL [HttpClient][WebProfilerBundle] Do not generate cURL command when files are uploaded render newline in front of all script elements fix test fixture fix tests [Cache] Fix property types in PdoAdapter PHP files cannot be executable without shebang [TwigBridge] Mark CodeExtension as @internal Remove full DSNs from exception messages [Yaml] Fix uid binary parsing Disable the "Copy as cURL" button when the debug info are disabled [HttpClient] Replace `escapeshellarg` to prevent overpassing `ARG_MAX` Fix missing `profile` option for console commands [HttpFoundation][Lock] Makes MongoDB adapters usable with `ext-mongodb` only [HttpKernel] Preventing error 500 when function putenv is disabled ...
2 parents fc05855 + 37f8ee1 commit 2c792bc

File tree

7 files changed

+205
-96
lines changed

7 files changed

+205
-96
lines changed

CHANGELOG.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,11 @@ CHANGELOG
77
* Add parameter `$isSameDatabase` to `DoctrineDbalStore::configureSchema()`
88
* Remove the `gcProbablity` (notice the typo) option, use `gcProbability` instead
99

10+
6.4
11+
---
12+
13+
* Make `MongoDbStore` instantiable with the mongodb extension directly
14+
1015
6.3
1116
---
1217

Store/MongoDbStore.php

Lines changed: 77 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,12 @@
1414
use MongoDB\BSON\UTCDateTime;
1515
use MongoDB\Client;
1616
use MongoDB\Collection;
17+
use MongoDB\Database;
18+
use MongoDB\Driver\BulkWrite;
19+
use MongoDB\Driver\Command;
1720
use MongoDB\Driver\Exception\WriteException;
18-
use MongoDB\Driver\ReadPreference;
21+
use MongoDB\Driver\Manager;
22+
use MongoDB\Driver\Query;
1923
use MongoDB\Exception\DriverRuntimeException;
2024
use MongoDB\Exception\InvalidArgumentException as MongoInvalidArgumentException;
2125
use MongoDB\Exception\UnsupportedException;
@@ -44,21 +48,22 @@
4448
* @see https://docs.mongodb.com/manual/reference/limits/#Index-Key-Limit
4549
*
4650
* @author Joe Bennett <[email protected]>
51+
* @author Jérôme Tamarelle <[email protected]>
4752
*/
4853
class MongoDbStore implements PersistingStoreInterface
4954
{
5055
use ExpiringStoreTrait;
5156

52-
private Collection $collection;
53-
private Client $client;
57+
private Manager $manager;
58+
private string $namespace;
5459
private string $uri;
5560
private array $options;
5661
private float $initialTtl;
5762

5863
/**
59-
* @param Collection|Client|string $mongo An instance of a Collection or Client or URI @see https://docs.mongodb.com/manual/reference/connection-string/
60-
* @param array $options See below
61-
* @param float $initialTtl The expiration delay of locks in seconds
64+
* @param Collection|Client|Manager|string $mongo An instance of a Collection or Client or URI @see https://docs.mongodb.com/manual/reference/connection-string/
65+
* @param array $options See below
66+
* @param float $initialTtl The expiration delay of locks in seconds
6267
*
6368
* @throws InvalidArgumentException If required options are not provided
6469
* @throws InvalidTtlException When the initial ttl is not valid
@@ -88,7 +93,7 @@ class MongoDbStore implements PersistingStoreInterface
8893
* readPreference is primary for all queries.
8994
* @see https://docs.mongodb.com/manual/applications/replication/
9095
*/
91-
public function __construct(Collection|Client|string $mongo, array $options = [], float $initialTtl = 300.0)
96+
public function __construct(Collection|Database|Client|Manager|string $mongo, array $options = [], float $initialTtl = 300.0)
9297
{
9398
$this->options = array_merge([
9499
'gcProbability' => 0.001,
@@ -101,21 +106,27 @@ public function __construct(Collection|Client|string $mongo, array $options = []
101106
$this->initialTtl = $initialTtl;
102107

103108
if ($mongo instanceof Collection) {
104-
$this->collection = $mongo;
109+
$this->options['database'] ??= $mongo->getDatabaseName();
110+
$this->options['collection'] ??= $mongo->getCollectionName();
111+
$this->manager = $mongo->getManager();
112+
} elseif ($mongo instanceof Database) {
113+
$this->options['database'] ??= $mongo->getDatabaseName();
114+
$this->manager = $mongo->getManager();
105115
} elseif ($mongo instanceof Client) {
106-
$this->client = $mongo;
116+
$this->manager = $mongo->getManager();
117+
} elseif ($mongo instanceof Manager) {
118+
$this->manager = $mongo;
107119
} else {
108120
$this->uri = $this->skimUri($mongo);
109121
}
110122

111-
if (!($mongo instanceof Collection)) {
112-
if (null === $this->options['database']) {
113-
throw new InvalidArgumentException(sprintf('"%s()" requires the "database" in the URI path or option.', __METHOD__));
114-
}
115-
if (null === $this->options['collection']) {
116-
throw new InvalidArgumentException(sprintf('"%s()" requires the "collection" in the URI querystring or option.', __METHOD__));
117-
}
123+
if (null === $this->options['database']) {
124+
throw new InvalidArgumentException(sprintf('"%s()" requires the "database" in the URI path or option.', __METHOD__));
125+
}
126+
if (null === $this->options['collection']) {
127+
throw new InvalidArgumentException(sprintf('"%s()" requires the "collection" in the URI querystring or option.', __METHOD__));
118128
}
129+
$this->namespace = $this->options['database'].'.'.$this->options['collection'];
119130

120131
if ($this->options['gcProbability'] < 0.0 || $this->options['gcProbability'] > 1.0) {
121132
throw new InvalidArgumentException(sprintf('"%s()" gcProbability must be a float from 0.0 to 1.0, "%f" given.', __METHOD__, $this->options['gcProbability']));
@@ -135,6 +146,10 @@ public function __construct(Collection|Client|string $mongo, array $options = []
135146
*/
136147
private function skimUri(string $uri): string
137148
{
149+
if (!str_starts_with($uri, 'mongodb://') && !str_starts_with($uri, 'mongodb+srv://')) {
150+
throw new InvalidArgumentException(sprintf('The given MongoDB Connection URI "%s" is invalid. Expecting "mongodb://" or "mongodb+srv://".', $uri));
151+
}
152+
138153
if (false === $parsedUrl = parse_url($uri)) {
139154
throw new InvalidArgumentException(sprintf('The given MongoDB Connection URI "%s" is invalid.', $uri));
140155
}
@@ -186,14 +201,19 @@ private function skimUri(string $uri): string
186201
*/
187202
public function createTtlIndex(int $expireAfterSeconds = 0): void
188203
{
189-
$this->getCollection()->createIndex(
190-
[ // key
191-
'expires_at' => 1,
204+
$server = $this->getManager()->selectServer();
205+
$server->executeCommand($this->options['database'], new Command([
206+
'createIndexes' => $this->options['collection'],
207+
'indexes' => [
208+
[
209+
'key' => [
210+
'expires_at' => 1,
211+
],
212+
'name' => 'expires_at_1',
213+
'expireAfterSeconds' => $expireAfterSeconds,
214+
],
192215
],
193-
[ // options
194-
'expireAfterSeconds' => $expireAfterSeconds,
195-
]
196-
);
216+
]));
197217
}
198218

199219
/**
@@ -241,23 +261,35 @@ public function putOffExpiration(Key $key, float $ttl): void
241261

242262
public function delete(Key $key): void
243263
{
244-
$this->getCollection()->deleteOne([ // filter
245-
'_id' => (string) $key,
246-
'token' => $this->getUniqueToken($key),
247-
]);
264+
$write = new BulkWrite();
265+
$write->delete(
266+
[
267+
'_id' => (string) $key,
268+
'token' => $this->getUniqueToken($key),
269+
],
270+
['limit' => 1]
271+
);
272+
273+
$this->getManager()->executeBulkWrite($this->namespace, $write);
248274
}
249275

250276
public function exists(Key $key): bool
251277
{
252-
return null !== $this->getCollection()->findOne([ // filter
253-
'_id' => (string) $key,
254-
'token' => $this->getUniqueToken($key),
255-
'expires_at' => [
256-
'$gt' => $this->createMongoDateTime(microtime(true)),
278+
$cursor = $this->manager->executeQuery($this->namespace, new Query(
279+
[
280+
'_id' => (string) $key,
281+
'token' => $this->getUniqueToken($key),
282+
'expires_at' => [
283+
'$gt' => $this->createMongoDateTime(microtime(true)),
284+
],
257285
],
258-
], [
259-
'readPreference' => new ReadPreference(\defined(ReadPreference::PRIMARY) ? ReadPreference::PRIMARY : ReadPreference::RP_PRIMARY),
260-
]);
286+
[
287+
'limit' => 1,
288+
'projection' => ['_id' => 1],
289+
]
290+
));
291+
292+
return [] !== $cursor->toArray();
261293
}
262294

263295
/**
@@ -270,8 +302,9 @@ private function upsert(Key $key, float $ttl): void
270302
$now = microtime(true);
271303
$token = $this->getUniqueToken($key);
272304

273-
$this->getCollection()->updateOne(
274-
[ // filter
305+
$write = new BulkWrite();
306+
$write->update(
307+
[
275308
'_id' => (string) $key,
276309
'$or' => [
277310
[
@@ -284,17 +317,19 @@ private function upsert(Key $key, float $ttl): void
284317
],
285318
],
286319
],
287-
[ // update
320+
[
288321
'$set' => [
289322
'_id' => (string) $key,
290323
'token' => $token,
291324
'expires_at' => $this->createMongoDateTime($now + $ttl),
292325
],
293326
],
294-
[ // options
327+
[
295328
'upsert' => true,
296329
]
297330
);
331+
332+
$this->getManager()->executeBulkWrite($this->namespace, $write);
298333
}
299334

300335
private function isDuplicateKeyException(WriteException $e): bool
@@ -310,20 +345,9 @@ private function isDuplicateKeyException(WriteException $e): bool
310345
return 11000 === $code;
311346
}
312347

313-
private function getCollection(): Collection
348+
private function getManager(): Manager
314349
{
315-
if (isset($this->collection)) {
316-
return $this->collection;
317-
}
318-
319-
$this->client ??= new Client($this->uri, $this->options['uriOptions'], $this->options['driverOptions']);
320-
321-
$this->collection = $this->client->selectCollection(
322-
$this->options['database'],
323-
$this->options['collection']
324-
);
325-
326-
return $this->collection;
350+
return $this->manager ??= new Manager($this->uri, $this->options['uriOptions'], $this->options['driverOptions']);
327351
}
328352

329353
/**
@@ -335,7 +359,7 @@ private function createMongoDateTime(float $seconds): UTCDateTime
335359
}
336360

337361
/**
338-
* Retrieves an unique token for the given key namespaced to this store.
362+
* Retrieves a unique token for the given key namespaced to this store.
339363
*
340364
* @param Key $key lock state container
341365
*/

Store/PdoStore.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -38,8 +38,8 @@ class PdoStore implements PersistingStoreInterface
3838
private \PDO $conn;
3939
private string $dsn;
4040
private string $driver;
41-
private string $username = '';
42-
private string $password = '';
41+
private ?string $username = null;
42+
private ?string $password = null;
4343
private array $connectionOptions = [];
4444

4545
/**

Store/PostgreSqlStore.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,8 +28,8 @@ class PostgreSqlStore implements BlockingSharedLockStoreInterface, BlockingStore
2828
{
2929
private \PDO $conn;
3030
private string $dsn;
31-
private string $username = '';
32-
private string $password = '';
31+
private ?string $username = null;
32+
private ?string $password = null;
3333
private array $connectionOptions = [];
3434
private static array $storeRegistry = [];
3535

Tests/Store/MongoDbStoreFactoryTest.php

Lines changed: 15 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -12,29 +12,34 @@
1212
namespace Symfony\Component\Lock\Tests\Store;
1313

1414
use MongoDB\Collection;
15-
use MongoDB\Client;
16-
use PHPUnit\Framework\SkippedTestSuiteError;
15+
use MongoDB\Driver\Manager;
1716
use PHPUnit\Framework\TestCase;
1817
use Symfony\Component\Lock\Store\MongoDbStore;
1918
use Symfony\Component\Lock\Store\StoreFactory;
2019

20+
require_once __DIR__.'/stubs/mongodb.php';
21+
2122
/**
2223
* @author Alexandre Daubois <[email protected]>
2324
*
2425
* @requires extension mongodb
2526
*/
2627
class MongoDbStoreFactoryTest extends TestCase
2728
{
28-
public static function setupBeforeClass(): void
29-
{
30-
if (!class_exists(Client::class)) {
31-
throw new SkippedTestSuiteError('The mongodb/mongodb package is required.');
32-
}
33-
}
34-
3529
public function testCreateMongoDbCollectionStore()
3630
{
37-
$store = StoreFactory::createStore($this->createMock(Collection::class));
31+
$collection = $this->createMock(Collection::class);
32+
$collection->expects($this->once())
33+
->method('getManager')
34+
->willReturn(new Manager());
35+
$collection->expects($this->once())
36+
->method('getCollectionName')
37+
->willReturn('lock');
38+
$collection->expects($this->once())
39+
->method('getDatabaseName')
40+
->willReturn('test');
41+
42+
$store = StoreFactory::createStore($collection);
3843

3944
$this->assertInstanceOf(MongoDbStore::class, $store);
4045
}

0 commit comments

Comments
 (0)