Skip to content

Commit 469b8eb

Browse files
committed
Implement url_stats and accept context options
1 parent 9725fd1 commit 469b8eb

File tree

4 files changed

+100
-34
lines changed

4 files changed

+100
-34
lines changed

src/GridFS/Bucket.php

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -597,14 +597,14 @@ public function openUploadStream(string $filename, array $options = [])
597597
*
598598
* @param non-empty-string string $alias The alias to use for the bucket
599599
*/
600-
public function registerGlobalAliasForResolutionInStandaloneStreamOpen(string $alias): void
600+
public function registerGlobalStreamWrapperAlias(string $alias): void
601601
{
602602
if ($alias === '' || str_contains($alias, '/')) {
603603
throw new InvalidArgumentException(sprintf('The bucket alias must be a non-empty string without any slash, "%s" given', $alias));
604604
}
605605

606606
// Use a closure to expose the private method into another class
607-
StreamWrapper::setContextResolver($alias, fn (string $path, string $mode) => $this->resolveStreamContext($path, $mode));
607+
StreamWrapper::setContextResolver($alias, fn (string $path, string $mode, array $context) => $this->resolveStreamContext($path, $mode, $context));
608608
}
609609

610610
/**
@@ -787,13 +787,13 @@ private function registerStreamWrapper(): void
787787
*
788788
* @return array{collectionWrapper: CollectionWrapper, file: object}|array{collectionWrapper: CollectionWrapper, filename: string, options: array}|null
789789
*/
790-
private function resolveStreamContext(string $path, string $mode): ?array
790+
private function resolveStreamContext(string $path, string $mode, array $context): ?array
791791
{
792792
$parts = explode('/', $path, 4);
793793
$filename = $parts[3] ?? '';
794794

795-
if (str_contains($mode, 'r')) {
796-
$file = $this->collectionWrapper->findFileByFilenameAndRevision($filename, -1);
795+
if ($mode === 'r' || $mode === 'rb') {
796+
$file = $this->collectionWrapper->findFileByFilenameAndRevision($filename, $context['revision'] ?? -1);
797797

798798
// File not found
799799
if ($file === null) {
@@ -806,11 +806,11 @@ private function resolveStreamContext(string $path, string $mode): ?array
806806
];
807807
}
808808

809-
if (str_contains($mode, 'w')) {
809+
if ($mode === 'w' || $mode === 'wb') {
810810
return [
811811
'collectionWrapper' => $this->collectionWrapper,
812812
'filename' => $filename,
813-
'options' => [
813+
'options' => $context + [
814814
'chunkSizeBytes' => $this->chunkSizeBytes,
815815
'disableMD5' => $this->disableMD5,
816816
],

src/GridFS/StreamWrapper.php

Lines changed: 29 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -99,7 +99,7 @@ public static function register(string $protocol = 'gridfs'): void
9999
/**
100100
* @see Bucket::resolveStreamContext()
101101
*
102-
* @param Closure(string, string):ContextOptions|null $resolver
102+
* @param Closure(string, string, array):ContextOptions|null $resolver
103103
*/
104104
public static function setContextResolver(string $name, ?Closure $resolver): void
105105
{
@@ -147,10 +147,13 @@ public function stream_open(string $path, string $mode, int $options, ?string &$
147147
{
148148
[$protocol] = explode('://', $path, 2);
149149

150-
assert(is_resource($this->context));
151-
$contextOptions = stream_context_get_options($this->context)[$protocol] ?? null;
150+
$context = [];
151+
if (is_resource($this->context)) {
152+
$context = stream_context_get_options($this->context)[$protocol] ?? [];
153+
}
152154

153-
if ($contextOptions === null) {
155+
// Opening stream from gridfs
156+
if (! isset($context['collectionWrapper'])) {
154157
$parts = explode('/', $path, 4);
155158

156159
if (count($parts) < 4) {
@@ -169,8 +172,8 @@ public function stream_open(string $path, string $mode, int $options, ?string &$
169172
return false;
170173
}
171174

172-
$contextOptions = self::$contextResolvers[$parts[2]]($path, $mode);
173-
if ($contextOptions === null) {
175+
$context = self::$contextResolvers[$parts[2]]($path, $mode, $context);
176+
if ($context === null) {
174177
if ($options & STREAM_REPORT_ERRORS) {
175178
trigger_error(sprintf('File not found "%s".', $path), E_USER_WARNING);
176179
}
@@ -179,20 +182,20 @@ public function stream_open(string $path, string $mode, int $options, ?string &$
179182
}
180183
}
181184

182-
assert(is_array($contextOptions));
183-
assert(isset($contextOptions['collectionWrapper']) && $contextOptions['collectionWrapper'] instanceof CollectionWrapper);
185+
assert(is_array($context));
186+
assert(isset($context['collectionWrapper']) && $context['collectionWrapper'] instanceof CollectionWrapper);
184187

185-
if (str_contains($mode, 'r')) {
186-
assert(isset($contextOptions['file']) && is_object($contextOptions['file']));
188+
if ($mode === 'r' || $mode === 'rb') {
189+
assert(isset($context['file']) && is_object($context['file']));
187190

188-
return $this->initReadableStream($contextOptions);
191+
return $this->initReadableStream($context);
189192
}
190193

191-
if (str_contains($mode, 'w')) {
192-
assert(isset($contextOptions['filename']) && is_string($contextOptions['filename']));
193-
assert(isset($contextOptions['options']) && is_array($contextOptions['options']));
194+
if ($mode === 'w' || $mode === 'wb') {
195+
assert(isset($context['filename']) && is_string($context['filename']));
196+
assert(isset($context['options']) && is_array($context['options']));
194197

195-
return $this->initWritableStream($contextOptions);
198+
return $this->initWritableStream($context);
196199
}
197200

198201
return false;
@@ -312,6 +315,17 @@ public function stream_write(string $data): int
312315
return $this->stream->writeBytes($data);
313316
}
314317

318+
public function url_stat(string $path, int $flags)
319+
{
320+
$success = $this->stream_open($path, 'r', 0, $openedPath);
321+
322+
if (! $success) {
323+
return false;
324+
}
325+
326+
return $this->stream_stat();
327+
}
328+
315329
/**
316330
* Returns a stat template with default values.
317331
*/

tests/GridFS/BucketFunctionalTest.php

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -902,9 +902,8 @@ public function testResolveStreamContextForRead(): void
902902

903903
$method = new ReflectionMethod($this->bucket, 'resolveStreamContext');
904904
$method->setAccessible(true);
905-
$method->setAccessible(true);
906905

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

909908
$this->assertIsArray($context);
910909
$this->assertArrayHasKey('collectionWrapper', $context);
@@ -919,9 +918,8 @@ public function testResolveStreamContextForWrite(): void
919918
{
920919
$method = new ReflectionMethod($this->bucket, 'resolveStreamContext');
921920
$method->setAccessible(true);
922-
$method->setAccessible(true);
923921

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

926924
$this->assertIsArray($context);
927925
$this->assertArrayHasKey('collectionWrapper', $context);

tests/GridFS/StreamWrapperFunctionalTest.php

Lines changed: 62 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,21 @@
77

88
use function fclose;
99
use function feof;
10+
use function file_exists;
11+
use function file_get_contents;
12+
use function file_put_contents;
13+
use function filemtime;
14+
use function filesize;
15+
use function filetype;
1016
use function fopen;
1117
use function fread;
1218
use function fseek;
1319
use function fstat;
1420
use function fwrite;
21+
use function is_dir;
22+
use function is_file;
23+
use function is_link;
24+
use function time;
1525

1626
use const SEEK_CUR;
1727
use const SEEK_END;
@@ -206,37 +216,81 @@ public function testWritableStreamWrite(): void
206216
$this->assertSame(6, fwrite($stream, 'foobar'));
207217
}
208218

209-
public function testStreamWithContextResolver(): void
219+
/** @dataProvider provideUrl */
220+
public function testStreamWithContextResolver(string $url, string $expectedFilename): void
210221
{
211-
$filename = 'gridfs://bucket/path/to/filename';
212-
$this->bucket->registerGlobalAliasForResolutionInStandaloneStreamOpen('bucket');
222+
$this->bucket->registerGlobalStreamWrapperAlias('bucket');
213223

214-
$stream = fopen($filename, 'wb');
224+
$stream = fopen($url, 'wb');
215225

216226
$this->assertSame(6, fwrite($stream, 'foobar'));
217227
$this->assertTrue(fclose($stream));
218228

219-
$stream = fopen($filename, 'rb');
229+
$file = $this->filesCollection->findOne(['filename' => $expectedFilename]);
230+
$this->assertNotNull($file);
231+
232+
$stream = fopen($url, 'rb');
220233

221234
$this->assertSame('foobar', fread($stream, 10));
222235
$this->assertTrue(fclose($stream));
223236
}
224237

238+
public static function provideUrl()
239+
{
240+
yield 'simple file' => ['gridfs://bucket/filename', 'filename'];
241+
yield 'subdirectory file' => ['gridfs://bucket/path/to/filename.txt', 'path/to/filename.txt'];
242+
yield 'question mark can be used in file name' => ['gridfs://bucket/file%20name?foo=bar', 'file%20name?foo=bar'];
243+
}
244+
245+
public function testFilePutAndGetContents(): void
246+
{
247+
$this->bucket->registerGlobalStreamWrapperAlias('bucket');
248+
249+
$filename = 'gridfs://bucket/path/to/filename';
250+
251+
$this->assertSame(6, file_put_contents($filename, 'foobar'));
252+
253+
$file = $this->filesCollection->findOne(['filename' => 'path/to/filename']);
254+
$this->assertNotNull($file);
255+
256+
$this->assertSame('foobar', file_get_contents($filename));
257+
}
258+
225259
public function testFileNoFoundWithContextResolver(): void
226260
{
227-
$this->bucket->registerGlobalAliasForResolutionInStandaloneStreamOpen('default');
261+
$this->bucket->registerGlobalStreamWrapperAlias('bucket');
228262

229263
$this->expectWarning();
230-
$stream = fopen('gridfs://bucket/path/to/filename', 'r');
264+
$stream = fopen('gridfs://bucket/filename', 'r');
231265

232266
$this->assertFalse($stream);
233267
}
234268

235269
public function testFileNoFoundWithoutDefaultResolver(): void
236270
{
237271
$this->expectWarning();
238-
$stream = fopen('gridfs://bucket/path/to/filename', 'r');
272+
$stream = fopen('gridfs://bucket/filename', 'r');
239273

240274
$this->assertFalse($stream);
241275
}
276+
277+
public function testFileStats(): void
278+
{
279+
$this->bucket->registerGlobalStreamWrapperAlias('bucket');
280+
$path = 'gridfs://bucket/filename';
281+
282+
$this->assertFalse(file_exists($path));
283+
284+
$time = time();
285+
$this->assertSame(6, file_put_contents($path, 'foobar'));
286+
287+
$this->assertTrue(file_exists($path));
288+
$this->assertSame('file', filetype($path));
289+
$this->assertTrue(is_file($path));
290+
$this->assertFalse(is_dir($path));
291+
$this->assertFalse(is_link($path));
292+
$this->assertSame(6, filesize($path));
293+
$this->assertGreaterThanOrEqual($time, filemtime($path));
294+
$this->assertLessThanOrEqual(time(), filemtime($path));
295+
}
242296
}

0 commit comments

Comments
 (0)