Skip to content

Commit 4fff744

Browse files
GromNaNmaxhelias
authored andcommitted
Add support for GridFS adapter
Can be configured using a Doctrine MongoDB ODM, a MongoDB\Client configuration or a service providing a MongoDB\GridFS\Bucket instance.
1 parent dd057ba commit 4fff744

File tree

6 files changed

+325
-0
lines changed

6 files changed

+325
-0
lines changed

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,7 @@ to interact with your storage.
110110
3. [Interacting with FTP and SFTP servers](https://github.com/thephpleague/flysystem-bundle/blob/master/docs/3-interacting-with-ftp-and-sftp-servers.md)
111111
4. [Using a lazy adapter to switch storage backend using an environment variable](https://github.com/thephpleague/flysystem-bundle/blob/master/docs/4-using-lazy-adapter-to-switch-at-runtime.md)
112112
5. [Creating a custom adapter](https://github.com/thephpleague/flysystem-bundle/blob/master/docs/5-creating-a-custom-adapter.md)
113+
6. [MongoDB GridFS](https://github.com/thephpleague/flysystem-bundle/blob/master/docs/6-gridfs.md)
113114

114115
* [Security issue disclosure procedure](https://github.com/thephpleague/flysystem-bundle/blob/master/docs/A-security-disclosure-procedure.md)
115116
* [Configuration reference](https://github.com/thephpleague/flysystem-bundle/blob/master/docs/B-configuration-reference.md)

composer.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,11 +29,13 @@
2929
"symfony/options-resolver": "^5.4 || ^6.0 || ^7.0"
3030
},
3131
"require-dev": {
32+
"doctrine/mongodb-odm": "^2.0",
3233
"league/flysystem-async-aws-s3": "^3.1",
3334
"league/flysystem-aws-s3-v3": "^3.1",
3435
"league/flysystem-azure-blob-storage": "^3.1",
3536
"league/flysystem-ftp": "^3.1",
3637
"league/flysystem-google-cloud-storage": "^3.1",
38+
"league/flysystem-gridfs": "^3.28",
3739
"league/flysystem-memory": "^3.1",
3840
"league/flysystem-read-only": "^3.15",
3941
"league/flysystem-sftp-v3": "^3.1",

docs/6-gridfs.md

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
# MongoDB GridFS
2+
3+
GridFS stores files in a MongoDB database.
4+
5+
Install the GridFS adapter:
6+
7+
```
8+
composer require league/flysystem-gridfs
9+
```
10+
11+
## With `doctrine/mongodb-odm-bundle`
12+
13+
For applications that uses Doctrine MongoDB ODM, set the `doctrine_connection` name to use:
14+
15+
```yaml
16+
# config/packages/flysystem.yaml
17+
18+
flysystem:
19+
storages:
20+
users.storage:
21+
adapter: 'gridfs'
22+
options:
23+
# Name of a Doctrine MongoDB ODM connection
24+
doctrine_connection: 'default'
25+
# Use the default DB from the Doctrine MongoDB ODM configuration
26+
database: ~
27+
bucket: 'fs'
28+
```
29+
30+
## With a Full Configuration
31+
32+
To initialize the GridFS bucket from configuration, set the `mongodb_uri` and `database` options, others are optional.
33+
34+
```yaml
35+
# config/packages/flysystem.yaml
36+
37+
flysystem:
38+
storages:
39+
users.storage:
40+
adapter: 'gridfs'
41+
options:
42+
# MongoDB client configuration
43+
mongodb_uri: '%env(MONGODB_URI)%'
44+
mongodb_uri_options: []
45+
mongodb_driver_options: []
46+
# Database name is required
47+
database: '%env(MONGODB_DB)%'
48+
bucket: 'fs'
49+
```
50+
51+
```dotenv
52+
# .env
53+
54+
MONGODB_URI=mongodb://127.0.0.1:27017/
55+
MONGODB_DB=flysystem
56+
```
57+
58+
## With a Bucket Service
59+
60+
For a more advanced configuration, create a service for
61+
[`MongoDB\GridFS\Bucket`](https://www.mongodb.com/docs/php-library/current/tutorial/gridfs/):
62+
63+
```yaml
64+
# config/packages/flysystem.yaml
65+
66+
services:
67+
mongodb_client:
68+
class: 'MongoDB\Client'
69+
arguments:
70+
- '%env(MONGODB_URI)%'
71+
72+
mongodb_database:
73+
class: 'MongoDB\Database'
74+
factory: ['mongodb_client', 'selectDatabase']
75+
arguments: ['%env(MONGODB_DB)%']
76+
77+
mongodb_gridfs_bucket:
78+
class: 'MongoDB\GridFS\Bucket'
79+
factory: ['@mongodb_database', 'selectGridFSBucket']
80+
81+
flysystem:
82+
storages:
83+
users.storage:
84+
adapter: 'gridfs'
85+
options:
86+
# Service name
87+
bucket: 'mongodb_gridfs_bucket'
88+
```

src/Adapter/AdapterDefinitionFactory.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ public function __construct()
3434
new Builder\AzureAdapterDefinitionBuilder(),
3535
new Builder\FtpAdapterDefinitionBuilder(),
3636
new Builder\GcloudAdapterDefinitionBuilder(),
37+
new Builder\GridFSAdapterDefinitionBuilder(),
3738
new Builder\LocalAdapterDefinitionBuilder(),
3839
new Builder\MemoryAdapterDefinitionBuilder(),
3940
new Builder\SftpAdapterDefinitionBuilder(),
Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the flysystem-bundle project.
5+
*
6+
* (c) Titouan Galopin <[email protected]>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace League\FlysystemBundle\Adapter\Builder;
13+
14+
use Doctrine\ODM\MongoDB\DocumentManager;
15+
use League\Flysystem\GridFS\GridFSAdapter;
16+
use MongoDB\Client;
17+
use MongoDB\GridFS\Bucket;
18+
use Symfony\Component\DependencyInjection\Definition;
19+
use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException;
20+
use Symfony\Component\DependencyInjection\Reference;
21+
use Symfony\Component\OptionsResolver\OptionsResolver;
22+
23+
/**
24+
* @author Jérôme Tamarelle <[email protected]>
25+
*
26+
* @internal
27+
*/
28+
class GridFSAdapterDefinitionBuilder extends AbstractAdapterDefinitionBuilder
29+
{
30+
public function getName(): string
31+
{
32+
return 'gridfs';
33+
}
34+
35+
protected function getRequiredPackages(): array
36+
{
37+
return [
38+
GridFSAdapter::class => 'league/flysystem-gridfs',
39+
];
40+
}
41+
42+
protected function configureOptions(OptionsResolver $resolver): void
43+
{
44+
$resolver->define('bucket')->default(null)->allowedTypes('string', 'null');
45+
$resolver->define('prefix')->default('')->allowedTypes('string');
46+
$resolver->define('database')->default(null)->allowedTypes('string', 'null');
47+
$resolver->define('doctrine_connection')->allowedTypes('string');
48+
$resolver->define('mongodb_uri')->allowedTypes('string');
49+
$resolver->define('mongodb_uri_options')->default([])->allowedTypes('array');
50+
$resolver->define('mongodb_driver_options')->default([])->allowedTypes('array');
51+
}
52+
53+
/**
54+
* @param array{bucket:string|null, prefix:string, database:string|null, doctrine_connection?:string, mongodb_uri?:string, mongodb_uri_options:array, mongodb_driver_options:array} $options
55+
*/
56+
protected function configureDefinition(Definition $definition, array $options, ?string $defaultVisibilityForDirectories): void
57+
{
58+
if (isset($options['doctrine_connection'])) {
59+
if (isset($options['mongodb_uri'])) {
60+
throw new InvalidArgumentException('In GridFS configuration, "doctrine_connection" and "mongodb_uri" options cannot be set together.');
61+
}
62+
$bucket = new Definition(Bucket::class);
63+
$bucket->setFactory([self::class, 'initializeBucketFromDocumentManager']);
64+
$bucket->setArguments([
65+
new Reference(sprintf('doctrine_mongodb.odm.%s_document_manager', $options['doctrine_connection'])),
66+
$options['database'],
67+
$options['bucket'],
68+
]);
69+
} elseif (isset($options['mongodb_uri'])) {
70+
$bucket = new Definition(Bucket::class);
71+
$bucket->setFactory([self::class, 'initializeBucketFromConfig']);
72+
$bucket->setArguments([
73+
$options['mongodb_uri'],
74+
$options['mongodb_uri_options'],
75+
$options['mongodb_driver_options'],
76+
$options['database'] ?? throw new InvalidArgumentException('MongoDB "database" name is required for Flysystem GridFS configuration'),
77+
$options['bucket'],
78+
]);
79+
} elseif ($options['bucket']) {
80+
$bucket = new Reference($options['bucket']);
81+
} else {
82+
throw new InvalidArgumentException('Flysystem GridFS configuration requires a "bucket" service name, a "mongodb_uri" or a "doctrine_connection" name');
83+
}
84+
85+
$definition->setClass(GridFSAdapter::class);
86+
$definition->setArgument(0, $bucket);
87+
$definition->setArgument(1, $options['prefix']);
88+
}
89+
90+
public static function initializeBucketFromDocumentManager(DocumentManager $documentManager, ?string $dbName, ?string $bucketName): Bucket
91+
{
92+
return $documentManager
93+
->getClient()
94+
->selectDatabase($dbName ?? $documentManager->getConfiguration()->getDefaultDB())
95+
->selectGridFSBucket(['bucketName' => $bucketName ?? 'fs', 'disableMD5' => true]);
96+
}
97+
98+
public static function initializeBucketFromConfig(string $uri, array $uriOptions, array $driverOptions, ?string $dbName, ?string $bucketName): Bucket
99+
{
100+
return (new Client($uri, $uriOptions, $driverOptions))
101+
->selectDatabase($dbName)
102+
->selectGridFSBucket(['bucketName' => $bucketName ?? 'fs', 'disableMD5' => true]);
103+
}
104+
}
Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the flysystem-bundle project.
5+
*
6+
* (c) Titouan Galopin <[email protected]>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Tests\League\FlysystemBundle\Adapter\Builder;
13+
14+
use Doctrine\ODM\MongoDB\Configuration;
15+
use Doctrine\ODM\MongoDB\DocumentManager;
16+
use League\Flysystem\GridFS\GridFSAdapter;
17+
use League\FlysystemBundle\Adapter\Builder\GridFSAdapterDefinitionBuilder;
18+
use MongoDB\Client;
19+
use MongoDB\GridFS\Bucket;
20+
use PHPUnit\Framework\TestCase;
21+
use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException;
22+
23+
class GridFSAdapterDefinitionBuilderTest extends TestCase
24+
{
25+
public function createBuilder(): GridFSAdapterDefinitionBuilder
26+
{
27+
return new GridFSAdapterDefinitionBuilder();
28+
}
29+
30+
public static function provideValidOptions(): \Generator
31+
{
32+
yield 'doctrine_minimal' => [[
33+
'doctrine_connection' => 'default',
34+
]];
35+
36+
yield 'doctrine_full' => [[
37+
'doctrine_connection' => 'custom',
38+
'database' => 'testing',
39+
'bucket' => 'avatars',
40+
]];
41+
42+
yield 'config_minimal' => [[
43+
'mongodb_uri' => 'mongodb://localhost:27017/',
44+
'database' => 'testing',
45+
]];
46+
47+
yield 'config_full' => [[
48+
'mongodb_uri' => 'mongodb://server1:27017,server2:27017/',
49+
'mongodb_uri_options' => ['appname' => 'flysystem'],
50+
'mongodb_driver_options' => ['disableClientPersistence' => false],
51+
'database' => 'testing',
52+
'bucket' => 'avatars',
53+
]];
54+
55+
yield 'service' => [[
56+
'bucket' => 'bucket',
57+
]];
58+
}
59+
60+
/**
61+
* @dataProvider provideValidOptions
62+
*/
63+
public function testCreateDefinition($options)
64+
{
65+
$this->assertSame(GridFSAdapter::class, $this->createBuilder()->createDefinition($options, null)->getClass());
66+
}
67+
68+
public static function provideInvalidOptions(): \Generator
69+
{
70+
yield 'empty' => [
71+
[],
72+
'Flysystem GridFS configuration requires a "bucket" service name, a "mongodb_uri" or a "doctrine_connection" name',
73+
];
74+
75+
yield 'no database with mongodb_uri' => [
76+
['mongodb_uri' => 'mongodb://127.0.0.1:27017/'],
77+
'MongoDB "database" name is required for Flysystem GridFS configuration',
78+
];
79+
80+
yield 'both doctrine_connection and mongodb_uri' => [
81+
['doctrine_connection' => 'default', 'mongodb_uri' => 'mongodb://127.0.0.1:27017/'],
82+
'In GridFS configuration, "doctrine_connection" and "mongodb_uri" options cannot be set together.',
83+
];
84+
}
85+
86+
/**
87+
* @dataProvider provideInvalidOptions
88+
*/
89+
public function testInvalidOptions(array $options, string $message)
90+
{
91+
$builder = $this->createBuilder();
92+
93+
$this->expectException(InvalidArgumentException::class);
94+
$this->expectExceptionMessage($message);
95+
96+
$builder->createDefinition($options, null);
97+
}
98+
99+
public function testInitializeBucketFromDocumentManager()
100+
{
101+
$client = new Client();
102+
$config = new Configuration();
103+
$config->setDefaultDB('testing');
104+
$dm = $this->createMock(DocumentManager::class);
105+
$dm->expects($this->once())->method('getClient')->willReturn($client);
106+
$dm->expects($this->once())->method('getConfiguration')->willReturn($config);
107+
108+
$bucket = GridFSAdapterDefinitionBuilder::initializeBucketFromDocumentManager($dm, null, 'avatars');
109+
110+
$this->assertInstanceOf(Bucket::class, $bucket);
111+
$this->assertSame('testing', $bucket->getDatabaseName());
112+
$this->assertSame('avatars', $bucket->getBucketName());
113+
}
114+
115+
public function testInitializeBucketFromConfig()
116+
{
117+
$bucket = GridFSAdapterDefinitionBuilder::initializeBucketFromConfig(
118+
'mongodb://server:27017/',
119+
['appname' => 'flysystem'],
120+
['disableClientPersistence' => false],
121+
'testing',
122+
'avatars'
123+
);
124+
125+
$this->assertInstanceOf(Bucket::class, $bucket);
126+
$this->assertSame('testing', $bucket->getDatabaseName());
127+
$this->assertSame('avatars', $bucket->getBucketName());
128+
}
129+
}

0 commit comments

Comments
 (0)