Skip to content

Commit 220f52a

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 220f52a

File tree

5 files changed

+472
-0
lines changed

5 files changed

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

src/ValueObject/DriverOptions.php

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

0 commit comments

Comments
 (0)