From a301d77e309cef8435a53ad65873b67f7242f064 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A1gi-Kaz=C3=A1r=20M=C3=A1rk?= Date: Sun, 20 Dec 2015 18:41:11 +0100 Subject: [PATCH 1/7] Add Stream decorator --- spec/Decorator/StreamDecoratorSpec.php | 154 +++++++++++++++++++++++++ src/Decorator/StreamDecorator.php | 138 ++++++++++++++++++++++ 2 files changed, 292 insertions(+) create mode 100644 spec/Decorator/StreamDecoratorSpec.php create mode 100644 src/Decorator/StreamDecorator.php diff --git a/spec/Decorator/StreamDecoratorSpec.php b/spec/Decorator/StreamDecoratorSpec.php new file mode 100644 index 0000000..055e385 --- /dev/null +++ b/spec/Decorator/StreamDecoratorSpec.php @@ -0,0 +1,154 @@ +beAnInstanceOf('spec\Http\Message\Decorator\StreamDecoratorStub', [$stream]); + } + + function it_is_initializable() + { + $this->shouldHaveType('spec\Http\Message\Decorator\StreamDecoratorStub'); + } + + function it_is_a_stream_decorator() + { + $this->shouldUseTrait('Http\Message\Decorator\StreamDecorator'); + } + + function it_casts_the_stream_to_string(StreamInterface $stream) + { + $stream->__toString()->willReturn('body'); + + $this->__toString()->shouldReturn('body'); + } + + function it_closes_the_stream(StreamInterface $stream) + { + $stream->close()->shouldBeCalled(); + + $this->close(); + } + + function it_detaches_the_stream(StreamInterface $stream) + { + $stream->detach()->willReturn('detached'); + + $this->detach()->shouldReturn('detached'); + } + + function it_returns_the_size_of_the_stream(StreamInterface $stream) + { + $stream->getSize()->willReturn(1234); + + $this->getSize()->shouldReturn(1234); + } + + function it_returns_the_current_position_of_the_stream(StreamInterface $stream) + { + $stream->tell()->willReturn(0); + + $this->tell()->shouldReturn(0); + } + + function it_checks_whether_the_stream_is_eof(StreamInterface $stream) + { + $stream->eof()->willReturn(false); + + $this->eof()->shouldReturn(false); + } + + function it_checks_whether_the_stream_is_seekable(StreamInterface $stream) + { + $stream->isSeekable()->willReturn(true); + + $this->isSeekable()->shouldReturn(true); + } + + function it_seeks_the_current_position_of_the_stream(StreamInterface $stream) + { + $stream->seek(0, SEEK_SET)->shouldBeCalled(); + + $this->seek(0); + } + + function it_rewinds_to_the_beginning_of_the_stream(StreamInterface $stream) + { + $stream->rewind()->shouldBeCalled(); + + $this->rewind(); + } + + function it_checks_whether_the_stream_is_writable(StreamInterface $stream) + { + $stream->isWritable()->willReturn(true); + + $this->isWritable()->shouldReturn(true); + } + + function it_writes_to_the_stream(StreamInterface $stream) + { + $stream->write('body')->shouldBeCalled(); + + $this->write('body'); + } + + function it_checks_whether_the_stream_is_readable(StreamInterface $stream) + { + $stream->isReadable()->willReturn(true); + + $this->isReadable()->shouldReturn(true); + } + + function it_reads_from_the_stream(StreamInterface $stream) + { + $stream->read(4)->willReturn('body'); + + $this->read(4)->shouldReturn('body'); + } + + function it_returns_the_contents_of_the_stream(StreamInterface $stream) + { + $stream->getContents()->willReturn('body'); + + $this->getContents()->shouldReturn('body'); + } + + function it_returns_metadata_of_the_stream(StreamInterface $stream) + { + $stream->getMetadata(null)->willReturn(['key' => 'value']); + $stream->getMetadata('key')->willReturn('value'); + $stream->getMetadata('key2')->willReturn(null); + + $this->getMetadata()->shouldReturn(['key' => 'value']); + $this->getMetadata('key')->shouldReturn('value'); + $this->getMetadata('key2')->shouldReturn(null); + } + + function getMatchers() + { + return [ + 'useTrait' => function ($subject, $trait) { + return class_uses($subject, $trait); + } + ]; + } +} + +class StreamDecoratorStub implements StreamInterface +{ + use StreamDecorator; + + public function __construct(StreamInterface $stream) + { + $this->stream = $stream; + } +} diff --git a/src/Decorator/StreamDecorator.php b/src/Decorator/StreamDecorator.php new file mode 100644 index 0000000..f405c7a --- /dev/null +++ b/src/Decorator/StreamDecorator.php @@ -0,0 +1,138 @@ + + */ +trait StreamDecorator +{ + /** + * @var StreamInterface + */ + protected $stream; + + /** + * {@inheritdoc} + */ + public function __toString() + { + return $this->stream->__toString(); + } + + /** + * {@inheritdoc} + */ + public function close() + { + $this->stream->close(); + } + + /** + * {@inheritdoc} + */ + public function detach() + { + return $this->stream->detach(); + } + + /** + * {@inheritdoc} + */ + public function getSize() + { + return $this->stream->getSize(); + } + + /** + * {@inheritdoc} + */ + public function tell() + { + return $this->stream->tell(); + } + + /** + * {@inheritdoc} + */ + public function eof() + { + return $this->stream->eof(); + } + + /** + * {@inheritdoc} + */ + public function isSeekable() + { + return $this->stream->isSeekable(); + } + + /** + * {@inheritdoc} + */ + public function seek($offset, $whence = SEEK_SET) + { + $this->stream->seek($offset, $whence); + } + + /** + * {@inheritdoc} + */ + public function rewind() + { + $this->stream->rewind(); + } + + /** + * {@inheritdoc} + */ + public function isWritable() + { + return $this->stream->isWritable(); + } + + /** + * {@inheritdoc} + */ + public function write($string) + { + return $this->stream->write($string); + } + + /** + * {@inheritdoc} + */ + public function isReadable() + { + return $this->stream->isReadable(); + } + + /** + * {@inheritdoc} + */ + public function read($length) + { + return $this->stream->read($length); + } + + /** + * {@inheritdoc} + */ + public function getContents() + { + return $this->stream->getContents(); + } + + /** + * {@inheritdoc} + */ + public function getMetadata($key = null) + { + return $this->stream->getMetadata($key); + } +} From 94cc919f4370782f45a6ff0543e59211f9bc734b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A1gi-Kaz=C3=A1r=20M=C3=A1rk?= Date: Sun, 20 Dec 2015 19:47:57 +0100 Subject: [PATCH 2/7] Add stream encoding --- composer.json | 2 + spec/Encoding/ChunkStreamSpec.php | 40 +++++++ spec/Encoding/CompressStreamSpec.php | 42 ++++++++ spec/Encoding/DechunkStreamSpec.php | 41 +++++++ spec/Encoding/DecompressStreamSpec.php | 42 ++++++++ spec/Encoding/DeflateStreamSpec.php | 42 ++++++++ spec/Encoding/GzipDecodeStreamSpec.php | 42 ++++++++ spec/Encoding/GzipEncodeStreamSpec.php | 42 ++++++++ spec/Encoding/InflateStreamSpec.php | 42 ++++++++ spec/Encoding/MemoryStream.php | 112 +++++++++++++++++++ src/Encoding/ChunkStream.php | 40 +++++++ src/Encoding/CompressStream.php | 28 +++++ src/Encoding/DechunkStream.php | 33 ++++++ src/Encoding/DecompressStream.php | 28 +++++ src/Encoding/DeflateStream.php | 28 +++++ src/Encoding/Filter/Chunk.php | 22 ++++ src/Encoding/FilteredStream.php | 144 +++++++++++++++++++++++++ src/Encoding/GzipDecodeStream.php | 28 +++++ src/Encoding/GzipEncodeStream.php | 35 ++++++ src/Encoding/InflateStream.php | 28 +++++ 20 files changed, 861 insertions(+) create mode 100644 spec/Encoding/ChunkStreamSpec.php create mode 100644 spec/Encoding/CompressStreamSpec.php create mode 100644 spec/Encoding/DechunkStreamSpec.php create mode 100644 spec/Encoding/DecompressStreamSpec.php create mode 100644 spec/Encoding/DeflateStreamSpec.php create mode 100644 spec/Encoding/GzipDecodeStreamSpec.php create mode 100644 spec/Encoding/GzipEncodeStreamSpec.php create mode 100644 spec/Encoding/InflateStreamSpec.php create mode 100644 spec/Encoding/MemoryStream.php create mode 100644 src/Encoding/ChunkStream.php create mode 100644 src/Encoding/CompressStream.php create mode 100644 src/Encoding/DechunkStream.php create mode 100644 src/Encoding/DecompressStream.php create mode 100644 src/Encoding/DeflateStream.php create mode 100644 src/Encoding/Filter/Chunk.php create mode 100644 src/Encoding/FilteredStream.php create mode 100644 src/Encoding/GzipDecodeStream.php create mode 100644 src/Encoding/GzipEncodeStream.php create mode 100644 src/Encoding/InflateStream.php diff --git a/composer.json b/composer.json index 742f6c2..ece5c65 100644 --- a/composer.json +++ b/composer.json @@ -18,6 +18,8 @@ "require-dev": { "zendframework/zend-diactoros": "^1.0", "guzzlehttp/psr7": "^1.0", + "ext-zlib": "*", + "clue/stream-filter": "^1.3", "phpspec/phpspec": "^2.4", "henrikbjorn/phpspec-code-coverage" : "^1.0" }, diff --git a/spec/Encoding/ChunkStreamSpec.php b/spec/Encoding/ChunkStreamSpec.php new file mode 100644 index 0000000..c0bd5a9 --- /dev/null +++ b/spec/Encoding/ChunkStreamSpec.php @@ -0,0 +1,40 @@ +beConstructedWith($stream); + } + + function it_is_initializable() + { + $this->shouldHaveType('Http\Message\Encoding\ChunkStream'); + } + + function it_is_a_stream() + { + $this->shouldImplement('Psr\Http\Message\StreamInterface'); + } + + function it_chunks_content() + { + $stream = new MemoryStream('This is a stream'); + $this->beConstructedWith($stream, 6); + + $this->getContents()->shouldReturn("10\r\nThis is a stream\r\n0\r\n\r\n"); + } + + function it_chunks_in_multiple() + { + $stream = new MemoryStream('This is a stream', 6); + $this->beConstructedWith($stream, 6); + + $this->getContents()->shouldReturn("6\r\nThis i\r\n6\r\ns a st\r\n4\r\nream\r\n0\r\n\r\n"); + } +} diff --git a/spec/Encoding/CompressStreamSpec.php b/spec/Encoding/CompressStreamSpec.php new file mode 100644 index 0000000..ffb6879 --- /dev/null +++ b/spec/Encoding/CompressStreamSpec.php @@ -0,0 +1,42 @@ +beConstructedWith($stream); + } + + function it_is_initializable() + { + $this->shouldHaveType('Http\Message\Encoding\CompressStream'); + } + + function it_is_a_stream() + { + $this->shouldImplement('Psr\Http\Message\StreamInterface'); + } + + function it_reads() + { + $stream = new MemoryStream('This is a test stream'); + $this->beConstructedWith($stream); + + $stream->rewind(); + $this->read(4)->shouldReturn(substr(gzcompress('This is a test stream'), 0, 4)); + } + + function it_gets_content() + { + $stream = new MemoryStream('This is a test stream'); + $this->beConstructedWith($stream); + + $stream->rewind(); + $this->getContents()->shouldReturn(gzcompress('This is a test stream')); + } +} diff --git a/spec/Encoding/DechunkStreamSpec.php b/spec/Encoding/DechunkStreamSpec.php new file mode 100644 index 0000000..39c9fa5 --- /dev/null +++ b/spec/Encoding/DechunkStreamSpec.php @@ -0,0 +1,41 @@ +beConstructedWith($stream); + } + + function it_is_initializable() + { + $this->shouldHaveType('Http\Message\Encoding\DechunkStream'); + } + + function it_is_a_stream() + { + $this->shouldImplement('Psr\Http\Message\StreamInterface'); + } + + function it_reads() + { + $stream = new MemoryStream("4\r\ntest\r\n4\r\ntest\r\n0\r\n\r\n\0"); + $this->beConstructedWith($stream); + + $this->read(6)->shouldReturn('testte'); + $this->read(6)->shouldReturn('st'); + } + + function it_gets_content() + { + $stream = new MemoryStream("4\r\ntest\r\n0\r\n\r\n\0"); + $this->beConstructedWith($stream); + + $this->getContents()->shouldReturn('test'); + } +} diff --git a/spec/Encoding/DecompressStreamSpec.php b/spec/Encoding/DecompressStreamSpec.php new file mode 100644 index 0000000..141b0f9 --- /dev/null +++ b/spec/Encoding/DecompressStreamSpec.php @@ -0,0 +1,42 @@ +beConstructedWith($stream); + } + + function it_is_initializable() + { + $this->shouldHaveType('Http\Message\Encoding\DecompressStream'); + } + + function it_is_a_stream() + { + $this->shouldImplement('Psr\Http\Message\StreamInterface'); + } + + function it_reads() + { + // "This is a test stream" | deflate + $stream = new MemoryStream(gzcompress('This is a test stream')); + $this->beConstructedWith($stream); + + $this->read(4)->shouldReturn('This'); + } + + function it_gets_content() + { + // "This is a test stream" | deflate + $stream = new MemoryStream(gzcompress('This is a test stream')); + $this->beConstructedWith($stream); + + $this->getContents()->shouldReturn('This is a test stream'); + } +} diff --git a/spec/Encoding/DeflateStreamSpec.php b/spec/Encoding/DeflateStreamSpec.php new file mode 100644 index 0000000..83b608e --- /dev/null +++ b/spec/Encoding/DeflateStreamSpec.php @@ -0,0 +1,42 @@ +beConstructedWith($stream); + } + + function it_is_initializable() + { + $this->shouldHaveType('Http\Message\Encoding\DeflateStream'); + } + + function it_is_a_stream() + { + $this->shouldImplement('Psr\Http\Message\StreamInterface'); + } + + function it_reads() + { + $stream = new MemoryStream('This is a test stream'); + $this->beConstructedWith($stream); + + $stream->rewind(); + $this->read(4)->shouldReturn(substr(gzdeflate('This is a test stream'),0, 4)); + } + + function it_gets_content() + { + $stream = new MemoryStream('This is a test stream'); + $this->beConstructedWith($stream); + + $stream->rewind(); + $this->getContents()->shouldReturn(gzdeflate('This is a test stream')); + } +} diff --git a/spec/Encoding/GzipDecodeStreamSpec.php b/spec/Encoding/GzipDecodeStreamSpec.php new file mode 100644 index 0000000..e9d1f29 --- /dev/null +++ b/spec/Encoding/GzipDecodeStreamSpec.php @@ -0,0 +1,42 @@ +beConstructedWith($stream); + } + + function it_is_initializable() + { + $this->shouldHaveType('Http\Message\Encoding\GzipDecodeStream'); + } + + function it_is_a_stream() + { + $this->shouldImplement('Psr\Http\Message\StreamInterface'); + } + + function it_reads() + { + // "This is a test stream" | deflate + $stream = new MemoryStream(gzencode('This is a test stream')); + $this->beConstructedWith($stream); + + $this->read(4)->shouldReturn('This'); + } + + function it_gets_content() + { + // "This is a test stream" | deflate + $stream = new MemoryStream(gzencode('This is a test stream')); + $this->beConstructedWith($stream); + + $this->getContents()->shouldReturn('This is a test stream'); + } +} diff --git a/spec/Encoding/GzipEncodeStreamSpec.php b/spec/Encoding/GzipEncodeStreamSpec.php new file mode 100644 index 0000000..459f8a4 --- /dev/null +++ b/spec/Encoding/GzipEncodeStreamSpec.php @@ -0,0 +1,42 @@ +beConstructedWith($stream); + } + + function it_is_initializable() + { + $this->shouldHaveType('Http\Message\Encoding\GzipEncodeStream'); + } + + function it_is_a_stream() + { + $this->shouldImplement('Psr\Http\Message\StreamInterface'); + } + + function it_reads() + { + $stream = new MemoryStream('This is a test stream'); + $this->beConstructedWith($stream); + + $stream->rewind(); + $this->read(4)->shouldReturn(substr(gzencode('This is a test stream'),0, 4)); + } + + function it_gets_content() + { + $stream = new MemoryStream('This is a test stream'); + $this->beConstructedWith($stream); + + $stream->rewind(); + $this->getContents()->shouldReturn(gzencode('This is a test stream')); + } +} diff --git a/spec/Encoding/InflateStreamSpec.php b/spec/Encoding/InflateStreamSpec.php new file mode 100644 index 0000000..52d08c9 --- /dev/null +++ b/spec/Encoding/InflateStreamSpec.php @@ -0,0 +1,42 @@ +beConstructedWith($stream); + } + + function it_is_initializable() + { + $this->shouldHaveType('Http\Message\Encoding\InflateStream'); + } + + function it_is_a_stream() + { + $this->shouldImplement('Psr\Http\Message\StreamInterface'); + } + + function it_reads() + { + // "This is a test stream" | deflate + $stream = new MemoryStream(gzdeflate('This is a test stream')); + $this->beConstructedWith($stream); + + $this->read(4)->shouldReturn('This'); + } + + function it_gets_content() + { + // "This is a test stream" | deflate + $stream = new MemoryStream(gzdeflate('This is a test stream')); + $this->beConstructedWith($stream); + + $this->getContents()->shouldReturn('This is a test stream'); + } +} diff --git a/spec/Encoding/MemoryStream.php b/spec/Encoding/MemoryStream.php new file mode 100644 index 0000000..4403536 --- /dev/null +++ b/spec/Encoding/MemoryStream.php @@ -0,0 +1,112 @@ +size = strlen($body); + $this->resource = fopen('php://memory', 'rw+'); + + if (null !== $chunkSize) { + stream_set_read_buffer($this->resource, $chunkSize); + stream_set_chunk_size($this->resource, $chunkSize); + } + + fwrite($this->resource, $body); + fseek($this->resource, 0); + } + + public function __toString() + { + return $this->getContents(); + } + + public function close() + { + fclose($this->resource); + } + + public function detach() + { + $resource = $this->resource; + $this->resource = null; + + return $resource; + } + + public function getSize() + { + return $this->size; + } + + public function tell() + { + return ftell($this->resource); + } + + public function eof() + { + return feof($this->resource); + } + + public function isSeekable() + { + return true; + } + + public function seek($offset, $whence = SEEK_SET) + { + fseek($this->resource, $offset, $whence); + } + + public function rewind() + { + $this->seek(0); + } + + public function isWritable() + { + return true; + } + + public function write($string) + { + fwrite($this->resource, $string); + } + + public function isReadable() + { + return true; + } + + public function read($length) + { + return fread($this->resource, $length); + } + + public function getContents() + { + $this->rewind(); + + return $this->read($this->size); + } + + public function getMetadata($key = null) + { + $metadata = stream_get_meta_data($this->resource); + + if (null === $key) { + return $metadata; + } + + return $metadata[$key]; + } +} diff --git a/src/Encoding/ChunkStream.php b/src/Encoding/ChunkStream.php new file mode 100644 index 0000000..f80b143 --- /dev/null +++ b/src/Encoding/ChunkStream.php @@ -0,0 +1,40 @@ + + */ +class ChunkStream extends FilteredStream +{ + /** + * {@inheritdoc} + */ + public function getReadFilter() + { + if (!array_key_exists('chunk', stream_get_filters())) { + stream_filter_register('chunk', 'Http\Message\Encoding\Filter\Chunk'); + } + + return 'chunk'; + } + + /** + * {@inheritdoc} + */ + public function getWriteFilter() + { + return 'dechunk'; + } + + protected function fill() + { + parent::fill(); + + if ($this->stream->eof()) { + $this->buffer .= "0\r\n\r\n"; + } + } +} diff --git a/src/Encoding/CompressStream.php b/src/Encoding/CompressStream.php new file mode 100644 index 0000000..c18cd79 --- /dev/null +++ b/src/Encoding/CompressStream.php @@ -0,0 +1,28 @@ + + */ +class CompressStream extends FilteredStream +{ + public function __construct(StreamInterface $stream, $level = -1) + { + parent::__construct($stream, ['window' => 15, 'level' => $level], ['window' => 15]); + } + + public function getReadFilter() + { + return 'zlib.deflate'; + } + + public function getWriteFilter() + { + return 'zlib.inflate'; + } +} diff --git a/src/Encoding/DechunkStream.php b/src/Encoding/DechunkStream.php new file mode 100644 index 0000000..1f595a8 --- /dev/null +++ b/src/Encoding/DechunkStream.php @@ -0,0 +1,33 @@ + + */ +class DechunkStream extends FilteredStream +{ + /** + * {@inheritdoc} + */ + public function getReadFilter() + { + return 'dechunk'; + } + + /** + * {@inheritdoc} + */ + public function getWriteFilter() + { + if (!array_key_exists('chunk', stream_get_filters())) { + stream_filter_register('chunk', 'Http\Encoding\Filter\Chunk'); + } + + return 'chunk'; + } +} diff --git a/src/Encoding/DecompressStream.php b/src/Encoding/DecompressStream.php new file mode 100644 index 0000000..d0ac295 --- /dev/null +++ b/src/Encoding/DecompressStream.php @@ -0,0 +1,28 @@ + + */ +class DecompressStream extends FilteredStream +{ + public function __construct(StreamInterface $stream, $level = -1) + { + parent::__construct($stream, ['window' => 15], ['window' => 15, 'level' => $level]); + } + + public function getReadFilter() + { + return 'zlib.inflate'; + } + + public function getWriteFilter() + { + return 'zlib.deflate'; + } +} diff --git a/src/Encoding/DeflateStream.php b/src/Encoding/DeflateStream.php new file mode 100644 index 0000000..d817a98 --- /dev/null +++ b/src/Encoding/DeflateStream.php @@ -0,0 +1,28 @@ + + */ +class DeflateStream extends FilteredStream +{ + public function __construct(StreamInterface $stream, $level = -1) + { + parent::__construct($stream, ['window' => -15, 'level' => $level], ['window' => -15]); + } + + public function getReadFilter() + { + return 'zlib.deflate'; + } + + public function getWriteFilter() + { + return 'zlib.inflate'; + } +} diff --git a/src/Encoding/Filter/Chunk.php b/src/Encoding/Filter/Chunk.php new file mode 100644 index 0000000..b7dd20b --- /dev/null +++ b/src/Encoding/Filter/Chunk.php @@ -0,0 +1,22 @@ +stream, dechex($bucket->datalen)."\r\n"); + stream_bucket_append($out, $lenbucket); + + $consumed += $bucket->datalen; + stream_bucket_append($out, $bucket); + + $lenbucket = stream_bucket_new($this->stream, "\r\n"); + stream_bucket_append($out, $lenbucket); + } + + return PSFS_PASS_ON; + } +} diff --git a/src/Encoding/FilteredStream.php b/src/Encoding/FilteredStream.php new file mode 100644 index 0000000..545ffb3 --- /dev/null +++ b/src/Encoding/FilteredStream.php @@ -0,0 +1,144 @@ + + */ +abstract class FilteredStream implements StreamInterface +{ + const BUFFER_SIZE = 65536; + + use StreamDecorator; + + /** + * @var callable + */ + protected $readFilterCallback; + + /** + * @var resource + */ + protected $readFilter; + + /** + * @var callable + */ + protected $writeFilterCallback; + + /** + * @var resource + */ + protected $writeFilter; + + /** + * Internal buffer + * + * @var string + */ + protected $buffer = ''; + + /** + * @param StreamInterface $stream + * @param null $readFilterOptions + * @param null $writeFilterOptions + */ + public function __construct(StreamInterface $stream, $readFilterOptions = null, $writeFilterOptions = null) + { + $this->readFilterCallback = Filter\fun($this->getReadFilter(), $readFilterOptions); + $this->writeFilterCallback = Filter\fun($this->getWriteFilter(), $writeFilterOptions); + $this->stream = $stream; + } + + /** + * {@inheritdoc} + */ + public function read($length) + { + if (strlen($this->buffer) >= $length) { + $read = substr($this->buffer, 0, $length); + $this->buffer = substr($this->buffer, $length); + + return $read; + } + + if ($this->stream->eof()) { + $buffer = $this->buffer; + $this->buffer = ''; + + return $buffer; + } + + $read = $this->buffer; + $this->buffer = ''; + $this->fill(); + + return $read . $this->read($length - strlen($read)); + } + + /** + * {@inheritdoc} + */ + public function eof() + { + return ($this->stream->eof() && $this->buffer === ''); + } + + /** + * Buffer is filled by reading underlying stream + * + * Callback is reading once more even if the stream is ended. + * This allow to get last data in the PHP buffer otherwise this + * bug is present : https://bugs.php.net/bug.php?id=48725 + */ + protected function fill() + { + $readFilterCallback = $this->readFilterCallback; + $this->buffer .= $readFilterCallback($this->stream->read(self::BUFFER_SIZE)); + + if ($this->stream->eof()) { + $this->buffer .= $readFilterCallback(); + } + } + + /** + * {@inheritdoc} + */ + public function getContents() + { + $buffer = ''; + + while (!$this->eof()) { + $buf = $this->read(1048576); + // Using a loose equality here to match on '' and false. + if ($buf == null) { + break; + } + + $buffer .= $buf; + } + + return $buffer; + } + + /** + * Return the read filter name + * + * @return string + */ + abstract public function getReadFilter(); + + /** + * Return the write filter name + * + * @return mixed + */ + abstract public function getWriteFilter(); +} diff --git a/src/Encoding/GzipDecodeStream.php b/src/Encoding/GzipDecodeStream.php new file mode 100644 index 0000000..280ace4 --- /dev/null +++ b/src/Encoding/GzipDecodeStream.php @@ -0,0 +1,28 @@ + + */ +class GzipDecodeStream extends FilteredStream +{ + public function __construct(StreamInterface $stream, $level = -1) + { + parent::__construct($stream, ['window' => 31], ['window' => 31, 'level' => $level]); + } + + public function getReadFilter() + { + return 'zlib.inflate'; + } + + public function getWriteFilter() + { + return 'zlib.deflate'; + } +} diff --git a/src/Encoding/GzipEncodeStream.php b/src/Encoding/GzipEncodeStream.php new file mode 100644 index 0000000..ef4423b --- /dev/null +++ b/src/Encoding/GzipEncodeStream.php @@ -0,0 +1,35 @@ + + */ +class GzipEncodeStream extends FilteredStream +{ + public function __construct(StreamInterface $stream, $level = -1) + { + parent::__construct($stream, ['window' => 31, 'level' => $level], ['window' => 31]); + } + + /** + * {@inheritdoc} + */ + public function getReadFilter() + { + return 'zlib.deflate'; + } + + /** + * {@inheritdoc} + */ + public function getWriteFilter() + { + return 'zlib.inflate'; + } +} diff --git a/src/Encoding/InflateStream.php b/src/Encoding/InflateStream.php new file mode 100644 index 0000000..766332b --- /dev/null +++ b/src/Encoding/InflateStream.php @@ -0,0 +1,28 @@ + + */ +class InflateStream extends FilteredStream +{ + public function __construct(StreamInterface $stream, $level = -1) + { + parent::__construct($stream, ['window' => -15], ['window' => -15, 'level' => $level]); + } + + public function getReadFilter() + { + return 'zlib.inflate'; + } + + public function getWriteFilter() + { + return 'zlib.deflate'; + } +} From 946666d0efc9fb9851daaa5ed9f788278cc78cce Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A1gi-Kaz=C3=A1r=20M=C3=A1rk?= Date: Sun, 20 Dec 2015 19:54:07 +0100 Subject: [PATCH 3/7] Temporarily allow HHVM tests to fail, see #6 --- .travis.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.travis.yml b/.travis.yml index 7c466c1..0bee5b1 100644 --- a/.travis.yml +++ b/.travis.yml @@ -22,6 +22,8 @@ branches: - /^analysis-.*$/ matrix: + allow_failures: + - php: hhvm fast_finish: true include: - php: 5.4 From c7fcd53e23a85c64bf52298f79fa4495f28dffeb Mon Sep 17 00:00:00 2001 From: sagikazarmark Date: Sun, 20 Dec 2015 13:55:32 -0500 Subject: [PATCH 4/7] Applied fixes from StyleCI --- src/Encoding/ChunkStream.php | 2 +- src/Encoding/CompressStream.php | 2 +- src/Encoding/DechunkStream.php | 2 +- src/Encoding/DecompressStream.php | 2 +- src/Encoding/DeflateStream.php | 2 +- src/Encoding/Filter/Chunk.php | 2 +- src/Encoding/FilteredStream.php | 19 +++++++++---------- src/Encoding/GzipDecodeStream.php | 2 +- src/Encoding/GzipEncodeStream.php | 3 +-- src/Encoding/InflateStream.php | 2 +- 10 files changed, 18 insertions(+), 20 deletions(-) diff --git a/src/Encoding/ChunkStream.php b/src/Encoding/ChunkStream.php index f80b143..9d982b5 100644 --- a/src/Encoding/ChunkStream.php +++ b/src/Encoding/ChunkStream.php @@ -3,7 +3,7 @@ namespace Http\Message\Encoding; /** - * Transform a regular stream into a chunked one + * Transform a regular stream into a chunked one. * * @author Joel Wurtz */ diff --git a/src/Encoding/CompressStream.php b/src/Encoding/CompressStream.php index c18cd79..811b2ac 100644 --- a/src/Encoding/CompressStream.php +++ b/src/Encoding/CompressStream.php @@ -5,7 +5,7 @@ use Psr\Http\Message\StreamInterface; /** - * Stream compress (RFC 1950) + * Stream compress (RFC 1950). * * @author Joel Wurtz */ diff --git a/src/Encoding/DechunkStream.php b/src/Encoding/DechunkStream.php index 1f595a8..0c3c93d 100644 --- a/src/Encoding/DechunkStream.php +++ b/src/Encoding/DechunkStream.php @@ -3,7 +3,7 @@ namespace Http\Message\Encoding; /** - * Decorate a stream which is chunked + * Decorate a stream which is chunked. * * Allow to decode a chunked stream * diff --git a/src/Encoding/DecompressStream.php b/src/Encoding/DecompressStream.php index d0ac295..659a137 100644 --- a/src/Encoding/DecompressStream.php +++ b/src/Encoding/DecompressStream.php @@ -5,7 +5,7 @@ use Psr\Http\Message\StreamInterface; /** - * Stream decompress (RFC 1950) + * Stream decompress (RFC 1950). * * @author Joel Wurtz */ diff --git a/src/Encoding/DeflateStream.php b/src/Encoding/DeflateStream.php index d817a98..db4bca0 100644 --- a/src/Encoding/DeflateStream.php +++ b/src/Encoding/DeflateStream.php @@ -5,7 +5,7 @@ use Psr\Http\Message\StreamInterface; /** - * Stream deflate (RFC 1951) + * Stream deflate (RFC 1951). * * @author Joel Wurtz */ diff --git a/src/Encoding/Filter/Chunk.php b/src/Encoding/Filter/Chunk.php index b7dd20b..3d5a96d 100644 --- a/src/Encoding/Filter/Chunk.php +++ b/src/Encoding/Filter/Chunk.php @@ -7,7 +7,7 @@ class Chunk extends \php_user_filter public function filter($in, $out, &$consumed, $closing) { while ($bucket = stream_bucket_make_writeable($in)) { - $lenbucket = stream_bucket_new($this->stream, dechex($bucket->datalen)."\r\n"); + $lenbucket = stream_bucket_new($this->stream, dechex($bucket->datalen)."\r\n"); stream_bucket_append($out, $lenbucket); $consumed += $bucket->datalen; diff --git a/src/Encoding/FilteredStream.php b/src/Encoding/FilteredStream.php index 545ffb3..a544583 100644 --- a/src/Encoding/FilteredStream.php +++ b/src/Encoding/FilteredStream.php @@ -3,12 +3,11 @@ namespace Http\Message\Encoding; use Clue\StreamFilter as Filter; -use GuzzleHttp\Psr7\StreamDecoratorTrait; use Http\Message\Decorator\StreamDecorator; use Psr\Http\Message\StreamInterface; /** - * A filtered stream has a filter for filtering output and a filter for filtering input made to a underlying stream + * A filtered stream has a filter for filtering output and a filter for filtering input made to a underlying stream. * * @author Joel Wurtz */ @@ -39,7 +38,7 @@ abstract class FilteredStream implements StreamInterface protected $writeFilter; /** - * Internal buffer + * Internal buffer. * * @var string */ @@ -52,9 +51,9 @@ abstract class FilteredStream implements StreamInterface */ public function __construct(StreamInterface $stream, $readFilterOptions = null, $writeFilterOptions = null) { - $this->readFilterCallback = Filter\fun($this->getReadFilter(), $readFilterOptions); + $this->readFilterCallback = Filter\fun($this->getReadFilter(), $readFilterOptions); $this->writeFilterCallback = Filter\fun($this->getWriteFilter(), $writeFilterOptions); - $this->stream = $stream; + $this->stream = $stream; } /** @@ -80,7 +79,7 @@ public function read($length) $this->buffer = ''; $this->fill(); - return $read . $this->read($length - strlen($read)); + return $read.$this->read($length - strlen($read)); } /** @@ -88,11 +87,11 @@ public function read($length) */ public function eof() { - return ($this->stream->eof() && $this->buffer === ''); + return $this->stream->eof() && $this->buffer === ''; } /** - * Buffer is filled by reading underlying stream + * Buffer is filled by reading underlying stream. * * Callback is reading once more even if the stream is ended. * This allow to get last data in the PHP buffer otherwise this @@ -129,14 +128,14 @@ public function getContents() } /** - * Return the read filter name + * Return the read filter name. * * @return string */ abstract public function getReadFilter(); /** - * Return the write filter name + * Return the write filter name. * * @return mixed */ diff --git a/src/Encoding/GzipDecodeStream.php b/src/Encoding/GzipDecodeStream.php index 280ace4..5cca534 100644 --- a/src/Encoding/GzipDecodeStream.php +++ b/src/Encoding/GzipDecodeStream.php @@ -5,7 +5,7 @@ use Psr\Http\Message\StreamInterface; /** - * Stream for decoding from gzip format (RFC 1952) + * Stream for decoding from gzip format (RFC 1952). * * @author Joel Wurtz */ diff --git a/src/Encoding/GzipEncodeStream.php b/src/Encoding/GzipEncodeStream.php index ef4423b..1d08324 100644 --- a/src/Encoding/GzipEncodeStream.php +++ b/src/Encoding/GzipEncodeStream.php @@ -3,10 +3,9 @@ namespace Http\Message\Encoding; use Psr\Http\Message\StreamInterface; -use Clue\StreamFilter as Filter; /** - * Stream for encoding to gzip format (RFC 1952) + * Stream for encoding to gzip format (RFC 1952). * * @author Joel Wurtz */ diff --git a/src/Encoding/InflateStream.php b/src/Encoding/InflateStream.php index 766332b..f91855a 100644 --- a/src/Encoding/InflateStream.php +++ b/src/Encoding/InflateStream.php @@ -5,7 +5,7 @@ use Psr\Http\Message\StreamInterface; /** - * Stream inflate (RFC 1951) + * Stream inflate (RFC 1951). * * @author Joel Wurtz */ From 7f7dd42dca4cb8bdebbebd1f861482e9098b2161 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A1gi-Kaz=C3=A1r=20M=C3=A1rk?= Date: Mon, 21 Dec 2015 11:13:15 +0100 Subject: [PATCH 5/7] Skip specific tests on HHVM instead of allowing to fail, see #6 --- .travis.yml | 2 -- spec/Encoding/ChunkStreamSpec.php | 5 +++++ spec/Encoding/CompressStreamSpec.php | 5 +++++ spec/Encoding/DechunkStreamSpec.php | 5 +++++ spec/Encoding/DecompressStreamSpec.php | 5 +++++ spec/Encoding/GzipDecodeStreamSpec.php | 5 +++++ spec/Encoding/GzipEncodeStreamSpec.php | 5 +++++ 7 files changed, 30 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index 0bee5b1..7c466c1 100644 --- a/.travis.yml +++ b/.travis.yml @@ -22,8 +22,6 @@ branches: - /^analysis-.*$/ matrix: - allow_failures: - - php: hhvm fast_finish: true include: - php: 5.4 diff --git a/spec/Encoding/ChunkStreamSpec.php b/spec/Encoding/ChunkStreamSpec.php index c0bd5a9..743031a 100644 --- a/spec/Encoding/ChunkStreamSpec.php +++ b/spec/Encoding/ChunkStreamSpec.php @@ -3,12 +3,17 @@ namespace spec\Http\Message\Encoding; use Psr\Http\Message\StreamInterface; +use PhpSpec\Exception\Example\SkippingException; use PhpSpec\ObjectBehavior; class ChunkStreamSpec extends ObjectBehavior { function let(StreamInterface $stream) { + if (defined('HHVM_VERSION')) { + throw new SkippingException('Skipping test as there is no dechunk filter on hhvm'); + } + $this->beConstructedWith($stream); } diff --git a/spec/Encoding/CompressStreamSpec.php b/spec/Encoding/CompressStreamSpec.php index ffb6879..c304629 100644 --- a/spec/Encoding/CompressStreamSpec.php +++ b/spec/Encoding/CompressStreamSpec.php @@ -3,12 +3,17 @@ namespace spec\Http\Message\Encoding; use Psr\Http\Message\StreamInterface; +use PhpSpec\Exception\Example\SkippingException; use PhpSpec\ObjectBehavior; class CompressStreamSpec extends ObjectBehavior { function let(StreamInterface $stream) { + if (defined('HHVM_VERSION')) { + throw new SkippingException('Skipping test as zlib is not working on hhvm'); + } + $this->beConstructedWith($stream); } diff --git a/spec/Encoding/DechunkStreamSpec.php b/spec/Encoding/DechunkStreamSpec.php index 39c9fa5..6725dc4 100644 --- a/spec/Encoding/DechunkStreamSpec.php +++ b/spec/Encoding/DechunkStreamSpec.php @@ -3,12 +3,17 @@ namespace spec\Http\Message\Encoding; use Psr\Http\Message\StreamInterface; +use PhpSpec\Exception\Example\SkippingException; use PhpSpec\ObjectBehavior; class DechunkStreamSpec extends ObjectBehavior { function let(StreamInterface $stream) { + if (defined('HHVM_VERSION')) { + throw new SkippingException('Skipping test as there is no dechunk filter on hhvm'); + } + $this->beConstructedWith($stream); } diff --git a/spec/Encoding/DecompressStreamSpec.php b/spec/Encoding/DecompressStreamSpec.php index 141b0f9..d105c53 100644 --- a/spec/Encoding/DecompressStreamSpec.php +++ b/spec/Encoding/DecompressStreamSpec.php @@ -3,12 +3,17 @@ namespace spec\Http\Message\Encoding; use Psr\Http\Message\StreamInterface; +use PhpSpec\Exception\Example\SkippingException; use PhpSpec\ObjectBehavior; class DecompressStreamSpec extends ObjectBehavior { function let(StreamInterface $stream) { + if (defined('HHVM_VERSION')) { + throw new SkippingException('Skipping test as zlib is not working on hhvm'); + } + $this->beConstructedWith($stream); } diff --git a/spec/Encoding/GzipDecodeStreamSpec.php b/spec/Encoding/GzipDecodeStreamSpec.php index e9d1f29..6d4835a 100644 --- a/spec/Encoding/GzipDecodeStreamSpec.php +++ b/spec/Encoding/GzipDecodeStreamSpec.php @@ -3,12 +3,17 @@ namespace spec\Http\Message\Encoding; use Psr\Http\Message\StreamInterface; +use PhpSpec\Exception\Example\SkippingException; use PhpSpec\ObjectBehavior; class GzipDecodeStreamSpec extends ObjectBehavior { function let(StreamInterface $stream) { + if (defined('HHVM_VERSION')) { + throw new SkippingException('Skipping test as zlib is not working on hhvm'); + } + $this->beConstructedWith($stream); } diff --git a/spec/Encoding/GzipEncodeStreamSpec.php b/spec/Encoding/GzipEncodeStreamSpec.php index 459f8a4..997c324 100644 --- a/spec/Encoding/GzipEncodeStreamSpec.php +++ b/spec/Encoding/GzipEncodeStreamSpec.php @@ -3,12 +3,17 @@ namespace spec\Http\Message\Encoding; use Psr\Http\Message\StreamInterface; +use PhpSpec\Exception\Example\SkippingException; use PhpSpec\ObjectBehavior; class GzipEncodeStreamSpec extends ObjectBehavior { function let(StreamInterface $stream) { + if (defined('HHVM_VERSION')) { + throw new SkippingException('Skipping test as zlib is not working on hhvm'); + } + $this->beConstructedWith($stream); } From 6478a777bd3454edf30dbd764f5a231f8a956aff Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A1gi-Kaz=C3=A1r=20M=C3=A1rk?= Date: Mon, 21 Dec 2015 17:46:18 +0100 Subject: [PATCH 6/7] Fix dependency issues with stream filter, closes #8 --- composer.json | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/composer.json b/composer.json index ece5c65..497d90d 100644 --- a/composer.json +++ b/composer.json @@ -13,19 +13,20 @@ "require": { "php": ">=5.4", "psr/http-message": "^1.0", - "php-http/message-factory": "^1.0" + "php-http/message-factory": "^1.0", + "clue/stream-filter": "^1.3" }, "require-dev": { "zendframework/zend-diactoros": "^1.0", "guzzlehttp/psr7": "^1.0", "ext-zlib": "*", - "clue/stream-filter": "^1.3", "phpspec/phpspec": "^2.4", "henrikbjorn/phpspec-code-coverage" : "^1.0" }, "suggest": { "zendframework/zend-diactoros": "Used with Diactoros Factories", - "guzzlehttp/psr7": "Used with Guzzle PSR-7 Factories" + "guzzlehttp/psr7": "Used with Guzzle PSR-7 Factories", + "ext-zlib": "Used with compressor/decompressor streams" }, "autoload": { "psr-4": { From 827ed4eaa328122053ce699df4d6df42fe521e52 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A1gi-Kaz=C3=A1r=20M=C3=A1rk?= Date: Tue, 22 Dec 2015 13:01:01 +0100 Subject: [PATCH 7/7] Add exception to zlib dependant streams when zlib is not enabled Add stream behavior Improve zlib tests Remove php-mock dependency Add zlib extension check to InflateStream --- spec/Encoding/ChunkStreamSpec.php | 7 +--- spec/Encoding/CompressStreamSpec.php | 7 +--- spec/Encoding/DechunkStreamSpec.php | 7 +--- spec/Encoding/DecompressStreamSpec.php | 7 +--- spec/Encoding/DeflateStreamSpec.php | 7 +--- spec/Encoding/GzipDecodeStreamSpec.php | 7 +--- spec/Encoding/GzipEncodeStreamSpec.php | 7 +--- spec/Encoding/InflateStreamSpec.php | 7 +--- spec/Encoding/StreamBehavior.php | 11 ++++++ spec/Encoding/ZlibStreamBehavior.php | 51 ++++++++++++++++++++++++++ src/Encoding/CompressStream.php | 4 ++ src/Encoding/DecompressStream.php | 4 ++ src/Encoding/GzipDecodeStream.php | 4 ++ src/Encoding/GzipEncodeStream.php | 4 ++ src/Encoding/InflateStream.php | 4 ++ 15 files changed, 98 insertions(+), 40 deletions(-) create mode 100644 spec/Encoding/StreamBehavior.php create mode 100644 spec/Encoding/ZlibStreamBehavior.php diff --git a/spec/Encoding/ChunkStreamSpec.php b/spec/Encoding/ChunkStreamSpec.php index 743031a..eff8a41 100644 --- a/spec/Encoding/ChunkStreamSpec.php +++ b/spec/Encoding/ChunkStreamSpec.php @@ -8,6 +8,8 @@ class ChunkStreamSpec extends ObjectBehavior { + use StreamBehavior; + function let(StreamInterface $stream) { if (defined('HHVM_VERSION')) { @@ -22,11 +24,6 @@ function it_is_initializable() $this->shouldHaveType('Http\Message\Encoding\ChunkStream'); } - function it_is_a_stream() - { - $this->shouldImplement('Psr\Http\Message\StreamInterface'); - } - function it_chunks_content() { $stream = new MemoryStream('This is a stream'); diff --git a/spec/Encoding/CompressStreamSpec.php b/spec/Encoding/CompressStreamSpec.php index c304629..4e96849 100644 --- a/spec/Encoding/CompressStreamSpec.php +++ b/spec/Encoding/CompressStreamSpec.php @@ -8,6 +8,8 @@ class CompressStreamSpec extends ObjectBehavior { + use StreamBehavior, ZlibStreamBehavior; + function let(StreamInterface $stream) { if (defined('HHVM_VERSION')) { @@ -22,11 +24,6 @@ function it_is_initializable() $this->shouldHaveType('Http\Message\Encoding\CompressStream'); } - function it_is_a_stream() - { - $this->shouldImplement('Psr\Http\Message\StreamInterface'); - } - function it_reads() { $stream = new MemoryStream('This is a test stream'); diff --git a/spec/Encoding/DechunkStreamSpec.php b/spec/Encoding/DechunkStreamSpec.php index 6725dc4..715e33a 100644 --- a/spec/Encoding/DechunkStreamSpec.php +++ b/spec/Encoding/DechunkStreamSpec.php @@ -8,6 +8,8 @@ class DechunkStreamSpec extends ObjectBehavior { + use StreamBehavior; + function let(StreamInterface $stream) { if (defined('HHVM_VERSION')) { @@ -22,11 +24,6 @@ function it_is_initializable() $this->shouldHaveType('Http\Message\Encoding\DechunkStream'); } - function it_is_a_stream() - { - $this->shouldImplement('Psr\Http\Message\StreamInterface'); - } - function it_reads() { $stream = new MemoryStream("4\r\ntest\r\n4\r\ntest\r\n0\r\n\r\n\0"); diff --git a/spec/Encoding/DecompressStreamSpec.php b/spec/Encoding/DecompressStreamSpec.php index d105c53..d3295ff 100644 --- a/spec/Encoding/DecompressStreamSpec.php +++ b/spec/Encoding/DecompressStreamSpec.php @@ -8,6 +8,8 @@ class DecompressStreamSpec extends ObjectBehavior { + use StreamBehavior, ZlibStreamBehavior; + function let(StreamInterface $stream) { if (defined('HHVM_VERSION')) { @@ -22,11 +24,6 @@ function it_is_initializable() $this->shouldHaveType('Http\Message\Encoding\DecompressStream'); } - function it_is_a_stream() - { - $this->shouldImplement('Psr\Http\Message\StreamInterface'); - } - function it_reads() { // "This is a test stream" | deflate diff --git a/spec/Encoding/DeflateStreamSpec.php b/spec/Encoding/DeflateStreamSpec.php index 83b608e..89c9406 100644 --- a/spec/Encoding/DeflateStreamSpec.php +++ b/spec/Encoding/DeflateStreamSpec.php @@ -7,6 +7,8 @@ class DeflateStreamSpec extends ObjectBehavior { + use StreamBehavior; + function let(StreamInterface $stream) { $this->beConstructedWith($stream); @@ -17,11 +19,6 @@ function it_is_initializable() $this->shouldHaveType('Http\Message\Encoding\DeflateStream'); } - function it_is_a_stream() - { - $this->shouldImplement('Psr\Http\Message\StreamInterface'); - } - function it_reads() { $stream = new MemoryStream('This is a test stream'); diff --git a/spec/Encoding/GzipDecodeStreamSpec.php b/spec/Encoding/GzipDecodeStreamSpec.php index 6d4835a..5243238 100644 --- a/spec/Encoding/GzipDecodeStreamSpec.php +++ b/spec/Encoding/GzipDecodeStreamSpec.php @@ -8,6 +8,8 @@ class GzipDecodeStreamSpec extends ObjectBehavior { + use StreamBehavior, ZlibStreamBehavior; + function let(StreamInterface $stream) { if (defined('HHVM_VERSION')) { @@ -22,11 +24,6 @@ function it_is_initializable() $this->shouldHaveType('Http\Message\Encoding\GzipDecodeStream'); } - function it_is_a_stream() - { - $this->shouldImplement('Psr\Http\Message\StreamInterface'); - } - function it_reads() { // "This is a test stream" | deflate diff --git a/spec/Encoding/GzipEncodeStreamSpec.php b/spec/Encoding/GzipEncodeStreamSpec.php index 997c324..b11d4c5 100644 --- a/spec/Encoding/GzipEncodeStreamSpec.php +++ b/spec/Encoding/GzipEncodeStreamSpec.php @@ -8,6 +8,8 @@ class GzipEncodeStreamSpec extends ObjectBehavior { + use StreamBehavior, ZlibStreamBehavior; + function let(StreamInterface $stream) { if (defined('HHVM_VERSION')) { @@ -22,11 +24,6 @@ function it_is_initializable() $this->shouldHaveType('Http\Message\Encoding\GzipEncodeStream'); } - function it_is_a_stream() - { - $this->shouldImplement('Psr\Http\Message\StreamInterface'); - } - function it_reads() { $stream = new MemoryStream('This is a test stream'); diff --git a/spec/Encoding/InflateStreamSpec.php b/spec/Encoding/InflateStreamSpec.php index 52d08c9..6dcd846 100644 --- a/spec/Encoding/InflateStreamSpec.php +++ b/spec/Encoding/InflateStreamSpec.php @@ -7,6 +7,8 @@ class InflateStreamSpec extends ObjectBehavior { + use StreamBehavior, ZlibStreamBehavior; + function let(StreamInterface $stream) { $this->beConstructedWith($stream); @@ -17,11 +19,6 @@ function it_is_initializable() $this->shouldHaveType('Http\Message\Encoding\InflateStream'); } - function it_is_a_stream() - { - $this->shouldImplement('Psr\Http\Message\StreamInterface'); - } - function it_reads() { // "This is a test stream" | deflate diff --git a/spec/Encoding/StreamBehavior.php b/spec/Encoding/StreamBehavior.php new file mode 100644 index 0000000..54e1f5e --- /dev/null +++ b/spec/Encoding/StreamBehavior.php @@ -0,0 +1,11 @@ +shouldImplement('Psr\Http\Message\StreamInterface'); + } +} diff --git a/spec/Encoding/ZlibStreamBehavior.php b/spec/Encoding/ZlibStreamBehavior.php new file mode 100644 index 0000000..bcd8311 --- /dev/null +++ b/spec/Encoding/ZlibStreamBehavior.php @@ -0,0 +1,51 @@ +shouldThrow('RuntimeException')->duringInstantiation(); + + ZlibExtensionLoadedMock::$enabled = false; + } + } + + class ZlibExtensionLoadedMock + { + /** + * If enabled, the mocked method is used. + * + * @var bool + */ + public static $enabled = false; + + /** + * extension_loaded function mock. + */ + public static function extensionLoaded() + { + if (static::$enabled) { + return false; + } else { + return call_user_func_array('\extension_loaded', func_get_args()); + } + } + } +} + +namespace Http\Message\Encoding { + + use spec\Http\Message\Encoding\ZlibExtensionLoadedMock; + + function extension_loaded($extension) + { + return ZlibExtensionLoadedMock::extensionLoaded($extension); + } +} diff --git a/src/Encoding/CompressStream.php b/src/Encoding/CompressStream.php index 811b2ac..c59e2b9 100644 --- a/src/Encoding/CompressStream.php +++ b/src/Encoding/CompressStream.php @@ -13,6 +13,10 @@ class CompressStream extends FilteredStream { public function __construct(StreamInterface $stream, $level = -1) { + if (!extension_loaded('zlib')) { + throw new \RuntimeException('The zlib extension must be enabled to use this stream'); + } + parent::__construct($stream, ['window' => 15, 'level' => $level], ['window' => 15]); } diff --git a/src/Encoding/DecompressStream.php b/src/Encoding/DecompressStream.php index 659a137..136e33a 100644 --- a/src/Encoding/DecompressStream.php +++ b/src/Encoding/DecompressStream.php @@ -13,6 +13,10 @@ class DecompressStream extends FilteredStream { public function __construct(StreamInterface $stream, $level = -1) { + if (!extension_loaded('zlib')) { + throw new \RuntimeException('The zlib extension must be enabled to use this stream'); + } + parent::__construct($stream, ['window' => 15], ['window' => 15, 'level' => $level]); } diff --git a/src/Encoding/GzipDecodeStream.php b/src/Encoding/GzipDecodeStream.php index 5cca534..d43e9d9 100644 --- a/src/Encoding/GzipDecodeStream.php +++ b/src/Encoding/GzipDecodeStream.php @@ -13,6 +13,10 @@ class GzipDecodeStream extends FilteredStream { public function __construct(StreamInterface $stream, $level = -1) { + if (!extension_loaded('zlib')) { + throw new \RuntimeException('The zlib extension must be enabled to use this stream'); + } + parent::__construct($stream, ['window' => 31], ['window' => 31, 'level' => $level]); } diff --git a/src/Encoding/GzipEncodeStream.php b/src/Encoding/GzipEncodeStream.php index 1d08324..aa5e551 100644 --- a/src/Encoding/GzipEncodeStream.php +++ b/src/Encoding/GzipEncodeStream.php @@ -13,6 +13,10 @@ class GzipEncodeStream extends FilteredStream { public function __construct(StreamInterface $stream, $level = -1) { + if (!extension_loaded('zlib')) { + throw new \RuntimeException('The zlib extension must be enabled to use this stream'); + } + parent::__construct($stream, ['window' => 31, 'level' => $level], ['window' => 31]); } diff --git a/src/Encoding/InflateStream.php b/src/Encoding/InflateStream.php index f91855a..f8b5788 100644 --- a/src/Encoding/InflateStream.php +++ b/src/Encoding/InflateStream.php @@ -13,6 +13,10 @@ class InflateStream extends FilteredStream { public function __construct(StreamInterface $stream, $level = -1) { + if (!extension_loaded('zlib')) { + throw new \RuntimeException('The zlib extension must be enabled to use this stream'); + } + parent::__construct($stream, ['window' => -15], ['window' => -15, 'level' => $level]); }