Skip to content

Commit 64136c0

Browse files
committed
Extract driver options into value objects
So we can unit test options, as we're transforming some of the options that are passed stuff
1 parent 98c020d commit 64136c0

File tree

5 files changed

+429
-0
lines changed

5 files changed

+429
-0
lines changed

src/Client.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -141,6 +141,7 @@ public function __construct(?string $uri = null, array $uriOptions = [], array $
141141
$driverOptions = array_diff_key($driverOptions, ['builderEncoder' => 1, 'typeMap' => 1]);
142142

143143
$this->manager = new Manager($uri, $uriOptions, $driverOptions);
144+
144145
$this->readConcern = $this->manager->getReadConcern();
145146
$this->readPreference = $this->manager->getReadPreference();
146147
$this->writeConcern = $this->manager->getWriteConcern();
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
<?php
2+
3+
namespace MongoDB\ValueObject;
4+
5+
use MongoDB\Client;
6+
use MongoDB\Driver\Manager;
7+
use MongoDB\Exception\InvalidArgumentException;
8+
use stdClass;
9+
10+
/**
11+
* @internal
12+
*/
13+
final class AutoEncryptionOptions
14+
{
15+
private const KEY_KEY_VAULT_CLIENT = 'keyVaultClient';
16+
private const KEY_KMS_PROVIDERS = 'kmsProviders';
17+
18+
private function __construct(
19+
private readonly ?Manager $keyVaultClient,
20+
private readonly array $kmsProviders,
21+
private readonly array $miscOptions,
22+
) {
23+
}
24+
25+
public static function fromArray(array $options): self
26+
{
27+
self::ensureValidArrayOptions($options);
28+
29+
// The server requires an empty document for automatic credentials.
30+
if (isset($options[self::KEY_KMS_PROVIDERS]) && is_array($options[self::KEY_KMS_PROVIDERS])) {
31+
foreach ($options[self::KEY_KMS_PROVIDERS] as $name => $provider) {
32+
if ($provider === []) {
33+
$options[self::KEY_KMS_PROVIDERS][$name] = new stdClass();
34+
}
35+
}
36+
}
37+
38+
$keyVaultClient = $options[self::KEY_KEY_VAULT_CLIENT];
39+
40+
return new self(
41+
keyVaultClient: $keyVaultClient instanceof Client ? $keyVaultClient->getManager() : $keyVaultClient,
42+
kmsProviders: $options[self::KEY_KMS_PROVIDERS] ?? [],
43+
miscOptions: array_diff_key($options, [self::KEY_KEY_VAULT_CLIENT => 1, self::KEY_KMS_PROVIDERS => 1]),
44+
);
45+
}
46+
47+
public function toArray(): array
48+
{
49+
return array_filter([
50+
self::KEY_KEY_VAULT_CLIENT => $this->keyVaultClient,
51+
self::KEY_KMS_PROVIDERS => $this->kmsProviders,
52+
] + $this->miscOptions);
53+
}
54+
55+
/**
56+
* @throws InvalidArgumentException
57+
*/
58+
private static function ensureValidArrayOptions(array $options): void
59+
{
60+
$keyVaultClient = $options[self::KEY_KEY_VAULT_CLIENT] ?? null;
61+
62+
if ($keyVaultClient !== null && ! $keyVaultClient instanceof Client && ! $keyVaultClient instanceof Manager) {
63+
throw InvalidArgumentException::invalidType(
64+
sprintf('"%s" option', self::KEY_KEY_VAULT_CLIENT),
65+
$keyVaultClient,
66+
[Client::class, Manager::class]
67+
);
68+
}
69+
}
70+
}

src/ValueObject/DriverOptions.php

Lines changed: 140 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,140 @@
1+
<?php
2+
3+
namespace MongoDB\ValueObject;
4+
5+
use MongoDB\Builder\BuilderEncoder;
6+
use MongoDB\Client;
7+
use MongoDB\Codec\Encoder;
8+
use MongoDB\Exception\InvalidArgumentException;
9+
use MongoDB\Model\BSONArray;
10+
use MongoDB\Model\BSONDocument;
11+
12+
/**
13+
* @internal
14+
*/
15+
final class DriverOptions
16+
{
17+
private const KEY_TYPE_MAP = 'typeMap';
18+
private const KEY_BUILDER_ENCODER = 'builderEncoder';
19+
private const KEY_AUTO_ENCRYPTION = 'autoEncryption';
20+
private const KEY_DRIVER = 'driver';
21+
private const DEFAULT_TYPE_MAP = [
22+
'array' => BSONArray::class,
23+
'document' => BSONDocument::class,
24+
'root' => BSONDocument::class,
25+
];
26+
27+
private const HANDSHAKE_SEPARATOR = '/';
28+
29+
private function __construct(
30+
public readonly array $typeMap,
31+
public readonly Encoder $builderEncoder,
32+
public readonly array $autoEncryption,
33+
public readonly array $driver,
34+
private readonly array $miscOptions,
35+
) {
36+
}
37+
38+
public function toArray(): array
39+
{
40+
return array_filter([
41+
'typeMap' => $this->typeMap,
42+
'builderEncoder' => $this->builderEncoder,
43+
'autoEncryption' => $this->autoEncryption,
44+
'driver' => $this->driver,
45+
] + $this->miscOptions);
46+
}
47+
48+
public static function fromArray(array $options): self
49+
{
50+
$options += ['typeMap' => self::DEFAULT_TYPE_MAP];
51+
52+
self::ensureValidArrayOptions($options);
53+
54+
$autoEncryption = match (isset($options[self::KEY_AUTO_ENCRYPTION])) {
55+
true => AutoEncryptionOptions::fromArray($options[self::KEY_AUTO_ENCRYPTION])->toArray(),
56+
false => []
57+
};
58+
59+
return (new self(
60+
typeMap: $options[self::KEY_TYPE_MAP],
61+
builderEncoder: $options[self::KEY_BUILDER_ENCODER] ?? new BuilderEncoder(),
62+
autoEncryption: $autoEncryption,
63+
driver: [],
64+
miscOptions: array_diff_key($options, [
65+
self::KEY_TYPE_MAP => 1,
66+
self::KEY_BUILDER_ENCODER => 1,
67+
self::KEY_AUTO_ENCRYPTION => 1,
68+
self::KEY_DRIVER => 1,
69+
]),
70+
))->withDriverInfo($options[self::KEY_DRIVER] ?? []);
71+
}
72+
73+
public function isAutoEncryptionEnabled(): bool
74+
{
75+
return isset($this->autoEncryption['keyVaultNamespace']);
76+
}
77+
78+
/**
79+
* @throws InvalidArgumentException
80+
*/
81+
private static function ensureValidArrayOptions(array $options): void
82+
{
83+
if (! is_array($options[self::KEY_TYPE_MAP])) {
84+
throw InvalidArgumentException::invalidType(
85+
sprintf('"%s" driver option', self::KEY_TYPE_MAP),
86+
$options[self::KEY_TYPE_MAP],
87+
'array'
88+
);
89+
}
90+
91+
if (isset($options[self::KEY_BUILDER_ENCODER]) && !$options[self::KEY_BUILDER_ENCODER] instanceof Encoder) {
92+
throw InvalidArgumentException::invalidType(
93+
sprintf('"%s" option', self::KEY_BUILDER_ENCODER),
94+
$options[self::KEY_BUILDER_ENCODER],
95+
Encoder::class
96+
);
97+
}
98+
99+
$driver = $options[self::KEY_DRIVER] ?? [];
100+
if (isset($driver['name'])) {
101+
if (!is_string($driver['name'])) {
102+
throw InvalidArgumentException::invalidType('"name" handshake option', $driver['name'], 'string');
103+
}
104+
}
105+
106+
if (isset($driver['version'])) {
107+
if (!is_string($driver['version'])) {
108+
throw InvalidArgumentException::invalidType('"version" handshake option', $driver['version'], 'string');
109+
}
110+
}
111+
}
112+
113+
private function withDriverInfo(array $driver): self
114+
{
115+
$mergedDriver = [
116+
'name' => 'PHPLIB',
117+
'version' => Client::getVersion(),
118+
];
119+
120+
if (isset($driver['name'])) {
121+
$mergedDriver['name'] .= self::HANDSHAKE_SEPARATOR . $driver['name'];
122+
}
123+
124+
if (isset($driver['version'])) {
125+
$mergedDriver['version'] .= self::HANDSHAKE_SEPARATOR . $driver['version'];
126+
}
127+
128+
if (isset($driver['platform'])) {
129+
$mergedDriver['platform'] = $driver['platform'];
130+
}
131+
132+
return new self(
133+
typeMap: $this->typeMap,
134+
builderEncoder: $this->builderEncoder,
135+
autoEncryption: $this->autoEncryption,
136+
driver: $mergedDriver,
137+
miscOptions: $this->miscOptions,
138+
);
139+
}
140+
}
Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
<?php
2+
3+
namespace MongoDB\Tests\ValueObject;
4+
5+
use MongoDB\Client;
6+
use MongoDB\Exception\InvalidArgumentException;
7+
use MongoDB\ValueObject\AutoEncryptionOptions;
8+
use PHPUnit\Framework\Attributes\DataProvider;
9+
use PHPUnit\Framework\TestCase;
10+
use stdClass;
11+
12+
class AutoEncryptionOptionsTest extends TestCase
13+
{
14+
#[DataProvider('fromArrayProvider')]
15+
public function testFromArray(array $options, array $expected): void
16+
{
17+
$actual = AutoEncryptionOptions::fromArray($options);
18+
$this->assertEquals($expected, $actual->toArray());
19+
}
20+
21+
public function testFromArrayFailsForInvalidOptions(): void
22+
{
23+
$this->expectException(InvalidArgumentException::class);
24+
25+
AutoEncryptionOptions::fromArray([
26+
'keyVaultClient' => new StdClass(),
27+
]);
28+
}
29+
30+
public static function fromArrayProvider(): \Generator
31+
{
32+
$client = new Client();
33+
yield 'with manager passed for `keyVaultClient`' => [
34+
[
35+
'keyVaultClient' => $client->getManager(),
36+
'kmsProviders' => [
37+
'foo' => new StdClass(),
38+
'aws' => ['foo' => 'bar']
39+
]
40+
],
41+
[
42+
'keyVaultClient' => $client->getManager(),
43+
'kmsProviders' => [
44+
'foo' => new StdClass(),
45+
'aws' => ['foo' => 'bar']
46+
]
47+
]
48+
];
49+
yield 'with client passed for `keyVaultClient`' => [
50+
[
51+
'keyVaultClient' => $client,
52+
],
53+
[
54+
'keyVaultClient' => $client->getManager(),
55+
]
56+
];
57+
yield 'with extra options' => [
58+
[
59+
'kmsProviders' => [
60+
'foo' => [],
61+
'aws' => ['foo' => 'bar']
62+
],
63+
'tlsProviders' => [
64+
['foo' => 'bar']
65+
]
66+
],
67+
[
68+
'kmsProviders' => [
69+
'foo' => new StdClass(),
70+
'aws' => ['foo' => 'bar']
71+
],
72+
'tlsProviders' => [
73+
['foo' => 'bar']
74+
]
75+
],
76+
];
77+
}
78+
}

0 commit comments

Comments
 (0)