.gitignore000064400000000073151742727760006556 0ustar00.idea .DS_STORE coverage phpunit.xml composer.lock vendor/ .travis.yml000064400000000166151742727760006702 0ustar00language: php php: - 5.4 - 5.5 - 5.6 - hhvm before_script: - composer install script: vendor/bin/phpunit CHANGELOG.rst000064400000000674151742727760006616 0ustar00========= Changelog ========= 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 LICENSE000064400000002127151742727760005575 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. Makefile000064400000000301151742727760006220 0ustar00all: clean coverage test: vendor/bin/phpunit coverage: vendor/bin/phpunit --coverage-html=artifacts/coverage view-coverage: open artifacts/coverage/index.html clean: rm -rf artifacts/* README.rst000064400000002000151742727760006245 0ustar00============== Guzzle Streams ============== Provides a simple abstraction over streams of data. This library is used in `Guzzle 4 `_ and is an implementation of the proposed `PSR-7 stream interface `_. 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": "1.*" } } 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.json000064400000001331151742727760007306 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": "1.2.x-dev" } } } phpunit.xml.dist000064400000000543151742727760007743 0ustar00 tests src src/CachingStream.php000064400000007367151742727760010613 0ustar00remoteStream = $stream; $this->stream = $target ?: new Stream(fopen('php://temp', 'r+')); } public function getSize() { return max($this->stream->getSize(), $this->remoteStream->getSize()); } /** * {@inheritdoc} * @throws \RuntimeException 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 { throw new \RuntimeException(__CLASS__ . ' supports only SEEK_SET' .' and SEEK_CUR seek operations'); } // You cannot skip ahead past where you've read from the remote stream if ($byte > $this->stream->getSize()) { throw new \RuntimeException(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/GuzzleStreamWrapper.php000064400000005076151742727760012073 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] ])); } 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 ]; } } src/LimitStream.php000064400000007703151742727760010327 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 \RuntimeException */ public function setOffset($offset) { $this->offset = $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 \RuntimeException("Cannot seek to stream offset {$offset}"); } else { $this->stream->read($offset - $current); } } } 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.php000064400000001523151742727760012604 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 StreamInterface * @throws \InvalidArgumentException if the $resource arg is not valid. */ public static function factory($resource = '', $size = null) { return create($resource, $size); } /** * @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; $this->meta = stream_get_meta_data($this->stream); $this->seekable = $this->meta['seekable']; $this->readable = isset(self::$readWriteHash['read'][$this->meta['mode']]); $this->writable = isset(self::$readWriteHash['write'][$this->meta['mode']]); } /** * 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->meta = []; $this->stream = null; } public function detach() { $result = $this->stream; $this->stream = $this->size = null; $this->readable = $this->writable = $this->seekable = false; return $result; } public function getSize() { if ($this->size !== null) { return $this->size; } elseif (!$this->stream) { return null; } // If the stream is a file based stream and local, then use fstat if (isset($this->meta['uri'])) { clearstatcache(true, $this->meta['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; } /** * 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) { return !$key ? $this->meta : (isset($this->meta[$key]) ? $this->meta[$key] : null); } } src/StreamDecoratorTrait.php000064400000004675151742727760012204 0ustar00stream = $stream; } 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 copy_to_string($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() { return $this->stream->close(); } public function getMetadata($key = null) { return $this->stream instanceof MetadataStreamInterface ? $this->stream->getMetadata($key) : null; } public function detach() { $this->stream->detach(); return $this; } 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); } } src/StreamInterface.php000064400000007264151742727760011153 0ustar00eof()) { $buf = $stream->read(1048576); if ($buf === '' || $buf === false) { break; } $buffer .= $buf; } } else { $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. */ function copy_to_stream( 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 bool|string Returns false on failure or a hash string on success */ function hash( StreamInterface $stream, $algo, $rawOutput = false ) { $pos = $stream->tell(); if (!$stream->seek(0)) { return false; } $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 */ function read_line(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; } } tests/CachingStreamTest.php000064400000011246151742727760012015 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); } /** * @expectedException \RuntimeException * @expectedExceptionMessage supports only SEEK_SET and SEEK_CUR */ public function testCannotUseSeekEnd() { $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", \GuzzleHttp\Stream\read_line($body)); $this->assertEquals("0001\n", \GuzzleHttp\Stream\read_line($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", \GuzzleHttp\Stream\read_line($body)); $this->assertEquals(0, $this->readAttribute($body, 'skipReadBytes')); $this->assertEquals("0004\n", \GuzzleHttp\Stream\read_line($body)); $this->assertEquals("0005\n", \GuzzleHttp\Stream\read_line($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", \GuzzleHttp\Stream\read_line($body)); $this->assertEquals("0003\n", \GuzzleHttp\Stream\read_line($body)); $this->assertEquals("0004\n", \GuzzleHttp\Stream\read_line($body)); $this->assertEquals("0005\n", \GuzzleHttp\Stream\read_line($body)); $this->assertEquals("0006\n", \GuzzleHttp\Stream\read_line($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/GuzzleStreamWrapperTest.php000064400000006151151742727760013301 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/LimitStreamTest.php000064400000005244151742727760011540 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); } 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()); } } tests/NoSeekStreamTest.php000064400000001256151742727760011645 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)); } } tests/StreamDecoratorTraitTest.php000064400000006437151742727760013415 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); } } tests/StreamTest.php000064400000011735151742727760010543 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 testCreatesWithFactory() { $stream = Stream::factory('foo'); $this->assertInstanceOf('GuzzleHttp\Stream\Stream', $stream); $this->assertEquals('foo', $stream->getContents()); $stream->close(); } } tests/functionsTest.php000064400000007146151742727760011321 0ustar00assertEquals('foobaz', Stream\copy_to_string($s)); $s->seek(0); $this->assertEquals('foo', Stream\copy_to_string($s, 3)); $this->assertEquals('baz', Stream\copy_to_string($s, 3)); $this->assertEquals('', Stream\copy_to_string($s)); } public function testCopiesToStream() { $s1 = Stream\create('foobaz'); $s2 = Stream\create(''); Stream\copy_to_stream($s1, $s2); $this->assertEquals('foobaz', (string) $s2); $s2 = Stream\create(''); $s1->seek(0); Stream\copy_to_stream($s1, $s2, 3); $this->assertEquals('foo', (string) $s2); Stream\copy_to_stream($s1, $s2, 3); $this->assertEquals('foobaz', (string) $s2); } public function testReadsLines() { $s = Stream\create("foo\nbaz\nbar"); $this->assertEquals("foo\n", Stream\read_line($s)); $this->assertEquals("baz\n", Stream\read_line($s)); $this->assertEquals("bar", Stream\read_line($s)); } public function testReadsLinesUpToMaxLength() { $s = Stream\create("12345\n"); $this->assertEquals("123", Stream\read_line($s, 4)); $this->assertEquals("45\n", Stream\read_line($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", Stream\read_line($s)); } public function testCalculatesHash() { $s = Stream\create('foobazbar'); $this->assertEquals(md5('foobazbar'), Stream\hash($s, 'md5')); } public function testCalculatesHashSeeksToOriginalPosition() { $s = Stream\create('foobazbar'); $s->seek(4); $this->assertEquals(md5('foobazbar'), Stream\hash($s, 'md5')); $this->assertEquals(4, $s->tell()); } public function testFactoryCreatesFromEmptyString() { $s = Stream\create(); $this->assertInstanceOf('GuzzleHttp\Stream\Stream', $s); } public function testFactoryCreatesFromResource() { $r = fopen(__FILE__, 'r'); $s = Stream\create($r); $this->assertInstanceOf('GuzzleHttp\Stream\Stream', $s); $this->assertSame(file_get_contents(__FILE__), (string) $s); } public function testFactoryCreatesFromObjectWithToString() { $r = new HasToString(); $s = Stream\create($r); $this->assertInstanceOf('GuzzleHttp\Stream\Stream', $s); $this->assertEquals('foo', (string) $s); } public function testCreatePassesThrough() { $s = Stream\create('foo'); $this->assertSame($s, Stream\create($s)); } /** * @expectedException \InvalidArgumentException */ public function testThrowsExceptionForUnknown() { Stream\create(new \stdClass()); } } class HasToString { public function __toString() { return 'foo'; } }