pax_global_header00006660000000000000000000000064124165520200014507gustar00rootroot0000000000000052 comment=47aaa48e27dae43d39fc1cea0ccf0d84ac1a2ba5 streams-3.0.0/000077500000000000000000000000001241655202000131655ustar00rootroot00000000000000streams-3.0.0/.gitignore000066400000000000000000000000731241655202000151550ustar00rootroot00000000000000.idea .DS_STORE coverage phpunit.xml composer.lock vendor/ streams-3.0.0/.travis.yml000066400000000000000000000003411241655202000152740ustar00rootroot00000000000000language: php php: - 5.4 - 5.5 - 5.6 - hhvm before_script: - composer self-update - composer install --no-interaction --prefer-source --dev script: vendor/bin/phpunit matrix: allow_failures: - php: hhvm streams-3.0.0/CHANGELOG.rst000066400000000000000000000074051241655202000152140ustar00rootroot00000000000000========= Changelog ========= 3.0.0 (2014-10-12) ------------------ * Now supports creating streams from functions and iterators. * Supports creating buffered streams and asynchronous streams. * Removed ``functions.php``. Use the corresponding functions provided by ``GuzzleHttp\Streams\Utils`` instead. * Moved ``GuzzleHttp\Stream\MetadataStreamInterface::getMetadata`` to ``GuzzleHttp\Stream\StreamInterface``. MetadataStreamInterface is no longer used and is marked as deprecated. * Added ``attach()`` to ``GuzzleHttp\Stream\StreamInterface`` for PSR-7 compatibility. * Removed ``flush()`` from StreamInterface. * Removed the ``$maxLength`` parameter from ``GuzzleHttp\Stream\StreamInterface::getContents()``. This function now returns the entire remainder of the stream. If you want to limit the maximum amount of data read from the stream, use the ``GuzzleHttp\Stream\Utils::copyToString()`` function. * Streams that return an empty string, ``''``, are no longer considered a failure. You MUST return ``false`` to mark the read as a failure, and ensure that any decorators you create properly return ``true`` in response to the ``eof()`` method when the stream is consumed. * ``GuzzleHttp\Stream\Stream::__construct``, ``GuzzleHttp\Stream\Stream::factory``, and ``GuzzleHttp\Stream\Utils::create`` no longer accept a size in the second argument. They now accept an associative array of options, including the "size" key and "metadata" key which can be used to provide custom metadata. * Added ``GuzzleHttp\Stream\BufferStream`` to add support for buffering data, and when read, shifting data off of the buffer. * Added ``GuzzleHttp\Stream\NullBuffer`` which can be used as a buffer that does not actually store any data. * Added ``GuzzleHttp\Stream\AsyncStream`` to provide support for non-blocking streams that can be filled by a remote source (e.g., an event-loop). If a ``drain`` option is provided, the stream can also act as if it is a blocking stream. 2.1.0 (2014-08-17) ------------------ * Added an InflateStream to inflate gzipped or deflated content. * Added ``flush`` to stream wrapper. * Added the ability to easily register the GuzzleStreamWrapper if needed. 2.0.0 (2014-08-16) ------------------ * Deprecated functions.php and moved all of those methods to ``GuzzleHttp\Streams\Utils``. Use ``GuzzleHttp\Stream\Stream::factory()`` instead of ``GuzzleHttp\Stream\create()`` to create new streams. * Added ``flush()`` to ``StreamInterface``. This method is used to flush any underlying stream write buffers. * Added ``FnStream`` to easily decorate stream behavior with callables. * ``Utils::hash`` now throws an exception when the stream cannot seek to 0. 1.5.1 (2014-09-10) ------------------ * Stream metadata is grabbed from the underlying stream each time ``getMetadata`` is called rather than returning a value from a cache. * Properly closing all underlying streams when AppendStream is closed. * Seek functions no longer throw exceptions. * LazyOpenStream now correctly returns the underlying stream resource when detached. 1.5.0 (2014-08-07) ------------------ * Added ``Stream\safe_open`` to open stream resources and throw exceptions instead of raising errors. 1.4.0 (2014-07-19) ------------------ * Added a LazyOpenStream 1.3.0 (2014-07-15) ------------------ * Added an AppendStream to stream over multiple stream one after the other. 1.2.0 (2014-07-15) ------------------ * Updated the ``detach()`` method to return the underlying stream resource or ``null`` if it does not wrap a resource. * Multiple fixes for how streams behave when the underlying resource is detached * Do not clear statcache when a stream does not have a 'uri' * Added a fix to LimitStream * Added a condition to ensure that functions.php can be required multiple times streams-3.0.0/LICENSE000066400000000000000000000021271241655202000141740ustar00rootroot00000000000000Copyright (c) 2014 Michael Dowling, https://github.com/mtdowling Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. streams-3.0.0/Makefile000066400000000000000000000004241241655202000146250ustar00rootroot00000000000000all: clean coverage release: tag git push origin --tags tag: chag tag --sign --debug CHANGELOG.rst test: vendor/bin/phpunit coverage: vendor/bin/phpunit --coverage-html=artifacts/coverage view-coverage: open artifacts/coverage/index.html clean: rm -rf artifacts/* streams-3.0.0/README.rst000066400000000000000000000016061241655202000146570ustar00rootroot00000000000000============== Guzzle Streams ============== Provides a simple abstraction over streams of data. This library is used in `Guzzle 5 `_, and is (currently) compatible with the WIP PSR-7. Installation ============ This package can be installed easily using `Composer `_. Simply add the following to the composer.json file at the root of your project: .. code-block:: javascript { "require": { "guzzlehttp/streams": "~3.0" } } Then install your dependencies using ``composer.phar install``. Documentation ============= The documentation for this package can be found on the main Guzzle website at http://docs.guzzlephp.org/en/guzzle4/streams.html. Testing ======= This library is tested using PHPUnit. You'll need to install the dependencies using `Composer `_ then run ``make test``. streams-3.0.0/composer.json000066400000000000000000000012431241655202000157070ustar00rootroot00000000000000{ "name": "guzzlehttp/streams", "description": "Provides a simple abstraction over streams of data", "homepage": "http://guzzlephp.org/", "keywords": ["stream", "guzzle"], "license": "MIT", "authors": [ { "name": "Michael Dowling", "email": "mtdowling@gmail.com", "homepage": "https://github.com/mtdowling" } ], "require": { "php": ">=5.4.0" }, "require-dev": { "phpunit/phpunit": "~4.0" }, "autoload": { "psr-4": { "GuzzleHttp\\Stream\\": "src/" } }, "extra": { "branch-alias": { "dev-master": "3.0-dev" } } } streams-3.0.0/phpunit.xml.dist000066400000000000000000000006771241655202000163520ustar00rootroot00000000000000 tests src src/functions.php streams-3.0.0/src/000077500000000000000000000000001241655202000137545ustar00rootroot00000000000000streams-3.0.0/src/AppendStream.php000066400000000000000000000114571241655202000170600ustar00rootroot00000000000000addStream($stream); } } public function __toString() { try { $this->seek(0); return $this->getContents(); } catch (\Exception $e) { return ''; } } /** * Add a stream to the AppendStream * * @param StreamInterface $stream Stream to append. Must be readable. * * @throws \InvalidArgumentException if the stream is not readable */ public function addStream(StreamInterface $stream) { if (!$stream->isReadable()) { throw new \InvalidArgumentException('Each stream must be readable'); } // The stream is only seekable if all streams are seekable if (!$stream->isSeekable()) { $this->seekable = false; } $this->streams[] = $stream; } public function getContents() { return Utils::copyToString($this); } /** * Closes each attached stream. * * {@inheritdoc} */ public function close() { $this->pos = $this->current = 0; foreach ($this->streams as $stream) { $stream->close(); } $this->streams = []; } /** * Detaches each attached stream * * {@inheritdoc} */ public function detach() { $this->close(); $this->detached = true; } public function attach($stream) { throw new CannotAttachException(); } public function tell() { return $this->pos; } /** * Tries to calculate the size by adding the size of each stream. * * If any of the streams do not return a valid number, then the size of the * append stream cannot be determined and null is returned. * * {@inheritdoc} */ public function getSize() { $size = 0; foreach ($this->streams as $stream) { $s = $stream->getSize(); if ($s === null) { return null; } $size += $s; } return $size; } public function eof() { return !$this->streams || ($this->current >= count($this->streams) - 1 && $this->streams[$this->current]->eof()); } /** * Attempts to seek to the given position. Only supports SEEK_SET. * * {@inheritdoc} */ public function seek($offset, $whence = SEEK_SET) { if (!$this->seekable || $whence !== SEEK_SET) { return false; } $success = true; $this->pos = $this->current = 0; // Rewind each stream foreach ($this->streams as $stream) { if (!$stream->seek(0)) { $success = false; } } if (!$success) { return false; } // Seek to the actual position by reading from each stream while ($this->pos < $offset && !$this->eof()) { $this->read(min(8096, $offset - $this->pos)); } return $this->pos == $offset; } /** * Reads from all of the appended streams until the length is met or EOF. * * {@inheritdoc} */ public function read($length) { $buffer = ''; $total = count($this->streams) - 1; $remaining = $length; while ($remaining > 0) { // Progress to the next stream if needed. if ($this->streams[$this->current]->eof()) { if ($this->current == $total) { break; } $this->current++; } $buffer .= $this->streams[$this->current]->read($remaining); $remaining = $length - strlen($buffer); } $this->pos += strlen($buffer); return $buffer; } public function isReadable() { return true; } public function isWritable() { return false; } public function isSeekable() { return $this->seekable; } public function write($string) { return false; } public function getMetadata($key = null) { return $key ? null : []; } } streams-3.0.0/src/AsyncReadStream.php000066400000000000000000000166261241655202000175250ustar00rootroot00000000000000isReadable() || !$buffer->isWritable()) { throw new \InvalidArgumentException( 'Buffer must be readable and writable' ); } if (isset($config['size'])) { $this->size = $config['size']; } static $callables = ['pump', 'drain']; foreach ($callables as $check) { if (isset($config[$check])) { if (!is_callable($config[$check])) { throw new \InvalidArgumentException( $check . ' must be callable' ); } $this->{$check} = $config[$check]; } } $this->hwm = $buffer->getMetadata('hwm'); // Cannot drain when there's no high water mark. if ($this->hwm === null) { $this->drain = null; } $this->stream = $buffer; } /** * Factory method used to create new async stream and an underlying buffer * if no buffer is provided. * * This function accepts the same options as AsyncReadStream::__construct, * but added the following key value pairs: * * - buffer: (StreamInterface) Buffer used to buffer data. If none is * provided, a default buffer is created. * - hwm: (int) High water mark to use if a buffer is created on your * behalf. * - max_buffer: (int) If provided, wraps the utilized buffer in a * DroppingStream decorator to ensure that buffer does not exceed a given * length. When exceeded, the stream will begin dropping data. Set the * max_buffer to 0, to use a NullStream which does not store data. * - write: (callable) A function that is invoked when data is written * to the underlying buffer. The function accepts the buffer as the first * argument, and the data being written as the second. The function MUST * return the number of bytes that were written or false to let writers * know to slow down. * - drain: (callable) See constructor documentation. * - pump: (callable) See constructor documentation. * * @param array $options Associative array of options. * * @return array Returns an array containing the buffer used to buffer * data, followed by the ready to use AsyncReadStream object. */ public static function create(array $options = []) { $maxBuffer = isset($options['max_buffer']) ? $options['max_buffer'] : null; if ($maxBuffer === 0) { $buffer = new NullStream(); } elseif (isset($options['buffer'])) { $buffer = $options['buffer']; } else { $hwm = isset($options['hwm']) ? $options['hwm'] : 16384; $buffer = new BufferStream($hwm); } if ($maxBuffer > 0) { $buffer = new DroppingStream($buffer, $options['max_buffer']); } // Call the on_write callback if an on_write function was provided. if (isset($options['write'])) { $onWrite = $options['write']; $buffer = FnStream::decorate($buffer, [ 'write' => function ($string) use ($buffer, $onWrite) { $result = $buffer->write($string); $onWrite($buffer, $string); return $result; } ]); } return [$buffer, new self($buffer, $options)]; } public function getSize() { return $this->size; } public function isWritable() { return false; } public function write($string) { return false; } public function read($length) { if (!$this->needsDrain && $this->drain) { $this->needsDrain = $this->stream->getSize() >= $this->hwm; } $result = $this->stream->read($length); // If we need to drain, then drain when the buffer is empty. if ($this->needsDrain && $this->stream->getSize() === 0) { $this->needsDrain = false; $drainFn = $this->drain; $drainFn($this->stream); } $resultLen = strlen($result); // If a pump was provided, the buffer is still open, and not enough // data was given, then block until the data is provided. if ($this->pump && $resultLen < $length) { $pumpFn = $this->pump; $result .= $pumpFn($length - $resultLen); } return $result; } } streams-3.0.0/src/BufferStream.php000066400000000000000000000056301241655202000170560ustar00rootroot00000000000000hwm = $hwm; } public function __toString() { return $this->getContents(); } public function getContents() { $buffer = $this->buffer; $this->buffer = ''; return $buffer; } public function close() { $this->buffer = ''; } public function detach() { $this->close(); } public function attach($stream) { throw new CannotAttachException(); } public function getSize() { return strlen($this->buffer); } public function isReadable() { return true; } public function isWritable() { return true; } public function isSeekable() { return false; } public function seek($offset, $whence = SEEK_SET) { return false; } public function eof() { return strlen($this->buffer) === 0; } public function tell() { return false; } /** * Reads data from the buffer. */ public function read($length) { $currentLength = strlen($this->buffer); if ($length >= $currentLength) { // No need to slice the buffer because we don't have enough data. $result = $this->buffer; $this->buffer = ''; } else { // Slice up the result to provide a subset of the buffer. $result = substr($this->buffer, 0, $length); $this->buffer = substr($this->buffer, $length); } return $result; } /** * Writes data to the buffer. */ public function write($string) { $this->buffer .= $string; if (strlen($this->buffer) >= $this->hwm) { return false; } return strlen($string); } public function getMetadata($key = null) { if ($key == 'hwm') { return $this->hwm; } return $key ? null : []; } } streams-3.0.0/src/CachingStream.php000066400000000000000000000073311241655202000172010ustar00rootroot00000000000000remoteStream = $stream; $this->stream = $target ?: new Stream(fopen('php://temp', 'r+')); } public function getSize() { return max($this->stream->getSize(), $this->remoteStream->getSize()); } /** * {@inheritdoc} * @throws SeekException When seeking with SEEK_END or when seeking * past the total size of the buffer stream */ public function seek($offset, $whence = SEEK_SET) { if ($whence == SEEK_SET) { $byte = $offset; } elseif ($whence == SEEK_CUR) { $byte = $offset + $this->tell(); } else { return false; } // You cannot skip ahead past where you've read from the remote stream if ($byte > $this->stream->getSize()) { throw new SeekException( $this, $byte, sprintf('Cannot seek to byte %d when the buffered stream only' . ' contains %d bytes', $byte, $this->stream->getSize()) ); } return $this->stream->seek($byte); } public function read($length) { // Perform a regular read on any previously read data from the buffer $data = $this->stream->read($length); $remaining = $length - strlen($data); // More data was requested so read from the remote stream if ($remaining) { // If data was written to the buffer in a position that would have // been filled from the remote stream, then we must skip bytes on // the remote stream to emulate overwriting bytes from that // position. This mimics the behavior of other PHP stream wrappers. $remoteData = $this->remoteStream->read( $remaining + $this->skipReadBytes ); if ($this->skipReadBytes) { $len = strlen($remoteData); $remoteData = substr($remoteData, $this->skipReadBytes); $this->skipReadBytes = max(0, $this->skipReadBytes - $len); } $data .= $remoteData; $this->stream->write($remoteData); } return $data; } public function write($string) { // When appending to the end of the currently read stream, you'll want // to skip bytes from being read from the remote stream to emulate // other stream wrappers. Basically replacing bytes of data of a fixed // length. $overflow = (strlen($string) + $this->tell()) - $this->remoteStream->tell(); if ($overflow > 0) { $this->skipReadBytes += $overflow; } return $this->stream->write($string); } public function eof() { return $this->stream->eof() && $this->remoteStream->eof(); } /** * Close both the remote stream and buffer stream */ public function close() { $this->remoteStream->close() && $this->stream->close(); } } streams-3.0.0/src/DroppingStream.php000066400000000000000000000020531241655202000174230ustar00rootroot00000000000000stream = $stream; $this->maxLength = $maxLength; } public function write($string) { $diff = $this->maxLength - $this->stream->getSize(); // Begin returning false when the underlying stream is too large. if ($diff <= 0) { return false; } // Write the stream or a subset of the stream if needed. if (strlen($string) < $diff) { return $this->stream->write($string); } $this->stream->write(substr($string, 0, $diff)); return false; } } streams-3.0.0/src/Exception/000077500000000000000000000000001241655202000157125ustar00rootroot00000000000000streams-3.0.0/src/Exception/CannotAttachException.php000066400000000000000000000001471241655202000226530ustar00rootroot00000000000000stream = $stream; $msg = $msg ?: 'Could not seek the stream to position ' . $pos; parent::__construct($msg); } /** * @return StreamInterface */ public function getStream() { return $this->stream; } } streams-3.0.0/src/FnStream.php000066400000000000000000000071051241655202000162070ustar00rootroot00000000000000methods = $methods; // Create the functions on the class foreach ($methods as $name => $fn) { $this->{'_fn_' . $name} = $fn; } } /** * Lazily determine which methods are not implemented. * @throws \BadMethodCallException */ public function __get($name) { throw new \BadMethodCallException(str_replace('_fn_', '', $name) . '() is not implemented in the FnStream'); } /** * The close method is called on the underlying stream only if possible. */ public function __destruct() { if (isset($this->_fn_close)) { call_user_func($this->_fn_close); } } /** * Adds custom functionality to an underlying stream by intercepting * specific method calls. * * @param StreamInterface $stream Stream to decorate * @param array $methods Hash of method name to a closure * * @return FnStream */ public static function decorate(StreamInterface $stream, array $methods) { // If any of the required methods were not provided, then simply // proxy to the decorated stream. foreach (array_diff(self::$slots, array_keys($methods)) as $diff) { $methods[$diff] = [$stream, $diff]; } return new self($methods); } public function __toString() { return call_user_func($this->_fn___toString); } public function close() { return call_user_func($this->_fn_close); } public function detach() { return call_user_func($this->_fn_detach); } public function attach($stream) { return call_user_func($this->_fn_attach, $stream); } public function getSize() { return call_user_func($this->_fn_getSize); } public function tell() { return call_user_func($this->_fn_tell); } public function eof() { return call_user_func($this->_fn_eof); } public function isSeekable() { return call_user_func($this->_fn_isSeekable); } public function seek($offset, $whence = SEEK_SET) { return call_user_func($this->_fn_seek, $offset, $whence); } public function isWritable() { return call_user_func($this->_fn_isWritable); } public function write($string) { return call_user_func($this->_fn_write, $string); } public function isReadable() { return call_user_func($this->_fn_isReadable); } public function read($length) { return call_user_func($this->_fn_read, $length); } public function getContents() { return call_user_func($this->_fn_getContents); } public function getMetadata($key = null) { return call_user_func($this->_fn_getMetadata, $key); } } streams-3.0.0/src/GuzzleStreamWrapper.php000066400000000000000000000053101241655202000204610ustar00rootroot00000000000000isReadable()) { $mode = $stream->isWritable() ? 'r+' : 'r'; } elseif ($stream->isWritable()) { $mode = 'w'; } else { throw new \InvalidArgumentException('The stream must be readable, ' . 'writable, or both.'); } return fopen('guzzle://stream', $mode, null, stream_context_create([ 'guzzle' => ['stream' => $stream] ])); } /** * Registers the stream wrapper if needed */ public static function register() { if (!in_array('guzzle', stream_get_wrappers())) { stream_wrapper_register('guzzle', __CLASS__); } } public function stream_open($path, $mode, $options, &$opened_path) { $options = stream_context_get_options($this->context); if (!isset($options['guzzle']['stream'])) { return false; } $this->mode = $mode; $this->stream = $options['guzzle']['stream']; return true; } public function stream_read($count) { return $this->stream->read($count); } public function stream_write($data) { return (int) $this->stream->write($data); } public function stream_tell() { return $this->stream->tell(); } public function stream_eof() { return $this->stream->eof(); } public function stream_seek($offset, $whence) { return $this->stream->seek($offset, $whence); } public function stream_stat() { static $modeMap = [ 'r' => 33060, 'r+' => 33206, 'w' => 33188 ]; return [ 'dev' => 0, 'ino' => 0, 'mode' => $modeMap[$this->mode], 'nlink' => 0, 'uid' => 0, 'gid' => 0, 'rdev' => 0, 'size' => $this->stream->getSize() ?: 0, 'atime' => 0, 'mtime' => 0, 'ctime' => 0, 'blksize' => 0, 'blocks' => 0 ]; } } streams-3.0.0/src/InflateStream.php000066400000000000000000000016561241655202000172330ustar00rootroot00000000000000stream = new Stream($resource); } } streams-3.0.0/src/LazyOpenStream.php000066400000000000000000000015221241655202000174020ustar00rootroot00000000000000filename = $filename; $this->mode = $mode; } /** * Creates the underlying stream lazily when required. * * @return StreamInterface */ protected function createStream() { return Stream::factory(Utils::open($this->filename, $this->mode)); } } streams-3.0.0/src/LimitStream.php000066400000000000000000000101461241655202000167210ustar00rootroot00000000000000stream = $stream; $this->setLimit($limit); $this->setOffset($offset); } public function eof() { // Always return true if the underlying stream is EOF if ($this->stream->eof()) { return true; } // No limit and the underlying stream is not at EOF if ($this->limit == -1) { return false; } $tell = $this->stream->tell(); if ($tell === false) { return false; } return $tell >= $this->offset + $this->limit; } /** * Returns the size of the limited subset of data * {@inheritdoc} */ public function getSize() { if (null === ($length = $this->stream->getSize())) { return null; } elseif ($this->limit == -1) { return $length - $this->offset; } else { return min($this->limit, $length - $this->offset); } } /** * Allow for a bounded seek on the read limited stream * {@inheritdoc} */ public function seek($offset, $whence = SEEK_SET) { if ($whence !== SEEK_SET || $offset < 0) { return false; } $offset += $this->offset; if ($this->limit !== -1) { if ($offset > $this->offset + $this->limit) { $offset = $this->offset + $this->limit; } } return $this->stream->seek($offset); } /** * Give a relative tell() * {@inheritdoc} */ public function tell() { return $this->stream->tell() - $this->offset; } /** * Set the offset to start limiting from * * @param int $offset Offset to seek to and begin byte limiting from * * @return self * @throws SeekException */ public function setOffset($offset) { $current = $this->stream->tell(); if ($current !== $offset) { // If the stream cannot seek to the offset position, then read to it if (!$this->stream->seek($offset)) { if ($current > $offset) { throw new SeekException($this, $offset); } else { $this->stream->read($offset - $current); } } } $this->offset = $offset; return $this; } /** * Set the limit of bytes that the decorator allows to be read from the * stream. * * @param int $limit Number of bytes to allow to be read from the stream. * Use -1 for no limit. * @return self */ public function setLimit($limit) { $this->limit = $limit; return $this; } public function read($length) { if ($this->limit == -1) { return $this->stream->read($length); } // Check if the current position is less than the total allowed // bytes + original offset $remaining = ($this->offset + $this->limit) - $this->stream->tell(); if ($remaining > 0) { // Only return the amount of requested data, ensuring that the byte // limit is not exceeded return $this->stream->read(min($remaining, $length)); } else { return false; } } } streams-3.0.0/src/MetadataStreamInterface.php000066400000000000000000000004331241655202000212020ustar00rootroot00000000000000stream->attach($stream); } } streams-3.0.0/src/NullStream.php000066400000000000000000000022411241655202000165520ustar00rootroot00000000000000source = $source; $this->size = isset($options['size']) ? $options['size'] : null; $this->metadata = isset($options['metadata']) ? $options['metadata'] : []; $this->buffer = new BufferStream(); } public function __toString() { return Utils::copyToString($this); } public function close() { $this->detach(); } public function detach() { $this->tellPos = false; $this->source = null; } public function attach($stream) { throw new CannotAttachException(); } public function getSize() { return $this->size; } public function tell() { return $this->tellPos; } public function eof() { return !$this->source; } public function isSeekable() { return false; } public function seek($offset, $whence = SEEK_SET) { return false; } public function isWritable() { return false; } public function write($string) { return false; } public function isReadable() { return true; } public function read($length) { $data = $this->buffer->read($length); $readLen = strlen($data); $this->tellPos += $readLen; $remaining = $length - $readLen; if ($remaining) { $this->pump($remaining); $data .= $this->buffer->read($remaining); $this->tellPos += strlen($data) - $readLen; } return $data; } public function getContents() { $result = ''; while (!$this->eof()) { $result .= $this->read(1000000); } return $result; } public function getMetadata($key = null) { if (!$key) { return $this->metadata; } return isset($this->metadata[$key]) ? $this->metadata[$key] : null; } private function pump($length) { if ($this->source) { do { $data = call_user_func($this->source, $length); if ($data === false || $data === null) { $this->source = null; return; } $this->buffer->write($data); $length -= strlen($data); } while ($length > 0); } } } streams-3.0.0/src/Stream.php000066400000000000000000000154771241655202000157360ustar00rootroot00000000000000 [ 'r' => true, 'w+' => true, 'r+' => true, 'x+' => true, 'c+' => true, 'rb' => true, 'w+b' => true, 'r+b' => true, 'x+b' => true, 'c+b' => true, 'rt' => true, 'w+t' => true, 'r+t' => true, 'x+t' => true, 'c+t' => true, 'a+' => true ], 'write' => [ 'w' => true, 'w+' => true, 'rw' => true, 'r+' => true, 'x+' => true, 'c+' => true, 'wb' => true, 'w+b' => true, 'r+b' => true, 'x+b' => true, 'c+b' => true, 'w+t' => true, 'r+t' => true, 'x+t' => true, 'c+t' => true, 'a' => true, 'a+' => true ] ]; /** * Create a new stream based on the input type. * * This factory accepts the same associative array of options as described * in the constructor. * * @param resource|string|StreamInterface $resource Entity body data * @param array $options Additional options * * @return Stream * @throws \InvalidArgumentException if the $resource arg is not valid. */ public static function factory($resource = '', array $options = []) { $type = gettype($resource); if ($type == 'string') { $stream = fopen('php://temp', 'r+'); if ($resource !== '') { fwrite($stream, $resource); fseek($stream, 0); } return new self($stream, $options); } if ($type == 'resource') { return new self($resource, $options); } if ($resource instanceof StreamInterface) { return $resource; } if ($type == 'object' && method_exists($resource, '__toString')) { return self::factory((string) $resource, $options); } if (is_callable($resource)) { return new PumpStream($resource, $options); } if ($resource instanceof \Iterator) { return new PumpStream(function () use ($resource) { if (!$resource->valid()) { return false; } $result = $resource->current(); $resource->next(); return $result; }, $options); } throw new \InvalidArgumentException('Invalid resource type: ' . $type); } /** * This constructor accepts an associative array of options. * * - size: (int) If a read stream would otherwise have an indeterminate * size, but the size is known due to foreknownledge, then you can * provide that size, in bytes. * - metadata: (array) Any additional metadata to return when the metadata * of the stream is accessed. * * @param resource $stream Stream resource to wrap. * @param array $options Associative array of options. * * @throws \InvalidArgumentException if the stream is not a stream resource */ public function __construct($stream, $options = []) { if (!is_resource($stream)) { throw new \InvalidArgumentException('Stream must be a resource'); } if (isset($options['size'])) { $this->size = $options['size']; } $this->customMetadata = isset($options['metadata']) ? $options['metadata'] : []; $this->attach($stream); } /** * Closes the stream when the destructed */ public function __destruct() { $this->close(); } public function __toString() { if (!$this->stream) { return ''; } $this->seek(0); return (string) stream_get_contents($this->stream); } public function getContents() { return $this->stream ? stream_get_contents($this->stream) : ''; } public function close() { if (is_resource($this->stream)) { fclose($this->stream); } $this->detach(); } public function detach() { $result = $this->stream; $this->stream = $this->size = $this->uri = null; $this->readable = $this->writable = $this->seekable = false; return $result; } public function attach($stream) { $this->stream = $stream; $meta = stream_get_meta_data($this->stream); $this->seekable = $meta['seekable']; $this->readable = isset(self::$readWriteHash['read'][$meta['mode']]); $this->writable = isset(self::$readWriteHash['write'][$meta['mode']]); $this->uri = $this->getMetadata('uri'); } public function getSize() { if ($this->size !== null) { return $this->size; } if (!$this->stream) { return null; } // Clear the stat cache if the stream has a URI if ($this->uri) { clearstatcache(true, $this->uri); } $stats = fstat($this->stream); if (isset($stats['size'])) { $this->size = $stats['size']; return $this->size; } return null; } public function isReadable() { return $this->readable; } public function isWritable() { return $this->writable; } public function isSeekable() { return $this->seekable; } public function eof() { return !$this->stream || feof($this->stream); } public function tell() { return $this->stream ? ftell($this->stream) : false; } public function setSize($size) { $this->size = $size; return $this; } public function seek($offset, $whence = SEEK_SET) { return $this->seekable ? fseek($this->stream, $offset, $whence) === 0 : false; } public function read($length) { return $this->readable ? fread($this->stream, $length) : false; } public function write($string) { // We can't know the size after writing anything $this->size = null; return $this->writable ? fwrite($this->stream, $string) : false; } public function getMetadata($key = null) { if (!$this->stream) { return $key ? null : []; } elseif (!$key) { return $this->customMetadata + stream_get_meta_data($this->stream); } elseif (isset($this->customMetadata[$key])) { return $this->customMetadata[$key]; } $meta = stream_get_meta_data($this->stream); return isset($meta[$key]) ? $meta[$key] : null; } } streams-3.0.0/src/StreamDecoratorTrait.php000066400000000000000000000062141241655202000205720ustar00rootroot00000000000000stream = $stream; } /** * Magic method used to create a new stream if streams are not added in * the constructor of a decorator (e.g., LazyOpenStream). */ public function __get($name) { if ($name == 'stream') { $this->stream = $this->createStream(); return $this->stream; } throw new \UnexpectedValueException("$name not found on class"); } public function __toString() { try { $this->seek(0); return $this->getContents(); } catch (\Exception $e) { // Really, PHP? https://bugs.php.net/bug.php?id=53648 trigger_error('StreamDecorator::__toString exception: ' . (string) $e, E_USER_ERROR); return ''; } } public function getContents() { return Utils::copyToString($this); } /** * Allow decorators to implement custom methods * * @param string $method Missing method name * @param array $args Method arguments * * @return mixed */ public function __call($method, array $args) { $result = call_user_func_array(array($this->stream, $method), $args); // Always return the wrapped object if the result is a return $this return $result === $this->stream ? $this : $result; } public function close() { $this->stream->close(); } public function getMetadata($key = null) { return $this->stream->getMetadata($key); } public function detach() { return $this->stream->detach(); } public function attach($stream) { throw new CannotAttachException(); } public function getSize() { return $this->stream->getSize(); } public function eof() { return $this->stream->eof(); } public function tell() { return $this->stream->tell(); } public function isReadable() { return $this->stream->isReadable(); } public function isWritable() { return $this->stream->isWritable(); } public function isSeekable() { return $this->stream->isSeekable(); } public function seek($offset, $whence = SEEK_SET) { return $this->stream->seek($offset, $whence); } public function read($length) { return $this->stream->read($length); } public function write($string) { return $this->stream->write($string); } /** * Implement in subclasses to dynamically create streams when requested. * * @return StreamInterface * @throws \BadMethodCallException */ protected function createStream() { throw new \BadMethodCallException('createStream() not implemented in ' . get_class($this)); } } streams-3.0.0/src/StreamInterface.php000066400000000000000000000117101241655202000175410ustar00rootroot00000000000000eof()) { $buf = $stream->read(1048576); if ($buf === false) { break; } $buffer .= $buf; } return $buffer; } $len = 0; while (!$stream->eof() && $len < $maxLen) { $buf = $stream->read($maxLen - $len); if ($buf === false) { break; } $buffer .= $buf; $len = strlen($buffer); } return $buffer; } /** * Copy the contents of a stream into another stream until the given number * of bytes have been read. * * @param StreamInterface $source Stream to read from * @param StreamInterface $dest Stream to write to * @param int $maxLen Maximum number of bytes to read. Pass -1 * to read the entire stream. */ public static function copyToStream( StreamInterface $source, StreamInterface $dest, $maxLen = -1 ) { if ($maxLen === -1) { while (!$source->eof()) { if (!$dest->write($source->read(1048576))) { break; } } return; } $bytes = 0; while (!$source->eof()) { $buf = $source->read($maxLen - $bytes); if (!($len = strlen($buf))) { break; } $bytes += $len; $dest->write($buf); if ($bytes == $maxLen) { break; } } } /** * Calculate a hash of a Stream * * @param StreamInterface $stream Stream to calculate the hash for * @param string $algo Hash algorithm (e.g. md5, crc32, etc) * @param bool $rawOutput Whether or not to use raw output * * @return string Returns the hash of the stream * @throws SeekException */ public static function hash( StreamInterface $stream, $algo, $rawOutput = false ) { $pos = $stream->tell(); if ($pos > 0 && !$stream->seek(0)) { throw new SeekException($stream); } $ctx = hash_init($algo); while (!$stream->eof()) { hash_update($ctx, $stream->read(1048576)); } $out = hash_final($ctx, (bool) $rawOutput); $stream->seek($pos); return $out; } /** * Read a line from the stream up to the maximum allowed buffer length * * @param StreamInterface $stream Stream to read from * @param int $maxLength Maximum buffer length * * @return string|bool */ public static function readline(StreamInterface $stream, $maxLength = null) { $buffer = ''; $size = 0; while (!$stream->eof()) { if (false === ($byte = $stream->read(1))) { return $buffer; } $buffer .= $byte; // Break when a new line is found or the max length - 1 is reached if ($byte == PHP_EOL || ++$size == $maxLength - 1) { break; } } return $buffer; } /** * Alias of GuzzleHttp\Stream\Stream::factory. * * @param mixed $resource Resource to create * @param array $options Associative array of stream options defined in * {@see \GuzzleHttp\Stream\Stream::__construct} * * @return StreamInterface * * @see GuzzleHttp\Stream\Stream::factory * @see GuzzleHttp\Stream\Stream::__construct */ public static function create($resource, array $options = []) { return Stream::factory($resource, $options); } } streams-3.0.0/tests/000077500000000000000000000000001241655202000143275ustar00rootroot00000000000000streams-3.0.0/tests/AppendStreamTest.php000066400000000000000000000125341241655202000202700ustar00rootroot00000000000000getMockBuilder('GuzzleHttp\Stream\StreamInterface') ->setMethods(['isReadable']) ->getMockForAbstractClass(); $s->expects($this->once()) ->method('isReadable') ->will($this->returnValue(false)); $a->addStream($s); } public function testValidatesSeekType() { $a = new AppendStream(); $this->assertFalse($a->seek(100, SEEK_CUR)); } public function testTriesToRewindOnSeek() { $a = new AppendStream(); $s = $this->getMockBuilder('GuzzleHttp\Stream\StreamInterface') ->setMethods(['isReadable', 'seek', 'isSeekable']) ->getMockForAbstractClass(); $s->expects($this->once()) ->method('isReadable') ->will($this->returnValue(true)); $s->expects($this->once()) ->method('isSeekable') ->will($this->returnValue(true)); $s->expects($this->once()) ->method('seek') ->will($this->returnValue(false)); $a->addStream($s); $this->assertFalse($a->seek(10)); } public function testSeeksToPositionByReading() { $a = new AppendStream([ Stream::factory('foo'), Stream::factory('bar'), Stream::factory('baz'), ]); $this->assertTrue($a->seek(3)); $this->assertEquals(3, $a->tell()); $this->assertEquals('bar', $a->read(3)); $a->seek(6); $this->assertEquals(6, $a->tell()); $this->assertEquals('baz', $a->read(3)); } public function testDetachesEachStream() { $s1 = Stream::factory('foo'); $s2 = Stream::factory('foo'); $a = new AppendStream([$s1, $s2]); $this->assertSame('foofoo', (string) $a); $a->detach(); $this->assertSame('', (string) $a); $this->assertSame(0, $a->getSize()); } public function testClosesEachStream() { $s1 = Stream::factory('foo'); $a = new AppendStream([$s1]); $a->close(); $this->assertSame('', (string) $a); } public function testIsNotWritable() { $a = new AppendStream([Stream::factory('foo')]); $this->assertFalse($a->isWritable()); $this->assertTrue($a->isSeekable()); $this->assertTrue($a->isReadable()); $this->assertFalse($a->write('foo')); } public function testDoesNotNeedStreams() { $a = new AppendStream(); $this->assertEquals('', (string) $a); } public function testCanReadFromMultipleStreams() { $a = new AppendStream([ Stream::factory('foo'), Stream::factory('bar'), Stream::factory('baz'), ]); $this->assertFalse($a->eof()); $this->assertSame(0, $a->tell()); $this->assertEquals('foo', $a->read(3)); $this->assertEquals('bar', $a->read(3)); $this->assertEquals('baz', $a->read(3)); $this->assertTrue($a->eof()); $this->assertSame(9, $a->tell()); $this->assertEquals('foobarbaz', (string) $a); } public function testCanDetermineSizeFromMultipleStreams() { $a = new AppendStream([ Stream::factory('foo'), Stream::factory('bar') ]); $this->assertEquals(6, $a->getSize()); $s = $this->getMockBuilder('GuzzleHttp\Stream\StreamInterface') ->setMethods(['isSeekable', 'isReadable']) ->getMockForAbstractClass(); $s->expects($this->once()) ->method('isSeekable') ->will($this->returnValue(null)); $s->expects($this->once()) ->method('isReadable') ->will($this->returnValue(true)); $a->addStream($s); $this->assertNull($a->getSize()); } public function testCatchesExceptionsWhenCastingToString() { $s = $this->getMockBuilder('GuzzleHttp\Stream\StreamInterface') ->setMethods(['read', 'isReadable', 'eof']) ->getMockForAbstractClass(); $s->expects($this->once()) ->method('read') ->will($this->throwException(new \RuntimeException('foo'))); $s->expects($this->once()) ->method('isReadable') ->will($this->returnValue(true)); $s->expects($this->any()) ->method('eof') ->will($this->returnValue(false)); $a = new AppendStream([$s]); $this->assertFalse($a->eof()); $this->assertSame('', (string) $a); } public function testCanDetach() { $s = new AppendStream(); $s->detach(); } public function testReturnsEmptyMetadata() { $s = new AppendStream(); $this->assertEquals([], $s->getMetadata()); $this->assertNull($s->getMetadata('foo')); } /** * @expectedException \GuzzleHttp\Stream\Exception\CannotAttachException */ public function testCannotAttach() { $p = new AppendStream(); $p->attach('a'); } } streams-3.0.0/tests/AsyncReadStreamTest.php000066400000000000000000000142071241655202000207310ustar00rootroot00000000000000 function () { return false; }] )); } /** * @expectedException \InvalidArgumentException * @expectedExceptionMessage Buffer must be readable and writable */ public function testValidatesWritableBuffer() { new AsyncReadStream(FnStream::decorate( Stream::factory(), ['isWritable' => function () { return false; }] )); } public function testValidatesHwmMetadata() { $a = new AsyncReadStream(Stream::factory(), [ 'drain' => function() {} ]); $this->assertNull($this->readAttribute($a, 'drain')); } /** * @expectedException \InvalidArgumentException * @expectedExceptionMessage pump must be callable */ public function testValidatesPumpIsCallable() { new AsyncReadStream(new BufferStream(), ['pump' => true]); } /** * @expectedException \InvalidArgumentException * @expectedExceptionMessage drain must be callable */ public function testValidatesDrainIsCallable() { new AsyncReadStream(new BufferStream(), ['drain' => true]); } public function testCanInitialize() { $buffer = new BufferStream(); $a = new AsyncReadStream($buffer, [ 'size' => 10, 'drain' => function () {}, 'pump' => function () {}, ]); $this->assertSame($buffer, $this->readAttribute($a, 'stream')); $this->assertTrue(is_callable($this->readAttribute($a, 'drain'))); $this->assertTrue(is_callable($this->readAttribute($a, 'pump'))); $this->assertTrue($a->isReadable()); $this->assertFalse($a->isSeekable()); $this->assertFalse($a->isWritable()); $this->assertFalse($a->write('foo')); $this->assertEquals(10, $a->getSize()); } public function testReadsFromBufferWithNoDrainOrPump() { $buffer = new BufferStream(); $a = new AsyncReadStream($buffer); $buffer->write('foo'); $this->assertNull($a->getSize()); $this->assertEquals('foo', $a->read(10)); $this->assertEquals('', $a->read(10)); } public function testCallsPumpForMoreDataWhenRequested() { $called = 0; $buffer = new BufferStream(); $a = new AsyncReadStream($buffer, [ 'pump' => function ($size) use (&$called) { $called++; return str_repeat('.', $size); } ]); $buffer->write('foobar'); $this->assertEquals('foo', $a->read(3)); $this->assertEquals(0, $called); $this->assertEquals('bar.....', $a->read(8)); $this->assertEquals(1, $called); $this->assertEquals('..', $a->read(2)); $this->assertEquals(2, $called); } public function testCallsDrainWhenNeeded() { $called = 0; $buffer = new BufferStream(5); $a = new AsyncReadStream($buffer, [ 'drain' => function (BufferStream $b) use (&$called, $buffer) { $this->assertSame($b, $buffer); $called++; } ]); $buffer->write('foobar'); $this->assertEquals(6, $buffer->getSize()); $this->assertEquals(0, $called); $a->read(3); $this->assertTrue($this->readAttribute($a, 'needsDrain')); $this->assertEquals(3, $buffer->getSize()); $this->assertEquals(0, $called); $a->read(3); $this->assertEquals(0, $buffer->getSize()); $this->assertFalse($this->readAttribute($a, 'needsDrain')); $this->assertEquals(1, $called); } public function testCreatesBufferWithNoConfig() { list($buffer, $async) = AsyncReadStream::create(); $this->assertInstanceOf('GuzzleHttp\Stream\BufferStream', $buffer); $this->assertInstanceOf('GuzzleHttp\Stream\AsyncReadStream', $async); } public function testCreatesBufferWithSpecifiedBuffer() { $buf = new BufferStream(); list($buffer, $async) = AsyncReadStream::create(['buffer' => $buf]); $this->assertSame($buf, $buffer); $this->assertInstanceOf('GuzzleHttp\Stream\AsyncReadStream', $async); } public function testCreatesNullStream() { list($buffer, $async) = AsyncReadStream::create(['max_buffer' => 0]); $this->assertInstanceOf('GuzzleHttp\Stream\NullStream', $buffer); $this->assertInstanceOf('GuzzleHttp\Stream\AsyncReadStream', $async); } public function testCreatesDroppingStream() { list($buffer, $async) = AsyncReadStream::create(['max_buffer' => 5]); $this->assertInstanceOf('GuzzleHttp\Stream\DroppingStream', $buffer); $this->assertInstanceOf('GuzzleHttp\Stream\AsyncReadStream', $async); $buffer->write('12345678910'); $this->assertEquals(5, $buffer->getSize()); } public function testCreatesOnWriteStream() { $c = 0; $b = new BufferStream(); list($buffer, $async) = AsyncReadStream::create([ 'buffer' => $b, 'write' => function (BufferStream $buf, $data) use (&$c, $b) { $this->assertSame($buf, $b); $this->assertEquals('foo', $data); $c++; } ]); $this->assertInstanceOf('GuzzleHttp\Stream\FnStream', $buffer); $this->assertInstanceOf('GuzzleHttp\Stream\AsyncReadStream', $async); $this->assertEquals(0, $c); $this->assertEquals(3, $buffer->write('foo')); $this->assertEquals(1, $c); $this->assertEquals(3, $buffer->write('foo')); $this->assertEquals(2, $c); $this->assertEquals('foofoo', (string) $buffer); } } streams-3.0.0/tests/BufferStreamTest.php000066400000000000000000000040261241655202000202670ustar00rootroot00000000000000assertTrue($b->isReadable()); $this->assertTrue($b->isWritable()); $this->assertFalse($b->isSeekable()); $this->assertEquals(null, $b->getMetadata('foo')); $this->assertEquals(10, $b->getMetadata('hwm')); $this->assertEquals([], $b->getMetadata()); } public function testRemovesReadDataFromBuffer() { $b = new BufferStream(); $this->assertEquals(3, $b->write('foo')); $this->assertEquals(3, $b->getSize()); $this->assertFalse($b->eof()); $this->assertEquals('foo', $b->read(10)); $this->assertTrue($b->eof()); $this->assertEquals('', $b->read(10)); } public function testCanCastToStringOrGetContents() { $b = new BufferStream(); $b->write('foo'); $b->write('baz'); $this->assertEquals('foo', $b->read(3)); $b->write('bar'); $this->assertEquals('bazbar', (string) $b); $this->assertFalse($b->tell()); } public function testDetachClearsBuffer() { $b = new BufferStream(); $b->write('foo'); $b->detach(); $this->assertEquals(0, $b->tell()); $this->assertTrue($b->eof()); $this->assertEquals(3, $b->write('abc')); $this->assertEquals('abc', $b->read(10)); } public function testExceedingHighwaterMarkReturnsFalseButStillBuffers() { $b = new BufferStream(5); $this->assertEquals(3, $b->write('hi ')); $this->assertFalse($b->write('hello')); $this->assertEquals('hi hello', (string) $b); $this->assertEquals(4, $b->write('test')); } /** * @expectedException \GuzzleHttp\Stream\Exception\CannotAttachException */ public function testCannotAttach() { $p = new BufferStream(); $p->attach('a'); } } streams-3.0.0/tests/CachingStreamTest.php000066400000000000000000000107011241655202000204070ustar00rootroot00000000000000decorated = Stream::factory('testing'); $this->body = new CachingStream($this->decorated); } public function tearDown() { $this->decorated->close(); $this->body->close(); } public function testUsesRemoteSizeIfPossible() { $body = Stream::factory('test'); $caching = new CachingStream($body); $this->assertEquals(4, $caching->getSize()); } /** * @expectedException \RuntimeException * @expectedExceptionMessage Cannot seek to byte 10 */ public function testCannotSeekPastWhatHasBeenRead() { $this->body->seek(10); } public function testCannotUseSeekEnd() { $this->assertFalse($this->body->seek(2, SEEK_END)); } public function testRewindUsesSeek() { $a = Stream::factory('foo'); $d = $this->getMockBuilder('GuzzleHttp\Stream\CachingStream') ->setMethods(array('seek')) ->setConstructorArgs(array($a)) ->getMock(); $d->expects($this->once()) ->method('seek') ->with(0) ->will($this->returnValue(true)); $d->seek(0); } public function testCanSeekToReadBytes() { $this->assertEquals('te', $this->body->read(2)); $this->body->seek(0); $this->assertEquals('test', $this->body->read(4)); $this->assertEquals(4, $this->body->tell()); $this->body->seek(2); $this->assertEquals(2, $this->body->tell()); $this->body->seek(2, SEEK_CUR); $this->assertEquals(4, $this->body->tell()); $this->assertEquals('ing', $this->body->read(3)); } public function testWritesToBufferStream() { $this->body->read(2); $this->body->write('hi'); $this->body->seek(0); $this->assertEquals('tehiing', (string) $this->body); } public function testSkipsOverwrittenBytes() { $decorated = Stream::factory( implode("\n", array_map(function ($n) { return str_pad($n, 4, '0', STR_PAD_LEFT); }, range(0, 25))) ); $body = new CachingStream($decorated); $this->assertEquals("0000\n", Utils::readline($body)); $this->assertEquals("0001\n", Utils::readline($body)); // Write over part of the body yet to be read, so skip some bytes $this->assertEquals(5, $body->write("TEST\n")); $this->assertEquals(5, $this->readAttribute($body, 'skipReadBytes')); // Read, which skips bytes, then reads $this->assertEquals("0003\n", Utils::readline($body)); $this->assertEquals(0, $this->readAttribute($body, 'skipReadBytes')); $this->assertEquals("0004\n", Utils::readline($body)); $this->assertEquals("0005\n", Utils::readline($body)); // Overwrite part of the cached body (so don't skip any bytes) $body->seek(5); $this->assertEquals(5, $body->write("ABCD\n")); $this->assertEquals(0, $this->readAttribute($body, 'skipReadBytes')); $this->assertEquals("TEST\n", Utils::readline($body)); $this->assertEquals("0003\n", Utils::readline($body)); $this->assertEquals("0004\n", Utils::readline($body)); $this->assertEquals("0005\n", Utils::readline($body)); $this->assertEquals("0006\n", Utils::readline($body)); $this->assertEquals(5, $body->write("1234\n")); $this->assertEquals(5, $this->readAttribute($body, 'skipReadBytes')); // Seek to 0 and ensure the overwritten bit is replaced $body->seek(0); $this->assertEquals("0000\nABCD\nTEST\n0003\n0004\n0005\n0006\n1234\n0008\n0009\n", $body->read(50)); // Ensure that casting it to a string does not include the bit that was overwritten $this->assertContains("0000\nABCD\nTEST\n0003\n0004\n0005\n0006\n1234\n0008\n0009\n", (string) $body); } public function testClosesBothStreams() { $s = fopen('php://temp', 'r'); $a = Stream::factory($s); $d = new CachingStream($a); $d->close(); $this->assertFalse(is_resource($s)); } } streams-3.0.0/tests/DroppingStreamTest.php000066400000000000000000000016241241655202000206410ustar00rootroot00000000000000assertEquals(3, $drop->write('hel')); $this->assertFalse($drop->write('lo')); $this->assertEquals(5, $drop->getSize()); $this->assertEquals('hello', $drop->read(5)); $this->assertEquals(0, $drop->getSize()); $drop->write('12345678910'); $this->assertEquals(5, $stream->getSize()); $this->assertEquals(5, $drop->getSize()); $this->assertEquals('12345', (string) $drop); $this->assertEquals(0, $drop->getSize()); $drop->write('hello'); $this->assertFalse($drop->write('test')); } } streams-3.0.0/tests/Exception/000077500000000000000000000000001241655202000162655ustar00rootroot00000000000000streams-3.0.0/tests/Exception/SeekExceptionTest.php000066400000000000000000000006461241655202000224120ustar00rootroot00000000000000assertSame($s, $e->getStream()); $this->assertContains('10', $e->getMessage()); } } streams-3.0.0/tests/FnStreamTest.php000066400000000000000000000046751241655202000174330ustar00rootroot00000000000000seek(1); } public function testProxiesToFunction() { $s = new FnStream([ 'read' => function ($len) { $this->assertEquals(3, $len); return 'foo'; } ]); $this->assertEquals('foo', $s->read(3)); } public function testCanCloseOnDestruct() { $called = false; $s = new FnStream([ 'close' => function () use (&$called) { $called = true; } ]); unset($s); $this->assertTrue($called); } public function testDoesNotRequireClose() { $s = new FnStream([]); unset($s); } public function testDecoratesStream() { $a = Stream::factory('foo'); $b = FnStream::decorate($a, []); $this->assertEquals(3, $b->getSize()); $this->assertEquals($b->isWritable(), true); $this->assertEquals($b->isReadable(), true); $this->assertEquals($b->isSeekable(), true); $this->assertEquals($b->read(3), 'foo'); $this->assertEquals($b->tell(), 3); $this->assertEquals($a->tell(), 3); $this->assertEquals($b->eof(), true); $this->assertEquals($a->eof(), true); $b->seek(0); $this->assertEquals('foo', (string) $b); $b->seek(0); $this->assertEquals('foo', $b->getContents()); $this->assertEquals($a->getMetadata(), $b->getMetadata()); $b->seek(0, SEEK_END); $b->write('bar'); $this->assertEquals('foobar', (string) $b); $this->assertInternalType('resource', $b->detach()); $b->close(); } public function testDecoratesWithCustomizations() { $called = false; $a = Stream::factory('foo'); $b = FnStream::decorate($a, [ 'read' => function ($len) use (&$called, $a) { $called = true; return $a->read($len); } ]); $this->assertEquals('foo', $b->read(3)); $this->assertTrue($called); } } streams-3.0.0/tests/GuzzleStreamWrapperTest.php000066400000000000000000000061501241655202000216770ustar00rootroot00000000000000assertSame('foo', fread($handle, 3)); $this->assertSame(3, ftell($handle)); $this->assertSame(3, fwrite($handle, 'bar')); $this->assertSame(0, fseek($handle, 0)); $this->assertSame('foobar', fread($handle, 6)); $this->assertTrue(feof($handle)); // This fails on HHVM for some reason if (!defined('HHVM_VERSION')) { $this->assertEquals([ 'dev' => 0, 'ino' => 0, 'mode' => 33206, 'nlink' => 0, 'uid' => 0, 'gid' => 0, 'rdev' => 0, 'size' => 6, 'atime' => 0, 'mtime' => 0, 'ctime' => 0, 'blksize' => 0, 'blocks' => 0, 0 => 0, 1 => 0, 2 => 33206, 3 => 0, 4 => 0, 5 => 0, 6 => 0, 7 => 6, 8 => 0, 9 => 0, 10 => 0, 11 => 0, 12 => 0, ], fstat($handle)); } $this->assertTrue(fclose($handle)); $this->assertSame('foobar', (string) $stream); } /** * @expectedException \InvalidArgumentException */ public function testValidatesStream() { $stream = $this->getMockBuilder('GuzzleHttp\Stream\StreamInterface') ->setMethods(['isReadable', 'isWritable']) ->getMockForAbstractClass(); $stream->expects($this->once()) ->method('isReadable') ->will($this->returnValue(false)); $stream->expects($this->once()) ->method('isWritable') ->will($this->returnValue(false)); GuzzleStreamWrapper::getResource($stream); } /** * @expectedException \PHPUnit_Framework_Error_Warning */ public function testReturnsFalseWhenStreamDoesNotExist() { fopen('guzzle://foo', 'r'); } public function testCanOpenReadonlyStream() { $stream = $this->getMockBuilder('GuzzleHttp\Stream\StreamInterface') ->setMethods(['isReadable', 'isWritable']) ->getMockForAbstractClass(); $stream->expects($this->once()) ->method('isReadable') ->will($this->returnValue(false)); $stream->expects($this->once()) ->method('isWritable') ->will($this->returnValue(true)); $r = GuzzleStreamWrapper::getResource($stream); $this->assertInternalType('resource', $r); fclose($r); } } streams-3.0.0/tests/InflateStreamTest.php000066400000000000000000000006071241655202000204410ustar00rootroot00000000000000assertEquals('test', (string) $b); } } streams-3.0.0/tests/LazyOpenStreamTest.php000066400000000000000000000034711241655202000206220ustar00rootroot00000000000000fname = tempnam('/tmp', 'tfile'); if (file_exists($this->fname)) { unlink($this->fname); } } public function tearDown() { if (file_exists($this->fname)) { unlink($this->fname); } } public function testOpensLazily() { $l = new LazyOpenStream($this->fname, 'w+'); $l->write('foo'); $this->assertInternalType('array', $l->getMetadata()); $this->assertFileExists($this->fname); $this->assertEquals('foo', file_get_contents($this->fname)); $this->assertEquals('foo', (string) $l); } public function testProxiesToFile() { file_put_contents($this->fname, 'foo'); $l = new LazyOpenStream($this->fname, 'r'); $this->assertEquals('foo', $l->read(4)); $this->assertTrue($l->eof()); $this->assertEquals(3, $l->tell()); $this->assertTrue($l->isReadable()); $this->assertTrue($l->isSeekable()); $this->assertFalse($l->isWritable()); $l->seek(1); $this->assertEquals('oo', $l->getContents()); $this->assertEquals('foo', (string) $l); $this->assertEquals(3, $l->getSize()); $this->assertInternalType('array', $l->getMetadata()); $l->close(); } public function testDetachesUnderlyingStream() { file_put_contents($this->fname, 'foo'); $l = new LazyOpenStream($this->fname, 'r'); $r = $l->detach(); $this->assertInternalType('resource', $r); fseek($r, 0); $this->assertEquals('foo', stream_get_contents($r)); fclose($r); } } streams-3.0.0/tests/LimitStreamTest.php000066400000000000000000000100471241655202000201340ustar00rootroot00000000000000decorated = Stream::factory(fopen(__FILE__, 'r')); $this->body = new LimitStream($this->decorated, 10, 3); } public function testReturnsSubset() { $body = new LimitStream(Stream::factory('foo'), -1, 1); $this->assertEquals('oo', (string) $body); $this->assertTrue($body->eof()); $body->seek(0); $this->assertFalse($body->eof()); $this->assertEquals('oo', $body->read(100)); $this->assertTrue($body->eof()); } public function testReturnsSubsetWhenCastToString() { $body = Stream::factory('foo_baz_bar'); $limited = new LimitStream($body, 3, 4); $this->assertEquals('baz', (string) $limited); } public function testReturnsSubsetOfEmptyBodyWhenCastToString() { $body = Stream::factory(''); $limited = new LimitStream($body, 0, 10); $this->assertEquals('', (string) $limited); } public function testSeeksWhenConstructed() { $this->assertEquals(0, $this->body->tell()); $this->assertEquals(3, $this->decorated->tell()); } public function testAllowsBoundedSeek() { $this->assertEquals(true, $this->body->seek(100)); $this->assertEquals(10, $this->body->tell()); $this->assertEquals(13, $this->decorated->tell()); $this->assertEquals(true, $this->body->seek(0)); $this->assertEquals(0, $this->body->tell()); $this->assertEquals(3, $this->decorated->tell()); $this->assertEquals(false, $this->body->seek(-10)); $this->assertEquals(0, $this->body->tell()); $this->assertEquals(3, $this->decorated->tell()); $this->assertEquals(true, $this->body->seek(5)); $this->assertEquals(5, $this->body->tell()); $this->assertEquals(8, $this->decorated->tell()); $this->assertEquals(false, $this->body->seek(1000, SEEK_END)); } public function testReadsOnlySubsetOfData() { $data = $this->body->read(100); $this->assertEquals(10, strlen($data)); $this->assertFalse($this->body->read(1000)); $this->body->setOffset(10); $newData = $this->body->read(100); $this->assertEquals(10, strlen($newData)); $this->assertNotSame($data, $newData); } /** * @expectedException \GuzzleHttp\Stream\Exception\SeekException * @expectedExceptionMessage Could not seek the stream to position 2 */ public function testThrowsWhenCurrentGreaterThanOffsetSeek() { $a = Stream::factory('foo_bar'); $b = new NoSeekStream($a); $c = new LimitStream($b); $a->getContents(); $c->setOffset(2); } public function testClaimsConsumedWhenReadLimitIsReached() { $this->assertFalse($this->body->eof()); $this->body->read(1000); $this->assertTrue($this->body->eof()); } public function testContentLengthIsBounded() { $this->assertEquals(10, $this->body->getSize()); } public function testGetContentsIsBasedOnSubset() { $body = new LimitStream(Stream::factory('foobazbar'), 3, 3); $this->assertEquals('baz', $body->getContents()); } public function testReturnsNullIfSizeCannotBeDetermined() { $a = new FnStream([ 'getSize' => function () { return null; }, 'tell' => function () { return 0; }, ]); $b = new LimitStream($a); $this->assertNull($b->getSize()); } public function testLengthLessOffsetWhenNoLimitSize() { $a = Stream::factory('foo_bar'); $b = new LimitStream($a, -1, 4); $this->assertEquals(3, $b->getSize()); } } streams-3.0.0/tests/NoSeekStreamTest.php000066400000000000000000000022671241655202000202470ustar00rootroot00000000000000getMockBuilder('GuzzleHttp\Stream\StreamInterface') ->setMethods(['isSeekable', 'seek']) ->getMockForAbstractClass(); $s->expects($this->never())->method('seek'); $s->expects($this->never())->method('isSeekable'); $wrapped = new NoSeekStream($s); $this->assertFalse($wrapped->isSeekable()); $this->assertFalse($wrapped->seek(2)); } public function testHandlesClose() { $s = Stream::factory('foo'); $wrapped = new NoSeekStream($s); $wrapped->close(); $this->assertFalse($wrapped->write('foo')); } public function testCanAttach() { $s1 = Stream::factory('foo'); $s2 = Stream::factory('bar'); $wrapped = new NoSeekStream($s1); $wrapped->attach($s2->detach()); $this->assertEquals('bar', (string) $wrapped); } } streams-3.0.0/tests/NullStreamTest.php000066400000000000000000000021221241655202000177630ustar00rootroot00000000000000assertEquals('', $b->read(10)); $this->assertEquals(4, $b->write('test')); $this->assertEquals('', (string) $b); $this->assertNull($b->getMetadata('a')); $this->assertEquals([], $b->getMetadata()); $this->assertEquals(0, $b->getSize()); $this->assertEquals('', $b->getContents()); $this->assertEquals(0, $b->tell()); $this->assertTrue($b->isReadable()); $this->assertTrue($b->isWritable()); $this->assertTrue($b->isSeekable()); $this->assertFalse($b->seek(10)); $this->assertTrue($b->eof()); $b->detach(); $this->assertTrue($b->eof()); $b->close(); } /** * @expectedException \GuzzleHttp\Stream\Exception\CannotAttachException */ public function testCannotAttach() { $p = new NullStream(); $p->attach('a'); } } streams-3.0.0/tests/PumpStreamTest.php000066400000000000000000000044431241655202000200020ustar00rootroot00000000000000 ['foo' => 'bar'], 'size' => 100 ]); $this->assertEquals('bar', $p->getMetadata('foo')); $this->assertEquals(['foo' => 'bar'], $p->getMetadata()); $this->assertEquals(100, $p->getSize()); } public function testCanReadFromCallable() { $p = Stream::factory(function ($size) { return 'a'; }); $this->assertEquals('a', $p->read(1)); $this->assertEquals(1, $p->tell()); $this->assertEquals('aaaaa', $p->read(5)); $this->assertEquals(6, $p->tell()); } public function testStoresExcessDataInBuffer() { $called = []; $p = Stream::factory(function ($size) use (&$called) { $called[] = $size; return 'abcdef'; }); $this->assertEquals('a', $p->read(1)); $this->assertEquals('b', $p->read(1)); $this->assertEquals('cdef', $p->read(4)); $this->assertEquals('abcdefabc', $p->read(9)); $this->assertEquals([1, 9, 3], $called); } public function testInifiniteStreamWrappedInLimitStream() { $p = Stream::factory(function () { return 'a'; }); $s = new LimitStream($p, 5); $this->assertEquals('aaaaa', (string) $s); } public function testDescribesCapabilities() { $p = Stream::factory(function () {}); $this->assertTrue($p->isReadable()); $this->assertFalse($p->isSeekable()); $this->assertFalse($p->isWritable()); $this->assertNull($p->getSize()); $this->assertFalse($p->write('aa')); $this->assertEquals('', $p->getContents()); $this->assertEquals('', (string) $p); $p->close(); $this->assertEquals('', $p->read(10)); $this->assertTrue($p->eof()); } /** * @expectedException \GuzzleHttp\Stream\Exception\CannotAttachException */ public function testCannotAttach() { $p = Stream::factory(function () {}); $p->attach('a'); } } streams-3.0.0/tests/StreamDecoratorTraitTest.php000066400000000000000000000075121241655202000220070ustar00rootroot00000000000000c = fopen('php://temp', 'r+'); fwrite($this->c, 'foo'); fseek($this->c, 0); $this->a = Stream::factory($this->c); $this->b = new Str($this->a); } public function testCatchesExceptionsWhenCastingToString() { $s = $this->getMockBuilder('GuzzleHttp\Stream\StreamInterface') ->setMethods(['read']) ->getMockForAbstractClass(); $s->expects($this->once()) ->method('read') ->will($this->throwException(new \Exception('foo'))); $msg = ''; set_error_handler(function ($errNo, $str) use (&$msg) { $msg = $str; }); echo new Str($s); restore_error_handler(); $this->assertContains('foo', $msg); } public function testToString() { $this->assertEquals('foo', (string) $this->b); } public function testHasSize() { $this->assertEquals(3, $this->b->getSize()); $this->assertSame($this->b, $this->b->setSize(2)); $this->assertEquals(2, $this->b->getSize()); } public function testReads() { $this->assertEquals('foo', $this->b->read(10)); } public function testCheckMethods() { $this->assertEquals($this->a->isReadable(), $this->b->isReadable()); $this->assertEquals($this->a->isWritable(), $this->b->isWritable()); $this->assertEquals($this->a->isSeekable(), $this->b->isSeekable()); } public function testSeeksAndTells() { $this->assertTrue($this->b->seek(1)); $this->assertEquals(1, $this->a->tell()); $this->assertEquals(1, $this->b->tell()); $this->assertTrue($this->b->seek(0)); $this->assertEquals(0, $this->a->tell()); $this->assertEquals(0, $this->b->tell()); $this->assertTrue($this->b->seek(0, SEEK_END)); $this->assertEquals(3, $this->a->tell()); $this->assertEquals(3, $this->b->tell()); } public function testGetsContents() { $this->assertEquals('foo', $this->b->getContents()); $this->assertEquals('', $this->b->getContents()); $this->b->seek(1); $this->assertEquals('oo', $this->b->getContents(1)); } public function testCloses() { $this->b->close(); $this->assertFalse(is_resource($this->c)); } public function testDetaches() { $this->b->detach(); $this->assertFalse($this->b->isReadable()); } /** * @expectedException \GuzzleHttp\Stream\Exception\CannotAttachException */ public function testCannotAttachByDefault() { $this->b->attach('a'); } public function testWrapsMetadata() { $this->assertSame($this->b->getMetadata(), $this->a->getMetadata()); $this->assertSame($this->b->getMetadata('uri'), $this->a->getMetadata('uri')); } public function testWrapsWrites() { $this->b->seek(0, SEEK_END); $this->b->write('foo'); $this->assertEquals('foofoo', (string) $this->a); } /** * @expectedException \UnexpectedValueException */ public function testThrowsWithInvalidGetter() { $this->b->foo; } /** * @expectedException \BadMethodCallException */ public function testThrowsWhenGetterNotImplemented() { $s = new BadStream(); $s->stream; } } class BadStream { use StreamDecoratorTrait; public function __construct() {} } streams-3.0.0/tests/StreamTest.php000066400000000000000000000172171241655202000171430ustar00rootroot00000000000000assertTrue($stream->isReadable()); $this->assertTrue($stream->isWritable()); $this->assertTrue($stream->isSeekable()); $this->assertEquals('php://temp', $stream->getMetadata('uri')); $this->assertInternalType('array', $stream->getMetadata()); $this->assertEquals(4, $stream->getSize()); $this->assertFalse($stream->eof()); $stream->close(); } public function testStreamClosesHandleOnDestruct() { $handle = fopen('php://temp', 'r'); $stream = new Stream($handle); unset($stream); $this->assertFalse(is_resource($handle)); } public function testConvertsToString() { $handle = fopen('php://temp', 'w+'); fwrite($handle, 'data'); $stream = new Stream($handle); $this->assertEquals('data', (string) $stream); $this->assertEquals('data', (string) $stream); $stream->close(); } public function testGetsContents() { $handle = fopen('php://temp', 'w+'); fwrite($handle, 'data'); $stream = new Stream($handle); $this->assertEquals('', $stream->getContents()); $stream->seek(0); $this->assertEquals('data', $stream->getContents()); $this->assertEquals('', $stream->getContents()); } public function testChecksEof() { $handle = fopen('php://temp', 'w+'); fwrite($handle, 'data'); $stream = new Stream($handle); $this->assertFalse($stream->eof()); $stream->read(4); $this->assertTrue($stream->eof()); $stream->close(); } public function testAllowsSettingManualSize() { $handle = fopen('php://temp', 'w+'); fwrite($handle, 'data'); $stream = new Stream($handle); $stream->setSize(10); $this->assertEquals(10, $stream->getSize()); $stream->close(); } public function testGetSize() { $size = filesize(__FILE__); $handle = fopen(__FILE__, 'r'); $stream = new Stream($handle); $this->assertEquals($size, $stream->getSize()); // Load from cache $this->assertEquals($size, $stream->getSize()); $stream->close(); } public function testEnsuresSizeIsConsistent() { $h = fopen('php://temp', 'w+'); $this->assertEquals(3, fwrite($h, 'foo')); $stream = new Stream($h); $this->assertEquals(3, $stream->getSize()); $this->assertEquals(4, $stream->write('test')); $this->assertEquals(7, $stream->getSize()); $this->assertEquals(7, $stream->getSize()); $stream->close(); } public function testProvidesStreamPosition() { $handle = fopen('php://temp', 'w+'); $stream = new Stream($handle); $this->assertEquals(0, $stream->tell()); $stream->write('foo'); $this->assertEquals(3, $stream->tell()); $stream->seek(1); $this->assertEquals(1, $stream->tell()); $this->assertSame(ftell($handle), $stream->tell()); $stream->close(); } public function testKeepsPositionOfResource() { $h = fopen(__FILE__, 'r'); fseek($h, 10); $stream = Stream::factory($h); $this->assertEquals(10, $stream->tell()); $stream->close(); } public function testCanDetachAndAttachStream() { $r = fopen('php://temp', 'w+'); $stream = new Stream($r); $stream->write('foo'); $this->assertTrue($stream->isReadable()); $this->assertSame($r, $stream->detach()); $this->assertNull($stream->detach()); $this->assertFalse($stream->isReadable()); $this->assertFalse($stream->read(10)); $this->assertFalse($stream->isWritable()); $this->assertFalse($stream->write('bar')); $this->assertFalse($stream->isSeekable()); $this->assertFalse($stream->seek(10)); $this->assertFalse($stream->tell()); $this->assertTrue($stream->eof()); $this->assertNull($stream->getSize()); $this->assertSame('', (string) $stream); $this->assertSame('', $stream->getContents()); $stream->attach($r); $stream->seek(0); $this->assertEquals('foo', $stream->getContents()); $this->assertTrue($stream->isReadable()); $this->assertTrue($stream->isWritable()); $this->assertTrue($stream->isSeekable()); $stream->close(); } public function testCloseClearProperties() { $handle = fopen('php://temp', 'r+'); $stream = new Stream($handle); $stream->close(); $this->assertEmpty($stream->getMetadata()); $this->assertFalse($stream->isSeekable()); $this->assertFalse($stream->isReadable()); $this->assertFalse($stream->isWritable()); $this->assertNull($stream->getSize()); } public function testCreatesWithFactory() { $stream = Stream::factory('foo'); $this->assertInstanceOf('GuzzleHttp\Stream\Stream', $stream); $this->assertEquals('foo', $stream->getContents()); $stream->close(); } public function testFactoryCreatesFromEmptyString() { $s = Stream::factory(); $this->assertInstanceOf('GuzzleHttp\Stream\Stream', $s); } public function testFactoryCreatesFromResource() { $r = fopen(__FILE__, 'r'); $s = Stream::factory($r); $this->assertInstanceOf('GuzzleHttp\Stream\Stream', $s); $this->assertSame(file_get_contents(__FILE__), (string) $s); } public function testFactoryCreatesFromObjectWithToString() { $r = new HasToString(); $s = Stream::factory($r); $this->assertInstanceOf('GuzzleHttp\Stream\Stream', $s); $this->assertEquals('foo', (string) $s); } public function testCreatePassesThrough() { $s = Stream::factory('foo'); $this->assertSame($s, Stream::factory($s)); } /** * @expectedException \InvalidArgumentException */ public function testThrowsExceptionForUnknown() { Stream::factory(new \stdClass()); } public function testReturnsCustomMetadata() { $s = Stream::factory('foo', ['metadata' => ['hwm' => 3]]); $this->assertEquals(3, $s->getMetadata('hwm')); $this->assertArrayHasKey('hwm', $s->getMetadata()); } public function testCanSetSize() { $s = Stream::factory('', ['size' => 10]); $this->assertEquals(10, $s->getSize()); } public function testCanCreateIteratorBasedStream() { $a = new \ArrayIterator(['foo', 'bar', '123']); $p = Stream::factory($a); $this->assertInstanceOf('GuzzleHttp\Stream\PumpStream', $p); $this->assertEquals('foo', $p->read(3)); $this->assertFalse($p->eof()); $this->assertEquals('b', $p->read(1)); $this->assertEquals('a', $p->read(1)); $this->assertEquals('r12', $p->read(3)); $this->assertFalse($p->eof()); $this->assertEquals('3', $p->getContents()); $this->assertTrue($p->eof()); $this->assertEquals(9, $p->tell()); } } class HasToString { public function __toString() { return 'foo'; } } streams-3.0.0/tests/UtilsTest.php000066400000000000000000000113271241655202000170040ustar00rootroot00000000000000assertEquals('foobaz', Utils::copyToString($s)); $s->seek(0); $this->assertEquals('foo', Utils::copyToString($s, 3)); $this->assertEquals('baz', Utils::copyToString($s, 3)); $this->assertEquals('', Utils::copyToString($s)); } public function testCopiesToStringStopsWhenReadFails() { $s1 = Stream::factory('foobaz'); $s1 = FnStream::decorate($s1, [ 'read' => function () { return false; } ]); $result = Utils::copyToString($s1); $this->assertEquals('', $result); } public function testCopiesToStream() { $s1 = Stream::factory('foobaz'); $s2 = Stream::factory(''); Utils::copyToStream($s1, $s2); $this->assertEquals('foobaz', (string) $s2); $s2 = Stream::factory(''); $s1->seek(0); Utils::copyToStream($s1, $s2, 3); $this->assertEquals('foo', (string) $s2); Utils::copyToStream($s1, $s2, 3); $this->assertEquals('foobaz', (string) $s2); } public function testStopsCopyToStreamWhenWriteFails() { $s1 = Stream::factory('foobaz'); $s2 = Stream::factory(''); $s2 = FnStream::decorate($s2, ['write' => function () { return 0; }]); Utils::copyToStream($s1, $s2); $this->assertEquals('', (string) $s2); } public function testStopsCopyToSteamWhenWriteFailsWithMaxLen() { $s1 = Stream::factory('foobaz'); $s2 = Stream::factory(''); $s2 = FnStream::decorate($s2, ['write' => function () { return 0; }]); Utils::copyToStream($s1, $s2, 10); $this->assertEquals('', (string) $s2); } public function testStopsCopyToSteamWhenReadFailsWithMaxLen() { $s1 = Stream::factory('foobaz'); $s1 = FnStream::decorate($s1, ['read' => function () { return ''; }]); $s2 = Stream::factory(''); Utils::copyToStream($s1, $s2, 10); $this->assertEquals('', (string) $s2); } public function testReadsLines() { $s = Stream::factory("foo\nbaz\nbar"); $this->assertEquals("foo\n", Utils::readline($s)); $this->assertEquals("baz\n", Utils::readline($s)); $this->assertEquals("bar", Utils::readline($s)); } public function testReadsLinesUpToMaxLength() { $s = Stream::factory("12345\n"); $this->assertEquals("123", Utils::readline($s, 4)); $this->assertEquals("45\n", Utils::readline($s)); } public function testReadsLineUntilFalseReturnedFromRead() { $s = $this->getMockBuilder('GuzzleHttp\Stream\Stream') ->setMethods(['read', 'eof']) ->disableOriginalConstructor() ->getMock(); $s->expects($this->exactly(2)) ->method('read') ->will($this->returnCallback(function () { static $c = false; if ($c) { return false; } $c = true; return 'h'; })); $s->expects($this->exactly(2)) ->method('eof') ->will($this->returnValue(false)); $this->assertEquals("h", Utils::readline($s)); } public function testCalculatesHash() { $s = Stream::factory('foobazbar'); $this->assertEquals(md5('foobazbar'), Utils::hash($s, 'md5')); } /** * @expectedException \GuzzleHttp\Stream\Exception\SeekException */ public function testCalculatesHashThrowsWhenSeekFails() { $s = new NoSeekStream(Stream::factory('foobazbar')); $s->read(2); Utils::hash($s, 'md5'); } public function testCalculatesHashSeeksToOriginalPosition() { $s = Stream::factory('foobazbar'); $s->seek(4); $this->assertEquals(md5('foobazbar'), Utils::hash($s, 'md5')); $this->assertEquals(4, $s->tell()); } public function testOpensFilesSuccessfully() { $r = Utils::open(__FILE__, 'r'); $this->assertInternalType('resource', $r); fclose($r); } /** * @expectedException \RuntimeException * @expectedExceptionMessage Unable to open /path/to/does/not/exist using mode r */ public function testThrowsExceptionNotWarning() { Utils::open('/path/to/does/not/exist', 'r'); } public function testProxiesToFactory() { $this->assertEquals('foo', (string) Utils::create('foo')); } }