Skip to content

Commit 55bbda4

Browse files
committed
PHPLIB-1505 Add driver option "builderEncoder"
1 parent d2a9458 commit 55bbda4

File tree

6 files changed

+119
-63
lines changed

6 files changed

+119
-63
lines changed

src/Client.php

+17-3
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@
1919

2020
use Composer\InstalledVersions;
2121
use Iterator;
22+
use MongoDB\Builder\BuilderEncoder;
23+
use MongoDB\Codec\Encoder;
2224
use MongoDB\Driver\ClientEncryption;
2325
use MongoDB\Driver\Exception\InvalidArgumentException as DriverInvalidArgumentException;
2426
use MongoDB\Driver\Exception\RuntimeException as DriverRuntimeException;
@@ -40,6 +42,7 @@
4042
use MongoDB\Operation\Watch;
4143
use Throwable;
4244

45+
use function array_diff_key;
4346
use function is_array;
4447
use function is_string;
4548

@@ -67,6 +70,8 @@ class Client
6770

6871
private array $typeMap;
6972

73+
private Encoder $builderEncoder;
74+
7075
private WriteConcern $writeConcern;
7176

7277
/**
@@ -80,6 +85,9 @@ class Client
8085
*
8186
* * typeMap (array): Default type map for cursors and BSON documents.
8287
*
88+
* * builderEncoder(MongoDB\Codec\Encoder): Encoder for converting aggregation
89+
* builder stages and operators into BSON.
90+
*
8391
* Other options are documented in MongoDB\Driver\Manager::__construct().
8492
*
8593
* @see https://mongodb.com/docs/manual/reference/connection-string/
@@ -96,6 +104,10 @@ public function __construct(?string $uri = null, array $uriOptions = [], array $
96104
{
97105
$driverOptions += ['typeMap' => self::DEFAULT_TYPE_MAP];
98106

107+
if (isset($driverOptions['builderEncoder']) && ! $driverOptions['builderEncoder'] instanceof Encoder) {
108+
throw InvalidArgumentException::invalidType('"builderEncoder" option', $driverOptions['builderEncoder'], Encoder::class);
109+
}
110+
99111
if (! is_array($driverOptions['typeMap'])) {
100112
throw InvalidArgumentException::invalidType('"typeMap" driver option', $driverOptions['typeMap'], 'array');
101113
}
@@ -111,9 +123,10 @@ public function __construct(?string $uri = null, array $uriOptions = [], array $
111123
$driverOptions['driver'] = $this->mergeDriverInfo($driverOptions['driver'] ?? []);
112124

113125
$this->uri = $uri ?? self::DEFAULT_URI;
126+
$this->builderEncoder = $driverOptions['builderEncoder'] ?? new BuilderEncoder();
114127
$this->typeMap = $driverOptions['typeMap'];
115128

116-
unset($driverOptions['typeMap']);
129+
$driverOptions = array_diff_key($driverOptions, ['builderEncoder' => 1, 'typeMap' => 1]);
117130

118131
$this->manager = new Manager($uri, $uriOptions, $driverOptions);
119132
$this->readConcern = $this->manager->getReadConcern();
@@ -133,6 +146,7 @@ public function __debugInfo()
133146
'manager' => $this->manager,
134147
'uri' => $this->uri,
135148
'typeMap' => $this->typeMap,
149+
'builderEncoder' => $this->builderEncoder,
136150
'writeConcern' => $this->writeConcern,
137151
];
138152
}
@@ -329,7 +343,7 @@ final public function removeSubscriber(Subscriber $subscriber): void
329343
*/
330344
public function selectCollection(string $databaseName, string $collectionName, array $options = [])
331345
{
332-
$options += ['typeMap' => $this->typeMap];
346+
$options += ['typeMap' => $this->typeMap, 'builderEncoder' => $this->builderEncoder];
333347

334348
return new Collection($this->manager, $databaseName, $collectionName, $options);
335349
}
@@ -345,7 +359,7 @@ public function selectCollection(string $databaseName, string $collectionName, a
345359
*/
346360
public function selectDatabase(string $databaseName, array $options = [])
347361
{
348-
$options += ['typeMap' => $this->typeMap];
362+
$options += ['typeMap' => $this->typeMap, 'builderEncoder' => $this->builderEncoder];
349363

350364
return new Database($this->manager, $databaseName, $options);
351365
}

src/Collection.php

+11
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,9 @@
2020
use Countable;
2121
use Iterator;
2222
use MongoDB\BSON\JavascriptInterface;
23+
use MongoDB\Builder\BuilderEncoder;
2324
use MongoDB\Codec\DocumentCodec;
25+
use MongoDB\Codec\Encoder;
2426
use MongoDB\Driver\CursorInterface;
2527
use MongoDB\Driver\Exception\RuntimeException as DriverRuntimeException;
2628
use MongoDB\Driver\Manager;
@@ -84,6 +86,8 @@ class Collection
8486

8587
private const WIRE_VERSION_FOR_READ_CONCERN_WITH_WRITE_STAGE = 8;
8688

89+
private Encoder $builderEncoder;
90+
8791
private ?DocumentCodec $codec = null;
8892

8993
private ReadConcern $readConcern;
@@ -134,6 +138,10 @@ public function __construct(private Manager $manager, private string $databaseNa
134138
throw new InvalidArgumentException('$collectionName is invalid: ' . $collectionName);
135139
}
136140

141+
if (isset($options['builderEncoder']) && ! $options['builderEncoder'] instanceof Encoder) {
142+
throw InvalidArgumentException::invalidType('"builderEncoder" option', $options['builderEncoder'], Encoder::class);
143+
}
144+
137145
if (isset($options['codec']) && ! $options['codec'] instanceof DocumentCodec) {
138146
throw InvalidArgumentException::invalidType('"codec" option', $options['codec'], DocumentCodec::class);
139147
}
@@ -154,6 +162,7 @@ public function __construct(private Manager $manager, private string $databaseNa
154162
throw InvalidArgumentException::invalidType('"writeConcern" option', $options['writeConcern'], WriteConcern::class);
155163
}
156164

165+
$this->builderEncoder = $options['builderEncoder'] ?? new BuilderEncoder();
157166
$this->codec = $options['codec'] ?? null;
158167
$this->readConcern = $options['readConcern'] ?? $this->manager->getReadConcern();
159168
$this->readPreference = $options['readPreference'] ?? $this->manager->getReadPreference();
@@ -170,6 +179,7 @@ public function __construct(private Manager $manager, private string $databaseNa
170179
public function __debugInfo()
171180
{
172181
return [
182+
'builderEncoder' => $this->builderEncoder,
173183
'codec' => $this->codec,
174184
'collectionName' => $this->collectionName,
175185
'databaseName' => $this->databaseName,
@@ -1084,6 +1094,7 @@ public function watch(array $pipeline = [], array $options = [])
10841094
public function withOptions(array $options = [])
10851095
{
10861096
$options += [
1097+
'builderEncoder' => $this->builderEncoder,
10871098
'codec' => $this->codec,
10881099
'readConcern' => $this->readConcern,
10891100
'readPreference' => $this->readPreference,

src/Database.php

+11
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@
1818
namespace MongoDB;
1919

2020
use Iterator;
21+
use MongoDB\Builder\BuilderEncoder;
22+
use MongoDB\Codec\Encoder;
2123
use MongoDB\Driver\ClientEncryption;
2224
use MongoDB\Driver\Cursor;
2325
use MongoDB\Driver\Exception\RuntimeException as DriverRuntimeException;
@@ -61,6 +63,8 @@ class Database
6163

6264
private const WIRE_VERSION_FOR_READ_CONCERN_WITH_WRITE_STAGE = 8;
6365

66+
private Encoder $builderEncoder;
67+
6468
private ReadConcern $readConcern;
6569

6670
private ReadPreference $readPreference;
@@ -102,6 +106,10 @@ public function __construct(private Manager $manager, private string $databaseNa
102106
throw new InvalidArgumentException('$databaseName is invalid: ' . $databaseName);
103107
}
104108

109+
if (isset($options['builderEncoder']) && ! $options['builderEncoder'] instanceof Encoder) {
110+
throw InvalidArgumentException::invalidType('"builderEncoder" option', $options['builderEncoder'], Encoder::class);
111+
}
112+
105113
if (isset($options['readConcern']) && ! $options['readConcern'] instanceof ReadConcern) {
106114
throw InvalidArgumentException::invalidType('"readConcern" option', $options['readConcern'], ReadConcern::class);
107115
}
@@ -118,6 +126,7 @@ public function __construct(private Manager $manager, private string $databaseNa
118126
throw InvalidArgumentException::invalidType('"writeConcern" option', $options['writeConcern'], WriteConcern::class);
119127
}
120128

129+
$this->builderEncoder = $options['builderEncoder'] ?? new BuilderEncoder();
121130
$this->readConcern = $options['readConcern'] ?? $this->manager->getReadConcern();
122131
$this->readPreference = $options['readPreference'] ?? $this->manager->getReadPreference();
123132
$this->typeMap = $options['typeMap'] ?? self::DEFAULT_TYPE_MAP;
@@ -133,6 +142,7 @@ public function __construct(private Manager $manager, private string $databaseNa
133142
public function __debugInfo()
134143
{
135144
return [
145+
'builderEncoder' => $this->builderEncoder,
136146
'databaseName' => $this->databaseName,
137147
'manager' => $this->manager,
138148
'readConcern' => $this->readConcern,
@@ -553,6 +563,7 @@ public function renameCollection(string $fromCollectionName, string $toCollectio
553563
public function selectCollection(string $collectionName, array $options = [])
554564
{
555565
$options += [
566+
'builderEncoder' => $this->builderEncoder,
556567
'readConcern' => $this->readConcern,
557568
'readPreference' => $this->readPreference,
558569
'typeMap' => $this->typeMap,

tests/ClientTest.php

+21-1
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
namespace MongoDB\Tests;
44

55
use MongoDB\Client;
6+
use MongoDB\Codec\Encoder;
67
use MongoDB\Driver\ClientEncryption;
78
use MongoDB\Driver\Exception\InvalidArgumentException as DriverInvalidArgumentException;
89
use MongoDB\Driver\ReadConcern;
@@ -45,6 +46,10 @@ public function provideInvalidConstructorDriverOptions()
4546
{
4647
$options = [];
4748

49+
foreach ($this->getInvalidObjectValues() as $value) {
50+
$options[][] = ['builderEncoder' => $value];
51+
}
52+
4853
foreach ($this->getInvalidArrayValues(true) as $value) {
4954
$options[][] = ['typeMap' => $value];
5055
}
@@ -85,13 +90,15 @@ public function testSelectCollectionInheritsOptions(): void
8590
];
8691

8792
$driverOptions = [
93+
'builderEncoder' => $builderEncoder = $this->createMock(Encoder::class),
8894
'typeMap' => ['root' => 'array'],
8995
];
9096

9197
$client = new Client(static::getUri(), $uriOptions, $driverOptions);
9298
$collection = $client->selectCollection($this->getDatabaseName(), $this->getCollectionName());
9399
$debug = $collection->__debugInfo();
94100

101+
$this->assertSame($builderEncoder, $debug['builderEncoder']);
95102
$this->assertInstanceOf(ReadConcern::class, $debug['readConcern']);
96103
$this->assertSame(ReadConcern::LOCAL, $debug['readConcern']->getLevel());
97104
$this->assertInstanceOf(ReadPreference::class, $debug['readPreference']);
@@ -105,6 +112,7 @@ public function testSelectCollectionInheritsOptions(): void
105112
public function testSelectCollectionPassesOptions(): void
106113
{
107114
$collectionOptions = [
115+
'builderEncoder' => $builderEncoder = $this->createMock(Encoder::class),
108116
'readConcern' => new ReadConcern(ReadConcern::LOCAL),
109117
'readPreference' => new ReadPreference(ReadPreference::SECONDARY_PREFERRED),
110118
'typeMap' => ['root' => 'array'],
@@ -115,6 +123,7 @@ public function testSelectCollectionPassesOptions(): void
115123
$collection = $client->selectCollection($this->getDatabaseName(), $this->getCollectionName(), $collectionOptions);
116124
$debug = $collection->__debugInfo();
117125

126+
$this->assertSame($builderEncoder, $debug['builderEncoder']);
118127
$this->assertInstanceOf(ReadConcern::class, $debug['readConcern']);
119128
$this->assertSame(ReadConcern::LOCAL, $debug['readConcern']->getLevel());
120129
$this->assertInstanceOf(ReadPreference::class, $debug['readPreference']);
@@ -129,11 +138,19 @@ public function testGetSelectsDatabaseAndInheritsOptions(): void
129138
{
130139
$uriOptions = ['w' => WriteConcern::MAJORITY];
131140

132-
$client = new Client(static::getUri(), $uriOptions);
141+
$driverOptions = [
142+
'builderEncoder' => $builderEncoder = $this->createMock(Encoder::class),
143+
'typeMap' => ['root' => 'array'],
144+
];
145+
146+
$client = new Client(static::getUri(), $uriOptions, $driverOptions);
133147
$database = $client->{$this->getDatabaseName()};
134148
$debug = $database->__debugInfo();
135149

150+
$this->assertSame($builderEncoder, $debug['builderEncoder']);
136151
$this->assertSame($this->getDatabaseName(), $debug['databaseName']);
152+
$this->assertIsArray($debug['typeMap']);
153+
$this->assertSame(['root' => 'array'], $debug['typeMap']);
137154
$this->assertInstanceOf(WriteConcern::class, $debug['writeConcern']);
138155
$this->assertSame(WriteConcern::MAJORITY, $debug['writeConcern']->getW());
139156
}
@@ -147,13 +164,15 @@ public function testSelectDatabaseInheritsOptions(): void
147164
];
148165

149166
$driverOptions = [
167+
'builderEncoder' => $builderEncoder = $this->createMock(Encoder::class),
150168
'typeMap' => ['root' => 'array'],
151169
];
152170

153171
$client = new Client(static::getUri(), $uriOptions, $driverOptions);
154172
$database = $client->selectDatabase($this->getDatabaseName());
155173
$debug = $database->__debugInfo();
156174

175+
$this->assertSame($builderEncoder, $debug['builderEncoder']);
157176
$this->assertInstanceOf(ReadConcern::class, $debug['readConcern']);
158177
$this->assertSame(ReadConcern::LOCAL, $debug['readConcern']->getLevel());
159178
$this->assertInstanceOf(ReadPreference::class, $debug['readPreference']);
@@ -167,6 +186,7 @@ public function testSelectDatabaseInheritsOptions(): void
167186
public function testSelectDatabasePassesOptions(): void
168187
{
169188
$databaseOptions = [
189+
'builderEncoder' => $builderEncoder = $this->createMock(Encoder::class),
170190
'readConcern' => new ReadConcern(ReadConcern::LOCAL),
171191
'readPreference' => new ReadPreference(ReadPreference::SECONDARY_PREFERRED),
172192
'typeMap' => ['root' => 'array'],

tests/Collection/CollectionFunctionalTest.php

+4
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
use Closure;
66
use MongoDB\BSON\Javascript;
7+
use MongoDB\Codec\Encoder;
78
use MongoDB\Collection;
89
use MongoDB\Database;
910
use MongoDB\Driver\BulkWrite;
@@ -66,6 +67,7 @@ public function testConstructorOptionTypeChecks(array $options): void
6667
public function provideInvalidConstructorOptions(): array
6768
{
6869
return $this->createOptionDataProvider([
70+
'builderEncoder' => $this->getInvalidObjectValues(),
6971
'codec' => $this->getInvalidDocumentCodecValues(),
7072
'readConcern' => $this->getInvalidReadConcernValues(),
7173
'readPreference' => $this->getInvalidReadPreferenceValues(),
@@ -396,6 +398,7 @@ public function testWithOptionsInheritsOptions(): void
396398
public function testWithOptionsPassesOptions(): void
397399
{
398400
$collectionOptions = [
401+
'builderEncoder' => $builderEncoder = $this->createMock(Encoder::class),
399402
'readConcern' => new ReadConcern(ReadConcern::LOCAL),
400403
'readPreference' => new ReadPreference(ReadPreference::SECONDARY_PREFERRED),
401404
'typeMap' => ['root' => 'array'],
@@ -405,6 +408,7 @@ public function testWithOptionsPassesOptions(): void
405408
$clone = $this->collection->withOptions($collectionOptions);
406409
$debug = $clone->__debugInfo();
407410

411+
$this->assertSame($builderEncoder, $debug['builderEncoder']);
408412
$this->assertInstanceOf(ReadConcern::class, $debug['readConcern']);
409413
$this->assertSame(ReadConcern::LOCAL, $debug['readConcern']->getLevel());
410414
$this->assertInstanceOf(ReadPreference::class, $debug['readPreference']);

0 commit comments

Comments
 (0)