Bucket.php 23.6 KB
Newer Older
1
<?php
2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
/*
 * Copyright 2016-2017 MongoDB, Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
17

18 19
namespace MongoDB\GridFS;

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 26
use MongoDB\Driver\ReadPreference;
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 32
use MongoDB\Model\BSONArray;
use MongoDB\Model\BSONDocument;
33
use MongoDB\Operation\Find;
34
use stdClass;
35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55
use function array_intersect_key;
use function fopen;
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;
use function is_string;
use function method_exists;
use function MongoDB\apply_type_map_to_document;
use function MongoDB\BSON\fromPHP;
use function MongoDB\BSON\toJSON;
use function property_exists;
use function sprintf;
use function stream_context_create;
use function stream_copy_to_stream;
use function stream_get_meta_data;
use function stream_get_wrappers;
use function urlencode;
56

57
/**
58 59 60
 * Bucket provides a public API for interacting with the GridFS files and chunks
 * collections.
 *
61 62 63 64
 * @api
 */
class Bucket
{
65
    /** @var string */
66
    private static $defaultBucketName = 'fs';
67 68

    /** @var integer */
69
    private static $defaultChunkSizeBytes = 261120;
70 71

    /** @var array */
72
    private static $defaultTypeMap = [
73 74 75
        'array' => BSONArray::class,
        'document' => BSONDocument::class,
        'root' => BSONDocument::class,
76
    ];
77 78

    /** @var string */
79
    private static $streamWrapperProtocol = 'gridfs';
80

81
    /** @var CollectionWrapper */
82
    private $collectionWrapper;
83 84

    /** @var string */
85
    private $databaseName;
86 87

    /** @var Manager */
88
    private $manager;
89 90

    /** @var string */
91
    private $bucketName;
92 93

    /** @var boolean */
94
    private $disableMD5;
95 96

    /** @var integer */
97
    private $chunkSizeBytes;
98 99

    /** @var ReadConcern */
100
    private $readConcern;
101 102

    /** @var ReadPreference */
103
    private $readPreference;
104 105

    /** @var array */
106
    private $typeMap;
107 108

