Skip to content

Commit 6ebcc42

Browse files
committed
Use bucket alias for stream wrapper
1 parent 082080f commit 6ebcc42

File tree

5 files changed

+46
-54
lines changed

5 files changed

+46
-54
lines changed

src/GridFS/Bucket.php

Lines changed: 14 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@
3939
use function array_intersect_key;
4040
use function array_key_exists;
4141
use function assert;
42+
use function explode;
4243
use function fopen;
4344
use function get_resource_type;
4445
use function in_array;
@@ -55,14 +56,10 @@
5556
use function property_exists;
5657
use function sprintf;
5758
use function str_contains;
58-
use function str_starts_with;
5959
use function stream_context_create;
6060
use function stream_copy_to_stream;
6161
use function stream_get_meta_data;
6262
use function stream_get_wrappers;
63-
use function strlen;
64-
use function substr;
65-
use function urldecode;
6663
use function urlencode;
6764

6865
/**
@@ -596,12 +593,18 @@ public function openUploadStream(string $filename, array $options = [])
596593
* For legacy applications that needs to open stream from a string, the bucket can be registered in the stream
597594
* wrapper to be used by default when no GridFS context is provided.
598595
* Supported file name must follow this pattern:
599-
* gridfs://<database_name>/<bucket_name>.fs/<file_name>
596+
* gridfs://<bucket-alias>/<filename>
597+
*
598+
* @param non-empty-string string $alias The alias to use for the bucket
600599
*/
601-
public function registerAsDefaultStreamWrapper(): void
600+
public function registerGlobalAliasForResolutionInStandaloneStreamOpen(string $alias): void
602601
{
602+
if ($alias === '' || str_contains($alias, '/')) {
603+
throw new InvalidArgumentException(sprintf('The bucket alias must be a non-empty string without any slash, "%s" given', $alias));
604+
}
605+
603606
// Use a closure to expose the private method into another class
604-
StreamWrapper::setDefaultContextResolver(fn (string $path, string $mode) => $this->resolveStreamContext($path, $mode));
607+
StreamWrapper::setContextResolver($alias, fn (string $path, string $mode) => $this->resolveStreamContext($path, $mode));
605608
}
606609

