Skip to content

Commit 74668e0

Browse files
authored
Allow specifying options in AutowireDatabase and AutowireCollection (#14)
* Resolve service references for codec collection options * Add codec option to AutowireCollection * Document codec usage * Bump MongoDB dependency to 1.17 * Allow passing all options to AutowireCollection and AutowireDatabase * Use parameter for type maps
1 parent 2d3784a commit 74668e0

File tree

7 files changed

+219
-18
lines changed

7 files changed

+219
-18
lines changed

README.md

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -128,7 +128,7 @@ class MyService
128128
}
129129
```
130130

131-
## Database and Collection Usage
131+
## Database Usage
132132

133133
The client service provides access to databases and collections. You can access a database by calling the
134134
`selectDatabase` method, passing the database name and potential options:
@@ -183,6 +183,8 @@ class MyService
183183
}
184184
```
185185

186+
## Collection Usage
187+
186188
To inject a collection, you can either call the `selectCollection` method on a `Client` or `Database` instance.
187189
For convenience, the `#[AutowireCollection]` attribute provides a quicker alternative:
188190

@@ -261,3 +263,30 @@ class MyService
261263
) {}
262264
}
263265
```
266+
267+
## Specifying options
268+
269+
When using the `AutowireDatabase` or `AutowireCollection` attributes, you can specify additional options for the
270+
resulting instances. You can pass the following options:
271+
|| Option || Accepted type ||
272+
| `codec` | `DocumentCodec` instance |
273+
| `typeMap`| `array` containing type map information |
274+
| `readPreference` | `MongoDB\Driver\ReadPreference` instance |
275+
| `writeConcern` | `MongoDB\Driver\writeConcern` instance |
276+
| `readConcern` | `MongoDB\Driver\ReadConcern` instance |
277+
278+
In addition to passing an instance, you can also pass a service reference by specifying a string for the given option:
279+
280+
```php
281+
use MongoDB\Bundle\Attribute\AutowireCollection;
282+
use MongoDB\Collection;
283+
use MongoDB\Driver\ReadPreference;
284+
285+
class MyService
286+
{
287+
public function __construct(
288+
#[AutowireCollection(codec: Codec::class, readPreference: new ReadPreference('secondary'))]
289+
private Collection $myCollection,
290+
) {}
291+
}
292+
```

composer.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
],
1414
"require": {
1515
"php": ">=8.1",
16-
"mongodb/mongodb": "^1.16",
16+
"mongodb/mongodb": "^1.17",
1717
"symfony/config": "^6.3 || ^7.0",
1818
"symfony/console": "^6.3 || ^7.0",
1919
"symfony/dependency-injection": "^6.3.5 || ^7.0",

src/Attribute/AutowireCollection.php

Lines changed: 26 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,13 +23,18 @@
2323
use Attribute;
2424
use MongoDB\Bundle\DependencyInjection\MongoDBExtension;
2525
use MongoDB\Client;
26+
use MongoDB\Codec\DocumentCodec;
2627
use MongoDB\Collection;
28+
use MongoDB\Driver\ReadConcern;
29+
use MongoDB\Driver\ReadPreference;
30+
use MongoDB\Driver\WriteConcern;
2731
use ReflectionParameter;
2832
use Symfony\Component\DependencyInjection\Attribute\AutowireCallable;
2933
use Symfony\Component\DependencyInjection\Definition;
3034
use Symfony\Component\DependencyInjection\Reference;
3135

3236
use function is_string;
37+
use function ltrim;
3338
use function sprintf;
3439

3540
/**
@@ -44,7 +49,11 @@ public function __construct(
4449
private readonly ?string $collection = null,
4550
private readonly ?string $database = null,
4651
?string $client = null,
47-
private readonly array $options = [],
52+
private readonly string|DocumentCodec|null $codec = null,
53+
private readonly string|array|null $typeMap = null,
54+
private readonly string|ReadPreference|null $readPreference = null,
55+
private readonly string|WriteConcern|null $writeConcern = null,
56+
private readonly string|ReadConcern|null $readConcern = null,
4857
bool|string $lazy = false,
4958
) {
5059
$this->serviceId = $client === null
@@ -59,12 +68,27 @@ public function __construct(
5968

6069
public function buildDefinition(mixed $value, ?string $type, ReflectionParameter $parameter): Definition
6170
{
71+
$options = [];
72+
foreach (['codec', 'typeMap', 'readPreference', 'writeConcern', 'readConcern'] as $option) {
73+
$optionValue = $this->$option;
74+
if ($optionValue === null) {
75+
continue;
76+
}
77+
78+
// If a string was given, it may be a service ID or parameter. Handle it accordingly
79+
if (is_string($optionValue)) {
80+
$optionValue = $option === 'typeMap' ? sprintf('%%%s%%', $optionValue) : new Reference($optionValue);
81+
}
82+
83+
$options[$option] = $optionValue;
84+
}
85+
6286
return (new Definition(is_string($this->lazy) ? $this->lazy : ($type ?: Collection::class)))
6387
->setFactory($value)
6488
->setArguments([
6589
$this->database ?? sprintf('%%%s.default_database%%', $this->serviceId),
6690
$this->collection ?? $parameter->getName(),
67-
$this->options,
91+
$options,
6892
])
6993
->setLazy($this->lazy);
7094
}

src/Attribute/AutowireDatabase.php

Lines changed: 25 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,11 @@
2323
use Attribute;
2424
use MongoDB\Bundle\DependencyInjection\MongoDBExtension;
2525
use MongoDB\Client;
26+
use MongoDB\Codec\DocumentCodec;
2627
use MongoDB\Database;
28+
use MongoDB\Driver\ReadConcern;
29+
use MongoDB\Driver\ReadPreference;
30+
use MongoDB\Driver\WriteConcern;
2731
use ReflectionParameter;
2832
use Symfony\Component\DependencyInjection\Attribute\AutowireCallable;
2933
use Symfony\Component\DependencyInjection\Definition;
@@ -43,7 +47,11 @@ final class AutowireDatabase extends AutowireCallable
4347
public function __construct(
4448
private readonly ?string $database = null,
4549
?string $client = null,
46-
private readonly array $options = [],
50+
private readonly string|DocumentCodec|null $codec = null,
51+
private readonly string|array|null $typeMap = null,
52+
private readonly string|ReadPreference|null $readPreference = null,
53+
private readonly string|WriteConcern|null $writeConcern = null,
54+
private readonly string|ReadConcern|null $readConcern = null,
4755
bool|string $lazy = false,
4856
) {
4957
$this->serviceId = $client === null
@@ -58,11 +66,26 @@ public function __construct(
5866

5967
public function buildDefinition(mixed $value, ?string $type, ReflectionParameter $parameter): Definition
6068
{
69+
$options = [];
70+
foreach (['codec', 'typeMap', 'readPreference', 'writeConcern', 'readConcern'] as $option) {
71+
$optionValue = $this->$option;
72+
if ($optionValue === null) {
73+
continue;
74+
}
75+
76+
// If a string was given, it may be a service ID or parameter. Handle it accordingly
77+
if (is_string($optionValue)) {
78+
$optionValue = $option === 'typeMap' ? sprintf('%%%s%%', $optionValue) : new Reference($optionValue);
79+
}
80+
81+
$options[$option] = $optionValue;
82+
}
83+
6184
return (new Definition(is_string($this->lazy) ? $this->lazy : ($type ?: Database::class)))
6285
->setFactory($value)
6386
->setArguments([
6487
$this->database ?? sprintf('%%%s.default_database%%', $this->serviceId),
65-
$this->options,
88+
$options,
6689
])
6790
->setLazy($this->lazy);
6891
}
Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
<?php
2+
3+
namespace MongoDB\Bundle\Tests\Unit\Attribute;
4+
5+
use Generator;
6+
use MongoDB\BSON\Document;
7+
use MongoDB\Codec\DecodeIfSupported;
8+
use MongoDB\Codec\DocumentCodec;
9+
use MongoDB\Codec\EncodeIfSupported;
10+
use MongoDB\Driver\ReadConcern;
11+
use MongoDB\Driver\ReadPreference;
12+
use MongoDB\Driver\WriteConcern;
13+
use PHPUnit\Framework\TestCase;
14+
use Symfony\Component\DependencyInjection\Reference;
15+
16+
use function sprintf;
17+
18+
abstract class AttributeTestCase extends TestCase
19+
{
20+
public static function provideOptions(): Generator
21+
{
22+
$codec = new class implements DocumentCodec {
23+
use DecodeIfSupported;
24+
use EncodeIfSupported;
25+
26+
public function canDecode($value): bool
27+
{
28+
return $value instanceof Document;
29+
}
30+
31+
public function canEncode($value): bool
32+
{
33+
return $value instanceof Document;
34+
}
35+
36+
public function decode($value): Document
37+
{
38+
return $value;
39+
}
40+
41+
public function encode($value): Document
42+
{
43+
return $value;
44+
}
45+
};
46+
47+
$options = [
48+
'codec' => $codec,
49+
'writeConcern' => new WriteConcern(0),
50+
'readConcern' => new ReadConcern('majority'),
51+
'readPreference' => new ReadPreference('primary'),
52+
];
53+
54+
foreach ($options as $option => $value) {
55+
yield sprintf('%s option: null', $option) => [
56+
'attributeArguments' => [$option => null],
57+
'expectedOptions' => [],
58+
];
59+
60+
yield sprintf('%s option: instance', $option) => [
61+
'attributeArguments' => [$option => $value],
62+
'expectedOptions' => [$option => $value],
63+
];
64+
65+
yield sprintf('%s option: reference', $option) => [
66+
'attributeArguments' => [$option => sprintf('%s_service', $option)],
67+
'expectedOptions' => [$option => new Reference(sprintf('%s_service', $option))],
68+
];
69+
}
70+
71+
// Type map
72+
yield 'typeMap option: null' => [
73+
'attributeArguments' => ['typeMap' => null],
74+
'expectedOptions' => [],
75+
];
76+
77+
yield 'typeMap option: value' => [
78+
'attributeArguments' => ['typeMap' => ['root' => 'bson']],
79+
'expectedOptions' => ['typeMap' => ['root' => 'bson']],
80+
];
81+
82+
yield 'typeMap option: parameter' => [
83+
'attributeArguments' => ['typeMap' => 'default_typeMap'],
84+
'expectedOptions' => ['typeMap' => '%default_typeMap%'],
85+
];
86+
}
87+
}

tests/Unit/Attribute/AutowireCollectionTest.php

Lines changed: 25 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -23,14 +23,13 @@
2323
use MongoDB\Bundle\Attribute\AutowireCollection;
2424
use MongoDB\Client;
2525
use MongoDB\Collection;
26-
use PHPUnit\Framework\TestCase;
2726
use ReflectionParameter;
2827
use Symfony\Component\DependencyInjection\Reference;
2928

3029
use function sprintf;
3130

3231
/** @covers \MongoDB\Bundle\Attribute\AutowireCollection */
33-
final class AutowireCollectionTest extends TestCase
32+
final class AutowireCollectionTest extends AttributeTestCase
3433
{
3534
public function testMinimal(): void
3635
{
@@ -61,7 +60,6 @@ public function testCollection(): void
6160
collection: 'test',
6261
database: 'mydb',
6362
client: 'default',
64-
options: ['foo' => 'bar'],
6563
);
6664

6765
$this->assertEquals([new Reference('mongodb.client.default'), 'selectCollection'], $autowire->value);
@@ -80,15 +78,14 @@ static function (Collection $collection): void {
8078
$this->assertEquals($autowire->value, $definition->getFactory());
8179
$this->assertSame('mydb', $definition->getArgument(0));
8280
$this->assertSame('test', $definition->getArgument(1));
83-
$this->assertSame(['foo' => 'bar'], $definition->getArgument(2));
81+
$this->assertSame([], $definition->getArgument(2));
8482
}
8583

8684
public function testWithoutCollection(): void
8785
{
8886
$autowire = new AutowireCollection(
8987
database: 'mydb',
9088
client: 'default',
91-
options: ['foo' => 'bar'],
9289
);
9390

9491
$this->assertEquals([new Reference('mongodb.client.default'), 'selectCollection'], $autowire->value);
@@ -107,6 +104,28 @@ static function (Collection $priceReports): void {
107104
$this->assertEquals($autowire->value, $definition->getFactory());
108105
$this->assertSame('mydb', $definition->getArgument(0));
109106
$this->assertSame('priceReports', $definition->getArgument(1));
110-
$this->assertSame(['foo' => 'bar'], $definition->getArgument(2));
107+
$this->assertSame([], $definition->getArgument(2));
108+
}
109+
110+
/** @dataProvider provideOptions */
111+
public function testWithOptions(array $attributeArguments, array $expectedOptions): void
112+
{
113+
$autowire = new AutowireCollection(
114+
...$attributeArguments,
115+
database: 'mydb',
116+
client: 'default',
117+
);
118+
119+
$definition = $autowire->buildDefinition(
120+
value: $autowire->value,
121+
type: Collection::class,
122+
parameter: new ReflectionParameter(
123+
static function (Collection $priceReports): void {
124+
},
125+
'priceReports',
126+
),
127+
);
128+
129+
$this->assertEquals($expectedOptions, $definition->getArgument(2));
111130
}
112131
}

tests/Unit/Attribute/AutowireDatabaseTest.php

Lines changed: 25 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -22,13 +22,13 @@
2222

2323
use MongoDB\Bundle\Attribute\AutowireDatabase;
2424
use MongoDB\Client;
25+
use MongoDB\Collection;
2526
use MongoDB\Database;
26-
use PHPUnit\Framework\TestCase;
2727
use ReflectionParameter;
2828
use Symfony\Component\DependencyInjection\Reference;
2929

3030
/** @covers \MongoDB\Bundle\Attribute\AutowireDatabase */
31-
final class AutowireDatabaseTest extends TestCase
31+
final class AutowireDatabaseTest extends AttributeTestCase
3232
{
3333
public function testMinimal(): void
3434
{
@@ -56,7 +56,6 @@ public function testDatabase(): void
5656
$autowire = new AutowireDatabase(
5757
database: 'mydb',
5858
client: 'default',
59-
options: ['foo' => 'bar'],
6059
);
6160

6261
$this->assertEquals([new Reference('mongodb.client.default'), 'selectDatabase'], $autowire->value);
@@ -74,14 +73,13 @@ static function (Database $db): void {
7473
$this->assertSame(Database::class, $definition->getClass());
7574
$this->assertEquals($autowire->value, $definition->getFactory());
7675
$this->assertSame('mydb', $definition->getArgument(0));
77-
$this->assertSame(['foo' => 'bar'], $definition->getArgument(1));
76+
$this->assertSame([], $definition->getArgument(1));
7877
}
7978

8079
public function testWithoutDatabase(): void
8180
{
8281
$autowire = new AutowireDatabase(
8382
client: 'default',
84-
options: ['foo' => 'bar'],
8583
);
8684

8785
$this->assertEquals([new Reference('mongodb.client.default'), 'selectDatabase'], $autowire->value);
@@ -99,6 +97,27 @@ static function (Database $mydb): void {
9997
$this->assertSame(Database::class, $definition->getClass());
10098
$this->assertEquals($autowire->value, $definition->getFactory());
10199
$this->assertSame('%mongodb.client.default.default_database%', $definition->getArgument(0));
102-
$this->assertSame(['foo' => 'bar'], $definition->getArgument(1));
100+
$this->assertSame([], $definition->getArgument(1));
101+
}
102+
103+
/** @dataProvider provideOptions */
104+
public function testWithOptions(array $attributeArguments, array $expectedOptions): void
105+
{
106+
$autowire = new AutowireDatabase(
107+
...$attributeArguments,
108+
client: 'default',
109+
);
110+
111+
$definition = $autowire->buildDefinition(
112+
value: $autowire->value,
113+
type: Collection::class,
114+
parameter: new ReflectionParameter(
115+
static function (Database $database): void {
116+
},
117+
'database',
118+
),
119+
);
120+
121+
$this->assertEquals($expectedOptions, $definition->getArgument(1));
103122
}
104123
}

0 commit comments

Comments
 (0)