    /** @var WriteConcern */
109
    private $writeConcern;
110

111 112 113 114 115 116 117 118 119 120 121
    /**
     * Constructs a GridFS bucket.
     *
     * Supported options:
     *
     *  * bucketName (string): The bucket name, which will be used as a prefix
     *    for the files and chunks collections. Defaults to "fs".
     *
     *  * chunkSizeBytes (integer): The chunk size in bytes. Defaults to
     *    261120 (i.e. 255 KiB).
     *
122 123 124
     *  * disableMD5 (boolean): When true, no MD5 sum will be generated for
     *    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 135
     * @param Manager $manager      Manager instance from the driver
     * @param string  $databaseName Database name
     * @param array   $options      Bucket options
136
     * @throws InvalidArgumentException for parameter/option parsing errors
137 138 139 140
     */
    public function __construct(Manager $manager, $databaseName, array $options = [])
    {
        $options += [
141
            'bucketName' => self::$defaultBucketName,
142
            'chunkSizeBytes' => self::$defaultChunkSizeBytes,
143
            'disableMD5' => false,
144
        ];
145

146
        if (! is_string($options['bucketName'])) {
147
            throw InvalidArgumentException::invalidType('"bucketName" option', $options['bucketName'], 'string');
148 149
        }

150
        if (! is_integer($options['chunkSizeBytes'])) {
151
            throw InvalidArgumentException::invalidType('"chunkSizeBytes" option', $options['chunkSizeBytes'], 'integer');
152 153
        }

154
        if ($options['chunkSizeBytes'] < 1) {
155 156 157
            throw new InvalidArgumentException(sprintf('Expected "chunkSizeBytes" option to be >= 1, %d given', $options['chunkSizeBytes']));
        }

158
        if (! is_bool($options['disableMD5'])) {
159 160 161
            throw InvalidArgumentException::invalidType('"disableMD5" option', $options['disableMD5'], 'boolean');
        }

162
        if (isset($options['readConcern']) && ! $options['readConcern'] instanceof ReadConcern) {
163
            throw InvalidArgumentException::invalidType('"readConcern" option', $options['readConcern'], ReadConcern::class);
164 165
        }

166
        if (isset($options['readPreference']) && ! $options['readPreference'] instanceof ReadPreference) {
167
            throw InvalidArgumentException::invalidType('"readPreference" option', $options['readPreference'], ReadPreference::class);
168 169
        }

170 171 172 173
        if (isset($options['typeMap']) && ! is_array($options['typeMap'])) {
            throw InvalidArgumentException::invalidType('"typeMap" option', $options['typeMap'], 'array');
        }

174
        if (isset($options['writeConcern']) && ! $options['writeConcern'] instanceof WriteConcern) {
175
            throw InvalidArgumentException::invalidType('"writeConcern" option', $options['writeConcern'], WriteConcern::class);
176 177
        }

178
        $this->manager = $manager;
179
        $this->databaseName = (string) $databaseName;
180 181
        $this->bucketName = $options['bucketName'];
        $this->chunkSizeBytes = $options['chunkSizeBytes'];
182
        $this->disableMD5 = $options['disableMD5'];
183 184 185 186
        $this->readConcern = $options['readConcern'] ?? $this->manager->getReadConcern();
        $this->readPreference = $options['readPreference'] ?? $this->manager->getReadPreference();
        $this->typeMap = $options['typeMap'] ?? self::$defaultTypeMap;
        $this->writeConcern = $options['writeConcern'] ?? $this->manager->getWriteConcern();
187

188
        $collectionOptions = array_intersect_key($options, ['readConcern' => 1, 'readPreference' => 1, 'typeMap' => 1, 'writeConcern' => 1]);
189

190
        $this->collectionWrapper = new CollectionWrapper($manager, $databaseName, $options['bucketName'], $collectionOptions);
191
        $this->registerStreamWrapper();
192
    }
193

194 195 196 197 198 199 200 201 202 203 204
    /**
     * Return internal properties for debugging purposes.
     *
     * @see http://php.net/manual/en/language.oop5.magic.php#language.oop5.magic.debuginfo
     * @return array
     */
    public function __debugInfo()
    {
        return [
            'bucketName' => $this->bucketName,
            'databaseName' => $this->databaseName,
205
            'manager' => $this->manager,
206
            'chunkSizeBytes' => $this->chunkSizeBytes,
207 208
            'readConcern' => $this->readConcern,
            'readPreference' => $this->readPreference,
209
            'typeMap' => $this->typeMap,
210
            'writeConcern' => $this->writeConcern,
211 212 213
        ];
    }

214
    /**
215 216 217 218
     * Delete a file from the GridFS bucket.
     *
     * If the files collection document is not found, this method will still
     * attempt to delete orphaned chunks.
219
     *
220
     * @param mixed $id File ID
221 222
     * @throws FileNotFoundException if no file could be selected
     * @throws DriverRuntimeException for other driver errors (e.g. connection errors)
223
     */
224
    public function delete($id)
225
    {
226 227
        $file = $this->collectionWrapper->findFileById($id);
        $this->collectionWrapper->deleteFileAndChunksById($id);
228 229

        if ($file === null) {
230
            throw FileNotFoundException::byId($id, $this->getFilesNamespace());
231
        }
232
    }
233

234
    /**
235
     * Writes the contents of a GridFS file to a writable stream.
236
     *
237
     * @param mixed    $id          File ID
238
     * @param resource $destination Writable Stream
239
     * @throws FileNotFoundException if no file could be selected
240
     * @throws InvalidArgumentException if $destination is not a stream
241
     * @throws DriverRuntimeException for other driver errors (e.g. connection errors)
242
     */
243
    public function downloadToStream($id, $destination)
244
    {
245
        if (! is_resource($destination) || get_resource_type($destination) != "stream") {
246
            throw InvalidArgumentException::invalidType('$destination', $destination, 'resource');
247 248
        }

249
        stream_copy_to_stream($this->openDownloadStream($id), $destination);
250
    }
251

252
    /**
253 254 255 256 257 258 259 260 261 262
     * Writes the contents of a GridFS file, which is selected by name and
     * revision, to a writable stream.
     *
     * Supported options:
     *
     *  * revision (integer): Which revision (i.e. documents with the same
     *    filename and different uploadDate) of the file to retrieve. Defaults
     *    to -1 (i.e. the most recent revision).
     *
     * Revision numbers are defined as follows:
263
     *
264 265 266 267 268 269 270
     *  * 0 = the original stored file
     *  * 1 = the first revision
     *  * 2 = the second revision
     *  * etc…
     *  * -2 = the second most recent revision
     *  * -1 = the most recent revision
     *
271
     * @param string   $filename    Filename
272 273
     * @param resource $destination Writable Stream
     * @param array    $options     Download options
274
     * @throws FileNotFoundException if no file could be selected
275
     * @throws InvalidArgumentException if $destination is not a stream
276
     * @throws DriverRuntimeException for other driver errors (e.g. connection errors)
277
     */
278
    public function downloadToStreamByName($filename, $destination, array $options = [])
279
    {
280
        if (! is_resource($destination) || get_resource_type($destination) != "stream") {
281
            throw InvalidArgumentException::invalidType('$destination', $destination, 'resource');
282 283
        }

284
        stream_copy_to_stream($this->openDownloadStreamByName($filename, $options), $destination);
285
    }
286

287
    /**
288 289
     * Drops the files and chunks collections associated with this GridFS
     * bucket.
290 291
     *
     * @throws DriverRuntimeException for other driver errors (e.g. connection errors)
292
     */
293 294
    public function drop()
    {
295
        $this->collectionWrapper->dropCollections();
296 297
    }

298
    /**
299 300
     * Finds documents from the GridFS bucket's files collection matching the
     * query.
301 302 303 304 305
     *
     * @see Find::__construct() for supported options
     * @param array|object $filter  Query by which to filter documents
     * @param array        $options Additional options
     * @return Cursor
306 307 308
     * @throws UnsupportedException if options are not supported by the selected server
     * @throws InvalidArgumentException for parameter/option parsing errors
     * @throws DriverRuntimeException for other driver errors (e.g. connection errors)
309
     */
310
    public function find($filter = [], array $options = [])
311
    {
312
        return $this->collectionWrapper->findFiles($filter, $options);
313
    }
314

315 316 317 318 319 320 321 322
    /**
     * Finds a single document from the GridFS bucket's files collection
     * matching the query.
     *
     * @see FindOne::__construct() for supported options
     * @param array|object $filter  Query by which to filter documents
     * @param array        $options Additional options
     * @return array|object|null
323 324 325
     * @throws UnsupportedException if options are not supported by the selected server
     * @throws InvalidArgumentException for parameter/option parsing errors
     * @throws DriverRuntimeException for other driver errors (e.g. connection errors)
326 327 328 329 330 331
     */
    public function findOne($filter = [], array $options = [])
    {
        return $this->collectionWrapper->findOneFile($filter, $options);
    }

332 333 334 335 336 337 338 339 340 341
    /**
     * Return the bucket name.
     *
     * @return string
     */
    public function getBucketName()
    {
        return $this->bucketName;
    }

342 343 344 345 346 347 348 349 350 351
    /**
     * Return the chunks collection.
     *
     * @return Collection
     */
    public function getChunksCollection()
    {
        return $this->collectionWrapper->getChunksCollection();
    }

352 353 354 355 356 357 358 359 360 361
    /**
     * Return the chunk size in bytes.
     *
     * @return integer
     */
    public function getChunkSizeBytes()
    {
        return $this->chunkSizeBytes;
    }

362 363 364 365 366
    /**
     * Return the database name.
     *
     * @return string
     */
367 368 369 370 371
    public function getDatabaseName()
    {
        return $this->databaseName;
    }

372
    /**
373
     * Gets the file document of the GridFS file associated with a stream.
374 375
     *
     * @param resource $stream GridFS stream
376
     * @return array|object
377 378
     * @throws InvalidArgumentException if $stream is not a GridFS stream
     * @throws DriverRuntimeException for other driver errors (e.g. connection errors)
379
     */
380
    public function getFileDocumentForStream($stream)
381
    {
382
        $file = $this->getRawFileDocumentForStream($stream);
383

384
        // Filter the raw document through the specified type map
385
        return apply_type_map_to_document($file, $this->typeMap);
386 387 388 389 390 391
    }

