Skip to content

Commit c1a297b

Browse files
committed
PHPLIB-1220: Support codec option for GridFS buckets
1 parent b5462cf commit c1a297b

File tree

5 files changed

+200
-0
lines changed

5 files changed

+200
-0
lines changed

src/GridFS/Bucket.php

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@
1818
namespace MongoDB\GridFS;
1919

2020
use Iterator;
21+
use MongoDB\BSON\Document;
22+
use MongoDB\Codec\DocumentCodec;
2123
use MongoDB\Collection;
2224
use MongoDB\Driver\CursorInterface;
2325
use MongoDB\Driver\Exception\RuntimeException as DriverRuntimeException;
@@ -75,6 +77,8 @@ class Bucket
7577

7678
private const STREAM_WRAPPER_PROTOCOL = 'gridfs';
7779

80+
private ?DocumentCodec $codec = null;
81+
7882
private CollectionWrapper $collectionWrapper;
7983

8084
private string $databaseName;
@@ -142,6 +146,10 @@ public function __construct(Manager $manager, string $databaseName, array $optio
142146
throw new InvalidArgumentException(sprintf('Expected "chunkSizeBytes" option to be >= 1, %d given', $options['chunkSizeBytes']));
143147
}
144148

149+
if (isset($options['codec']) && ! $options['codec'] instanceof DocumentCodec) {
150+
throw InvalidArgumentException::invalidType('"codec" option', $options['codec'], DocumentCodec::class);
151+
}
152+
145153
if (! is_bool($options['disableMD5'])) {
146154
throw InvalidArgumentException::invalidType('"disableMD5" option', $options['disableMD5'], 'boolean');
147155
}
@@ -166,6 +174,7 @@ public function __construct(Manager $manager, string $databaseName, array $optio
166174
$this->databaseName = $databaseName;
167175
$this->bucketName = $options['bucketName'];
168176
$this->chunkSizeBytes = $options['chunkSizeBytes'];
177+
$this->codec = $options['codec'] ?? null;
169178
$this->disableMD5 = $options['disableMD5'];
170179
$this->readConcern = $options['readConcern'] ?? $this->manager->getReadConcern();
171180
$this->readPreference = $options['readPreference'] ?? $this->manager->getReadPreference();
@@ -188,6 +197,7 @@ public function __debugInfo()
188197
{
189198
return [
190199
'bucketName' => $this->bucketName,
200+
'codec' => $this->codec,
191201
'databaseName' => $this->databaseName,
192202
'disableMD5' => $this->disableMD5,
193203
'manager' => $this->manager,
@@ -309,6 +319,10 @@ public function drop()
309319
*/
310320
public function find($filter = [], array $options = [])
311321
{
322+
if ($this->codec && ! isset($options['codec'])) {
323+
$options['codec'] = $this->codec;
324+
}
325+
312326
return $this->collectionWrapper->findFiles($filter, $options);
313327
}
314328

@@ -326,6 +340,10 @@ public function find($filter = [], array $options = [])
326340
*/
327341
public function findOne($filter = [], array $options = [])
328342
{
343+
if ($this->codec && ! isset($options['codec'])) {
344+
$options['codec'] = $this->codec;
345+
}
346+
329347
return $this->collectionWrapper->findOneFile($filter, $options);
330348
}
331349

@@ -381,6 +399,10 @@ public function getFileDocumentForStream($stream)
381399
{
382400
$file = $this->getRawFileDocumentForStream($stream);
383401

402+
if ($this->codec instanceof DocumentCodec) {
403+
return $this->codec->decode(Document::fromPHP($file));
404+
}
405+
384406
// Filter the raw document through the specified type map
385407
return apply_type_map_to_document($file, $this->typeMap);
386408
}

src/GridFS/WritableStream.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -136,6 +136,8 @@ public function __construct(CollectionWrapper $collectionWrapper, string $filena
136136
'_id' => $options['_id'],
137137
'chunkSize' => $this->chunkSize,
138138
'filename' => $filename,
139+
'length' => 0,
140+
'uploadDate' => new UTCDateTime(0),
139141
] + array_intersect_key($options, ['aliases' => 1, 'contentType' => 1, 'metadata' => 1]);
140142
}
141143

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
<?php
2+
3+
namespace MongoDB\Tests\Fixtures\Codec;
4+
5+
use DateTimeImmutable;
6+
use MongoDB\BSON\Document;
7+
use MongoDB\BSON\UTCDateTime;
8+
use MongoDB\Codec\DecodeIfSupported;
9+
use MongoDB\Codec\DocumentCodec;
10+
use MongoDB\Codec\EncodeIfSupported;
11+
use MongoDB\Exception\UnsupportedValueException;
12+
use MongoDB\Tests\Fixtures\Document\TestFile;
13+
14+
final class TestFileCodec implements DocumentCodec
15+
{
16+
use DecodeIfSupported;
17+
use EncodeIfSupported;
18+
19+
public function canDecode($value): bool
20+
{
21+
return $value instanceof Document;
22+
}
23+
24+
public function decode($value): TestFile
25+
{
26+
if (! $value instanceof Document) {
27+
throw UnsupportedValueException::invalidDecodableValue($value);
28+
}
29+
30+
$fileObject = new TestFile();
31+
$fileObject->id = $value->get('_id');
32+
$fileObject->length = (int) $value->get('length');
33+
$fileObject->chunkSize = (int) $value->get('chunkSize');
34+
$fileObject->uploadDate = DateTimeImmutable::createFromMutable($value->get('uploadDate')->toDateTime());
35+
$fileObject->filename = $value->get('filename');
36+
37+
if ($value->has('metadata')) {
38+
$fileObject->metadata = $value->get('metadata');
39+
}
40+
41+
return $fileObject;
42+
}
43+
44+
public function canEncode($value): bool
45+
{
46+
return $value instanceof TestFile;
47+
}
48+
49+
public function encode($value): Document
50+
{
51+
if (! $value instanceof TestFile) {
52+
throw UnsupportedValueException::invalidEncodableValue($value);
53+
}
54+
55+
return Document::fromPHP([
56+
'_id' => $value->id,
57+
'length' => $value->length,
58+
'chunkSize' => $value->chunkSize,
59+
'uploadDate' => new UTCDateTime($value->uploadDate),
60+
'filename' => $value->filename,
61+
]);
62+
}
63+
}

tests/Fixtures/Document/TestFile.php

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
<?php
2+
/*
3+
* Copyright 2023-present MongoDB, Inc.
4+
*
5+
* Licensed under the Apache License, Version 2.0 (the "License");
6+
* you may not use this file except in compliance with the License.
7+
* You may obtain a copy of the License at
8+
*
9+
* https://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS,
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* See the License for the specific language governing permissions and
15+
* limitations under the License.
16+
*/
17+
18+
namespace MongoDB\Tests\Fixtures\Document;
19+
20+
use DateTimeImmutable;
21+
22+
final class TestFile
23+
{
24+
public $id;
25+
public int $length;
26+
public int $chunkSize;
27+
public DateTimeImmutable $uploadDate;
28+
public string $filename;
29+
public $metadata;
30+
}

tests/GridFS/BucketFunctionalTest.php

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
namespace MongoDB\Tests\GridFS;
44

55
use MongoDB\BSON\Binary;
6+
use MongoDB\BSON\Document;
67
use MongoDB\Collection;
78
use MongoDB\Driver\ReadConcern;
89
use MongoDB\Driver\ReadPreference;
@@ -15,6 +16,8 @@
1516
use MongoDB\Model\BSONDocument;
1617
use MongoDB\Model\IndexInfo;
1718
use MongoDB\Operation\ListIndexes;
19+
use MongoDB\Tests\Fixtures\Codec\TestFileCodec;
20+
use MongoDB\Tests\Fixtures\Document\TestFile;
1821

1922
use function array_merge;
2023
use function call_user_func;
@@ -68,6 +71,7 @@ public function provideInvalidConstructorOptions()
6871
return $this->createOptionDataProvider([
6972
'bucketName' => $this->getInvalidStringValues(true),
7073
'chunkSizeBytes' => $this->getInvalidIntegerValues(true),
74+
'codec' => $this->getInvalidDocumentCodecValues(),
7175
'disableMD5' => $this->getInvalidBooleanValues(true),
7276
'readConcern' => $this->getInvalidReadConcernValues(),
7377
'readPreference' => $this->getInvalidReadPreferenceValues(),
@@ -317,6 +321,29 @@ public function testFindUsesTypeMap(): void
317321
$this->assertInstanceOf(BSONDocument::class, $fileDocument);
318322
}
319323

324+
public function testFindUsesCodec(): void
325+
{
326+
$this->bucket->uploadFromStream('a', $this->createStream('foo'));
327+
328+
$cursor = $this->bucket->find([], ['codec' => new TestFileCodec()]);
329+
$fileDocument = current($cursor->toArray());
330+
331+
$this->assertInstanceOf(TestFile::class, $fileDocument);
332+
$this->assertSame('a', $fileDocument->filename);
333+
}
334+
335+
public function testFindInheritsBucketCodec(): void
336+
{
337+
$bucket = new Bucket($this->manager, $this->getDatabaseName(), ['codec' => new TestFileCodec()]);
338+
$bucket->uploadFromStream('a', $this->createStream('foo'));
339+
340+
$cursor = $bucket->find();
341+
$fileDocument = current($cursor->toArray());
342+
343+
$this->assertInstanceOf(TestFile::class, $fileDocument);
344+
$this->assertSame('a', $fileDocument->filename);
345+
}
346+
320347
public function testFindOne(): void
321348
{
322349
$this->bucket->uploadFromStream('a', $this->createStream('foo'));
@@ -339,6 +366,46 @@ public function testFindOne(): void
339366
$this->assertSameDocument(['filename' => 'b', 'length' => 6], $fileDocument);
340367
}
341368

369+
public function testFindOneUsesCodec(): void
370+
{
371+
$this->bucket->uploadFromStream('a', $this->createStream('foo'));
372+
$this->bucket->uploadFromStream('b', $this->createStream('foobar'));
373+
$this->bucket->uploadFromStream('c', $this->createStream('foobarbaz'));
374+
375+
$fileDocument = $this->bucket->findOne(
376+
['length' => ['$lte' => 6]],
377+
[
378+
'sort' => ['length' => -1],
379+
'codec' => new TestFileCodec(),
380+
],
381+
);
382+
383+
$this->assertInstanceOf(TestFile::class, $fileDocument);
384+
$this->assertSame('b', $fileDocument->filename);
385+
$this->assertSame(6, $fileDocument->length);
386+
}
387+
388+
public function testFindOneInheritsBucketCodec(): void
389+
{
390+
$bucket = new Bucket($this->manager, $this->getDatabaseName(), ['codec' => new TestFileCodec()]);
391+
392+
$bucket->uploadFromStream('a', $this->createStream('foo'));
393+
$bucket->uploadFromStream('b', $this->createStream('foobar'));
394+
$bucket->uploadFromStream('c', $this->createStream('foobarbaz'));
395+
396+
$fileDocument = $bucket->findOne(
397+
['length' => ['$lte' => 6]],
398+
[
399+
'sort' => ['length' => -1],
400+
'codec' => new TestFileCodec(),
401+
],
402+
);
403+
404+
$this->assertInstanceOf(TestFile::class, $fileDocument);
405+
$this->assertSame('b', $fileDocument->filename);
406+
$this->assertSame(6, $fileDocument->length);
407+
}
408+
342409
public function testGetBucketNameWithCustomValue(): void
343410
{
344411
$bucket = new Bucket($this->manager, $this->getDatabaseName(), ['bucketName' => 'custom_fs']);
@@ -388,6 +455,22 @@ public function testGetFileDocumentForStreamUsesTypeMap(): void
388455
$this->assertSame(['foo' => 'bar'], $fileDocument['metadata']->getArrayCopy());
389456
}
390457

458+
public function testGetFileDocumentForStreamUsesCodec(): void
459+
{
460+
$bucket = new Bucket($this->manager, $this->getDatabaseName(), ['codec' => new TestFileCodec()]);
461+
462+
$metadata = ['foo' => 'bar'];
463+
$stream = $bucket->openUploadStream('filename', ['_id' => 1, 'metadata' => $metadata]);
464+
465+
$fileDocument = $bucket->getFileDocumentForStream($stream);
466+
467+
$this->assertInstanceOf(TestFile::class, $fileDocument);
468+
469+
$this->assertSame('filename', $fileDocument->filename);
470+
$this->assertInstanceOf(Document::class, $fileDocument->metadata);
471+
$this->assertSame($metadata, $fileDocument->metadata->toPHP(['root' => 'array']));
472+
}
473+
391474
public function testGetFileDocumentForStreamWithReadableStream(): void
392475
{
393476
$metadata = ['foo' => 'bar'];

0 commit comments

Comments
 (0)