Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commit b1147cd

Browse files
authored
PHPLIB-1206 Add bucket alises for context resolver using GridFS StreamWrapper (#1138)
1 parent 91f3367 commit b1147cd

File tree

10 files changed

+678
-50
lines changed

10 files changed

+678
-50
lines changed

‎docs/reference/class/MongoDBGridFSBucket.txt‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,5 +55,6 @@ Methods
5555
/reference/method/MongoDBGridFSBucket-openDownloadStream
5656
/reference/method/MongoDBGridFSBucket-openDownloadStreamByName
5757
/reference/method/MongoDBGridFSBucket-openUploadStream
58+
/reference/method/MongoDBGridFSBucket-registerGlobalStreamWrapperAlias
5859
/reference/method/MongoDBGridFSBucket-rename
5960
/reference/method/MongoDBGridFSBucket-uploadFromStream
Lines changed: 132 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,132 @@
1+
===========================================================
2+
MongoDB\\GridFS\\Bucket::registerGlobalStreamWrapperAlias()
3+
===========================================================
4+
5+
.. versionadded:: 1.18
6+
7+
.. default-domain:: mongodb
8+
9+
.. contents:: On this page
10+
:local:
11+
:backlinks: none
12+
:depth: 1
13+
:class: singlecol
14+
15+
Definition
16+
----------
17+
18+
.. phpmethod:: MongoDB\\GridFS\\Bucket::registerGlobalStreamWrapperAlias()
19+
20+
Registers an alias for the bucket, which enables files within the bucket to
21+
be accessed using a basic filename string (e.g.
22+
`gridfs://<bucket-alias>/<filename>`).
23+
24+
.. code-block:: php
25+
26+
function registerGlobalStreamWrapperAlias(string $alias): void
27+
28+
Parameters
29+
----------
30+
31+
``$alias`` : array
32+
A non-empty string used to identify the GridFS bucket when accessing files
33+
using the ``gridfs://`` stream wrapper.
34+
35+
Behavior
36+
--------
37+
38+
After registering an alias for the bucket, the most recent revision of a file
39+
can be accessed using a filename string in the form ``gridfs://<bucket-alias>/<filename>``.
40+
41+
Supported stream functions:
42+
43+
- :php:`copy() <copy>`
44+
- :php:`file_exists() <file_exists>`
45+
- :php:`file_get_contents() <file_get_contents>`
46+
- :php:`file_put_contents() <file_put_contents>`
47+
- :php:`filemtime() <filemtime>`
48+
- :php:`filesize() <filesize>`
49+
- :php:`file() <file>`
50+
- :php:`fopen() <fopen>` (with "r", "rb", "w", and "wb" modes)
51+
52+
In read mode, the stream context can contain the option ``gridfs['revision']``
53+
to specify the revision number of the file to read. If omitted, the most recent
54+
revision is read (revision ``-1``).
55+
56+
In write mode, the stream context can contain the option ``gridfs['chunkSizeBytes']``.
57+
If omitted, the defaults are inherited from the ``Bucket`` instance option.
58+
59+
Example
60+
-------
61+
62+
Read and write to a GridFS bucket using the ``gridfs://`` stream wrapper
63+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
64+
65+
The following example demonstrates how to register an alias for a GridFS bucket
66+
and use the functions ``file_exists()``, ``file_get_contents()``, and
67+
``file_put_contents()`` to read and write to the bucket.
68+
69+
Each call to these functions makes a request to the server.
70+
71+
.. code-block:: php
72+
73+
<?php
74+
75+
$database = (new MongoDB\Client)->selectDatabase('test');
76+
$bucket = $database->selectGridFSBucket();
77+
78+
$bucket->registerGlobalStreamWrapperAlias('mybucket');
79+
80+
var_dump(file_exists('gridfs://mybucket/hello.txt'));
81+
82+
file_put_contents('gridfs://mybucket/hello.txt', 'Hello, GridFS!');
83+
84+
var_dump(file_exists('gridfs://mybucket/hello.txt'));
85+
86+
echo file_get_contents('gridfs://mybucket/hello.txt');
87+
88+
The output would then resemble:
89+
90+
.. code-block:: none
91+
92+
bool(false)
93+
bool(true)
94+
Hello, GridFS!
95+
96+
Read a specific revision of a file
97+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
98+
99+
Using a stream context, you can specify the revision number of the file to
100+
read. If omitted, the most recent revision is read.
101+
102+
.. code-block:: php
103+
104+
<?php
105+
106+
$database = (new MongoDB\Client)->selectDatabase('test');
107+
$bucket = $database->selectGridFSBucket();
108+
109+
$bucket->registerGlobalStreamWrapperAlias('mybucket');
110+
111+
// Creating revision 0
112+
$handle = fopen('gridfs://mybucket/hello.txt', 'w');
113+
fwrite($handle, 'Hello, GridFS! (v0)');
114+
fclose($handle);
115+
116+
// Creating revision 1
117+
$handle = fopen('gridfs://mybucket/hello.txt', 'w');
118+
fwrite($handle, 'Hello, GridFS! (v1)');
119+
fclose($handle);
120+
121+
// Read revision 0
122+
$context = stream_context_create([
123+
'gridfs' => ['revision' => 0],
124+
]);
125+
$handle = fopen('gridfs://mybucket/hello.txt', 'r', false, $context);
126+
echo fread($handle, 1024);
127+
128+
The output would then resemble:
129+
130+
.. code-block:: none
131+
132+
Hello, GridFS! (v0)

‎examples/gridfs-stream-wrapper.php‎

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
<?php
2+
3+
/**
4+
* For applications that need to interact with GridFS using only a filename string,
5+
* a bucket can be registered with an alias. Files can then be accessed using the
6+
* following pattern: gridfs://<bucket-alias>/<filename>
7+
*/
8+
9+
declare(strict_types=1);
10+
11+
namespace MongoDB\Examples;
12+
13+
use MongoDB\Client;
14+
15+
use function file_exists;
16+
use function file_get_contents;
17+
use function file_put_contents;
18+
use function getenv;
19+
use function stream_context_create;
20+
21+
use const PHP_EOL;
22+
23+
require __DIR__ . '/../vendor/autoload.php';
24+
25+
$client = new Client(getenv('MONGODB_URI') ?: 'mongodb://127.0.0.1/');
26+
$bucket = $client->test->selectGridFSBucket();
27+
$bucket->drop();
28+
29+
// Register the alias "mybucket" for default bucket of the "test" database
30+
$bucket->registerGlobalStreamWrapperAlias('mybucket');
31+
32+
echo 'File exists: ';
33+
echo file_exists('gridfs://mybucket/hello.txt') ? 'yes' : 'no';
34+
echo PHP_EOL;
35+
36+
echo 'Writing file';
37+
file_put_contents('gridfs://mybucket/hello.txt', 'Hello, GridFS!');
38+
echo PHP_EOL;
39+
40+
echo 'File exists: ';
41+
echo file_exists('gridfs://mybucket/hello.txt') ? 'yes' : 'no';
42+
echo PHP_EOL;
43+
44+
echo 'Reading file: ';
45+
echo file_get_contents('gridfs://mybucket/hello.txt');
46+
echo PHP_EOL;
47+
48+
echo 'Writing new version of the file';
49+
file_put_contents('gridfs://mybucket/hello.txt', 'Hello, GridFS! (v2)');
50+
echo PHP_EOL;
51+
52+
echo 'Reading new version of the file: ';
53+
echo file_get_contents('gridfs://mybucket/hello.txt');
54+
echo PHP_EOL;
55+
56+
echo 'Reading previous version of the file: ';
57+
$context = stream_context_create(['gridfs' => ['revision' => -2]]);
58+
echo file_get_contents('gridfs://mybucket/hello.txt', false, $context);
59+
echo PHP_EOL;

‎psalm-baseline.xml‎

Lines changed: 10 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,9 @@
8181
<code><![CDATA[$options['revision']]]></code>
8282
<code><![CDATA[$options['revision']]]></code>
8383
</MixedArgument>
84+
<MixedArgumentTypeCoercion>
85+
<code>$context</code>
86+
</MixedArgumentTypeCoercion>
8487
</file>
8588
<file src="src/GridFS/ReadableStream.php">
8689
<MixedArgument>
@@ -89,20 +92,13 @@
8992
</MixedArgument>
9093
</file>
9194
<file src="src/GridFS/StreamWrapper.php">
92-
<MixedArgument>
93-
<code><![CDATA[$context[$this->protocol]['collectionWrapper']]]></code>
94-
<code><![CDATA[$context[$this->protocol]['collectionWrapper']]]></code>
95-
<code><![CDATA[$context[$this->protocol]['file']]]></code>
96-
<code><![CDATA[$context[$this->protocol]['filename']]]></code>
97-
<code><![CDATA[$context[$this->protocol]['options']]]></code>
98-
</MixedArgument>
99-
<MixedArrayAccess>
100-
<code><![CDATA[$context[$this->protocol]['collectionWrapper']]]></code>
101-
<code><![CDATA[$context[$this->protocol]['collectionWrapper']]]></code>
102-
<code><![CDATA[$context[$this->protocol]['file']]]></code>
103-
<code><![CDATA[$context[$this->protocol]['filename']]]></code>
104-
<code><![CDATA[$context[$this->protocol]['options']]]></code>
105-
</MixedArrayAccess>
95+
<InvalidArgument>
96+
<code>$context</code>
97+
<code>$context</code>
98+
</InvalidArgument>
99+
<MixedAssignment>
100+
<code>$context</code>
101+
</MixedAssignment>
106102
</file>
107103
<file src="src/Model/BSONArray.php">
108104
<MixedAssignment>

‎src/GridFS/Bucket.php‎

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
use MongoDB\Exception\UnsupportedException;
3232
use MongoDB\GridFS\Exception\CorruptFileException;
3333
use MongoDB\GridFS\Exception\FileNotFoundException;
34+
use MongoDB\GridFS\Exception\LogicException;
3435
use MongoDB\GridFS\Exception\StreamException;
3536
use MongoDB\Model\BSONArray;
3637
use MongoDB\Model\BSONDocument;
@@ -39,6 +40,7 @@
3940
use function array_intersect_key;
4041
use function array_key_exists;
4142
use function assert;
43+
use function explode;
4244
use function fopen;
4345
use function get_resource_type;
4446
use function in_array;
@@ -54,6 +56,7 @@
5456
use function MongoDB\BSON\toJSON;
5557
use function property_exists;
5658
use function sprintf;
59+
use function str_contains;
5760
use function stream_context_create;
5861
use function stream_copy_to_stream;
5962
use function stream_get_meta_data;
@@ -587,6 +590,29 @@ public function openUploadStream(string $filename, array $options = [])
587590
return fopen($path, 'w', false, $context);
588591
}
589592

