Skip to content

Commit

Permalink
PHPLIB-1505 Add driver option "builderEncoder"
Browse files Browse the repository at this point in the history
  • Loading branch information
GromNaN committed Sep 10, 2024
1 parent d2a9458 commit c8571a7
Show file tree
Hide file tree
Showing 6 changed files with 137 additions and 63 deletions.
24 changes: 21 additions & 3 deletions src/Client.php
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,10 @@

use Composer\InstalledVersions;
use Iterator;
use MongoDB\BSON\Document;
use MongoDB\BSON\PackedArray;
use MongoDB\Builder\BuilderEncoder;
use MongoDB\Codec\Encoder;
use MongoDB\Driver\ClientEncryption;
use MongoDB\Driver\Exception\InvalidArgumentException as DriverInvalidArgumentException;
use MongoDB\Driver\Exception\RuntimeException as DriverRuntimeException;
Expand All @@ -38,8 +42,10 @@
use MongoDB\Operation\ListDatabaseNames;
use MongoDB\Operation\ListDatabases;
use MongoDB\Operation\Watch;
use stdClass;
use Throwable;

use function array_diff_key;
use function is_array;
use function is_string;

Expand Down Expand Up @@ -67,6 +73,9 @@ class Client

private array $typeMap;

/** @psalm-var Encoder<array|stdClass|Document|PackedArray, mixed> */
private readonly Encoder $builderEncoder;

private WriteConcern $writeConcern;

