diff --git a/composer.json b/composer.json index 742f6c2..497d90d 100644 --- a/composer.json +++ b/composer.json @@ -13,17 +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": "*", "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": { 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/spec/Encoding/ChunkStreamSpec.php b/spec/Encoding/ChunkStreamSpec.php new file mode 100644 index 0000000..eff8a41 --- /dev/null +++ b/spec/Encoding/ChunkStreamSpec.php @@ -0,0 +1,42 @@ +beConstructedWith($stream); + } + + function it_is_initializable() + { + $this->shouldHaveType('Http\Message\Encoding\ChunkStream'); + } + + 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..4e96849 --- /dev/null +++ b/spec/Encoding/CompressStreamSpec.php @@ -0,0 +1,44 @@ +beConstructedWith($stream); + } + + function it_is_initializable() + { + $this->shouldHaveType('Http\Message\Encoding\CompressStream'); + } + + 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..715e33a --- /dev/null +++ b/spec/Encoding/DechunkStreamSpec.php @@ -0,0 +1,43 @@ +beConstructedWith($stream); + } + + function it_is_initializable() + { + $this->shouldHaveType('Http\Message\Encoding\DechunkStream'); + } + + 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..d3295ff --- /dev/null +++ b/spec/Encoding/DecompressStreamSpec.php @@ -0,0 +1,44 @@ +beConstructedWith($stream); + } + + function it_is_initializable() + { + $this->shouldHaveType('Http\Message\Encoding\DecompressStream'); + } + + 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..89c9406 --- /dev/null +++ b/spec/Encoding/DeflateStreamSpec.php @@ -0,0 +1,39 @@ +beConstructedWith($stream); + } + + function it_is_initializable() + { + $this->shouldHaveType('Http\Message\Encoding\DeflateStream'); + } + + 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..5243238 --- /dev/null +++ b/spec/Encoding/GzipDecodeStreamSpec.php @@ -0,0 +1,44 @@ +beConstructedWith($stream); + } + + function it_is_initializable() + { + $this->shouldHaveType('Http\Message\Encoding\GzipDecodeStream'); + } + + 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..b11d4c5 --- /dev/null +++ b/spec/Encoding/GzipEncodeStreamSpec.php @@ -0,0 +1,44 @@ +beConstructedWith($stream); + } + + function it_is_initializable() + { + $this->shouldHaveType('Http\Message\Encoding\GzipEncodeStream'); + } + + 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..6dcd846 --- /dev/null +++ b/spec/Encoding/InflateStreamSpec.php @@ -0,0 +1,39 @@ +beConstructedWith($stream); + } + + function it_is_initializable() + { + $this->shouldHaveType('Http\Message\Encoding\InflateStream'); + } + + 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/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/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); + } +} diff --git a/src/Encoding/ChunkStream.php b/src/Encoding/ChunkStream.php new file mode 100644 index 0000000..9d982b5 --- /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..c59e2b9 --- /dev/null +++ b/src/Encoding/CompressStream.php @@ -0,0 +1,32 @@ + + */ +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]); + } + + 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..0c3c93d --- /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..136e33a --- /dev/null +++ b/src/Encoding/DecompressStream.php @@ -0,0 +1,32 @@ + + */ +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]); + } + + 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..db4bca0 --- /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..3d5a96d --- /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..a544583 --- /dev/null +++ b/src/Encoding/FilteredStream.php @@ -0,0 +1,143 @@ + + */ +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..d43e9d9 --- /dev/null +++ b/src/Encoding/GzipDecodeStream.php @@ -0,0 +1,32 @@ + + */ +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]); + } + + 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..aa5e551 --- /dev/null +++ b/src/Encoding/GzipEncodeStream.php @@ -0,0 +1,38 @@ + + */ +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]); + } + + /** + * {@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..f8b5788 --- /dev/null +++ b/src/Encoding/InflateStream.php @@ -0,0 +1,32 @@ + + */ +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]); + } + + public function getReadFilter() + { + return 'zlib.inflate'; + } + + public function getWriteFilter() + { + return 'zlib.deflate'; + } +}