.gitignore000064400000000073151742732460006547 0ustar00.idea .DS_STORE coverage phpunit.xml composer.lock vendor/ .travis.yml000064400000000341151742732460006666 0ustar00language: 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 CHANGELOG.rst000064400000003516151742732460006605 0ustar00========= Changelog ========= 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 LICENSE000064400000002127151742732460005566 0ustar00Copyright (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. Makefile000064400000000470151742732460006220 0ustar00all: 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 open artifacts/coverage/index.html view-coverage: open artifacts/coverage/index.html clean: rm -rf artifacts/* README.rst000064400000001524151742732460006250 0ustar00============== Guzzle Streams ============== Provides a simple abstraction over streams of data. This library is used in `Guzzle 4 `_. 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": "~2.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``. composer.json000064400000001327151742732460007304 0ustar00{ "name": "guzzlehttp/streams", "description": "Provides a simple abstraction over streams of data (Guzzle 4+)", "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/" }, "files": ["src/functions.php"] }, "extra": { "branch-alias": { "dev-master": "2.0-dev" } } } phpunit.xml.dist000064400000000677151742732460007744 0ustar00 tests src src/functions.php src/AppendStream.php000064400000011133151742732460010441 0ustar00addStream($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($maxLength = -1) { return Utils::copyToString($this, $maxLength); } /** * 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(); } 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 flush() { return false; } } src/CachingStream.php000064400000007361151742732460010576 0ustar00remoteStream = $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(); } } src/Exception/SeekException.php000064400000001071151742732460012562 0ustar00stream = $stream; $msg = $msg ?: 'Could not seek the stream to position ' . $pos; parent::__construct($msg); } /** * @return StreamInterface */ public function getStream() { return $this->stream; } } src/FnStream.php000064400000007125151742732460007603 0ustar00methods = $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 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 flush() { return call_user_func($this->_fn_flush); } 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($maxLength = -1) { return call_user_func($this->_fn_getContents, $maxLength); } public function getMetadata($key = null) { return call_user_func($this->_fn_getMetadata, $key); } } src/GuzzleStreamWrapper.php000064400000005437151742732460012065 0ustar00isReadable()) { $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_flush() { return $this->stream->flush(); } 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 ]; } } src/InflateStream.php000064400000001666151742732460010626 0ustar00stream = new Stream($resource); } } src/LazyOpenStream.php000064400000001553151742732460011000 0ustar00filename = $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)); } } src/LimitStream.php000064400000007721151742732460010320 0ustar00stream = $stream; $this->setLimit($limit); $this->setOffset($offset); } public function eof() { if ($this->limit == -1) { return $this->stream->eof(); } $tell = $this->stream->tell(); return $tell === false || ($tell >= $this->offset + $this->limit) || $this->stream->eof(); } /** * 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) { return false; } if ($offset < $this->offset) { $offset = $this->offset; } if ($this->limit !== -1 && $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; } } } src/MetadataStreamInterface.php000064400000001522151742732460012574 0ustar00 [ '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 * * @param resource|string|StreamInterface $resource Entity body data * @param int $size Size of the data contained in the resource * * @return Stream * @throws \InvalidArgumentException if the $resource arg is not valid. */ public static function factory($resource = '', $size = null) { $type = gettype($resource); if ($type == 'string') { $stream = fopen('php://temp', 'r+'); if ($resource !== '') { fwrite($stream, $resource); fseek($stream, 0); } return new self($stream); } if ($type == 'resource') { return new self($resource, $size); } if ($resource instanceof StreamInterface) { return $resource; } if ($type == 'object' && method_exists($resource, '__toString')) { return self::factory((string) $resource, $size); } throw new \InvalidArgumentException('Invalid resource type: ' . $type); } /** * @param resource $stream Stream resource to wrap * @param int $size Size of the stream in bytes. Only pass if the * size cannot be obtained from the stream. * * @throws \InvalidArgumentException if the stream is not a stream resource */ public function __construct($stream, $size = null) { if (!is_resource($stream)) { throw new \InvalidArgumentException('Stream must be a resource'); } $this->size = $size; $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 = isset($meta['uri']) ? $meta['uri'] : null; } /** * 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($maxLength = -1) { return $this->stream ? stream_get_contents($this->stream, $maxLength) : ''; } 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 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) : ''; } 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 flush() { return $this->stream ? fflush($this->stream) : false; } /** * Get stream metadata as an associative array or retrieve a specific key. * * The keys returned are identical to the keys returned from PHP's * stream_get_meta_data() function. * * @param string $key Specific metadata to retrieve. * * @return array|mixed|null Returns an associative array if no key is * no key is provided. Returns a specific key * value if a key is provided and the value is * found, or null if the key is not found. * @see http://php.net/manual/en/function.stream-get-meta-data.php */ public function getMetadata($key = null) { $meta = $this->stream ? stream_get_meta_data($this->stream) : []; return !$key ? $meta : (isset($meta[$key]) ? $meta[$key] : null); } } src/StreamDecoratorTrait.php000064400000006266151742732460012173 0ustar00stream = $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($maxLength = -1) { return Utils::copyToString($this, $maxLength); } /** * 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 instanceof MetadataStreamInterface ? $this->stream->getMetadata($key) : null; } public function detach() { return $this->stream->detach(); } 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); } public function flush() { return $this->stream->flush(); } /** * 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)); } } src/StreamInterface.php000064400000007527151742732460011146 0ustar00eof()) { $buf = $stream->read(1048576); if ($buf === '' || $buf === false) { break; } $buffer .= $buf; } return $buffer; } $len = 0; while (!$stream->eof() && $len < $maxLen) { $buf = $stream->read($maxLen - $len); if ($buf === '' || $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 int $size Size if known up front * * @return MetadataStreamInterface * * @see GuzzleHttp\Stream\Stream::factory */ public static function create($resource, $size = null) { return Stream::factory($resource, $size); } } src/functions.php000064400000003014151742732460010065 0ustar00getMockBuilder('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 testFlushReturnsFalse() { $s = new AppendStream(); $this->assertFalse($s->flush()); } } tests/CachingStreamTest.php000064400000010723151742732460012005 0ustar00decorated = 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))), true ); $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)); } } tests/Exception/SeekExceptionTest.php000064400000000646151742732460014004 0ustar00assertSame($s, $e->getStream()); $this->assertContains('10', $e->getMessage()); } } tests/FnStreamTest.php000064400000004722151742732460011016 0ustar00seek(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); $b->flush(); $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); } } tests/GuzzleStreamWrapperTest.php000064400000006150151742732460013271 0ustar00assertSame('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); } } tests/InflateStreamTest.php000064400000000607151742732460012033 0ustar00assertEquals('test', (string) $b); } } tests/LazyOpenStreamTest.php000064400000003471151742732460012214 0ustar00fname = 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); } } tests/LimitStreamTest.php000064400000007150151742732460011527 0ustar00decorated = 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->body->seek(100); $this->assertEquals(13, $this->decorated->tell()); $this->body->seek(0); $this->assertEquals(0, $this->body->tell()); $this->assertEquals(3, $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()); } } tests/NoSeekStreamTest.php000064400000001660151742732460011635 0ustar00getMockBuilder('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')); } } tests/StreamDecoratorTraitTest.php000064400000007430151742732460013400 0ustar00c = 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('o', $this->b->getContents(1)); $this->assertEquals('', $this->b->getContents(0)); } public function testCloses() { $this->b->close(); $this->assertFalse(is_resource($this->c)); } public function testDetaches() { $this->b->detach(); $this->assertFalse($this->b->isReadable()); } 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); } public function testWrapsFlush() { $this->b->flush(); } /** * @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() {} } tests/StreamTest.php000064400000015157151742732460010536 0ustar00assertTrue($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()); $stream->seek(0); $this->assertEquals('da', $stream->getContents(2)); $stream->close(); } 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 testCanDetachStream() { $r = fopen('php://temp', 'w+'); $stream = new Stream($r); $this->assertTrue($stream->isReadable()); $this->assertSame($r, $stream->detach()); $this->assertNull($stream->detach()); $this->assertFalse($stream->isReadable()); $this->assertSame('', $stream->read(10)); $this->assertFalse($stream->isWritable()); $this->assertFalse($stream->write('foo')); $this->assertFalse($stream->isSeekable()); $this->assertFalse($stream->seek(10)); $this->assertFalse($stream->tell()); $this->assertFalse($stream->eof()); $this->assertNull($stream->getSize()); $this->assertSame('', (string) $stream); $this->assertSame('', $stream->getContents()); $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 testFlushes() { $stream = Stream::factory('foo'); $this->assertTrue($stream->flush()); $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()); } } class HasToString { public function __toString() { return 'foo'; } } tests/UtilsTest.php000064400000011706151742732460010377 0ustar00assertEquals('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 ''; }]); $result = Utils::copyToString($s1); $this->assertEquals('', $result); } public function testCopiesToStringStopsWhenReadFailsWithMaxLen() { $s1 = Stream::factory('foobaz'); $s1 = FnStream::decorate($s1, ['read' => function () { return ''; }]); $result = Utils::copyToString($s1, 10); $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')); } }