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 c767ffe
Show file tree
Hide file tree
Showing 7 changed files with 151 additions and 64 deletions.
15 changes: 14 additions & 1 deletion psalm-baseline.xml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<files psalm-version="5.25.0@01a8eb06b9e9cc6cfb6a320bf9fb14331919d505">
<files psalm-version="5.26.1@d747f6500b38ac4f7dfc5edbcae6e4b637d7add0">
<file src="examples/atlas_search.php">
<MixedArgument>
<code><![CDATA[$document['name']]]></code>
Expand Down Expand Up @@ -195,6 +195,9 @@
<MixedAssignment>
<code><![CDATA[$mergedDriver['platform']]]></code>
</MixedAssignment>
<MixedPropertyTypeCoercion>
<code><![CDATA[$driverOptions['builderEncoder'] ?? new BuilderEncoder()]]></code>
</MixedPropertyTypeCoercion>
</file>
<file src="src/Codec/EncodeIfSupported.php">
<ArgumentTypeCoercion>
Expand All @@ -216,6 +219,11 @@
<code><![CDATA[$value]]></code>
</MixedArgumentTypeCoercion>
</file>
<file src="src/Collection.php">
<MixedPropertyTypeCoercion>
<code><![CDATA[$options['builderEncoder'] ?? new BuilderEncoder()]]></code>
</MixedPropertyTypeCoercion>
</file>
<file src="src/Command/ListCollections.php">
<MixedAssignment>
<code><![CDATA[$cmd[$option]]]></code>
Expand All @@ -228,6 +236,11 @@
<code><![CDATA[$options['session']]]></code>
</MixedAssignment>
</file>
<file src="src/Database.php">
<MixedPropertyTypeCoercion>
<code><![CDATA[$options['builderEncoder'] ?? new BuilderEncoder()]]></code>
</MixedPropertyTypeCoercion>
</file>
<file src="src/GridFS/Bucket.php">
<MixedArgument>
<code><![CDATA[$options['revision']]]></code>
Expand Down
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 notice

Code scanning / Psalm

MixedPropertyTypeCoercion Note

$this->builderEncoder expects 'MongoDB\Codec\Encoder<MongoDB\BSON\Document|MongoDB\BSON\PackedArray|array<array-key, mixed>|stdClass, mixed>', parent type MongoDB&#92;Builder&#92;BuilderEncoder|MongoDB&#92;Codec&#92;Encoder provided
$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 notice

Code scanning / Psalm

MixedPropertyTypeCoercion Note

$this->builderEncoder expects 'MongoDB\Codec\Encoder<MongoDB\BSON\Document|MongoDB\BSON\PackedArray|array<array-key, mixed>|stdClass, mixed>', parent type MongoDB&#92;Builder&#92;BuilderEncoder|MongoDB&#92;Codec&#92;Encoder provided
$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 notice

Code scanning / Psalm

MixedPropertyTypeCoercion Note

$this->builderEncoder expects 'MongoDB\Codec\Encoder<MongoDB\BSON\Document|MongoDB\BSON\PackedArray|array<array-key, mixed>|stdClass, mixed>', parent type MongoDB&#92;Builder&#92;BuilderEncoder|MongoDB&#92;Codec&#92;Encoder provided
$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
Loading

0 comments on commit c767ffe

Please sign in to comment.