    /**
     * Gets the file document's ID of the GridFS file associated with a stream.
     *
     * @param resource $stream GridFS stream
392
     * @return mixed
393 394 395
     * @throws CorruptFileException if the file "_id" field does not exist
     * @throws InvalidArgumentException if $stream is not a GridFS stream
     * @throws DriverRuntimeException for other driver errors (e.g. connection errors)
396 397 398
     */
    public function getFileIdForStream($stream)
    {
399 400 401 402 403 404
        $file = $this->getRawFileDocumentForStream($stream);

        /* Filter the raw document through the specified type map, but override
         * the root type so we can reliably access the ID.
         */
        $typeMap = ['root' => 'stdClass'] + $this->typeMap;
405
        $file = apply_type_map_to_document($file, $typeMap);
406

407
        if (! isset($file->_id) && ! property_exists($file, '_id')) {
408
            throw new CorruptFileException('file._id does not exist');
409
        }
410

411
        return $file->_id;
412
    }
413

414 415 416 417 418 419 420 421 422 423
    /**
     * Return the files collection.
     *
     * @return Collection
     */
    public function getFilesCollection()
    {
        return $this->collectionWrapper->getFilesCollection();
    }

424
    /**
425
     * Return the read concern for this GridFS bucket.
426
     *
427
     * @see http://php.net/manual/en/mongodb-driver-readconcern.isdefault.php
428 429 430 431 432 433 434 435
     * @return ReadConcern
     */
    public function getReadConcern()
    {
        return $this->readConcern;
    }