/**
Expand All @@ -78,6 +87,9 @@ class Client
*
* Supported driver-specific options:
*
* * builderEncoder (MongoDB\Builder\Encoder): Encoder for query and
* aggregation builders. If not given, the default encoder will be used.
*
* * typeMap (array): Default type map for cursors and BSON documents.
*
* Other options are documented in MongoDB\Driver\Manager::__construct().
Expand Down Expand Up @@ -108,12 +120,17 @@ public function __construct(?string $uri = null, array $uriOptions = [], array $
}
}

if (isset($driverOptions['builderEncoder']) && ! $driverOptions['builderEncoder'] instanceof Encoder) {
throw InvalidArgumentException::invalidType('"builderEncoder" option', $driverOptions['builderEncoder'], Encoder::class);
}

$driverOptions['driver'] = $this->mergeDriverInfo($driverOptions['driver'] ?? []);

$this->uri = $uri ?? self::DEFAULT_URI;
$this->builderEncoder = $driverOptions['builderEncoder'] ?? new BuilderEncoder();

Check failure on line 130 in src/Client.php

View workflow job for this annotation

GitHub Actions / Psalm

MixedPropertyTypeCoercion

src/Client.php:130:33: MixedPropertyTypeCoercion: $this->builderEncoder expects 'MongoDB\Codec\Encoder<MongoDB\BSON\Document|MongoDB\BSON\PackedArray|array<array-key, mixed>|stdClass, mixed>', parent type `MongoDB\Builder\BuilderEncoder|MongoDB\Codec\Encoder` provided (see https://psalm.dev/196)
$this->typeMap = $driverOptions['typeMap'];

unset($driverOptions['typeMap']);
$driverOptions = array_diff_key($driverOptions, ['builderEncoder' => 1, 'typeMap' => 1]);

$this->manager = new Manager($uri, $uriOptions, $driverOptions);
$this->readConcern = $this->manager->getReadConcern();
Expand All @@ -133,6 +150,7 @@ public function __debugInfo()
'manager' => $this->manager,
'uri' => $this->uri,
'typeMap' => $this->typeMap,
'builderEncoder' => $this->builderEncoder,
'writeConcern' => $this->writeConcern,
];
}
Expand Down Expand Up @@ -329,7 +347,7 @@ final public function removeSubscriber(Subscriber $subscriber): void
*/
public function selectCollection(string $databaseName, string $collectionName, array $options = [])
{
$options += ['typeMap' => $this->typeMap];
$options += ['typeMap' => $this->typeMap, 'builderEncoder' => $this->builderEncoder];

return new Collection($this->manager, $databaseName, $collectionName, $options);
}
Expand All @@ -345,7 +363,7 @@ public function selectCollection(string $databaseName, string $collectionName, a
*/
public function selectDatabase(string $databaseName, array $options = [])
{
$options += ['typeMap' => $this->typeMap];
$options += ['typeMap' => $this->typeMap, 'builderEncoder' => $this->builderEncoder];

return new Database($this->manager, $databaseName, $options);
}
Expand Down
18 changes: 18 additions & 0 deletions src/Collection.php
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,12 @@

use Countable;
use Iterator;
use MongoDB\BSON\Document;
use MongoDB\BSON\JavascriptInterface;
use MongoDB\BSON\PackedArray;
use MongoDB\Builder\BuilderEncoder;
use MongoDB\Codec\DocumentCodec;
use MongoDB\Codec\Encoder;
use MongoDB\Driver\CursorInterface;
use MongoDB\Driver\Exception\RuntimeException as DriverRuntimeException;
use MongoDB\Driver\Manager;
Expand Down Expand Up @@ -66,6 +70,7 @@
use MongoDB\Operation\UpdateOne;
use MongoDB\Operation\UpdateSearchIndex;
use MongoDB\Operation\Watch;
use stdClass;

use function array_diff_key;
use function array_intersect_key;
Expand All @@ -84,6 +89,9 @@ class Collection

private const WIRE_VERSION_FOR_READ_CONCERN_WITH_WRITE_STAGE = 8;

/** @psalm-var Encoder<array|stdClass|Document|PackedArray, mixed> */
private readonly Encoder $builderEncoder;

private ?DocumentCodec $codec = null;

private ReadConcern $readConcern;
Expand All @@ -102,6 +110,9 @@ class Collection
*
* Supported options:
*
* * builderEncoder (MongoDB\Builder\Encoder): Encoder for query and
* aggregation builders. If not given, the default encoder will be used.
*
* * codec (MongoDB\Codec\DocumentCodec): Codec used to decode documents
* from BSON to PHP objects.
*
Expand Down Expand Up @@ -134,6 +145,10 @@ public function __construct(private Manager $manager, private string $databaseNa
throw new InvalidArgumentException('$collectionName is invalid: ' . $collectionName);
}

if (isset($options['builderEncoder']) && ! $options['builderEncoder'] instanceof Encoder) {
throw InvalidArgumentException::invalidType('"builderEncoder" option', $options['builderEncoder'], Encoder::class);
}

if (isset($options['codec']) && ! $options['codec'] instanceof DocumentCodec) {
throw InvalidArgumentException::invalidType('"codec" option', $options['codec'], DocumentCodec::class);
}
Expand All @@ -154,6 +169,7 @@ public function __construct(private Manager $manager, private string $databaseNa
throw InvalidArgumentException::invalidType('"writeConcern" option', $options['writeConcern'], WriteConcern::class);
}

$this->builderEncoder = $options['builderEncoder'] ?? new BuilderEncoder();

Check failure on line 172 in src/Collection.php

View workflow job for this annotation

GitHub Actions / Psalm

MixedPropertyTypeCoercion

src/Collection.php:172:33: MixedPropertyTypeCoercion: $this->builderEncoder expects 'MongoDB\Codec\Encoder<MongoDB\BSON\Document|MongoDB\BSON\PackedArray|array<array-key, mixed>|stdClass, mixed>', parent type `MongoDB\Builder\BuilderEncoder|MongoDB\Codec\Encoder` provided (see https://psalm.dev/196)
$this->codec = $options['codec'] ?? null;
$this->readConcern = $options['readConcern'] ?? $this->manager->getReadConcern();
$this->readPreference = $options['readPreference'] ?? $this->manager->getReadPreference();
Expand All @@ -170,6 +186,7 @@ public function __construct(private Manager $manager, private string $databaseNa
public function __debugInfo()
{
return [
'builderEncoder' => $this->builderEncoder,
'codec' => $this->codec,
'collectionName' => $this->collectionName,
'databaseName' => $this->databaseName,
Expand Down Expand Up @@ -1084,6 +1101,7 @@ public function watch(array $pipeline = [], array $options = [])
public function withOptions(array $options = [])
{
$options += [
'builderEncoder' => $this->builderEncoder,
'codec' => $this->codec,
'readConcern' => $this->readConcern,
'readPreference' => $this->readPreference,
Expand Down
18 changes: 18 additions & 0 deletions src/Database.php
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,10 @@
namespace MongoDB;

use Iterator;
use MongoDB\BSON\Document;
use MongoDB\BSON\PackedArray;
use MongoDB\Builder\BuilderEncoder;
use MongoDB\Codec\Encoder;
use MongoDB\Driver\ClientEncryption;
use MongoDB\Driver\Cursor;
use MongoDB\Driver\Exception\RuntimeException as DriverRuntimeException;
Expand Down Expand Up @@ -45,6 +49,7 @@
use MongoDB\Operation\ModifyCollection;
use MongoDB\Operation\RenameCollection;
use MongoDB\Operation\Watch;
use stdClass;
use Throwable;
use Traversable;

Expand All @@ -61,6 +66,9 @@ class Database

private const WIRE_VERSION_FOR_READ_CONCERN_WITH_WRITE_STAGE = 8;

/** @psalm-var Encoder<array|stdClass|Document|PackedArray, mixed> */
private readonly Encoder $builderEncoder;

private ReadConcern $readConcern;

private ReadPreference $readPreference;
Expand All @@ -77,6 +85,9 @@ class Database
*
* Supported options:
*
* * builderEncoder (MongoDB\Builder\Encoder): Encoder for query and
* aggregation builders. If not given, the default encoder will be used.
*
* * readConcern (MongoDB\Driver\ReadConcern): The default read concern to
* use for database operations and selected collections. Defaults to the
* Manager's read concern.
Expand All @@ -102,6 +113,10 @@ public function __construct(private Manager $manager, private string $databaseNa
throw new InvalidArgumentException('$databaseName is invalid: ' . $databaseName);
}

if (isset($options['builderEncoder']) && ! $options['builderEncoder'] instanceof Encoder) {
throw InvalidArgumentException::invalidType('"builderEncoder" option', $options['builderEncoder'], Encoder::class);
}

if (isset($options['readConcern']) && ! $options['readConcern'] instanceof ReadConcern) {
throw InvalidArgumentException::invalidType('"readConcern" option', $options['readConcern'], ReadConcern::class);
}
Expand All @@ -118,6 +133,7 @@ public function __construct(private Manager $manager, private string $databaseNa
throw InvalidArgumentException::invalidType('"writeConcern" option', $options['writeConcern'], WriteConcern::class);
}

$this->builderEncoder = $options['builderEncoder'] ?? new BuilderEncoder();

Check failure on line 136 in src/Database.php

View workflow job for this annotation

GitHub Actions / Psalm

MixedPropertyTypeCoercion

src/Database.php:136:33: MixedPropertyTypeCoercion: $this->builderEncoder expects 'MongoDB\Codec\Encoder<MongoDB\BSON\Document|MongoDB\BSON\PackedArray|array<array-key, mixed>|stdClass, mixed>', parent type `MongoDB\Builder\BuilderEncoder|MongoDB\Codec\Encoder` provided (see https://psalm.dev/196)
$this->readConcern = $options['readConcern'] ?? $this->manager->getReadConcern();
$this->readPreference = $options['readPreference'] ?? $this->manager->getReadPreference();
$this->typeMap = $options['typeMap'] ?? self::DEFAULT_TYPE_MAP;
Expand All @@ -133,6 +149,7 @@ public function __construct(private Manager $manager, private string $databaseNa
public function __debugInfo()
{
return [
'builderEncoder' => $this->builderEncoder,
'databaseName' => $this->databaseName,
'manager' => $this->manager,
'readConcern' => $this->readConcern,
Expand Down Expand Up @@ -553,6 +570,7 @@ public function renameCollection(string $fromCollectionName, string $toCollectio
public function selectCollection(string $collectionName, array $options = [])
{
$options += [
'builderEncoder' => $this->builderEncoder,
'readConcern' => $this->readConcern,
'readPreference' => $this->readPreference,
'typeMap' => $this->typeMap,
Expand Down
22 changes: 21 additions & 1 deletion tests/ClientTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
namespace MongoDB\Tests;

use MongoDB\Client;
use MongoDB\Codec\Encoder;
use MongoDB\Driver\ClientEncryption;
use MongoDB\Driver\Exception\InvalidArgumentException as DriverInvalidArgumentException;
use MongoDB\Driver\ReadConcern;
Expand Down Expand Up @@ -45,6 +46,10 @@ public function provideInvalidConstructorDriverOptions()
{
$options = [];

foreach ($this->getInvalidObjectValues() as $value) {
$options[][] = ['builderEncoder' => $value];
}

foreach ($this->getInvalidArrayValues(true) as $value) {
$options[][] = ['typeMap' => $value];
}
Expand Down Expand Up @@ -85,13 +90,15 @@ public function testSelectCollectionInheritsOptions(): void
];

$driverOptions = [
'builderEncoder' => $builderEncoder = $this->createMock(Encoder::class),
'typeMap' => ['root' => 'array'],
];

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

$this->assertSame($builderEncoder, $debug['builderEncoder']);
$this->assertInstanceOf(ReadConcern::class, $debug['readConcern']);
$this->assertSame(ReadConcern::LOCAL, $debug['readConcern']->getLevel());
$this->assertInstanceOf(ReadPreference::class, $debug['readPreference']);
Expand All @@ -105,6 +112,7 @@ public function testSelectCollectionInheritsOptions(): void
public function testSelectCollectionPassesOptions(): void
{
$collectionOptions = [
'builderEncoder' => $builderEncoder = $this->createMock(Encoder::class),
'readConcern' => new ReadConcern(ReadConcern::LOCAL),
'readPreference' => new ReadPreference(ReadPreference::SECONDARY_PREFERRED),
'typeMap' => ['root' => 'array'],
Expand All @@ -115,6 +123,7 @@ public function testSelectCollectionPassesOptions(): void
$collection = $client->selectCollection($this->getDatabaseName(), $this->getCollectionName(), $collectionOptions);
$debug = $collection->__debugInfo();

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

$client = new Client(static::getUri(), $uriOptions);
$driverOptions = [
'builderEncoder' => $builderEncoder = $this->createMock(Encoder::class),
'typeMap' => ['root' => 'array'],
];

$client = new Client(static::getUri(), $uriOptions, $driverOptions);
$database = $client->{$this->getDatabaseName()};
$debug = $database->__debugInfo();

$this->assertSame($builderEncoder, $debug['builderEncoder']);
$this->assertSame($this->getDatabaseName(), $debug['databaseName']);
$this->assertIsArray($debug['typeMap']);
$this->assertSame(['root' => 'array'], $debug['typeMap']);
$this->assertInstanceOf(WriteConcern::class, $debug['writeConcern']);
$this->assertSame(WriteConcern::MAJORITY, $debug['writeConcern']->getW());
}
Expand All @@ -147,13 +164,15 @@ public function testSelectDatabaseInheritsOptions(): void
];

$driverOptions = [
'builderEncoder' => $builderEncoder = $this->createMock(Encoder::class),
'typeMap' => ['root' => 'array'],
];

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

$this->assertSame($builderEncoder, $debug['builderEncoder']);
$this->assertInstanceOf(ReadConcern::class, $debug['readConcern']);
$this->assertSame(ReadConcern::LOCAL, $debug['readConcern']->getLevel());
$this->assertInstanceOf(ReadPreference::class, $debug['readPreference']);
Expand All @@ -167,6 +186,7 @@ public function testSelectDatabaseInheritsOptions(): void
public function testSelectDatabasePassesOptions(): void
{
$databaseOptions = [
'builderEncoder' => $builderEncoder = $this->createMock(Encoder::class),
'readConcern' => new ReadConcern(ReadConcern::LOCAL),
'readPreference' => new ReadPreference(ReadPreference::SECONDARY_PREFERRED),
'typeMap' => ['root' => 'array'],
Expand Down
4 changes: 4 additions & 0 deletions tests/Collection/CollectionFunctionalTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

use Closure;
use MongoDB\BSON\Javascript;
use MongoDB\Codec\Encoder;
use MongoDB\Collection;
use MongoDB\Database;
use MongoDB\Driver\BulkWrite;
Expand Down Expand Up @@ -66,6 +67,7 @@ public function testConstructorOptionTypeChecks(array $options): void
public function provideInvalidConstructorOptions(): array
{
return $this->createOptionDataProvider([
'builderEncoder' => $this->getInvalidObjectValues(),
'codec' => $this->getInvalidDocumentCodecValues(),
'readConcern' => $this->getInvalidReadConcernValues(),
'readPreference' => $this->getInvalidReadPreferenceValues(),
Expand Down Expand Up @@ -396,6 +398,7 @@ public function testWithOptionsInheritsOptions(): void
public function testWithOptionsPassesOptions(): void
{
$collectionOptions = [
'builderEncoder' => $builderEncoder = $this->createMock(Encoder::class),
'readConcern' => new ReadConcern(ReadConcern::LOCAL),
'readPreference' => new ReadPreference(ReadPreference::SECONDARY_PREFERRED),
'typeMap' => ['root' => 'array'],
Expand All @@ -405,6 +408,7 @@ public function testWithOptionsPassesOptions(): void
$clone = $this->collection->withOptions($collectionOptions);
$debug = $clone->__debugInfo();

$this->assertSame($builderEncoder, $debug['builderEncoder']);
$this->assertInstanceOf(ReadConcern::class, $debug['readConcern']);
$this->assertSame(ReadConcern::LOCAL, $debug['readConcern']->getLevel());
$this->assertInstanceOf(ReadPreference::class, $debug['readPreference']);
Expand Down
Loading

0 comments on commit c8571a7

Please sign in to comment.