593+
/**
594+
* Register an alias to enable basic filename access for this bucket.
595+
*
596+
* For applications that need to interact with GridFS using only a filename
597+
* string, a bucket can be registered with an alias. Files can then be
598+
* accessed using the following pattern:
599+
*
600+
* gridfs://<bucket-alias>/<filename>
601+
*
602+
* Read operations will always target the most recent revision of a file.
603+
*
604+
* @param non-empty-string string $alias The alias to use for the bucket
605+
*/
606+
public function registerGlobalStreamWrapperAlias(string $alias): void
607+
{
608+
if ($alias === '' || str_contains($alias, '/')) {
609+
throw new InvalidArgumentException(sprintf('The bucket alias must be a non-empty string without any slash, "%s" given', $alias));
610+
}
611+
612+
// Use a closure to expose the private method into another class
613+
StreamWrapper::setContextResolver($alias, fn (string $path, string $mode, array $context) => $this->resolveStreamContext($path, $mode, $context));
614+
}
615+
590616
/**
591617
* Renames the GridFS file with the specified ID.
592618
*
@@ -756,4 +782,50 @@ private function registerStreamWrapper(): void
756782

757783
StreamWrapper::register(self::STREAM_WRAPPER_PROTOCOL);
758784
}
785+
786+
/**
787+
* Create a stream context from the path and mode provided to fopen().
788+
*
789+
* @see StreamWrapper::setContextResolver()
790+
*
791+
* @param string $path The full url provided to fopen(). It contains the filename.
792+
* gridfs://database_name/collection_name.files/file_name
793+
* @param array{revision?: int, chunkSizeBytes?: int, disableMD5?: bool} $context The options provided to fopen()
794+
*
795+
* @return array{collectionWrapper: CollectionWrapper, file: object}|array{collectionWrapper: CollectionWrapper, filename: string, options: array}
796+
*
797+
* @throws FileNotFoundException
798+
* @throws LogicException
799+
*/
800+
private function resolveStreamContext(string $path, string $mode, array $context): array
801+
{
802+
// Fallback to an empty filename if the path does not contain one: "gridfs://alias"
803+
$filename = explode('/', $path, 4)[3] ?? '';
804+
805+
if ($mode === 'r' || $mode === 'rb') {
806+
$file = $this->collectionWrapper->findFileByFilenameAndRevision($filename, $context['revision'] ?? -1);
807+
808+
if (! is_object($file)) {
809+
throw FileNotFoundException::byFilenameAndRevision($filename, $context['revision'] ?? -1, $path);
810+
}
811+
812+
return [
813+
'collectionWrapper' => $this->collectionWrapper,
814+
'file' => $file,
815+
];
816+
}
817+
818+
if ($mode === 'w' || $mode === 'wb') {
819+
return [
820+
'collectionWrapper' => $this->collectionWrapper,
821+
'filename' => $filename,
822+
'options' => $context + [
823+
'chunkSizeBytes' => $this->chunkSizeBytes,
824+
'disableMD5' => $this->disableMD5,
825+
],
826+
];
827+
}
828+
829+
throw LogicException::openModeNotSupported($mode);
830+
}
759831
}

0 commit comments

Comments
(0)

AltStyle によって変換されたページ (->オリジナル) /