    /**
436
     * Return the read preference for this GridFS bucket.
437 438 439 440 441 442 443 444 445
     *
     * @return ReadPreference
     */
    public function getReadPreference()
    {
        return $this->readPreference;
    }

    /**
446
     * Return the type map for this GridFS bucket.
447 448 449 450 451 452 453 454 455
     *
     * @return array
     */
    public function getTypeMap()
    {
        return $this->typeMap;
    }

    /**
456
     * Return the write concern for this GridFS bucket.
457
     *
458
     * @see http://php.net/manual/en/mongodb-driver-writeconcern.isdefault.php
459 460 461 462 463 464 465
     * @return WriteConcern
     */
    public function getWriteConcern()
    {
        return $this->writeConcern;
    }

466
    /**
467 468
     * Opens a readable stream for reading a GridFS file.
     *
469
     * @param mixed $id File ID
470
     * @return resource
471 472
     * @throws FileNotFoundException if no file could be selected
     * @throws DriverRuntimeException for other driver errors (e.g. connection errors)
473
     */
474
    public function openDownloadStream($id)
475
    {
476
        $file = $this->collectionWrapper->findFileById($id);
477 478

        if ($file === null) {
479
            throw FileNotFoundException::byId($id, $this->getFilesNamespace());
480 481
        }

482
        return $this->openDownloadStreamByFile($file);
483
    }
484

485
    /**
486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503
     * Opens a readable stream stream to read a GridFS file, which is selected
     * by name and revision.
     *
     * Supported options:
     *
     *  * revision (integer): Which revision (i.e. documents with the same
     *    filename and different uploadDate) of the file to retrieve. Defaults
     *    to -1 (i.e. the most recent revision).
     *
     * Revision numbers are defined as follows:
     *
     *  * 0 = the original stored file
     *  * 1 = the first revision
     *  * 2 = the second revision
     *  * etc…
     *  * -2 = the second most recent revision
     *  * -1 = the most recent revision
     *
504
     * @param string $filename Filename
505 506
     * @param array  $options  Download options
     * @return resource
507 508
     * @throws FileNotFoundException if no file could be selected
     * @throws DriverRuntimeException for other driver errors (e.g. connection errors)
509 510
     */
    public function openDownloadStreamByName($filename, array $options = [])
511
    {
512
        $options += ['revision' => -1];
513 514 515 516 517 518

        $file = $this->collectionWrapper->findFileByFilenameAndRevision($filename, $options['revision']);

        if ($file === null) {
            throw FileNotFoundException::byFilenameAndRevision($filename, $options['revision'], $this->getFilesNamespace());
        }
519 520

        return $this->openDownloadStreamByFile($file);
521
    }
522

523
    /**
524 525 526 527
     * Opens a writable stream for writing a GridFS file.
     *
     * Supported options:
     *
528 529
     *  * _id (mixed): File document identifier. Defaults to a new ObjectId.
     *
530 531 532
     *  * chunkSizeBytes (integer): The chunk size in bytes. Defaults to the
     *    bucket's chunk size.
     *
533 534 535
     *  * disableMD5 (boolean): When true, no MD5 sum will be generated for
     *    the stored file. Defaults to "false".
     *
536 537 538
     *  * metadata (document): User data for the "metadata" field of the files
     *    collection document.
     *
539
     * @param string $filename Filename
540
     * @param array  $options  Upload options
541 542 543
     * @return resource
     */
    public function openUploadStream($filename, array $options = [])
544
    {
545
        $options += ['chunkSizeBytes' => $this->chunkSizeBytes];
546

547 548 549 550 551 552 553 554
        $path = $this->createPathForUpload();
        $context = stream_context_create([
            self::$streamWrapperProtocol => [
                'collectionWrapper' => $this->collectionWrapper,
                'filename' => $filename,
                'options' => $options,
            ],
        ]);
555

556
        return fopen($path, 'w', false, $context);
557
    }
558

559
    /**
560 561
     * Renames the GridFS file with the specified ID.
     *
562
     * @param mixed  $id          File ID
563
     * @param string $newFilename New filename
564 565
     * @throws FileNotFoundException if no file could be selected
     * @throws DriverRuntimeException for other driver errors (e.g. connection errors)
566
     */
567
    public function rename($id, $newFilename)
568
    {
569 570 571 572 573 574 575 576 577 578 579 580 581 582 583
        $updateResult = $this->collectionWrapper->updateFilenameForId($id, $newFilename);

        if ($updateResult->getModifiedCount() === 1) {
            return;
        }

        /* If the update resulted in no modification, it's possible that the
         * file did not exist, in which case we must raise an error. Checking
         * the write result's matched count will be most efficient, but fall
         * back to a findOne operation if necessary (i.e. legacy writes).
         */
        $found = $updateResult->getMatchedCount() !== null
            ? $updateResult->getMatchedCount() === 1
            : $this->collectionWrapper->findFileById($id) !== null;

584
        if (! $found) {
585
            throw FileNotFoundException::byId($id, $this->getFilesNamespace());
586
        }
587
    }
588 589 590 591 592 593

