Skip to content

Commit 9bb2081

Browse files
committed
Add docs
1 parent ed91f36 commit 9bb2081

File tree

4 files changed

+232
-42
lines changed

4 files changed

+232
-42
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ All notable changes to this project will be documented in this file.
99
* Rename queue option `table` to `collection`
1010
* Replace queue option `expire` with `retry_after`
1111
* Revert behavior of `createOrFirst` to delegate to `firstOrCreate` when in transaction by @GromNaN in [#2984](https://github.com/mongodb/laravel-mongodb/pull/2984)
12+
* Add GridFS integration for Laravel File Storage by @GromNaN in [#2984](https://github.com/mongodb/laravel-mongodb/pull/2985)
1213

1314
## [4.3.1]
1415

docs/filesystems.txt

Lines changed: 144 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,144 @@
1+
.. _laravel-queues:
2+
3+
======
4+
Queues
5+
======
6+
7+
.. facet::
8+
:name: genre
9+
:values: tutorial
10+
11+
.. meta::
12+
:keywords: php framework, gridfs, code example
13+
14+
Overview
15+
--------
16+
17+
Using MongoDB to store files can be done using the
18+
`GridFS Adapter for Flysystem <https://flysystem.thephpleague.com/docs/adapter/gridfs/>`__.
19+
GridFS lets you store files of unlimited size in the same database as your data.
20+
This ensures the integrity of transactions and backups.
21+
22+
23+
Configuration
24+
-------------
25+
26+
Before using the GridFS driver, you will need to install the Flysystem GridFS package via the Composer package manager:
27+
28+
.. code-block:: bash
29+
30+
composer require league/flysystem-gridfs
31+
32+
Configure `Laravel File Storage <https://laravel.com/docs/{+laravel-docs-version+}/filesystem>`__,
33+
to use the ``gridfs`` driver in ``config/filesystems.php``:
34+
35+
.. code-block:: php
36+
37+
'disks' => [
38+
'gridfs' => [
39+
'driver' => 'gridfs',
40+
'connection' => 'mongodb',
41+
'database' => 'files',
42+
'bucket' => 'fs',
43+
'prefix' => '',
44+
'read-only' => false,
45+
'throw' => false,
46+
],
47+
],
48+
49+
.. list-table::
50+
:header-rows: 1
51+
:widths: 25 75
52+
53+
* - Setting
54+
- Description
55+
56+
* - ``driver``
57+
- **Required**. Specifies the filesystem driver to use. Must be ``gridfs`` for MongoDB.
58+
59+
* - ``connection``
60+
- The database connection used to store jobs. It must be a ``mongodb`` connection. The driver uses the default connection if a connection is not specified.
61+
62+
* - ``database``
63+
- Name of the MongoDB database fot the GridFS bucket. Use the database of the connection if not specified.
64+
65+
* - ``bucket``
66+
- Name of the GridFS bucket. A database can contain multiple buckets identified by their name. Defaults to ``fs``.
67+
68+
* - ``prefix``
69+
- Specifies a prefix for the name of the files that are stored in the bucket. Using a distinct bucket is recommended
70+
in order to store the files in a different collection.
71+
72+
* - ``read-only``
73+
- If ``true``, writing to the GridFS bucket is disabled. Write operations will return ``false`` or throw exceptions
74+
depending on the configuration of ``throw``. Defaults to ``false``.
75+
76+
* - ``throw``
77+
- If ``true``, exceptions are thrown when an operation cannot be performed. Defaults to ``false``, the operations
78+
return ``true`` in case of succes, ``false`` in case of error.
79+
80+
If you have specific requirements, you can use a factory or a service name to define the bucket. The options
81+
``connection`` and ``database`` are ignored when a ``MongoDB\GridFS\Bucket`` is provided:
82+
83+
.. code-block:: php
84+
85+
use Illuminate\Foundation\Application;
86+
use MongoDB\GridFS\Bucket;
87+
88+
'disks' => [
89+
'gridfs' => [
90+
'driver' => 'gridfs',
91+
'bucket' => static function (Application $app): Bucket {
92+
return $app['db']->connection('mongodb')
93+
->getMongoDB()
94+
->selectGridFSBucket([
95+
'bucketName' => 'avatars',
96+
'chunkSizeBytes' => 261120,
97+
]);
98+
},
99+
],
100+
],
101+
102+
Usage
103+
-----
104+
105+
The benefits of using Laravel File Storage facade, is that it provides a common
106+
interface for all the supported file systems. Use the ``gridfs`` disk in the
107+
same way as the ``local`` disk.
108+
109+
.. code-block:: php
110+
111+
$disk = Storage::disk('gridfs');
112+
113+
// Write the file "hello.txt" into GridFS
114+
$disk->put('hello.txt', 'Hello World!');
115+
116+
// Read the file
117+
echo $disk->get('hello.txt'); // Hello World!
118+
119+
To learn more Laravel File Storage, see
120+
`Laravel File Storage <https://laravel.com/docs/{+laravel-docs-version+}/filesystem>`__.
121+
122+
Versioning
123+
----------
124+
125+
In GridFS, file names are metadata to file objects identified by unique MongoDB ObjectID.
126+
There may be more than one file with the same name, they are called "revisions":
127+
128+
- Reading a file reads the last revision of this file name
129+
- Writing to a file name creates a new revision for this file name
130+
- Renaming a file renames all the revisions of this file name
131+
- Deleting a file deletes all the revisions of this file name
132+
133+
The GridFS Adapter for Flysystem does not provide access to a specific revision
134+
of a filename, you must use the :manual:`GridFS API </tutorial/gridfs/>` if you
135+
need to work with revisions.
136+
137+
.. code-block:: php
138+
139+
// Create a MongoDB Bucket service from the MongoDB connection
140+
/** @var \MongoDB\GridFS\Bucket $bucket */
141+
$bucket = $app['db']->connection('mongodb')->getMongoDB()->selectGridFSBucket();
142+
143+
// Download the last but one version of a file
144+
$bucket->openDownloadStreamByName('hello.txt', ['revision' => -2])

src/MongoDBServiceProvider.php

Lines changed: 20 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -21,10 +21,9 @@
2121
use MongoDB\Laravel\Queue\MongoConnector;
2222
use RuntimeException;
2323

24-
use function array_keys;
2524
use function assert;
2625
use function class_exists;
27-
use function implode;
26+
use function get_debug_type;
2827
use function is_string;
2928
use function sprintf;
3029

@@ -93,38 +92,30 @@ private function registerFlysystemAdapter(): void
9392
throw new RuntimeException('GridFS adapter for Flysystem is missing. Try running "composer require league/flysystem-gridfs"');
9493
}
9594

96-
// Reuse an existing database connection
97-
if (isset($config['connection'])) {
98-
if (isset($config['mongodb_uri'])) {
99-
throw new InvalidArgumentException('In GridFS configuration, "connection" and "mongodb_uri" options cannot be set together.');
100-
}
95+
$bucket = $config['bucket'] ?? null;
96+
97+
// Get the bucket from a factory function
98+
if ($bucket instanceof Closure) {
99+
$bucket = $bucket($app, $config);
100+
}
101101

102+
// Get the bucket from a service
103+
if (is_string($bucket) && $app->has($bucket)) {
104+
$bucket = $app->get($bucket);
105+
}
106+
107+
// Get the bucket from the database connection
108+
if (is_string($bucket) || $bucket === null) {
102109
$connection = $app['db']->connection($config['connection']);
103110
if (! $connection instanceof Connection) {
104-
throw new InvalidArgumentException(sprintf('The database connection "%s" does not use the "mongodb" driver.', $connection['connection']));
111+
throw new InvalidArgumentException(sprintf('The database connection "%s" does not use the "mongodb" driver.', $config['connection'] ?? $app['config']['database.default']));
105112
}
106113

107114
$bucket = $connection->getMongoClient()
108115
->selectDatabase($config['database'] ?? $connection->getDatabaseName())
109116
->selectGridFSBucket(['bucketName' => $config['bucket'] ?? 'fs', 'disableMD5' => true]);
110-
111-
// Allows setting the bucket instance directly
112-
} elseif (isset($config['bucket'])) {
113-
$bucket = $config['bucket'];
114-
// Resolves the "bucket" service
115-
if (is_string($bucket)) {
116-
$bucket = $app->get($bucket);
117-
} elseif ($bucket instanceof Closure) {
118-
$bucket = $bucket($app, $config);
119-
}
120-
121-
if (! $bucket instanceof Bucket) {
122-
throw new InvalidArgumentException(sprintf('Provided GridFS bucket is not a instance of "%s"', Bucket::class));
123-
}
124-
}
125-
126-
if (! isset($bucket)) {
127-
throw new InvalidArgumentException(sprintf('The "gridfs" configuration requires the "connection", "mongodb_uri", or "bucket" option. Got "%s"', implode('", "', array_keys($config))));
117+
} elseif (! $bucket instanceof Bucket) {
118+
throw new InvalidArgumentException(sprintf('Unexpected value for GridFS "bucket" configuration. Expecting "%s". Got "%s"', Bucket::class, get_debug_type($bucket)));
128119
}
129120

130121
$adapter = new GridFSAdapter($bucket, $config['prefix'] ?? '');
@@ -138,6 +129,9 @@ private function registerFlysystemAdapter(): void
138129
$adapter = new ReadOnlyFilesystemAdapter($adapter);
139130
}
140131

132+
/** Prevent using backslash on Windows in {@see FilesystemAdapter::__construct()} */
133+
$config['directory_separator'] = '/';
134+
141135
return new FilesystemAdapter(new Filesystem($adapter, $config), $adapter, $config);
142136
});
143137
});

