Skip to content

Commit

Permalink
Add support for GridFS adapter
Browse files Browse the repository at this point in the history
Can be configured using a Doctrine MongoDB ODM, a MongoDB\Client configuration
or a service providing a MongoDB\GridFS\Bucket instance.
  • Loading branch information
GromNaN committed May 30, 2024
1 parent dd057ba commit f5b375d
Show file tree
Hide file tree
Showing 6 changed files with 325 additions and 0 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,7 @@ to interact with your storage.
3. [Interacting with FTP and SFTP servers](https://github.com/thephpleague/flysystem-bundle/blob/master/docs/3-interacting-with-ftp-and-sftp-servers.md)
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)
5. [Creating a custom adapter](https://github.com/thephpleague/flysystem-bundle/blob/master/docs/5-creating-a-custom-adapter.md)
6. [MongoDB GridFS](https://github.com/thephpleague/flysystem-bundle/blob/master/docs/6-gridfs.md)

* [Security issue disclosure procedure](https://github.com/thephpleague/flysystem-bundle/blob/master/docs/A-security-disclosure-procedure.md)
* [Configuration reference](https://github.com/thephpleague/flysystem-bundle/blob/master/docs/B-configuration-reference.md)
Expand Down
2 changes: 2 additions & 0 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -29,11 +29,13 @@
"symfony/options-resolver": "^5.4 || ^6.0 || ^7.0"
},
"require-dev": {
"doctrine/mongodb-odm": "^2.0",
"league/flysystem-async-aws-s3": "^3.1",
"league/flysystem-aws-s3-v3": "^3.1",
"league/flysystem-azure-blob-storage": "^3.1",
"league/flysystem-ftp": "^3.1",
"league/flysystem-google-cloud-storage": "^3.1",
"league/flysystem-gridfs": "^3.28",
"league/flysystem-memory": "^3.1",
"league/flysystem-read-only": "^3.15",
"league/flysystem-sftp-v3": "^3.1",
Expand Down
88 changes: 88 additions & 0 deletions docs/6-gridfs.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
# MongoDB GridFS

GridFS stores files in a MongoDB database.

Install the GridFS adapter:

```
composer require league/flysystem-gridfs
```

## With `doctrine/mongodb-odm-bundle`

For applications that uses Doctrine MongoDB ODM, set the `doctrine_connection` name to use:

```yaml
# config/packages/flysystem.yaml

flysystem:
storages:
users.storage:
adapter: 'gridfs'
options:
# Name of a Doctrine MongoDB ODM connection
doctrine_connection: 'default'
# Use the default DB from the Doctrine MongoDB ODM configuration
database: ~
bucket: 'fs'
```
## With a Full Configuration
To initialize the GridFS bucket from configuration, set the `mongodb_uri` and `database` options, others are optional.

```yaml
# config/packages/flysystem.yaml
flysystem:
storages:
users.storage:
adapter: 'gridfs'
options:
# MongoDB client configuration
mongodb_uri: '%env(MONGODB_URI)%'
mongodb_uri_options: []
mongodb_driver_options: []
# Database name is required
database: '%env(MONGODB_DB)%'
bucket: 'fs'
```

```dotenv
# .env
MONGODB_URI=mongodb://127.0.0.1:27017/
MONGODB_DB=flysystem
```

## With a Bucket Service

For a more advanced configuration, create a service for
[`MongoDB\GridFS\Bucket`](https://www.mongodb.com/docs/php-library/current/tutorial/gridfs/):

```yaml
# config/packages/flysystem.yaml
services:
mongodb_client:
class: 'MongoDB\Client'
arguments:
- '%env(MONGODB_URI)%'
mongodb_database:
class: 'MongoDB\Database'
factory: ['mongodb_client', 'selectDatabase']
arguments: ['%env(MONGODB_DB)%']
mongodb_gridfs_bucket:
class: 'MongoDB\GridFS\Bucket'
factory: ['@mongodb_database', 'selectGridFSBucket']
flysystem:
storages:
users.storage:
adapter: 'gridfs'
options:
# Service name
bucket: 'mongodb_gridfs_bucket'
```
1 change: 1 addition & 0 deletions src/Adapter/AdapterDefinitionFactory.php
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ public function __construct()
new Builder\AzureAdapterDefinitionBuilder(),
new Builder\FtpAdapterDefinitionBuilder(),
new Builder\GcloudAdapterDefinitionBuilder(),
new Builder\GridFSAdapterDefinitionBuilder(),
new Builder\LocalAdapterDefinitionBuilder(),
new Builder\MemoryAdapterDefinitionBuilder(),
new Builder\SftpAdapterDefinitionBuilder(),
Expand Down
104 changes: 104 additions & 0 deletions src/Adapter/Builder/GridFSAdapterDefinitionBuilder.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
<?php

/*
* This file is part of the flysystem-bundle project.
*
* (c) Titouan Galopin <galopintitouan@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

namespace League\FlysystemBundle\Adapter\Builder;

use Doctrine\ODM\MongoDB\DocumentManager;
use League\Flysystem\GridFS\GridFSAdapter;
use MongoDB\Client;
use MongoDB\GridFS\Bucket;
use Symfony\Component\DependencyInjection\Definition;
use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException;
use Symfony\Component\DependencyInjection\Reference;
use Symfony\Component\OptionsResolver\OptionsResolver;

/**
* @author Jérôme Tamarelle <jerome@tamarelle.net>
*
* @internal
*/
class GridFSAdapterDefinitionBuilder extends AbstractAdapterDefinitionBuilder
{
public function getName(): string
{
return 'gridfs';
}

protected function getRequiredPackages(): array
{
return [
GridFSAdapter::class => 'league/flysystem-gridfs',
];
}

protected function configureOptions(OptionsResolver $resolver): void
{
$resolver->define('bucket')->default(null)->allowedTypes('string', 'null');
$resolver->define('prefix')->default('')->allowedTypes('string');
$resolver->define('database')->default(null)->allowedTypes('string', 'null');
$resolver->define('doctrine_connection')->allowedTypes('string');
$resolver->define('mongodb_uri')->allowedTypes('string');
$resolver->define('mongodb_uri_options')->default([])->allowedTypes('array');
$resolver->define('mongodb_driver_options')->default([])->allowedTypes('array');
}

/**
* @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
*/
protected function configureDefinition(Definition $definition, array $options, ?string $defaultVisibilityForDirectories): void
{
if (isset($options['doctrine_connection'])) {
if (isset($options['mongodb_uri'])) {
throw new InvalidArgumentException('In GridFS configuration, "doctrine_connection" and "mongodb_uri" options cannot be set together.');
}
$bucket = new Definition(Bucket::class);
$bucket->setFactory([self::class, 'initializeBucketFromDocumentManager']);
$bucket->setArguments([
new Reference(sprintf('doctrine_mongodb.odm.%s_document_manager', $options['doctrine_connection'])),
$options['database'],
$options['bucket'],
]);
} elseif (isset($options['mongodb_uri'])) {
$bucket = new Definition(Bucket::class);
$bucket->setFactory([self::class, 'initializeBucketFromConfig']);
$bucket->setArguments([
$options['mongodb_uri'],
$options['mongodb_uri_options'],
$options['mongodb_driver_options'],
$options['database'] ?? throw new InvalidArgumentException('MongoDB "database" name is required for Flysystem GridFS configuration'),
$options['bucket'],
]);
} elseif ($options['bucket']) {
$bucket = new Reference($options['bucket']);
} else {
throw new InvalidArgumentException('Flysystem GridFS configuration requires a "bucket" service name, a "mongodb_uri" or a "doctrine_connection" name');
}

$definition->setClass(GridFSAdapter::class);
$definition->setArgument(0, $bucket);
$definition->setArgument(1, $options['prefix']);
}

public static function initializeBucketFromDocumentManager(DocumentManager $documentManager, ?string $dbName, ?string $bucketName): Bucket
{
return $documentManager
->getClient()
->selectDatabase($dbName ?? $documentManager->getConfiguration()->getDefaultDB())
->selectGridFSBucket(['bucketName' => $bucketName ?? 'fs', 'disableMD5' => true]);
}

public static function initializeBucketFromConfig(string $uri, array $uriOptions, array $driverOptions, ?string $dbName, ?string $bucketName): Bucket
{
return (new Client($uri, $uriOptions, $driverOptions))
->selectDatabase($dbName)
->selectGridFSBucket(['bucketName' => $bucketName ?? 'fs', 'disableMD5' => true]);
}
}
129 changes: 129 additions & 0 deletions tests/Adapter/Builder/GridFSAdapterDefinitionBuilderTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
<?php

/*
* This file is part of the flysystem-bundle project.
*
* (c) Titouan Galopin <galopintitouan@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

namespace Tests\League\FlysystemBundle\Adapter\Builder;

use Doctrine\ODM\MongoDB\Configuration;
use Doctrine\ODM\MongoDB\DocumentManager;
use League\Flysystem\GridFS\GridFSAdapter;
use League\FlysystemBundle\Adapter\Builder\GridFSAdapterDefinitionBuilder;
use MongoDB\Client;
use MongoDB\GridFS\Bucket;
use PHPUnit\Framework\TestCase;
use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException;

class GridFSAdapterDefinitionBuilderTest extends TestCase
{
public function createBuilder(): GridFSAdapterDefinitionBuilder
{
return new GridFSAdapterDefinitionBuilder();
}

public static function provideValidOptions(): \Generator
{
yield 'doctrine_minimal' => [[
'doctrine_connection' => 'default',
]];

yield 'doctrine_full' => [[
'doctrine_connection' => 'custom',
'database' => 'testing',
'bucket' => 'avatars',
]];

yield 'config_minimal' => [[
'mongodb_uri' => 'mongodb://localhost:27017/',
'database' => 'testing',
]];

yield 'config_full' => [[
'mongodb_uri' => 'mongodb://server1:27017,server2:27017/',
'mongodb_uri_options' => ['appname' => 'flysystem'],
'mongodb_driver_options' => ['disableClientPersistence' => false],
'database' => 'testing',
'bucket' => 'avatars',
]];

yield 'service' => [[
'bucket' => 'bucket',
]];
}

/**
* @dataProvider provideValidOptions
*/
public function testCreateDefinition($options)
{
$this->assertSame(GridFSAdapter::class, $this->createBuilder()->createDefinition($options, null)->getClass());
}

public static function provideInvalidOptions(): \Generator
{
yield 'empty' => [
[],
'Flysystem GridFS configuration requires a "bucket" service name, a "mongodb_uri" or a "doctrine_connection" name',
];

yield 'no database with mongodb_uri' => [
['mongodb_uri' => 'mongodb://127.0.0.1:27017/'],
'MongoDB "database" name is required for Flysystem GridFS configuration',
];

yield 'both doctrine_connection and mongodb_uri' => [
['doctrine_connection' => 'default', 'mongodb_uri' => 'mongodb://127.0.0.1:27017/'],
'In GridFS configuration, "doctrine_connection" and "mongodb_uri" options cannot be set together.',
];
}

/**
* @dataProvider provideInvalidOptions
*/
public function testInvalidOptions(array $options, string $message)
{
$builder = $this->createBuilder();

$this->expectException(InvalidArgumentException::class);
$this->expectExceptionMessage($message);

$builder->createDefinition($options, null);
}

public function testInitializeBucketFromDocumentManager()
{
$client = new Client();
$config = new Configuration();
$config->setDefaultDB('testing');
$dm = $this->createMock(DocumentManager::class);
$dm->expects($this->once())->method('getClient')->willReturn($client);
$dm->expects($this->once())->method('getConfiguration')->willReturn($config);

$bucket = GridFSAdapterDefinitionBuilder::initializeBucketFromDocumentManager($dm, null, 'avatars');

$this->assertInstanceOf(Bucket::class, $bucket);
$this->assertSame('testing', $bucket->getDatabaseName());
$this->assertSame('avatars', $bucket->getBucketName());
}

public function testInitializeBucketFromConfig()
{
$bucket = GridFSAdapterDefinitionBuilder::initializeBucketFromConfig(
'mongodb://server:27017/',
['appname' => 'flysystem'],
['disableClientPersistence' => false],
'testing',
'avatars'
);

$this->assertInstanceOf(Bucket::class, $bucket);
$this->assertSame('testing', $bucket->getDatabaseName());
$this->assertSame('avatars', $bucket->getBucketName());
}
}

0 comments on commit f5b375d

Please sign in to comment.