Skip to content

Commit 847e3c7

Browse files
authored
PHPORM-186 GridFS adapter for Filesystem (#2985)
1 parent 0f64e67 commit 847e3c7

File tree

5 files changed

+383
-0
lines changed

5 files changed

+383
-0
lines changed

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
11
# Changelog
22
All notable changes to this project will be documented in this file.
33

4+
## [4.5.0] - upcoming
5+
6+
* Add GridFS integration for Laravel File Storage by @GromNaN in [#2984](https://github.com/mongodb/laravel-mongodb/pull/2985)
7+
48
## [4.4.0] - 2024-05-31
59

610
* Support collection name prefix by @GromNaN in [#2930](https://github.com/mongodb/laravel-mongodb/pull/2930)

composer.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,8 @@
3434
},
3535
"require-dev": {
3636
"mongodb/builder": "^0.2",
37+
"league/flysystem-gridfs": "^3.28",
38+
"league/flysystem-read-only": "^3.0",
3739
"phpunit/phpunit": "^10.3",
3840
"orchestra/testbench": "^8.0|^9.0",
3941
"mockery/mockery": "^1.4.4",
@@ -45,6 +47,7 @@
4547
"illuminate/bus": "< 10.37.2"
4648
},
4749
"suggest": {
50+
"league/flysystem-gridfs": "Filesystem storage in MongoDB with GridFS",
4851
"mongodb/builder": "Provides a fluent aggregation builder for MongoDB pipelines"
4952
},
5053
"minimum-stability": "dev",

docs/filesystems.txt

Lines changed: 159 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,159 @@
1+
.. _laravel-filesystems:
2+
3+
==================
4+
GridFS Filesystems
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+
You can use the
18+
`GridFS Adapter for Flysystem <https://flysystem.thephpleague.com/docs/adapter/gridfs/>`__
19+
to store large files in MongoDB.
20+
GridFS lets you store files of unlimited size in the same database as your data.
21+
22+
23+
Configuration
24+
-------------
25+
26+
Before using the GridFS driver, install the Flysystem GridFS package through the
27+
Composer package manager by running the following command:
28+
29+
.. code-block:: bash
30+
31+
composer require league/flysystem-gridfs
32+
33+
Configure `Laravel File Storage <https://laravel.com/docs/{+laravel-docs-version+}/filesystem>`__
34+
to use the ``gridfs`` driver in ``config/filesystems.php``:
35+
36+
.. code-block:: php
37+
38+
'disks' => [
39+
'gridfs' => [
40+
'driver' => 'gridfs',
41+
'connection' => 'mongodb',
42+
// 'database' => null,
43+
// 'bucket' => 'fs',
44+
// 'prefix' => '',
45+
// 'read-only' => false,
46+
// 'throw' => false,
47+
],
48+
],
49+
50+
You can configure the disk the following settings in ``config/filesystems.php``:
51+
52+
.. list-table::
53+
:header-rows: 1
54+
:widths: 25 75
55+
56+
* - Setting
57+
- Description
58+
59+
* - ``driver``
60+
- **Required**. Specifies the filesystem driver to use. Must be ``gridfs`` for MongoDB.
61+
62+
* - ``connection``
63+
- 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.
64+
65+
* - ``database``
66+
- Name of the MongoDB database for the GridFS bucket. The driver uses the database of the connection if a database is not specified.
67+
68+
* - ``bucket``
69+
- Name or instance of the GridFS bucket. A database can contain multiple buckets identified by their name. Defaults to ``fs``.
70+
71+
* - ``prefix``
72+
- Specifies a prefix for the name of the files that are stored in the bucket. Using a distinct bucket is recommended
73+
in order to store the files in a different collection, instead of using a prefix.
74+
The prefix should not start with a leading slash ``/``.
75+
76+
* - ``read-only``
77+
- If ``true``, writing to the GridFS bucket is disabled. Write operations will return ``false`` or throw exceptions
78+
depending on the configuration of ``throw``. Defaults to ``false``.
79+
80+
* - ``throw``
81+
- If ``true``, exceptions are thrown when an operation cannot be performed. If ``false``,
82+
operations return ``true`` on success and ``false`` on error. Defaults to ``false``.
83+
84+
You can also use a factory or a service name to create an instance of ``MongoDB\GridFS\Bucket``.
85+
In this case, the options ``connection`` and ``database`` are ignored:
86+
87+
.. code-block:: php
88+
89+
use Illuminate\Foundation\Application;
90+
use MongoDB\GridFS\Bucket;
91+
92+
'disks' => [
93+
'gridfs' => [
94+
'driver' => 'gridfs',
95+
'bucket' => static function (Application $app): Bucket {
96+
return $app['db']->connection('mongodb')
97+
->getMongoDB()
98+
->selectGridFSBucket([
99+
'bucketName' => 'avatars',
100+
'chunkSizeBytes' => 261120,
101+
]);
102+
},
103+
],
104+
],
105+
106+
Usage
107+
-----
108+
109+
A benefit of using Laravel Filesystem is that it provides a common interface
110+
for all the supported file systems. You can use the ``gridfs`` disk in the same
111+
way as the ``local`` disk.
112+
113+
.. code-block:: php
114+
115+
$disk = Storage::disk('gridfs');
116+
117+
// Write the file "hello.txt" into GridFS
118+
$disk->put('hello.txt', 'Hello World!');
119+
120+
// Read the file
121+
echo $disk->get('hello.txt'); // Hello World!
122+
123+
To learn more Laravel File Storage, see
124+
`Laravel File Storage <https://laravel.com/docs/{+laravel-docs-version+}/filesystem>`__
125+
in the Laravel documentation.
126+
127+
Versioning
128+
----------
129+
130+
File names in GridFS are metadata in file documents, which are identified by
131+
unique ObjectIDs. If multiple documents share the same file name, they are
132+
considered "revisions" and further distinguished by creation timestamps.
133+
134+
The Laravel MongoDB integration uses the GridFS Flysystem adapter. It interacts
135+
with file revisions in the following ways:
136+
137+
- Reading a file reads the last revision of this file name
138+
- Writing a file creates a new revision for this file name
139+
- Renaming a file renames all the revisions of this file name
140+
- Deleting a file deletes all the revisions of this file name
141+
142+
The GridFS Adapter for Flysystem does not provide access to a specific revision
143+
of a filename. You must use the
144+
`GridFS API <https://www.mongodb.com/docs/php-library/current/tutorial/gridfs/>`__
145+
if you need to work with revisions, as shown in the following code:
146+
147+
.. code-block:: php
148+
149+
// Create a bucket service from the MongoDB connection
150+
/** @var \MongoDB\GridFS\Bucket $bucket */
151+
$bucket = $app['db']->connection('mongodb')->getMongoDB()->selectGridFSBucket();
152+
153+
// Download the last but one version of a file
154+
$bucket->openDownloadStreamByName('hello.txt', ['revision' => -2])
155+
156+
.. note::
157+
158+
If you use a prefix the Filesystem component, you will have to handle it
159+
by yourself when using the GridFS API directly.

src/MongoDBServiceProvider.php

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,15 +4,28 @@
44

55
namespace MongoDB\Laravel;
66

7+
use Closure;
78
use Illuminate\Cache\CacheManager;
89
use Illuminate\Cache\Repository;
10+
use Illuminate\Filesystem\FilesystemAdapter;
11+
use Illuminate\Filesystem\FilesystemManager;
912
use Illuminate\Foundation\Application;
1013
use Illuminate\Support\ServiceProvider;
14+
use InvalidArgumentException;
15+
use League\Flysystem\Filesystem;
16+
use League\Flysystem\GridFS\GridFSAdapter;
17+
use League\Flysystem\ReadOnly\ReadOnlyFilesystemAdapter;
18+
use MongoDB\GridFS\Bucket;
1119
use MongoDB\Laravel\Cache\MongoStore;
1220
use MongoDB\Laravel\Eloquent\Model;
1321
use MongoDB\Laravel\Queue\MongoConnector;
22+
use RuntimeException;
1423

1524
use function assert;
25+
use function class_exists;
26+
use function get_debug_type;
27+
use function is_string;
28+
use function sprintf;
1629

1730
class MongoDBServiceProvider extends ServiceProvider
1831
{
@@ -66,5 +79,59 @@ public function register()
6679
return new MongoConnector($this->app['db']);
6780
});
6881
});
82+
83+
$this->registerFlysystemAdapter();
84+
}
85+
86+
private function registerFlysystemAdapter(): void
87+
{
88+
// GridFS adapter for filesystem
89+
$this->app->resolving('filesystem', static function (FilesystemManager $filesystemManager) {
90+
$filesystemManager->extend('gridfs', static function (Application $app, array $config) {
91+
if (! class_exists(GridFSAdapter::class)) {
92+
throw new RuntimeException('GridFS adapter for Flysystem is missing. Try running "composer require league/flysystem-gridfs"');
93+
}
94+
95+
$bucket = $config['bucket'] ?? null;
96+
97+
if ($bucket instanceof Closure) {
98+
// Get the bucket from a factory function
99+
$bucket = $bucket($app, $config);
100+
} elseif (is_string($bucket) && $app->has($bucket)) {
101+
// Get the bucket from a service
102+
$bucket = $app->get($bucket);
103+
} elseif (is_string($bucket) || $bucket === null) {
104+
// Get the bucket from the database connection
105+
$connection = $app['db']->connection($config['connection']);
106+
if (! $connection instanceof Connection) {
107+
throw new InvalidArgumentException(sprintf('The database connection "%s" does not use the "mongodb" driver.', $config['connection'] ?? $app['config']['database.default']));
108+
}
109+
110+
$bucket = $connection->getMongoClient()
111+
->selectDatabase($config['database'] ?? $connection->getDatabaseName())
112+
->selectGridFSBucket(['bucketName' => $config['bucket'] ?? 'fs', 'disableMD5' => true]);
113+
}
114+
115+
if (! $bucket instanceof Bucket) {
116+
throw new InvalidArgumentException(sprintf('Unexpected value for GridFS "bucket" configuration. Expecting "%s". Got "%s"', Bucket::class, get_debug_type($bucket)));
117+
}
118+
119+
$adapter = new GridFSAdapter($bucket, $config['prefix'] ?? '');
120+
121+
/** @see FilesystemManager::createFlysystem() */
122+
if ($config['read-only'] ?? false) {
123+
if (! class_exists(ReadOnlyFilesystemAdapter::class)) {
124+
throw new RuntimeException('Read-only Adapter for Flysystem is missing. Try running "composer require league/flysystem-read-only"');
125+
}
126+
127+
$adapter = new ReadOnlyFilesystemAdapter($adapter);
128+
}
129+
130+
/** Prevent using backslash on Windows in {@see FilesystemAdapter::__construct()} */
131+
$config['directory_separator'] = '/';
132+
133+
return new FilesystemAdapter(new Filesystem($adapter, $config), $adapter, $config);
134+
});
135+
});
69136
}
70137
}

0 commit comments

Comments
 (0)