tests/FilesystemsTest.php

Lines changed: 67 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -6,33 +6,35 @@
66
use Illuminate\Foundation\Application;
77
use Illuminate\Support\Facades\DB;
88
use Illuminate\Support\Facades\Storage;
9+
use InvalidArgumentException;
910
use League\Flysystem\UnableToWriteFile;
11+
use MongoDB\GridFS\Bucket;
1012
use PHPUnit\Framework\Attributes\DataProvider;
13+
use stdClass;
1114

1215
use function env;
1316
use function microtime;
17+
use function stream_get_contents;
1418

1519
class FilesystemsTest extends TestCase
1620
{
1721
public function tearDown(): void
1822
{
19-
DB::connection('mongodb')->getMongoDB()->drop();
23+
$this->getBucket()->drop();
2024

2125
parent::tearDown();
2226
}
2327

24-
public static function provideDiskConfigurations(): Generator
28+
public static function provideValidOptions(): Generator
2529
{
26-
yield [
27-
'gridfs-connection-minimal',
30+
yield 'connection-minimal' => [
2831
[
2932
'driver' => 'gridfs',
3033
'connection' => 'mongodb',
3134
],
3235
];
3336

34-
yield [
35-
'gridfs-connection-full',
37+
yield 'connection-full' => [
3638
[
3739
'driver' => 'gridfs',
3840
'connection' => 'mongodb',
@@ -42,16 +44,14 @@ public static function provideDiskConfigurations(): Generator
4244
],
4345
];
4446

45-
yield [
46-
'gridfs-bucket-service',
47+
yield 'bucket-service' => [
4748
[
4849
'driver' => 'gridfs',
4950
'bucket' => 'bucket',
5051
],
5152
];
5253

53-
yield [
54-
'gridfs-bucket-factory',
54+
yield 'bucket-factory' => [
5555
[
5656
'driver' => 'gridfs',
5757
'bucket' => static fn (Application $app) => $app['db']
@@ -62,22 +62,54 @@ public static function provideDiskConfigurations(): Generator
6262
];
6363
}
6464

65-
#[DataProvider('provideDiskConfigurations')]
66-
public function testConfig(string $name, array $options)
65+
#[DataProvider('provideValidOptions')]
66+
public function testValidOptions(array $options)
6767
{
68-
// Service used by "gridfs-bucket-service"
68+
// Service used by "bucket-service"
6969
$this->app->singleton('bucket', static fn (Application $app) => $app['db']
7070
->connection('mongodb')
7171
->getMongoDB()
7272
->selectGridFSBucket());
7373

74-
$this->app['config']->set('filesystems.disks.' . $name, $options);
74+
$this->app['config']->set('filesystems.disks.' . $this->dataName(), $options);
7575

76-
$disk = Storage::disk($name);
77-
$disk->put($filename = $name . '.txt', $value = microtime());
76+
$disk = Storage::disk($this->dataName());
77+
$disk->put($filename = $this->dataName() . '.txt', $value = microtime());
7878
$this->assertEquals($value, $disk->get($filename), 'File saved');
7979
}
8080

81+
public static function provideInvalidOptions(): Generator
82+
{
83+
yield 'not-mongodb-connection' => [
84+
['driver' => 'gridfs', 'connection' => 'sqlite'],
85+
'The database connection "sqlite" does not use the "mongodb" driver.',
86+
];
87+
88+
yield 'factory-not-bucket' => [
89+
['driver' => 'gridfs', 'bucket' => static fn () => new stdClass()],
90+
'Unexpected value for GridFS "bucket" configuration. Expecting "MongoDB\GridFS\Bucket". Got "stdClass"',
91+
];
92+
93+
yield 'service-not-bucket' => [
94+
['driver' => 'gridfs', 'bucket' => 'bucket'],
95+
'Unexpected value for GridFS "bucket" configuration. Expecting "MongoDB\GridFS\Bucket". Got "stdClass"',
96+
];
97+
}
98+
99+
#[DataProvider('provideInvalidOptions')]
100+
public function testInvalidOptions(array $options, string $message)
101+
{
102+
// Service used by "service-not-bucket"
103+
$this->app->singleton('bucket', static fn () => new stdClass());
104+
105+
$this->app['config']->set('filesystems.disks.' . $this->dataName(), $options);
106+
107+
$this->expectException(InvalidArgumentException::class);
108+
$this->expectExceptionMessage($message);
109+
110+
Storage::disk($this->dataName());
111+
}
112+
81113
public function testReadOnlyAndThrowOption()
82114
{
83115
$this->app['config']->set('filesystems.disks.gridfs-readonly', [
@@ -96,4 +128,23 @@ public function testReadOnlyAndThrowOption()
96128

97129
$disk->put('file.txt', '');
98130
}
131+
132+
public function testPrefix()
133+
{
134+
$this->app['config']->set('filesystems.disks.gridfs-prefix', [
135+
'driver' => 'gridfs',
136+
'connection' => 'mongodb',
137+
'prefix' => 'foo/bar/',
138+
]);
139+
140+
$disk = Storage::disk('gridfs-prefix');
141+
$disk->put('hello/world.txt', 'Hello World!');
142+
$this->assertSame('Hello World!', $disk->get('hello/world.txt'));
143+
$this->assertSame('Hello World!', stream_get_contents($this->getBucket()->openDownloadStreamByName('foo/bar/hello/world.txt')), 'File name is prefixed in the bucket');
144+
}
145+
146+
private function getBucket(): Bucket
147+
{
148+
return DB::connection('mongodb')->getMongoDB()->selectGridFSBucket();
149+
}
99150
}

0 commit comments

Comments
 (0)