Search moodle.org's
Developer Documentation

See Release Notes

  • Bug fixes for general core bugs in 3.11.x will end 14 Nov 2022 (12 months plus 6 months extension).
  • Bug fixes for security issues in 3.11.x will end 13 Nov 2023 (18 months plus 12 months extension).
  • PHP version: minimum PHP 7.3.0 Note: minimum PHP version has increased since Moodle 3.10. PHP 7.4.x is supported too.

Differences Between: [Versions 310 and 311] [Versions 311 and 401] [Versions 39 and 311]

   1  <?php
   2  /*
   3   * Copyright 2016-2017 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   *   http://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\GridFS;
  19  
  20  use MongoDB\Collection;
  21  use MongoDB\Driver\Cursor;
  22  use MongoDB\Driver\Exception\RuntimeException as DriverRuntimeException;
  23  use MongoDB\Driver\Manager;
  24  use MongoDB\Driver\ReadConcern;
  25  use MongoDB\Driver\ReadPreference;
  26  use MongoDB\Driver\WriteConcern;
  27  use MongoDB\Exception\InvalidArgumentException;
  28  use MongoDB\Exception\UnsupportedException;
  29  use MongoDB\GridFS\Exception\CorruptFileException;
  30  use MongoDB\GridFS\Exception\FileNotFoundException;
  31  use MongoDB\GridFS\Exception\StreamException;
  32  use MongoDB\Model\BSONArray;
  33  use MongoDB\Model\BSONDocument;
  34  use MongoDB\Operation\Find;
  35  use stdClass;
  36  use function array_intersect_key;
  37  use function fopen;
  38  use function get_resource_type;
  39  use function in_array;
  40  use function is_array;
  41  use function is_bool;
  42  use function is_integer;
  43  use function is_object;
  44  use function is_resource;
  45  use function is_string;
  46  use function method_exists;
  47  use function MongoDB\apply_type_map_to_document;
  48  use function MongoDB\BSON\fromPHP;
  49  use function MongoDB\BSON\toJSON;
  50  use function property_exists;
  51  use function sprintf;
  52  use function stream_context_create;
  53  use function stream_copy_to_stream;
  54  use function stream_get_meta_data;
  55  use function stream_get_wrappers;
  56  use function urlencode;
  57  
  58  /**
  59   * Bucket provides a public API for interacting with the GridFS files and chunks
  60   * collections.
  61   *
  62   * @api
  63   */
  64  class Bucket
  65  {
  66      /** @var string */
  67      private static $defaultBucketName = 'fs';
  68  
  69      /** @var integer */
  70      private static $defaultChunkSizeBytes = 261120;
  71  
  72      /** @var array */
  73      private static $defaultTypeMap = [
  74          'array' => BSONArray::class,
  75          'document' => BSONDocument::class,
  76          'root' => BSONDocument::class,
  77      ];
  78  
  79      /** @var string */
  80      private static $streamWrapperProtocol = 'gridfs';
  81  
  82      /** @var CollectionWrapper */
  83      private $collectionWrapper;
  84  
  85      /** @var string */
  86      private $databaseName;
  87  
  88      /** @var Manager */
  89      private $manager;
  90  
  91      /** @var string */
  92      private $bucketName;
  93  
  94      /** @var boolean */
  95      private $disableMD5;
  96  
  97      /** @var integer */
  98      private $chunkSizeBytes;
  99  
 100      /** @var ReadConcern */
 101      private $readConcern;
 102  
 103      /** @var ReadPreference */
 104      private $readPreference;
 105  
 106      /** @var array */
 107      private $typeMap;
 108  
 109      /** @var WriteConcern */
 110      private $writeConcern;
 111  
 112      /**
 113       * Constructs a GridFS bucket.
 114       *
 115       * Supported options:
 116       *
 117       *  * bucketName (string): The bucket name, which will be used as a prefix
 118       *    for the files and chunks collections. Defaults to "fs".
 119       *
 120       *  * chunkSizeBytes (integer): The chunk size in bytes. Defaults to
 121       *    261120 (i.e. 255 KiB).
 122       *
 123       *  * disableMD5 (boolean): When true, no MD5 sum will be generated for
 124       *    each stored file. Defaults to "false".
 125       *
 126       *  * readConcern (MongoDB\Driver\ReadConcern): Read concern.
 127       *
 128       *  * readPreference (MongoDB\Driver\ReadPreference): Read preference.
 129       *
 130       *  * typeMap (array): Default type map for cursors and BSON documents.
 131       *
 132       *  * writeConcern (MongoDB\Driver\WriteConcern): Write concern.
 133       *
 134       * @param Manager $manager      Manager instance from the driver
 135       * @param string  $databaseName Database name
 136       * @param array   $options      Bucket options
 137       * @throws InvalidArgumentException for parameter/option parsing errors
 138       */
 139      public function __construct(Manager $manager, $databaseName, array $options = [])
 140      {
 141          $options += [
 142              'bucketName' => self::$defaultBucketName,
 143              'chunkSizeBytes' => self::$defaultChunkSizeBytes,
 144              'disableMD5' => false,
 145          ];
 146  
 147          if (! is_string($options['bucketName'])) {
 148              throw InvalidArgumentException::invalidType('"bucketName" option', $options['bucketName'], 'string');
 149          }
 150  
 151          if (! is_integer($options['chunkSizeBytes'])) {
 152              throw InvalidArgumentException::invalidType('"chunkSizeBytes" option', $options['chunkSizeBytes'], 'integer');
 153          }
 154  
 155          if ($options['chunkSizeBytes'] < 1) {
 156              throw new InvalidArgumentException(sprintf('Expected "chunkSizeBytes" option to be >= 1, %d given', $options['chunkSizeBytes']));
 157          }
 158  
 159          if (! is_bool($options['disableMD5'])) {
 160              throw InvalidArgumentException::invalidType('"disableMD5" option', $options['disableMD5'], 'boolean');
 161          }
 162  
 163          if (isset($options['readConcern']) && ! $options['readConcern'] instanceof ReadConcern) {
 164              throw InvalidArgumentException::invalidType('"readConcern" option', $options['readConcern'], ReadConcern::class);
 165          }
 166  
 167          if (isset($options['readPreference']) && ! $options['readPreference'] instanceof ReadPreference) {
 168              throw InvalidArgumentException::invalidType('"readPreference" option', $options['readPreference'], ReadPreference::class);
 169          }
 170  
 171          if (isset($options['typeMap']) && ! is_array($options['typeMap'])) {
 172              throw InvalidArgumentException::invalidType('"typeMap" option', $options['typeMap'], 'array');
 173          }
 174  
 175          if (isset($options['writeConcern']) && ! $options['writeConcern'] instanceof WriteConcern) {
 176              throw InvalidArgumentException::invalidType('"writeConcern" option', $options['writeConcern'], WriteConcern::class);
 177          }
 178  
 179          $this->manager = $manager;
 180          $this->databaseName = (string) $databaseName;
 181          $this->bucketName = $options['bucketName'];
 182          $this->chunkSizeBytes = $options['chunkSizeBytes'];
 183          $this->disableMD5 = $options['disableMD5'];
 184          $this->readConcern = $options['readConcern'] ?? $this->manager->getReadConcern();
 185          $this->readPreference = $options['readPreference'] ?? $this->manager->getReadPreference();
 186          $this->typeMap = $options['typeMap'] ?? self::$defaultTypeMap;
 187          $this->writeConcern = $options['writeConcern'] ?? $this->manager->getWriteConcern();
 188  
 189          $collectionOptions = array_intersect_key($options, ['readConcern' => 1, 'readPreference' => 1, 'typeMap' => 1, 'writeConcern' => 1]);
 190  
 191          $this->collectionWrapper = new CollectionWrapper($manager, $databaseName, $options['bucketName'], $collectionOptions);
 192          $this->registerStreamWrapper();
 193      }
 194  
 195      /**
 196       * Return internal properties for debugging purposes.
 197       *
 198       * @see http://php.net/manual/en/language.oop5.magic.php#language.oop5.magic.debuginfo
 199       * @return array
 200       */
 201      public function __debugInfo()
 202      {
 203          return [
 204              'bucketName' => $this->bucketName,
 205              'databaseName' => $this->databaseName,
 206              'manager' => $this->manager,
 207              'chunkSizeBytes' => $this->chunkSizeBytes,
 208              'readConcern' => $this->readConcern,
 209              'readPreference' => $this->readPreference,
 210              'typeMap' => $this->typeMap,
 211              'writeConcern' => $this->writeConcern,
 212          ];
 213      }
 214  
 215      /**
 216       * Delete a file from the GridFS bucket.
 217       *
 218       * If the files collection document is not found, this method will still
 219       * attempt to delete orphaned chunks.
 220       *
 221       * @param mixed $id File ID
 222       * @throws FileNotFoundException if no file could be selected
 223       * @throws DriverRuntimeException for other driver errors (e.g. connection errors)
 224       */
 225      public function delete($id)
 226      {
 227          $file = $this->collectionWrapper->findFileById($id);
 228          $this->collectionWrapper->deleteFileAndChunksById($id);
 229  
 230          if ($file === null) {
 231              throw FileNotFoundException::byId($id, $this->getFilesNamespace());
 232          }
 233      }
 234  
 235      /**
 236       * Writes the contents of a GridFS file to a writable stream.
 237       *
 238       * @param mixed    $id          File ID
 239       * @param resource $destination Writable Stream
 240       * @throws FileNotFoundException if no file could be selected
 241       * @throws InvalidArgumentException if $destination is not a stream
 242       * @throws StreamException if the file could not be uploaded
 243       * @throws DriverRuntimeException for other driver errors (e.g. connection errors)
 244       */
 245      public function downloadToStream($id, $destination)
 246      {
 247          if (! is_resource($destination) || get_resource_type($destination) != "stream") {
 248              throw InvalidArgumentException::invalidType('$destination', $destination, 'resource');
 249          }
 250  
 251          $source = $this->openDownloadStream($id);
 252          if (@stream_copy_to_stream($source, $destination) === false) {
 253              throw StreamException::downloadFromIdFailed($id, $source, $destination);
 254          }
 255      }
 256  
 257      /**
 258       * Writes the contents of a GridFS file, which is selected by name and
 259       * revision, to a writable stream.
 260       *
 261       * Supported options:
 262       *
 263       *  * revision (integer): Which revision (i.e. documents with the same
 264       *    filename and different uploadDate) of the file to retrieve. Defaults
 265       *    to -1 (i.e. the most recent revision).
 266       *
 267       * Revision numbers are defined as follows:
 268       *
 269       *  * 0 = the original stored file
 270       *  * 1 = the first revision
 271       *  * 2 = the second revision
 272       *  * etc…
 273       *  * -2 = the second most recent revision
 274       *  * -1 = the most recent revision
 275       *
 276       * @param string   $filename    Filename
 277       * @param resource $destination Writable Stream
 278       * @param array    $options     Download options
 279       * @throws FileNotFoundException if no file could be selected
 280       * @throws InvalidArgumentException if $destination is not a stream
 281       * @throws StreamException if the file could not be uploaded
 282       * @throws DriverRuntimeException for other driver errors (e.g. connection errors)
 283       */
 284      public function downloadToStreamByName($filename, $destination, array $options = [])
 285      {
 286          if (! is_resource($destination) || get_resource_type($destination) != "stream") {
 287              throw InvalidArgumentException::invalidType('$destination', $destination, 'resource');
 288          }
 289  
 290          $source = $this->openDownloadStreamByName($filename, $options);
 291          if (@stream_copy_to_stream($source, $destination) === false) {
 292              throw StreamException::downloadFromFilenameFailed($filename, $source, $destination);
 293          }
 294      }
 295  
 296      /**
 297       * Drops the files and chunks collections associated with this GridFS
 298       * bucket.
 299       *
 300       * @throws DriverRuntimeException for other driver errors (e.g. connection errors)
 301       */
 302      public function drop()
 303      {
 304          $this->collectionWrapper->dropCollections();
 305      }
 306  
 307      /**
 308       * Finds documents from the GridFS bucket's files collection matching the
 309       * query.
 310       *
 311       * @see Find::__construct() for supported options
 312       * @param array|object $filter  Query by which to filter documents
 313       * @param array        $options Additional options
 314       * @return Cursor
 315       * @throws UnsupportedException if options are not supported by the selected server
 316       * @throws InvalidArgumentException for parameter/option parsing errors
 317       * @throws DriverRuntimeException for other driver errors (e.g. connection errors)
 318       */
 319      public function find($filter = [], array $options = [])
 320      {
 321          return $this->collectionWrapper->findFiles($filter, $options);
 322      }
 323  
 324      /**
 325       * Finds a single document from the GridFS bucket's files collection
 326       * matching the query.
 327       *
 328       * @see FindOne::__construct() for supported options
 329       * @param array|object $filter  Query by which to filter documents
 330       * @param array        $options Additional options
 331       * @return array|object|null
 332       * @throws UnsupportedException if options are not supported by the selected server
 333       * @throws InvalidArgumentException for parameter/option parsing errors
 334       * @throws DriverRuntimeException for other driver errors (e.g. connection errors)
 335       */
 336      public function findOne($filter = [], array $options = [])
 337      {
 338          return $this->collectionWrapper->findOneFile($filter, $options);
 339      }
 340  
 341      /**
 342       * Return the bucket name.
 343       *
 344       * @return string
 345       */
 346      public function getBucketName()
 347      {
 348          return $this->bucketName;
 349      }
 350  
 351      /**
 352       * Return the chunks collection.
 353       *
 354       * @return Collection
 355       */
 356      public function getChunksCollection()
 357      {
 358          return $this->collectionWrapper->getChunksCollection();
 359      }
 360  
 361      /**
 362       * Return the chunk size in bytes.
 363       *
 364       * @return integer
 365       */
 366      public function getChunkSizeBytes()
 367      {
 368          return $this->chunkSizeBytes;
 369      }
 370  
 371      /**
 372       * Return the database name.
 373       *
 374       * @return string
 375       */
 376      public function getDatabaseName()
 377      {
 378          return $this->databaseName;
 379      }
 380  
 381      /**
 382       * Gets the file document of the GridFS file associated with a stream.
 383       *
 384       * @param resource $stream GridFS stream
 385       * @return array|object
 386       * @throws InvalidArgumentException if $stream is not a GridFS stream
 387       * @throws DriverRuntimeException for other driver errors (e.g. connection errors)
 388       */
 389      public function getFileDocumentForStream($stream)
 390      {
 391          $file = $this->getRawFileDocumentForStream($stream);
 392  
 393          // Filter the raw document through the specified type map
 394          return apply_type_map_to_document($file, $this->typeMap);
 395      }
 396  
 397      /**
 398       * Gets the file document's ID of the GridFS file associated with a stream.
 399       *
 400       * @param resource $stream GridFS stream
 401       * @return mixed
 402       * @throws CorruptFileException if the file "_id" field does not exist
 403       * @throws InvalidArgumentException if $stream is not a GridFS stream
 404       * @throws DriverRuntimeException for other driver errors (e.g. connection errors)
 405       */
 406      public function getFileIdForStream($stream)
 407      {
 408          $file = $this->getRawFileDocumentForStream($stream);
 409  
 410          /* Filter the raw document through the specified type map, but override
 411           * the root type so we can reliably access the ID.
 412           */
 413          $typeMap = ['root' => 'stdClass'] + $this->typeMap;
 414          $file = apply_type_map_to_document($file, $typeMap);
 415  
 416          if (! isset($file->_id) && ! property_exists($file, '_id')) {
 417              throw new CorruptFileException('file._id does not exist');
 418          }
 419  
 420          return $file->_id;
 421      }
 422  
 423      /**
 424       * Return the files collection.
 425       *
 426       * @return Collection
 427       */
 428      public function getFilesCollection()
 429      {
 430          return $this->collectionWrapper->getFilesCollection();
 431      }
 432  
 433      /**
 434       * Return the read concern for this GridFS bucket.
 435       *
 436       * @see http://php.net/manual/en/mongodb-driver-readconcern.isdefault.php
 437       * @return ReadConcern
 438       */
 439      public function getReadConcern()
 440      {
 441          return $this->readConcern;
 442      }
 443  
 444      /**
 445       * Return the read preference for this GridFS bucket.
 446       *
 447       * @return ReadPreference
 448       */
 449      public function getReadPreference()
 450      {
 451          return $this->readPreference;
 452      }
 453  
 454      /**
 455       * Return the type map for this GridFS bucket.
 456       *
 457       * @return array
 458       */
 459      public function getTypeMap()
 460      {
 461          return $this->typeMap;
 462      }
 463  
 464      /**
 465       * Return the write concern for this GridFS bucket.
 466       *
 467       * @see http://php.net/manual/en/mongodb-driver-writeconcern.isdefault.php
 468       * @return WriteConcern
 469       */
 470      public function getWriteConcern()
 471      {
 472          return $this->writeConcern;
 473      }
 474  
 475      /**
 476       * Opens a readable stream for reading a GridFS file.
 477       *
 478       * @param mixed $id File ID
 479       * @return resource
 480       * @throws FileNotFoundException if no file could be selected
 481       * @throws DriverRuntimeException for other driver errors (e.g. connection errors)
 482       */
 483      public function openDownloadStream($id)
 484      {
 485          $file = $this->collectionWrapper->findFileById($id);
 486  
 487          if ($file === null) {
 488              throw FileNotFoundException::byId($id, $this->getFilesNamespace());
 489          }
 490  
 491          return $this->openDownloadStreamByFile($file);
 492      }
 493  
 494      /**
 495       * Opens a readable stream stream to read a GridFS file, which is selected
 496       * by name and revision.
 497       *
 498       * Supported options:
 499       *
 500       *  * revision (integer): Which revision (i.e. documents with the same
 501       *    filename and different uploadDate) of the file to retrieve. Defaults
 502       *    to -1 (i.e. the most recent revision).
 503       *
 504       * Revision numbers are defined as follows:
 505       *
 506       *  * 0 = the original stored file
 507       *  * 1 = the first revision
 508       *  * 2 = the second revision
 509       *  * etc…
 510       *  * -2 = the second most recent revision
 511       *  * -1 = the most recent revision
 512       *
 513       * @param string $filename Filename
 514       * @param array  $options  Download options
 515       * @return resource
 516       * @throws FileNotFoundException if no file could be selected
 517       * @throws DriverRuntimeException for other driver errors (e.g. connection errors)
 518       */
 519      public function openDownloadStreamByName($filename, array $options = [])
 520      {
 521          $options += ['revision' => -1];
 522  
 523          $file = $this->collectionWrapper->findFileByFilenameAndRevision($filename, $options['revision']);
 524  
 525          if ($file === null) {
 526              throw FileNotFoundException::byFilenameAndRevision($filename, $options['revision'], $this->getFilesNamespace());
 527          }
 528  
 529          return $this->openDownloadStreamByFile($file);
 530      }
 531  
 532      /**
 533       * Opens a writable stream for writing a GridFS file.
 534       *
 535       * Supported options:
 536       *
 537       *  * _id (mixed): File document identifier. Defaults to a new ObjectId.
 538       *
 539       *  * chunkSizeBytes (integer): The chunk size in bytes. Defaults to the
 540       *    bucket's chunk size.
 541       *
 542       *  * disableMD5 (boolean): When true, no MD5 sum will be generated for
 543       *    the stored file. Defaults to "false".
 544       *
 545       *  * metadata (document): User data for the "metadata" field of the files
 546       *    collection document.
 547       *
 548       * @param string $filename Filename
 549       * @param array  $options  Upload options
 550       * @return resource
 551       */
 552      public function openUploadStream($filename, array $options = [])
 553      {
 554          $options += ['chunkSizeBytes' => $this->chunkSizeBytes];
 555  
 556          $path = $this->createPathForUpload();
 557          $context = stream_context_create([
 558              self::$streamWrapperProtocol => [
 559                  'collectionWrapper' => $this->collectionWrapper,
 560                  'filename' => $filename,
 561                  'options' => $options,
 562              ],
 563          ]);
 564  
 565          return fopen($path, 'w', false, $context);
 566      }
 567  
 568      /**
 569       * Renames the GridFS file with the specified ID.
 570       *
 571       * @param mixed  $id          File ID
 572       * @param string $newFilename New filename
 573       * @throws FileNotFoundException if no file could be selected
 574       * @throws DriverRuntimeException for other driver errors (e.g. connection errors)
 575       */
 576      public function rename($id, $newFilename)
 577      {
 578          $updateResult = $this->collectionWrapper->updateFilenameForId($id, $newFilename);
 579  
 580          if ($updateResult->getModifiedCount() === 1) {
 581              return;
 582          }
 583  
 584          /* If the update resulted in no modification, it's possible that the
 585           * file did not exist, in which case we must raise an error. Checking
 586           * the write result's matched count will be most efficient, but fall
 587           * back to a findOne operation if necessary (i.e. legacy writes).
 588           */
 589          $found = $updateResult->getMatchedCount() !== null
 590              ? $updateResult->getMatchedCount() === 1
 591              : $this->collectionWrapper->findFileById($id) !== null;
 592  
 593          if (! $found) {
 594              throw FileNotFoundException::byId($id, $this->getFilesNamespace());
 595          }
 596      }
 597  
 598      /**
 599       * Writes the contents of a readable stream to a GridFS file.
 600       *
 601       * Supported options:
 602       *
 603       *  * _id (mixed): File document identifier. Defaults to a new ObjectId.
 604       *
 605       *  * chunkSizeBytes (integer): The chunk size in bytes. Defaults to the
 606       *    bucket's chunk size.
 607       *
 608       *  * disableMD5 (boolean): When true, no MD5 sum will be generated for
 609       *    the stored file. Defaults to "false".
 610       *
 611       *  * metadata (document): User data for the "metadata" field of the files
 612       *    collection document.
 613       *
 614       * @param string   $filename Filename
 615       * @param resource $source   Readable stream
 616       * @param array    $options  Stream options
 617       * @return mixed ID of the newly created GridFS file
 618       * @throws InvalidArgumentException if $source is not a GridFS stream
 619       * @throws StreamException if the file could not be uploaded
 620       * @throws DriverRuntimeException for other driver errors (e.g. connection errors)
 621       */
 622      public function uploadFromStream($filename, $source, array $options = [])
 623      {
 624          if (! is_resource($source) || get_resource_type($source) != "stream") {
 625              throw InvalidArgumentException::invalidType('$source', $source, 'resource');
 626          }
 627  
 628          $destination = $this->openUploadStream($filename, $options);
 629  
 630          if (@stream_copy_to_stream($source, $destination) === false) {
 631              $destinationUri = $this->createPathForFile($this->getRawFileDocumentForStream($destination));
 632              throw StreamException::uploadFailed($filename, $source, $destinationUri);
 633          }
 634  
 635          return $this->getFileIdForStream($destination);
 636      }
 637  
 638      /**
 639       * Creates a path for an existing GridFS file.
 640       *
 641       * @param stdClass $file GridFS file document
 642       * @return string
 643       */
 644      private function createPathForFile(stdClass $file)
 645      {
 646          if (! is_object($file->_id) || method_exists($file->_id, '__toString')) {
 647              $id = (string) $file->_id;
 648          } else {
 649              $id = toJSON(fromPHP(['_id' => $file->_id]));
 650          }
 651  
 652          return sprintf(
 653              '%s://%s/%s.files/%s',
 654              self::$streamWrapperProtocol,
 655              urlencode($this->databaseName),
 656              urlencode($this->bucketName),
 657              urlencode($id)
 658          );
 659      }
 660  
 661      /**
 662       * Creates a path for a new GridFS file, which does not yet have an ID.
 663       *
 664       * @return string
 665       */
 666      private function createPathForUpload()
 667      {
 668          return sprintf(
 669              '%s://%s/%s.files',
 670              self::$streamWrapperProtocol,
 671              urlencode($this->databaseName),
 672              urlencode($this->bucketName)
 673          );
 674      }
 675  
 676      /**
 677       * Returns the names of the files collection.
 678       *
 679       * @return string
 680       */
 681      private function getFilesNamespace()
 682      {
 683          return sprintf('%s.%s.files', $this->databaseName, $this->bucketName);
 684      }
 685  
 686      /**
 687       * Gets the file document of the GridFS file associated with a stream.
 688       *
 689       * This returns the raw document from the StreamWrapper, which does not
 690       * respect the Bucket's type map.
 691       *
 692       * @param resource $stream GridFS stream
 693       * @return stdClass
 694       * @throws InvalidArgumentException
 695       */
 696      private function getRawFileDocumentForStream($stream)
 697      {
 698          if (! is_resource($stream) || get_resource_type($stream) != "stream") {
 699              throw InvalidArgumentException::invalidType('$stream', $stream, 'resource');
 700          }
 701  
 702          $metadata = stream_get_meta_data($stream);
 703  
 704          if (! isset($metadata['wrapper_data']) || ! $metadata['wrapper_data'] instanceof StreamWrapper) {
 705              throw InvalidArgumentException::invalidType('$stream wrapper data', $metadata['wrapper_data'] ?? null, StreamWrapper::class);
 706          }
 707  
 708          return $metadata['wrapper_data']->getFile();
 709      }
 710  
 711      /**
 712       * Opens a readable stream for the GridFS file.
 713       *
 714       * @param stdClass $file GridFS file document
 715       * @return resource
 716       */
 717      private function openDownloadStreamByFile(stdClass $file)
 718      {
 719          $path = $this->createPathForFile($file);
 720          $context = stream_context_create([
 721              self::$streamWrapperProtocol => [
 722                  'collectionWrapper' => $this->collectionWrapper,
 723                  'file' => $file,
 724              ],
 725          ]);
 726  
 727          return fopen($path, 'r', false, $context);
 728      }
 729  
 730      /**
 731       * Registers the GridFS stream wrapper if it is not already registered.
 732       */
 733      private function registerStreamWrapper()
 734      {
 735          if (in_array(self::$streamWrapperProtocol, stream_get_wrappers())) {
 736              return;
 737          }
 738  
 739          StreamWrapper::register(self::$streamWrapperProtocol);
 740      }
 741  }