    /**
     * Writes the contents of a readable stream to a GridFS file.
     *
     * Supported options:
     *
594 595
     *  * _id (mixed): File document identifier. Defaults to a new ObjectId.
     *
596 597 598
     *  * chunkSizeBytes (integer): The chunk size in bytes. Defaults to the
     *    bucket's chunk size.
     *
599 600 601
     *  * disableMD5 (boolean): When true, no MD5 sum will be generated for
     *    the stored file. Defaults to "false".
     *
602 603 604
     *  * metadata (document): User data for the "metadata" field of the files
     *    collection document.
     *
605
     * @param string   $filename Filename
606 607
     * @param resource $source   Readable stream
     * @param array    $options  Stream options
608
     * @return mixed ID of the newly created GridFS file
609 610
     * @throws InvalidArgumentException if $source is not a GridFS stream
     * @throws DriverRuntimeException for other driver errors (e.g. connection errors)
611 612
     */
    public function uploadFromStream($filename, $source, array $options = [])
613
    {
614
        if (! is_resource($source) || get_resource_type($source) != "stream") {
615 616
            throw InvalidArgumentException::invalidType('$source', $source, 'resource');
        }
617

618 619
        $destination = $this->openUploadStream($filename, $options);
        stream_copy_to_stream($source, $destination);
620

621
        return $this->getFileIdForStream($destination);
622
    }
623

624 625 626 627 628 629 630
    /**
     * Creates a path for an existing GridFS file.
     *
     * @param stdClass $file GridFS file document
     * @return string
     */
    private function createPathForFile(stdClass $file)
631
    {
632
        if (! is_object($file->_id) || method_exists($file->_id, '__toString')) {
633
            $id = (string) $file->_id;
634
        } else {
635
            $id = toJSON(fromPHP(['_id' => $file->_id]));
636
        }
637

638 639 640 641
        return sprintf(
            '%s://%s/%s.files/%s',
            self::$streamWrapperProtocol,
            urlencode($this->databaseName),
642
            urlencode($this->bucketName),
643
            urlencode($id)
644
        );
645
    }
646

647 648 649 650 651 652
    /**
     * Creates a path for a new GridFS file, which does not yet have an ID.
     *
     * @return string
     */
    private function createPathForUpload()
653
    {
654 655 656 657
        return sprintf(
            '%s://%s/%s.files',
            self::$streamWrapperProtocol,
            urlencode($this->databaseName),
658
            urlencode($this->bucketName)
659 660
        );
    }
661

662 663 664 665 666 667 668
    /**
     * Returns the names of the files collection.
     *
     * @return string
     */
    private function getFilesNamespace()
    {
669
        return sprintf('%s.%s.files', $this->databaseName, $this->bucketName);
670
    }
671

672 673 674 675 676 677 678 679 680 681 682 683
    /**
     * Gets the file document of the GridFS file associated with a stream.
     *
     * This returns the raw document from the StreamWrapper, which does not
     * respect the Bucket's type map.
     *
     * @param resource $stream GridFS stream
     * @return stdClass
     * @throws InvalidArgumentException
     */
    private function getRawFileDocumentForStream($stream)
    {
684
        if (! is_resource($stream) || get_resource_type($stream) != "stream") {
685 686 687 688 689
            throw InvalidArgumentException::invalidType('$stream', $stream, 'resource');
        }

        $metadata = stream_get_meta_data($stream);

690
        if (! isset($metadata['wrapper_data']) || ! $metadata['wrapper_data'] instanceof StreamWrapper) {
691
            throw InvalidArgumentException::invalidType('$stream wrapper data', $metadata['wrapper_data'] ?? null, StreamWrapper::class);
692 693 694 695 696
        }

        return $metadata['wrapper_data']->getFile();
    }

697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713
    /**
     * Opens a readable stream for the GridFS file.
     *
     * @param stdClass $file GridFS file document
     * @return resource
     */
    private function openDownloadStreamByFile(stdClass $file)
    {
        $path = $this->createPathForFile($file);
        $context = stream_context_create([
            self::$streamWrapperProtocol => [
                'collectionWrapper' => $this->collectionWrapper,
                'file' => $file,
            ],
        ]);

        return fopen($path, 'r', false, $context);
714 715
    }

716 717 718 719
    /**
     * Registers the GridFS stream wrapper if it is not already registered.
     */
    private function registerStreamWrapper()
720
    {
721
        if (in_array(self::$streamWrapperProtocol, stream_get_wrappers())) {
722 723
            return;
        }
724

725
        StreamWrapper::register(self::$streamWrapperProtocol);
726
    }
727
}