<?php
/*
< * Copyright 2016-2017 MongoDB, Inc.
> * Copyright 2016-present 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
> * https://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.
*/
namespace MongoDB\GridFS;
use MongoDB\BSON\UTCDateTime;
< use stdClass;
< use Throwable;
>
> use function assert;
use function explode;
< use function get_class;
use function in_array;
use function is_integer;
< use function sprintf;
> use function is_resource;
use function stream_context_get_options;
use function stream_get_wrappers;
use function stream_wrapper_register;
use function stream_wrapper_unregister;
< use function trigger_error;
< use const E_USER_WARNING;
>
use const SEEK_CUR;
use const SEEK_END;
use const SEEK_SET;
use const STREAM_IS_URL;
/**
* Stream wrapper for reading and writing a GridFS file.
*
* @internal
* @see Bucket::openUploadStream()
* @see Bucket::openDownloadStream()
*/
class StreamWrapper
{
/** @var resource|null Stream context (set by PHP) */
public $context;
/** @var string|null */
private $mode;
/** @var string|null */
private $protocol;
/** @var ReadableStream|WritableStream|null */
private $stream;
public function __destruct()
{
/* This destructor is a workaround for PHP trying to use the stream well
* after all objects have been destructed. This can cause autoloading
* issues and possibly segmentation faults during PHP shutdown. */
$this->stream = null;
}
/**
* Return the stream's file document.
< *
< * @return stdClass
*/
< public function getFile()
> public function getFile(): object
{
> assert($this->stream !== null);
return $this->stream->getFile();
>
}
/**
* Register the GridFS stream wrapper.
*
* @param string $protocol Protocol to use for stream_wrapper_register()
*/
< public static function register($protocol = 'gridfs')
> public static function register(string $protocol = 'gridfs'): void
{
if (in_array($protocol, stream_get_wrappers())) {
stream_wrapper_unregister($protocol);
}
stream_wrapper_register($protocol, static::class, STREAM_IS_URL);
}
/**
* Closes the stream.
*
< * @see http://php.net/manual/en/streamwrapper.stream-close.php
> * @see https://php.net/manual/en/streamwrapper.stream-close.php
*/
< public function stream_close()
> public function stream_close(): void
{
if (! $this->stream) {
return;
}
$this->stream->close();
}
/**
* Returns whether the file pointer is at the end of the stream.
*
< * @see http://php.net/manual/en/streamwrapper.stream-eof.php
< * @return boolean
> * @see https://php.net/manual/en/streamwrapper.stream-eof.php
*/
< public function stream_eof()
> public function stream_eof(): bool
{
if (! $this->stream instanceof ReadableStream) {
return false;
}
return $this->stream->isEOF();
}
/**
* Opens the stream.
*
< * @see http://php.net/manual/en/streamwrapper.stream-open.php
> * @see https://php.net/manual/en/streamwrapper.stream-open.php
* @param string $path Path to the file resource
* @param string $mode Mode used to open the file (only "r" and "w" are supported)
* @param integer $options Additional flags set by the streams API
< * @param string $openedPath Not used
< * @return boolean
> * @param string|null $openedPath Not used
*/
< public function stream_open($path, $mode, $options, &$openedPath)
> public function stream_open(string $path, string $mode, int $options, ?string &$openedPath): bool
{
$this->initProtocol($path);
$this->mode = $mode;
if ($mode === 'r') {
return $this->initReadableStream();
}
if ($mode === 'w') {
return $this->initWritableStream();
}
return false;
}
/**
* Read bytes from the stream.
*
* Note: this method may return a string smaller than the requested length
* if data is not available to be read.
*
< * @see http://php.net/manual/en/streamwrapper.stream-read.php
> * @see https://php.net/manual/en/streamwrapper.stream-read.php
* @param integer $length Number of bytes to read
< * @return string
*/
< public function stream_read($length)
> public function stream_read(int $length): string
{
if (! $this->stream instanceof ReadableStream) {
return '';
}
< try {
return $this->stream->readBytes($length);
< } catch (Throwable $e) {
< trigger_error(sprintf('%s: %s', get_class($e), $e->getMessage()), E_USER_WARNING);
<
< return false;
< }
}
/**
* Return the current position of the stream.
*
< * @see http://php.net/manual/en/streamwrapper.stream-seek.php
> * @see https://php.net/manual/en/streamwrapper.stream-seek.php
* @param integer $offset Stream offset to seek to
* @param integer $whence One of SEEK_SET, SEEK_CUR, or SEEK_END
* @return boolean True if the position was updated and false otherwise
*/
< public function stream_seek($offset, $whence = SEEK_SET)
> public function stream_seek(int $offset, int $whence = SEEK_SET): bool
{
> assert($this->stream !== null);
$size = $this->stream->getSize();
>
if ($whence === SEEK_CUR) {
$offset += $this->stream->tell();
}
if ($whence === SEEK_END) {
$offset += $size;
}
// WritableStreams are always positioned at the end of the stream
if ($this->stream instanceof WritableStream) {
return $offset === $size;
}
if ($offset < 0 || $offset > $size) {
return false;
}
$this->stream->seek($offset);
return true;
}
/**
* Return information about the stream.
*
< * @see http://php.net/manual/en/streamwrapper.stream-stat.php
< * @return array
> * @see https://php.net/manual/en/streamwrapper.stream-stat.php
*/
< public function stream_stat()
> public function stream_stat(): array
{
> assert($this->stream !== null);
$stat = $this->getStatTemplate();
>
$stat[2] = $stat['mode'] = $this->stream instanceof ReadableStream
? 0100444 // S_IFREG & S_IRUSR & S_IRGRP & S_IROTH
: 0100222; // S_IFREG & S_IWUSR & S_IWGRP & S_IWOTH
$stat[7] = $stat['size'] = $this->stream->getSize();
$file = $this->stream->getFile();
if (isset($file->uploadDate) && $file->uploadDate instanceof UTCDateTime) {
$timestamp = $file->uploadDate->toDateTime()->getTimestamp();
$stat[9] = $stat['mtime'] = $timestamp;
$stat[10] = $stat['ctime'] = $timestamp;
}
if (isset($file->chunkSize) && is_integer($file->chunkSize)) {
$stat[11] = $stat['blksize'] = $file->chunkSize;
}
return $stat;
}
/**
* Return the current position of the stream.
*
< * @see http://php.net/manual/en/streamwrapper.stream-tell.php
> * @see https://php.net/manual/en/streamwrapper.stream-tell.php
* @return integer The current position of the stream
*/
< public function stream_tell()
> public function stream_tell(): int
{
> assert($this->stream !== null);
return $this->stream->tell();
>
}
/**
* Write bytes to the stream.
*
< * @see http://php.net/manual/en/streamwrapper.stream-write.php
> * @see https://php.net/manual/en/streamwrapper.stream-write.php
* @param string $data Data to write
* @return integer The number of bytes written
*/
< public function stream_write($data)
> public function stream_write(string $data): int
{
if (! $this->stream instanceof WritableStream) {
return 0;
}
< try {
return $this->stream->writeBytes($data);
< } catch (Throwable $e) {
< trigger_error(sprintf('%s: %s', get_class($e), $e->getMessage()), E_USER_WARNING);
<
< return false;
< }
}
/**
* Returns a stat template with default values.
< *
< * @return array
*/
< private function getStatTemplate()
> private function getStatTemplate(): array
{
return [
// phpcs:disable Squiz.Arrays.ArrayDeclaration.IndexNoNewline
0 => 0, 'dev' => 0,
1 => 0, 'ino' => 0,
2 => 0, 'mode' => 0,
3 => 0, 'nlink' => 0,
4 => 0, 'uid' => 0,
5 => 0, 'gid' => 0,
6 => -1, 'rdev' => -1,
7 => 0, 'size' => 0,
8 => 0, 'atime' => 0,
9 => 0, 'mtime' => 0,
10 => 0, 'ctime' => 0,
11 => -1, 'blksize' => -1,
12 => -1, 'blocks' => -1,
// phpcs:enable
];
}
/**
* Initialize the protocol from the given path.
*
* @see StreamWrapper::stream_open()
< * @param string $path
*/
< private function initProtocol($path)
> private function initProtocol(string $path): void
{
$parts = explode('://', $path, 2);
$this->protocol = $parts[0] ?: 'gridfs';
}
/**
* Initialize the internal stream for reading.
*
* @see StreamWrapper::stream_open()
< * @return boolean
*/
< private function initReadableStream()
> private function initReadableStream(): bool
{
> assert(is_resource($this->context));
$context = stream_context_get_options($this->context);
> assert($this->protocol !== null);
$this->stream = new ReadableStream(
$context[$this->protocol]['collectionWrapper'],
$context[$this->protocol]['file']
);
return true;
}
/**
* Initialize the internal stream for writing.
*
* @see StreamWrapper::stream_open()
< * @return boolean
*/
< private function initWritableStream()
> private function initWritableStream(): bool
{
> assert(is_resource($this->context));
$context = stream_context_get_options($this->context);
> assert($this->protocol !== null);
$this->stream = new WritableStream(
$context[$this->protocol]['collectionWrapper'],
$context[$this->protocol]['filename'],
$context[$this->protocol]['options']
);
return true;
}
}