1717
1818namespace MongoDB ;
1919
20- use Composer \InstalledVersions ;
2120use Iterator ;
22- use MongoDB \BSON \Document ;
23- use MongoDB \BSON \PackedArray ;
24- use MongoDB \Builder \BuilderEncoder ;
2521use MongoDB \Builder \Pipeline ;
26- use MongoDB \Codec \Encoder ;
22+ use MongoDB \Builder \Stage ;
23+ use MongoDB \Builder \Type \StageInterface ;
2724use MongoDB \Driver \BulkWriteCommand ;
2825use MongoDB \Driver \BulkWriteCommandResult ;
2926use MongoDB \Driver \ClientEncryption ;
3835use MongoDB \Exception \InvalidArgumentException ;
3936use MongoDB \Exception \UnexpectedValueException ;
4037use MongoDB \Exception \UnsupportedException ;
41- use MongoDB \Model \BSONArray ;
42- use MongoDB \Model \BSONDocument ;
38+ use MongoDB \Model \AutoEncryptionOptions ;
4339use MongoDB \Model \DatabaseInfo ;
40+ use MongoDB \Model \DriverOptions ;
4441use MongoDB \Operation \ClientBulkWriteCommand ;
4542use MongoDB \Operation \DropDatabase ;
4643use MongoDB \Operation \ListDatabaseNames ;
4744use MongoDB \Operation \ListDatabases ;
4845use MongoDB \Operation \Watch ;
4946use stdClass ;
5047use Stringable ;
51- use Throwable ;
5248
5349use function array_diff_key ;
54- use function is_array ;
55- use function is_string ;
5650
51+ /**
52+ * @psalm-type stage = StageInterface|array<string,mixed>|stdClass
53+ */
5754class Client implements Stringable
5855{
5956 public const DEFAULT_URI = 'mongodb://127.0.0.1/ ' ;
6057
61- private const DEFAULT_TYPE_MAP = [
62- 'array ' => BSONArray::class,
63- 'document ' => BSONDocument::class,
64- 'root ' => BSONDocument::class,
65- ];
66-
67- private const HANDSHAKE_SEPARATOR = '/ ' ;
68-
69- private static ?string $ version = null ;
70-
7158 private Manager $ manager ;
7259
7360 private ReadConcern $ readConcern ;
@@ -76,14 +63,9 @@ class Client implements Stringable
7663
7764 private string $ uri ;
7865
79- private array $ typeMap ;
80-
81- /** @psalm-var Encoder<array|stdClass|Document|PackedArray, mixed> */
82- private readonly Encoder $ builderEncoder ;
83-
8466 private WriteConcern $ writeConcern ;
8567
86- private bool $ autoEncryptionEnabled ;
68+ private DriverOptions $ driverOptions ;
8769
8870 /**
8971 * Constructs a new Client instance.
@@ -113,32 +95,11 @@ class Client implements Stringable
11395 */
11496 public function __construct (?string $ uri = null , array $ uriOptions = [], array $ driverOptions = [])
11597 {
116- $ driverOptions += ['typeMap ' => self ::DEFAULT_TYPE_MAP ];
117-
118- if (! is_array ($ driverOptions ['typeMap ' ])) {
119- throw InvalidArgumentException::invalidType ('"typeMap" driver option ' , $ driverOptions ['typeMap ' ], 'array ' );
120- }
121-
122- if (isset ($ driverOptions ['autoEncryption ' ]) && is_array ($ driverOptions ['autoEncryption ' ])) {
123- $ driverOptions ['autoEncryption ' ] = $ this ->prepareEncryptionOptions ($ driverOptions ['autoEncryption ' ]);
124- }
125-
126- if (isset ($ driverOptions ['builderEncoder ' ]) && ! $ driverOptions ['builderEncoder ' ] instanceof Encoder) {
127- throw InvalidArgumentException::invalidType ('"builderEncoder" option ' , $ driverOptions ['builderEncoder ' ], Encoder::class);
128- }
129-
130- $ driverOptions ['driver ' ] = $ this ->mergeDriverInfo ($ driverOptions ['driver ' ] ?? []);
98+ $ this ->driverOptions = DriverOptions::fromArray ($ driverOptions );
13199
132100 $ this ->uri = $ uri ?? self ::DEFAULT_URI ;
133- $ this ->builderEncoder = $ driverOptions ['builderEncoder ' ] ?? new BuilderEncoder ();
134- $ this ->typeMap = $ driverOptions ['typeMap ' ];
135-
136- /* Database and Collection objects may need to know whether auto
137- * encryption is enabled for dropping collections. Track this via an
138- * internal option until PHPC-2615 is implemented. */
139- $ this ->autoEncryptionEnabled = isset ($ driverOptions ['autoEncryption ' ]['keyVaultNamespace ' ]);
140101
141- $ driverOptions = array_diff_key ($ driverOptions , ['builderEncoder ' => 1 , 'typeMap ' => 1 ]);
102+ $ driverOptions = array_diff_key ($ this -> driverOptions -> toArray () , ['builderEncoder ' => 1 , 'typeMap ' => 1 ]);
142103
143104 $ this ->manager = new Manager ($ uri , $ uriOptions , $ driverOptions );
144105
@@ -157,8 +118,8 @@ public function __debugInfo(): array
157118 return [
158119 'manager ' => $ this ->manager ,
159120 'uri ' => $ this ->uri ,
160- 'typeMap ' => $ this ->typeMap ,
161- 'builderEncoder ' => $ this ->builderEncoder ,
121+ 'typeMap ' => $ this ->driverOptions -> typeMap ,
122+ 'builderEncoder ' => $ this ->driverOptions -> builderEncoder ,
162123 'writeConcern ' => $ this ->writeConcern ,
163124 ];
164125 }
@@ -226,13 +187,13 @@ public function bulkWrite(BulkWriteCommand|ClientBulkWrite $bulk, array $options
226187 /**
227188 * Returns a ClientEncryption instance for explicit encryption and decryption
228189 *
229- * @param array $options Encryption options
190+ * @param array{kmsProviders?: stdClass|array<string, array>, keyVaultClient?: Client|Manager} $ options
230191 */
231192 public function createClientEncryption (array $ options ): ClientEncryption
232193 {
233- $ options = $ this -> prepareEncryptionOptions ($ options );
194+ $ options = AutoEncryptionOptions:: fromArray ($ options );
234195
235- return $ this ->manager ->createClientEncryption ($ options );
196+ return $ this ->manager ->createClientEncryption ($ options-> toArray () );
236197 }
237198
238199 /**
@@ -269,7 +230,11 @@ public function dropDatabase(string $databaseName, array $options = []): void
269230 */
270231 public function getCollection (string $ databaseName , string $ collectionName , array $ options = []): Collection
271232 {
272- $ options += ['typeMap ' => $ this ->typeMap , 'builderEncoder ' => $ this ->builderEncoder , 'autoEncryptionEnabled ' => $ this ->autoEncryptionEnabled ];
233+ $ options += [
234+ 'typeMap ' => $ this ->driverOptions ->typeMap ,
235+ 'builderEncoder ' => $ this ->driverOptions ->builderEncoder ,
236+ 'autoEncryptionEnabled ' => $ this ->driverOptions ->isAutoEncryptionEnabled (),
237+ ];
273238
274239 return new Collection ($ this ->manager , $ databaseName , $ collectionName , $ options );
275240 }
@@ -284,7 +249,11 @@ public function getCollection(string $databaseName, string $collectionName, arra
284249 */
285250 public function getDatabase (string $ databaseName , array $ options = []): Database
286251 {
287- $ options += ['typeMap ' => $ this ->typeMap , 'builderEncoder ' => $ this ->builderEncoder , 'autoEncryptionEnabled ' => $ this ->autoEncryptionEnabled ];
252+ $ options += [
253+ 'typeMap ' => $ this ->driverOptions ->typeMap ,
254+ 'builderEncoder ' => $ this ->driverOptions ->builderEncoder ,
255+ 'autoEncryptionEnabled ' => $ this ->driverOptions ->isAutoEncryptionEnabled (),
256+ ];
288257
289258 return new Database ($ this ->manager , $ databaseName , $ options );
290259 }
@@ -320,7 +289,7 @@ public function getReadPreference(): ReadPreference
320289 */
321290 public function getTypeMap (): array
322291 {
323- return $ this ->typeMap ;
292+ return $ this ->driverOptions -> typeMap ;
324293 }
325294
326295 /**
@@ -419,7 +388,7 @@ public function startSession(array $options = []): Session
419388 * Create a change stream for watching changes to the cluster.
420389 *
421390 * @see Watch::__construct() for supported options
422- * @param array $pipeline Aggregation pipeline
391+ * @psalm- param list<stage> $pipeline Aggregation pipeline
423392 * @param array $options Command options
424393 * @throws InvalidArgumentException for parameter/option parsing errors
425394 */
@@ -429,7 +398,8 @@ public function watch(array $pipeline = [], array $options = []): ChangeStream
429398 $ pipeline = new Pipeline (...$ pipeline );
430399 }
431400
432- $ pipeline = $ this ->builderEncoder ->encodeIfSupported ($ pipeline );
401+ /** @var array<array-key, mixed> $pipeline */
402+ $ pipeline = $ this ->driverOptions ->builderEncoder ->encodeIfSupported ($ pipeline );
433403
434404 if (! isset ($ options ['readPreference ' ]) && ! is_in_transaction ($ options )) {
435405 $ options ['readPreference ' ] = $ this ->readPreference ;
@@ -442,76 +412,11 @@ public function watch(array $pipeline = [], array $options = []): ChangeStream
442412 }
443413
444414 if (! isset ($ options ['typeMap ' ])) {
445- $ options ['typeMap ' ] = $ this ->typeMap ;
415+ $ options ['typeMap ' ] = $ this ->driverOptions -> typeMap ;
446416 }
447417
448418 $ operation = new Watch ($ this ->manager , null , null , $ pipeline , $ options );
449419
450420 return $ operation ->execute ($ server );
451421 }
452-
453- private static function getVersion (): string
454- {
455- if (self ::$ version === null ) {
456- try {
457- self ::$ version = InstalledVersions::getPrettyVersion ('mongodb/mongodb ' ) ?? 'unknown ' ;
458- } catch (Throwable ) {
459- self ::$ version = 'error ' ;
460- }
461- }
462-
463- return self ::$ version ;
464- }
465-
466- private function mergeDriverInfo (array $ driver ): array
467- {
468- $ mergedDriver = [
469- 'name ' => 'PHPLIB ' ,
470- 'version ' => self ::getVersion (),
471- ];
472-
473- if (isset ($ driver ['name ' ])) {
474- if (! is_string ($ driver ['name ' ])) {
475- throw InvalidArgumentException::invalidType ('"name" handshake option ' , $ driver ['name ' ], 'string ' );
476- }
477-
478- $ mergedDriver ['name ' ] .= self ::HANDSHAKE_SEPARATOR . $ driver ['name ' ];
479- }
480-
481- if (isset ($ driver ['version ' ])) {
482- if (! is_string ($ driver ['version ' ])) {
483- throw InvalidArgumentException::invalidType ('"version" handshake option ' , $ driver ['version ' ], 'string ' );
484- }
485-
486- $ mergedDriver ['version ' ] .= self ::HANDSHAKE_SEPARATOR . $ driver ['version ' ];
487- }
488-
489- if (isset ($ driver ['platform ' ])) {
490- $ mergedDriver ['platform ' ] = $ driver ['platform ' ];
491- }
492-
493- return $ mergedDriver ;
494- }
495-
496- private function prepareEncryptionOptions (array $ options ): array
497- {
498- if (isset ($ options ['keyVaultClient ' ])) {
499- if ($ options ['keyVaultClient ' ] instanceof self) {
500- $ options ['keyVaultClient ' ] = $ options ['keyVaultClient ' ]->manager ;
501- } elseif (! $ options ['keyVaultClient ' ] instanceof Manager) {
502- throw InvalidArgumentException::invalidType ('"keyVaultClient" option ' , $ options ['keyVaultClient ' ], [self ::class, Manager::class]);
503- }
504- }
505-
506- // The server requires an empty document for automatic credentials.
507- if (isset ($ options ['kmsProviders ' ]) && is_array ($ options ['kmsProviders ' ])) {
508- foreach ($ options ['kmsProviders ' ] as $ name => $ provider ) {
509- if ($ provider === []) {
510- $ options ['kmsProviders ' ][$ name ] = new stdClass ();
511- }
512- }
513- }
514-
515- return $ options ;
516- }
517422}
0 commit comments