607610
/**
@@ -775,10 +778,9 @@ private function registerStreamWrapper(): void
775778
}
776779

777780
/**
778-
* Create a stream context from
781+
* Create a stream context from the path and mode provided to fopen().
779782
*
780-
* @see StreamWrapper::setDefaultContextResolver()
781-
* @see stream_context_create()
783+
* @see StreamWrapper::setContextResolver()
782784
*
783785
* @param string $path The full url provided to fopen(). It contains the filename.
784786
* gridfs://database_name/collection_name.files/file_name
@@ -787,13 +789,8 @@ private function registerStreamWrapper(): void
787789
*/
788790
private function resolveStreamContext(string $path, string $mode): ?array
789791
{
790-
// The file can be read only if it belongs to this bucket
791-
$basePath = $this->createPathForFile((object) ['_id' => '']);
792-
if (! str_starts_with($path, $basePath)) {
793-
return null;
794-
}
795-
796-
$filename = urldecode(substr($path, strlen($basePath)));
792+
$parts = explode('/', $path, 4);
793+
$filename = $parts[3] ?? '';
797794

798795
if (str_contains($mode, 'r')) {
799796
$file = $this->collectionWrapper->findFileByFilenameAndRevision($filename, -1);

src/GridFS/StreamWrapper.php

Lines changed: 20 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
use MongoDB\BSON\UTCDateTime;
2222

2323
use function assert;
24+
use function count;
2425
use function explode;
2526
use function in_array;
2627
use function is_array;
@@ -59,8 +60,8 @@ class StreamWrapper
5960
/** @var ReadableStream|WritableStream|null */
6061
private $stream;
6162

62-
/** @var Closure(string, string): ContextOptions|null */
63-
private static ?Closure $contextResolver = null;
63+
/** @var array<string, Closure(string, string): ContextOptions|null> */
64+
private static array $contextResolvers = [];
6465

6566
public function __destruct()
6667
{
@@ -100,9 +101,9 @@ public static function register(string $protocol = 'gridfs'): void
100101
*
101102
* @param Closure(string, string):ContextOptions|null $resolver
102103
*/
103-
public static function setDefaultContextResolver(?Closure $resolver): void
104+
public static function setContextResolver(string $name, ?Closure $resolver): void
104105
{
105-
self::$contextResolver = $resolver;
106+
self::$contextResolvers[$name] = $resolver;
106107
}
107108

108109
/**
@@ -144,24 +145,34 @@ public function stream_eof(): bool
144145
*/
145146
public function stream_open(string $path, string $mode, int $options, ?string &$openedPath): bool
146147
{
147-
$protocol = $this->initProtocol($path);
148+
[$protocol] = explode('://', $path, 2);
148149

149150
assert(is_resource($this->context));
150151
$contextOptions = stream_context_get_options($this->context)[$protocol] ?? null;
151152

152153
if ($contextOptions === null) {
153-
if (! isset(self::$contextResolver)) {
154+
$parts = explode('/', $path, 4);
155+
156+
if (count($parts) < 4) {
157+
if ($options & STREAM_REPORT_ERRORS) {
158+
trigger_error(sprintf('Invalid GridFS file name: "%s"', $path), E_USER_WARNING);
159+
}
160+
161+
return false;
162+
}
163+
164+
if (! isset(self::$contextResolvers[$parts[2]])) {
154165
if ($options & STREAM_REPORT_ERRORS) {
155-
trigger_error(sprintf('No stream context provided for "%s" protocol. Use "%s::setDefaultContextResolver() to provide a default context."', $protocol, self::class), E_USER_WARNING);
166+
trigger_error(sprintf('Unknown GridFS Bucket "%1$s". Call $bucket->asStreamWrap(\'%1$s\') on the Bucket you want to access.', $parts[2]), E_USER_WARNING);
156167
}
157168

158169
return false;
159170
}
160171

161-
$contextOptions = (self::$contextResolver)($path, $mode);
172+
$contextOptions = self::$contextResolvers[$parts[2]]($path, $mode);
162173
if ($contextOptions === null) {
163174
if ($options & STREAM_REPORT_ERRORS) {
164-
trigger_error(sprintf('File not found "%s" with the default GridFS resolver.', $path), E_USER_WARNING);
175+
trigger_error(sprintf('File not found "%s".', $path), E_USER_WARNING);
165176
}
166177

167178
return false;
@@ -325,18 +336,6 @@ private function getStatTemplate(): array
325336
];
326337
}
327338

328-
/**
329-
* Initialize the protocol from the given path.
330-
*
331-
* @see StreamWrapper::stream_open()
332-
*/
333-
private function initProtocol(string $path): string
334-
{
335-
$parts = explode('://', $path, 2);
336-
337-
return $parts[0] ?: 'gridfs';
338-
}
339-
340339
/**
341340
* Initialize the internal stream for reading.
342341
*

tests/GridFS/BucketFunctionalTest.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -904,7 +904,7 @@ public function testResolveStreamContextForRead(): void
904904
$method->setAccessible(true);
905905
$method->setAccessible(true);
906906

907-
$context = $method->invokeArgs($this->bucket, [$this->getFileUrl('filename'), 'rb']);
907+
$context = $method->invokeArgs($this->bucket, ['gridfs://bucket/filename', 'rb']);
908908

909909
$this->assertIsArray($context);
910910
$this->assertArrayHasKey('collectionWrapper', $context);
@@ -921,7 +921,7 @@ public function testResolveStreamContextForWrite(): void
921921
$method->setAccessible(true);
922922
$method->setAccessible(true);
923923

924-
$context = $method->invokeArgs($this->bucket, [$this->getFileUrl('filename'), 'wb']);
924+
$context = $method->invokeArgs($this->bucket, ['gridfs://bucket/filename', 'wb']);
925925

926926
$this->assertIsArray($context);
927927
$this->assertArrayHasKey('collectionWrapper', $context);

tests/GridFS/FunctionalTestCase.php

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ public function setUp(): void
3838

3939
public function tearDown(): void
4040
{
41-
StreamWrapper::setDefaultContextResolver(null);
41+
StreamWrapper::setContextResolver('default', null);
4242

4343
parent::tearDown();
4444
}
@@ -70,9 +70,4 @@ protected function createStream(string $data = '')
7070

7171
return $stream;
7272
}
73-
74-
protected function getFileUrl(string $filename): string
75-
{
76-
return sprintf('gridfs://%s/%s.files/%s', $this->bucket->getDatabaseName(), $this->bucket->getBucketName(), $filename);
77-
}
7873
}

tests/GridFS/StreamWrapperFunctionalTest.php

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -206,35 +206,36 @@ public function testWritableStreamWrite(): void
206206
$this->assertSame(6, fwrite($stream, 'foobar'));
207207
}
208208

209-
public function testStreamWithDefaultResolver(): void
209+
public function testStreamWithContextResolver(): void
210210
{
211-
$this->bucket->registerAsDefaultStreamWrapper();
211+
$filename = 'gridfs://bucket/path/to/filename';
212+
$this->bucket->registerGlobalAliasForResolutionInStandaloneStreamOpen('bucket');
212213

213-
$stream = fopen($this->getFileUrl('filename'), 'wb');
214+
$stream = fopen($filename, 'wb');
214215

215216
$this->assertSame(6, fwrite($stream, 'foobar'));
216217
$this->assertTrue(fclose($stream));
217218

218-
$stream = fopen($this->getFileUrl('filename'), 'rb');
219+
$stream = fopen($filename, 'rb');
219220

220221
$this->assertSame('foobar', fread($stream, 10));
221222
$this->assertTrue(fclose($stream));
222223
}
223224

224-
public function testFileNoFoundWithDefaultResolver(): void
225+
public function testFileNoFoundWithContextResolver(): void
225226
{
226-
$this->bucket->registerAsDefaultStreamWrapper();
227+
$this->bucket->registerGlobalAliasForResolutionInStandaloneStreamOpen('default');
227228

228229
$this->expectWarning();
229-
$stream = fopen($this->getFileUrl('filename'), 'r');
230+
$stream = fopen('gridfs://bucket/path/to/filename', 'r');
230231

231232
$this->assertFalse($stream);
232233
}
233234

234235
public function testFileNoFoundWithoutDefaultResolver(): void
235236
{
236237
$this->expectWarning();
237-
$stream = fopen($this->getFileUrl('filename'), 'r');
238+
$stream = fopen('gridfs://bucket/path/to/filename', 'r');
238239

239240
$this->assertFalse($stream);
240241
}

0 commit comments

Comments
 (0)