Skip to content

Allow specifying options in AutowireDatabase and AutowireCollection #14

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 6 commits into from
Dec 9, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 30 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -128,7 +128,7 @@ class MyService
}
```

## Database and Collection Usage
## Database Usage

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

## Collection Usage

To inject a collection, you can either call the `selectCollection` method on a `Client` or `Database` instance.
For convenience, the `#[AutowireCollection]` attribute provides a quicker alternative:

Expand Down Expand Up @@ -261,3 +263,30 @@ class MyService
) {}
}
```

## Specifying options

When using the `AutowireDatabase` or `AutowireCollection` attributes, you can specify additional options for the
resulting instances. You can pass the following options:
|| Option || Accepted type ||
| `codec` | `DocumentCodec` instance |
| `typeMap`| `array` containing type map information |
| `readPreference` | `MongoDB\Driver\ReadPreference` instance |
| `writeConcern` | `MongoDB\Driver\writeConcern` instance |
| `readConcern` | `MongoDB\Driver\ReadConcern` instance |

In addition to passing an instance, you can also pass a service reference by specifying a string for the given option:

```php
use MongoDB\Bundle\Attribute\AutowireCollection;
use MongoDB\Collection;
use MongoDB\Driver\ReadPreference;

class MyService
{
public function __construct(
#[AutowireCollection(codec: Codec::class, readPreference: new ReadPreference('secondary'))]
private Collection $myCollection,
) {}
}
```
2 changes: 1 addition & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
],
"require": {
"php": ">=8.1",
"mongodb/mongodb": "^1.16",
"mongodb/mongodb": "^1.17",
"symfony/config": "^6.3 || ^7.0",
"symfony/console": "^6.3 || ^7.0",
"symfony/dependency-injection": "^6.3.5 || ^7.0",
Expand Down
28 changes: 26 additions & 2 deletions src/Attribute/AutowireCollection.php
Original file line number Diff line number Diff line change
Expand Up @@ -23,13 +23,18 @@
use Attribute;
use MongoDB\Bundle\DependencyInjection\MongoDBExtension;
use MongoDB\Client;
use MongoDB\Codec\DocumentCodec;
use MongoDB\Collection;
use MongoDB\Driver\ReadConcern;
use MongoDB\Driver\ReadPreference;
use MongoDB\Driver\WriteConcern;
use ReflectionParameter;
use Symfony\Component\DependencyInjection\Attribute\AutowireCallable;
use Symfony\Component\DependencyInjection\Definition;
use Symfony\Component\DependencyInjection\Reference;

use function is_string;
use function ltrim;
use function sprintf;

/**
Expand All @@ -44,7 +49,11 @@ public function __construct(
private readonly ?string $collection = null,
private readonly ?string $database = null,
?string $client = null,
private readonly array $options = [],
private readonly string|DocumentCodec|null $codec = null,
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How would you pass a DocumentCodec instance?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I added an example, but it's rather straightforward:

use MongoDB\Bundle\Attribute\AutowireCollection;
use MongoDB\Collection;
use MongoDB\Driver\ReadPreference;

class MyService
{
    public function __construct(
        #[AutowireCollection(codec: Codec::class, readPreference: new ReadPreference('secondary'))]
        private Collection $myCollection,
    ) {}
}

private readonly string|array|null $typeMap = null,
private readonly string|ReadPreference|null $readPreference = null,
private readonly string|WriteConcern|null $writeConcern = null,
private readonly string|ReadConcern|null $readConcern = null,
bool|string $lazy = false,
) {
$this->serviceId = $client === null
Expand All @@ -59,12 +68,27 @@ public function __construct(

public function buildDefinition(mixed $value, ?string $type, ReflectionParameter $parameter): Definition
{
$options = [];
foreach (['codec', 'typeMap', 'readPreference', 'writeConcern', 'readConcern'] as $option) {
$optionValue = $this->$option;
if ($optionValue === null) {
continue;
}

// If a string was given, it may be a service ID or parameter. Handle it accordingly
if (is_string($optionValue)) {
$optionValue = $option === 'typeMap' ? sprintf('%%%s%%', $optionValue) : new Reference($optionValue);
}

$options[$option] = $optionValue;
}

return (new Definition(is_string($this->lazy) ? $this->lazy : ($type ?: Collection::class)))
->setFactory($value)
->setArguments([
$this->database ?? sprintf('%%%s.default_database%%', $this->serviceId),
$this->collection ?? $parameter->getName(),
$this->options,
$options,
])
->setLazy($this->lazy);
}
Expand Down
27 changes: 25 additions & 2 deletions src/Attribute/AutowireDatabase.php
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,11 @@
use Attribute;
use MongoDB\Bundle\DependencyInjection\MongoDBExtension;
use MongoDB\Client;
use MongoDB\Codec\DocumentCodec;
use MongoDB\Database;
use MongoDB\Driver\ReadConcern;
use MongoDB\Driver\ReadPreference;
use MongoDB\Driver\WriteConcern;
use ReflectionParameter;
use Symfony\Component\DependencyInjection\Attribute\AutowireCallable;
use Symfony\Component\DependencyInjection\Definition;
Expand All @@ -43,7 +47,11 @@ final class AutowireDatabase extends AutowireCallable
public function __construct(
private readonly ?string $database = null,
?string $client = null,
private readonly array $options = [],
private readonly string|DocumentCodec|null $codec = null,
private readonly string|array|null $typeMap = null,
private readonly string|ReadPreference|null $readPreference = null,
private readonly string|WriteConcern|null $writeConcern = null,
private readonly string|ReadConcern|null $readConcern = null,
bool|string $lazy = false,
) {
$this->serviceId = $client === null
Expand All @@ -58,11 +66,26 @@ public function __construct(

public function buildDefinition(mixed $value, ?string $type, ReflectionParameter $parameter): Definition
{
$options = [];
foreach (['codec', 'typeMap', 'readPreference', 'writeConcern', 'readConcern'] as $option) {
$optionValue = $this->$option;
if ($optionValue === null) {
continue;
}

// If a string was given, it may be a service ID or parameter. Handle it accordingly
if (is_string($optionValue)) {
$optionValue = $option === 'typeMap' ? sprintf('%%%s%%', $optionValue) : new Reference($optionValue);
}

$options[$option] = $optionValue;
}

return (new Definition(is_string($this->lazy) ? $this->lazy : ($type ?: Database::class)))
->setFactory($value)
->setArguments([
$this->database ?? sprintf('%%%s.default_database%%', $this->serviceId),
$this->options,
$options,
])
->setLazy($this->lazy);
}
Expand Down
87 changes: 87 additions & 0 deletions tests/Unit/Attribute/AttributeTestCase.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
<?php

namespace MongoDB\Bundle\Tests\Unit\Attribute;

use Generator;
use MongoDB\BSON\Document;
use MongoDB\Codec\DecodeIfSupported;
use MongoDB\Codec\DocumentCodec;
use MongoDB\Codec\EncodeIfSupported;
use MongoDB\Driver\ReadConcern;
use MongoDB\Driver\ReadPreference;
use MongoDB\Driver\WriteConcern;
use PHPUnit\Framework\TestCase;
use Symfony\Component\DependencyInjection\Reference;

use function sprintf;

abstract class AttributeTestCase extends TestCase
{
public static function provideOptions(): Generator
{
$codec = new class implements DocumentCodec {
use DecodeIfSupported;
use EncodeIfSupported;

public function canDecode($value): bool
{
return $value instanceof Document;
}

public function canEncode($value): bool
{
return $value instanceof Document;
}

public function decode($value): Document
{
return $value;
}

public function encode($value): Document
{
return $value;
}
};

$options = [
'codec' => $codec,
'writeConcern' => new WriteConcern(0),
'readConcern' => new ReadConcern('majority'),
'readPreference' => new ReadPreference('primary'),
];

foreach ($options as $option => $value) {
yield sprintf('%s option: null', $option) => [
'attributeArguments' => [$option => null],
'expectedOptions' => [],
];

yield sprintf('%s option: instance', $option) => [
'attributeArguments' => [$option => $value],
'expectedOptions' => [$option => $value],
];

yield sprintf('%s option: reference', $option) => [
'attributeArguments' => [$option => sprintf('%s_service', $option)],
'expectedOptions' => [$option => new Reference(sprintf('%s_service', $option))],
];
}

// Type map
yield 'typeMap option: null' => [
'attributeArguments' => ['typeMap' => null],
'expectedOptions' => [],
];

yield 'typeMap option: value' => [
'attributeArguments' => ['typeMap' => ['root' => 'bson']],
'expectedOptions' => ['typeMap' => ['root' => 'bson']],
];

yield 'typeMap option: parameter' => [
'attributeArguments' => ['typeMap' => 'default_typeMap'],
'expectedOptions' => ['typeMap' => '%default_typeMap%'],
];
}
}
31 changes: 25 additions & 6 deletions tests/Unit/Attribute/AutowireCollectionTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -23,14 +23,13 @@
use MongoDB\Bundle\Attribute\AutowireCollection;
use MongoDB\Client;
use MongoDB\Collection;
use PHPUnit\Framework\TestCase;
use ReflectionParameter;
use Symfony\Component\DependencyInjection\Reference;

use function sprintf;

/** @covers \MongoDB\Bundle\Attribute\AutowireCollection */
final class AutowireCollectionTest extends TestCase
final class AutowireCollectionTest extends AttributeTestCase
{
public function testMinimal(): void
{
Expand Down Expand Up @@ -61,7 +60,6 @@ public function testCollection(): void
collection: 'test',
database: 'mydb',
client: 'default',
options: ['foo' => 'bar'],
);

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

public function testWithoutCollection(): void
{
$autowire = new AutowireCollection(
database: 'mydb',
client: 'default',
options: ['foo' => 'bar'],
);

$this->assertEquals([new Reference('mongodb.client.default'), 'selectCollection'], $autowire->value);
Expand All @@ -107,6 +104,28 @@ static function (Collection $priceReports): void {
$this->assertEquals($autowire->value, $definition->getFactory());
$this->assertSame('mydb', $definition->getArgument(0));
$this->assertSame('priceReports', $definition->getArgument(1));
$this->assertSame(['foo' => 'bar'], $definition->getArgument(2));
$this->assertSame([], $definition->getArgument(2));
}

/** @dataProvider provideOptions */
public function testWithOptions(array $attributeArguments, array $expectedOptions): void
{
$autowire = new AutowireCollection(
...$attributeArguments,
database: 'mydb',
client: 'default',
);

$definition = $autowire->buildDefinition(
value: $autowire->value,
type: Collection::class,
parameter: new ReflectionParameter(
static function (Collection $priceReports): void {
},
'priceReports',
),
);

$this->assertEquals($expectedOptions, $definition->getArgument(2));
}
}
31 changes: 25 additions & 6 deletions tests/Unit/Attribute/AutowireDatabaseTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -22,13 +22,13 @@

use MongoDB\Bundle\Attribute\AutowireDatabase;
use MongoDB\Client;
use MongoDB\Collection;
use MongoDB\Database;
use PHPUnit\Framework\TestCase;
use ReflectionParameter;
use Symfony\Component\DependencyInjection\Reference;

/** @covers \MongoDB\Bundle\Attribute\AutowireDatabase */
final class AutowireDatabaseTest extends TestCase
final class AutowireDatabaseTest extends AttributeTestCase
{
public function testMinimal(): void
{
Expand Down Expand Up @@ -56,7 +56,6 @@ public function testDatabase(): void
$autowire = new AutowireDatabase(
database: 'mydb',
client: 'default',
options: ['foo' => 'bar'],
);

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

public function testWithoutDatabase(): void
{
$autowire = new AutowireDatabase(
client: 'default',
options: ['foo' => 'bar'],
);

$this->assertEquals([new Reference('mongodb.client.default'), 'selectDatabase'], $autowire->value);
Expand All @@ -99,6 +97,27 @@ static function (Database $mydb): void {
$this->assertSame(Database::class, $definition->getClass());
$this->assertEquals($autowire->value, $definition->getFactory());
$this->assertSame('%mongodb.client.default.default_database%', $definition->getArgument(0));
$this->assertSame(['foo' => 'bar'], $definition->getArgument(1));
$this->assertSame([], $definition->getArgument(1));
}

/** @dataProvider provideOptions */
public function testWithOptions(array $attributeArguments, array $expectedOptions): void
{
$autowire = new AutowireDatabase(
...$attributeArguments,
client: 'default',
);

$definition = $autowire->buildDefinition(
value: $autowire->value,
type: Collection::class,
parameter: new ReflectionParameter(
static function (Database $database): void {
},
'database',
),
);

$this->assertEquals($expectedOptions, $definition->getArgument(1));
}
}