diff --git a/UPGRADE-2.0.md b/UPGRADE-2.0.md new file mode 100644 index 000000000..3f012f6d2 --- /dev/null +++ b/UPGRADE-2.0.md @@ -0,0 +1,28 @@ +UPGRADE FROM 1.x to 2.0 +======================== + +GridFS +------ + + * The `md5` is no longer calculated when a file is uploaded to GridFS. + Applications that require a file digest should implement it outside GridFS + and store in metadata. + + ```php + $hash = hash_file('sha256', $filename); + $bucket->openUploadStream($fileId, ['metadata' => ['hash' => $hash]]); + ``` + + * The fields `contentType` and `aliases` are no longer stored in the `files` + collection. Applications that require this information should store it in + metadata. + + **Before:** + ```php + $bucket->openUploadStream($fileId, ['contentType' => 'image/png']); + ``` + + **After:** + ```php + $bucket->openUploadStream($fileId, ['metadata' => ['contentType' => 'image/png']]); + ``` diff --git a/composer.json b/composer.json index a856f4e84..fd6362af2 100644 --- a/composer.json +++ b/composer.json @@ -11,7 +11,6 @@ ], "require": { "php": "^8.1", - "ext-hash": "*", "ext-json": "*", "ext-mongodb": "^1.20.0", "composer-runtime-api": "^2.0", diff --git a/src/GridFS/Bucket.php b/src/GridFS/Bucket.php index 56a4e26e2..04324df09 100644 --- a/src/GridFS/Bucket.php +++ b/src/GridFS/Bucket.php @@ -45,7 +45,6 @@ use function get_resource_type; use function in_array; use function is_array; -use function is_bool; use function is_integer; use function is_object; use function is_resource; @@ -59,11 +58,8 @@ use function stream_copy_to_stream; use function stream_get_meta_data; use function stream_get_wrappers; -use function trigger_error; use function urlencode; -use const E_USER_DEPRECATED; - /** * Bucket provides a public API for interacting with the GridFS files and chunks * collections. @@ -88,8 +84,6 @@ class Bucket private string $bucketName; - private bool $disableMD5; - private int $chunkSizeBytes; private ReadConcern $readConcern; @@ -111,9 +105,6 @@ class Bucket * * chunkSizeBytes (integer): The chunk size in bytes. Defaults to * 261120 (i.e. 255 KiB). * - * * disableMD5 (boolean): When true, no MD5 sum will be generated for - * each stored file. Defaults to "false". - * * * readConcern (MongoDB\Driver\ReadConcern): Read concern. * * * readPreference (MongoDB\Driver\ReadPreference): Read preference. @@ -129,14 +120,9 @@ class Bucket */ public function __construct(private Manager $manager, private string $databaseName, array $options = []) { - if (isset($options['disableMD5']) && $options['disableMD5'] === false) { - @trigger_error('Setting GridFS "disableMD5" option to "false" is deprecated since mongodb/mongodb 1.18 and will not be supported in version 2.0.', E_USER_DEPRECATED); - } - $options += [ 'bucketName' => self::DEFAULT_BUCKET_NAME, 'chunkSizeBytes' => self::DEFAULT_CHUNK_SIZE_BYTES, - 'disableMD5' => false, ]; if (! is_string($options['bucketName'])) { @@ -155,10 +141,6 @@ public function __construct(private Manager $manager, private string $databaseNa throw InvalidArgumentException::invalidType('"codec" option', $options['codec'], DocumentCodec::class); } - if (! is_bool($options['disableMD5'])) { - throw InvalidArgumentException::invalidType('"disableMD5" option', $options['disableMD5'], 'boolean'); - } - if (isset($options['readConcern']) && ! $options['readConcern'] instanceof ReadConcern) { throw InvalidArgumentException::invalidType('"readConcern" option', $options['readConcern'], ReadConcern::class); } @@ -182,7 +164,6 @@ public function __construct(private Manager $manager, private string $databaseNa $this->bucketName = $options['bucketName']; $this->chunkSizeBytes = $options['chunkSizeBytes']; $this->codec = $options['codec'] ?? null; - $this->disableMD5 = $options['disableMD5']; $this->readConcern = $options['readConcern'] ?? $this->manager->getReadConcern(); $this->readPreference = $options['readPreference'] ?? $this->manager->getReadPreference(); $this->typeMap = $options['typeMap'] ?? self::DEFAULT_TYPE_MAP; @@ -211,7 +192,6 @@ public function __debugInfo() 'bucketName' => $this->bucketName, 'codec' => $this->codec, 'databaseName' => $this->databaseName, - 'disableMD5' => $this->disableMD5, 'manager' => $this->manager, 'chunkSizeBytes' => $this->chunkSizeBytes, 'readConcern' => $this->readConcern, @@ -565,9 +545,6 @@ public function openDownloadStreamByName(string $filename, array $options = []) * * chunkSizeBytes (integer): The chunk size in bytes. Defaults to the * bucket's chunk size. * - * * disableMD5 (boolean): When true, no MD5 sum will be generated for - * the stored file. Defaults to "false". - * * * metadata (document): User data for the "metadata" field of the files * collection document. * @@ -579,7 +556,6 @@ public function openUploadStream(string $filename, array $options = []) { $options += [ 'chunkSizeBytes' => $this->chunkSizeBytes, - 'disableMD5' => $this->disableMD5, ]; $path = $this->createPathForUpload(); @@ -658,9 +634,6 @@ public function rename(mixed $id, string $newFilename) * * chunkSizeBytes (integer): The chunk size in bytes. Defaults to the * bucket's chunk size. * - * * disableMD5 (boolean): When true, no MD5 sum will be generated for - * the stored file. Defaults to "false". - * * * metadata (document): User data for the "metadata" field of the files * collection document. * @@ -792,9 +765,9 @@ private function registerStreamWrapper(): void * * @see StreamWrapper::setContextResolver() * - * @param string $path The full url provided to fopen(). It contains the filename. - * gridfs://database_name/collection_name.files/file_name - * @param array{revision?: int, chunkSizeBytes?: int, disableMD5?: bool} $context The options provided to fopen() + * @param string $path The full url provided to fopen(). It contains the filename. + * gridfs://database_name/collection_name.files/file_name + * @param array{revision?: int, chunkSizeBytes?: int} $context The options provided to fopen() * * @return array{collectionWrapper: CollectionWrapper, file: object}|array{collectionWrapper: CollectionWrapper, filename: string, options: array} * @@ -825,7 +798,6 @@ private function resolveStreamContext(string $path, string $mode, array $context 'filename' => $filename, 'options' => $context + [ 'chunkSizeBytes' => $this->chunkSizeBytes, - 'disableMD5' => $this->disableMD5, ], ]; } diff --git a/src/GridFS/WritableStream.php b/src/GridFS/WritableStream.php index 65b35de90..093a10a7c 100644 --- a/src/GridFS/WritableStream.php +++ b/src/GridFS/WritableStream.php @@ -17,7 +17,6 @@ namespace MongoDB\GridFS; -use HashContext; use MongoDB\BSON\Binary; use MongoDB\BSON\ObjectId; use MongoDB\BSON\UTCDateTime; @@ -25,14 +24,8 @@ use MongoDB\Exception\InvalidArgumentException; use function array_intersect_key; -use function hash_final; -use function hash_init; -use function hash_update; -use function is_bool; use function is_integer; -use function is_string; use function MongoDB\is_document; -use function MongoDB\is_string_array; use function sprintf; use function strlen; use function substr; @@ -52,12 +45,8 @@ class WritableStream private int $chunkSize; - private bool $disableMD5; - private array $file; - private ?HashContext $hashCtx = null; - private bool $isClosed = false; private int $length = 0; @@ -69,19 +58,9 @@ class WritableStream * * * _id (mixed): File document identifier. Defaults to a new ObjectId. * - * * aliases (array of strings): DEPRECATED An array of aliases. - * Applications wishing to store aliases should add an aliases field to - * the metadata document instead. - * * * chunkSizeBytes (integer): The chunk size in bytes. Defaults to * 261120 (i.e. 255 KiB). * - * * disableMD5 (boolean): When true, no MD5 sum will be generated. - * Defaults to "false". - * - * * contentType (string): DEPRECATED content type to be stored with the - * file. This information should now be added to the metadata. - * * * metadata (document): User data for the "metadata" field of the files * collection document. * @@ -95,13 +74,8 @@ public function __construct(private CollectionWrapper $collectionWrapper, string $options += [ '_id' => new ObjectId(), 'chunkSizeBytes' => self::DEFAULT_CHUNK_SIZE_BYTES, - 'disableMD5' => false, ]; - if (isset($options['aliases']) && ! is_string_array($options['aliases'])) { - throw InvalidArgumentException::invalidType('"aliases" option', $options['aliases'], 'array of strings'); - } - if (! is_integer($options['chunkSizeBytes'])) { throw InvalidArgumentException::invalidType('"chunkSizeBytes" option', $options['chunkSizeBytes'], 'integer'); } @@ -110,32 +84,18 @@ public function __construct(private CollectionWrapper $collectionWrapper, string throw new InvalidArgumentException(sprintf('Expected "chunkSizeBytes" option to be >= 1, %d given', $options['chunkSizeBytes'])); } - if (! is_bool($options['disableMD5'])) { - throw InvalidArgumentException::invalidType('"disableMD5" option', $options['disableMD5'], 'boolean'); - } - - if (isset($options['contentType']) && ! is_string($options['contentType'])) { - throw InvalidArgumentException::invalidType('"contentType" option', $options['contentType'], 'string'); - } - if (isset($options['metadata']) && ! is_document($options['metadata'])) { throw InvalidArgumentException::expectedDocumentType('"metadata" option', $options['metadata']); } $this->chunkSize = $options['chunkSizeBytes']; - $this->disableMD5 = $options['disableMD5']; - - if (! $this->disableMD5) { - $this->hashCtx = hash_init('md5'); - } - $this->file = [ '_id' => $options['_id'], 'chunkSize' => $this->chunkSize, 'filename' => $filename, 'length' => null, 'uploadDate' => null, - ] + array_intersect_key($options, ['aliases' => 1, 'contentType' => 1, 'metadata' => 1]); + ] + array_intersect_key($options, ['metadata' => 1]); } /** @@ -248,10 +208,6 @@ private function fileCollectionInsert(): void $this->file['length'] = $this->length; $this->file['uploadDate'] = new UTCDateTime(); - if (! $this->disableMD5 && $this->hashCtx) { - $this->file['md5'] = hash_final($this->hashCtx); - } - try { $this->collectionWrapper->insertFile($this->file); } catch (DriverRuntimeException $e) { @@ -276,10 +232,6 @@ private function insertChunkFromBuffer(): void 'data' => new Binary($data), ]; - if (! $this->disableMD5 && $this->hashCtx) { - hash_update($this->hashCtx, $data); - } - try { $this->collectionWrapper->insertChunk($chunk); } catch (DriverRuntimeException $e) { diff --git a/tests/GridFS/BucketFunctionalTest.php b/tests/GridFS/BucketFunctionalTest.php index 2f2d126d4..23b2baff9 100644 --- a/tests/GridFS/BucketFunctionalTest.php +++ b/tests/GridFS/BucketFunctionalTest.php @@ -60,7 +60,6 @@ public function testValidConstructorOptions(): void 'readConcern' => new ReadConcern(ReadConcern::LOCAL), 'readPreference' => new ReadPreference(ReadPreference::PRIMARY), 'writeConcern' => new WriteConcern(WriteConcern::MAJORITY, 1000), - 'disableMD5' => true, ]); } @@ -77,7 +76,6 @@ public static function provideInvalidConstructorOptions() 'bucketName' => self::getInvalidStringValues(true), 'chunkSizeBytes' => self::getInvalidIntegerValues(true), 'codec' => self::getInvalidDocumentCodecValues(), - 'disableMD5' => self::getInvalidBooleanValues(true), 'readConcern' => self::getInvalidReadConcernValues(), 'readPreference' => self::getInvalidReadPreferenceValues(), 'typeMap' => self::getInvalidArrayValues(), @@ -762,46 +760,16 @@ public function testUploadingAnEmptyFile(): void [ 'projection' => [ 'length' => 1, - 'md5' => 1, '_id' => 0, ], ], ); - $expected = [ - 'length' => 0, - 'md5' => 'd41d8cd98f00b204e9800998ecf8427e', - ]; + $expected = ['length' => 0]; $this->assertSameDocument($expected, $fileDocument); } - public function testDisableMD5(): void - { - $options = ['disableMD5' => true]; - $id = $this->bucket->uploadFromStream('filename', self::createStream('data'), $options); - - $fileDocument = $this->filesCollection->findOne( - ['_id' => $id], - ); - - $this->assertArrayNotHasKey('md5', $fileDocument); - } - - public function testDisableMD5OptionInConstructor(): void - { - $options = ['disableMD5' => true]; - - $this->bucket = new Bucket($this->manager, $this->getDatabaseName(), $options); - $id = $this->bucket->uploadFromStream('filename', self::createStream('data')); - - $fileDocument = $this->filesCollection->findOne( - ['_id' => $id], - ); - - $this->assertArrayNotHasKey('md5', $fileDocument); - } - public function testUploadingFirstFileCreatesIndexes(): void { $this->bucket->uploadFromStream('filename', self::createStream('foo')); @@ -863,7 +831,7 @@ public function testDanglingOpenWritableStream(): void $client = MongoDB\Tests\FunctionalTestCase::createTestClient(); $database = $client->selectDatabase(getenv('MONGODB_DATABASE') ?: 'phplib_test'); $gridfs = $database->selectGridFSBucket(); - $stream = $gridfs->openUploadStream('hello.txt', ['disableMD5' => true]); + $stream = $gridfs->openUploadStream('hello.txt'); fwrite($stream, 'Hello MongoDB!'); PHP; @@ -970,7 +938,7 @@ public function testResolveStreamContextForWrite(): void $this->assertArrayHasKey('filename', $context); $this->assertSame('filename', $context['filename']); $this->assertArrayHasKey('options', $context); - $this->assertSame(['chunkSizeBytes' => 261120, 'disableMD5' => false], $context['options']); + $this->assertSame(['chunkSizeBytes' => 261120], $context['options']); } /** diff --git a/tests/GridFS/WritableStreamFunctionalTest.php b/tests/GridFS/WritableStreamFunctionalTest.php index a46741fae..fd5abf421 100644 --- a/tests/GridFS/WritableStreamFunctionalTest.php +++ b/tests/GridFS/WritableStreamFunctionalTest.php @@ -43,7 +43,6 @@ public static function provideInvalidConstructorOptions() { return self::createOptionDataProvider([ 'chunkSizeBytes' => self::getInvalidIntegerValues(true), - 'disableMD5' => self::getInvalidBooleanValues(true), 'metadata' => self::getInvalidDocumentValues(), ]); } @@ -70,31 +69,4 @@ public function testWriteBytesAlwaysUpdatesFileSize(): void $stream->close(); $this->assertSame(1536, $stream->getSize()); } - - /** @dataProvider provideInputDataAndExpectedMD5 */ - public function testWriteBytesCalculatesMD5($input, $expectedMD5): void - { - $stream = new WritableStream($this->collectionWrapper, 'filename'); - $stream->writeBytes($input); - $stream->close(); - - $fileDocument = $this->filesCollection->findOne( - ['_id' => $stream->getFile()->_id], - ['projection' => ['md5' => 1, '_id' => 0]], - ); - - $this->assertSameDocument(['md5' => $expectedMD5], $fileDocument); - } - - public static function provideInputDataAndExpectedMD5() - { - return [ - ['', 'd41d8cd98f00b204e9800998ecf8427e'], - ['foobar', '3858f62230ac3c915f300c664312c63f'], - [str_repeat('foobar', 43520), '88ff0e5fcb0acb27947d736b5d69cb73'], - [str_repeat('foobar', 43521), '8ff86511c95a06a611842ceb555d8454'], - [str_repeat('foobar', 87040), '45bfa1a9ec36728ee7338d15c5a30c13'], - [str_repeat('foobar', 87041), '95e78f624f8e745bcfd2d11691fa601e'], - ]; - } } diff --git a/tests/UnifiedSpecTests/Context.php b/tests/UnifiedSpecTests/Context.php index 56f68adc0..a06e7cb89 100644 --- a/tests/UnifiedSpecTests/Context.php +++ b/tests/UnifiedSpecTests/Context.php @@ -497,7 +497,7 @@ private static function prepareCollectionOrDatabaseOptions(array $options): arra private static function prepareBucketOptions(array $options): array { - Util::assertHasOnlyKeys($options, ['bucketName', 'chunkSizeBytes', 'disableMD5', 'readConcern', 'readPreference', 'writeConcern']); + Util::assertHasOnlyKeys($options, ['bucketName', 'chunkSizeBytes', 'readConcern', 'readPreference', 'writeConcern']); if (array_key_exists('bucketName', $options)) { assertIsString($options['bucketName']); @@ -507,10 +507,6 @@ private static function prepareBucketOptions(array $options): array assertIsInt($options['chunkSizeBytes']); } - if (array_key_exists('disableMD5', $options)) { - assertIsBool($options['disableMD5']); - } - return Util::prepareCommonOptions($options); } diff --git a/tests/UnifiedSpecTests/UnifiedSpecTest.php b/tests/UnifiedSpecTests/UnifiedSpecTest.php index 6b6031948..6500e59d7 100644 --- a/tests/UnifiedSpecTests/UnifiedSpecTest.php +++ b/tests/UnifiedSpecTests/UnifiedSpecTest.php @@ -187,6 +187,10 @@ class UnifiedSpecTest extends FunctionalTestCase 'crud/db-aggregate-write-readPreference: Database-level aggregate with $out omits read preference for pre-5.0 server' => 'PHPLIB-1458', 'crud/db-aggregate-write-readPreference: Database-level aggregate with $merge includes read preference for 5.0+ server' => 'PHPLIB-1458', 'crud/db-aggregate-write-readPreference: Database-level aggregate with $merge omits read preference for pre-5.0 server' => 'PHPLIB-1458', + // GridFS deprecated fields are removed + 'gridfs/gridfs-upload-disableMD5: upload when length is 0 sans MD5' => 'Deprecated fields are removed', + 'gridfs/gridfs-upload-disableMD5: upload when length is 1 sans MD5' => 'Deprecated fields are removed', + 'gridfs/gridfs-upload: upload when contentType is provided' => 'Deprecated fields are removed', ]; /** diff --git a/tests/UnifiedSpecTests/Util.php b/tests/UnifiedSpecTests/Util.php index 4f2fb22f1..d817d61d3 100644 --- a/tests/UnifiedSpecTests/Util.php +++ b/tests/UnifiedSpecTests/Util.php @@ -134,8 +134,8 @@ final class Util 'delete' => ['id'], 'downloadByName' => ['filename', 'revision'], 'download' => ['id'], - 'uploadWithId' => ['id', 'filename', 'source', 'chunkSizeBytes', 'disableMD5', 'contentType', 'metadata'], - 'upload' => ['filename', 'source', 'chunkSizeBytes', 'disableMD5', 'contentType', 'metadata'], + 'uploadWithId' => ['id', 'filename', 'source', 'chunkSizeBytes', 'metadata'], + 'upload' => ['filename', 'source', 'chunkSizeBytes', 'metadata'], ], ];