diff --git a/src/Compressor.php b/src/Compressor.php index 7ced4d7..faa3150 100644 --- a/src/Compressor.php +++ b/src/Compressor.php @@ -34,11 +34,15 @@ final class Compressor extends TransformStream /** @var ?resource */ private $context; + /** @var int */ + private $flush; + /** * @param int $encoding ZLIB_ENCODING_GZIP, ZLIB_ENCODING_RAW or ZLIB_ENCODING_DEFLATE * @param int $level optional compression level + * @param int $flush optional flush mode (ZLIB_NO_FLUSH, ZLIB_SYNC_FLUSH, ZLIB_FULL_FLUSH, ZLIB_FINISH) */ - public function __construct($encoding, $level = -1) + public function __construct($encoding, $level = -1, int $flush = ZLIB_NO_FLUSH) { $errstr = ''; set_error_handler(function ($_, $error) use (&$errstr) { @@ -61,12 +65,17 @@ public function __construct($encoding, $level = -1) throw new \InvalidArgumentException('Unable to initialize compressor' . $errstr); // @codeCoverageIgnore } + if (!in_array($flush, [ZLIB_NO_FLUSH, ZLIB_SYNC_FLUSH, ZLIB_FULL_FLUSH, ZLIB_FINISH], true)) { + throw new \InvalidArgumentException('Argument #3 ($flush) must be one of ZLIB_NO_FLUSH, ZLIB_SYNC_FLUSH, ZLIB_FULL_FLUSH or ZLIB_FINISH'); + } + $this->context = $context; + $this->flush = $flush; } protected function transformData($chunk) { - $ret = deflate_add($this->context, $chunk, ZLIB_NO_FLUSH); + $ret = deflate_add($this->context, $chunk, $this->flush); if ($ret !== '') { $this->emit('data', [$ret]); diff --git a/tests/CompressorTest.php b/tests/CompressorTest.php index 5eaf037..000942c 100644 --- a/tests/CompressorTest.php +++ b/tests/CompressorTest.php @@ -30,4 +30,10 @@ public function testCtorThrowsForInvalidEncodingAndUnsetsUsedErrorHandler() $this->assertEquals($handler, $checkHandler); } + + public function testCtorThrowsForInvalidFlushMode() + { + $this->expectException(\InvalidArgumentException::class); + new Compressor(ZLIB_ENCODING_GZIP, -1, -1); + } } diff --git a/tests/GzipCompressorTest.php b/tests/GzipCompressorTest.php index 9a6c78c..43646e4 100644 --- a/tests/GzipCompressorTest.php +++ b/tests/GzipCompressorTest.php @@ -8,22 +8,21 @@ class GzipCompressorTest extends TestCase { private $compressor; + /** @var string */ + private $os; + /** * @before */ public function setUpCompressor() { $this->compressor = new Compressor(ZLIB_ENCODING_GZIP); + $this->os = DIRECTORY_SEPARATOR !== '\\' ? "\x03" : (PHP_VERSION_ID >= 70200 ? "\x0a" : "\x0b"); // UNIX (0x03) or incorrect TOPS-20(0x0a) or NTFS(0x0b) } public function testCompressEmpty() { - if (DIRECTORY_SEPARATOR === '\\') { - $this->markTestSkipped('Not supported on Windows'); - } - - $os = DIRECTORY_SEPARATOR === '\\' ? "\x0a" : "\x03"; // NTFS(0x0a) or UNIX (0x03) - $this->compressor->on('data', $this->expectCallableOnceWith("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00" . $os . "\x03\x00" . "\x00\x00\x00\x00\x00\x00\x00\x00")); + $this->compressor->on('data', $this->expectCallableOnceWith("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00" . $this->os . "\x03\x00" . "\x00\x00\x00\x00\x00\x00\x00\x00")); $this->compressor->on('end', $this->expectCallableOnce()); $this->compressor->end(); @@ -58,4 +57,41 @@ public function testCompressBig() // PHP < 5.4 does not support gzdecode(), so let's assert this the other way around… $this->assertEquals(gzencode($data), $buffered); } + + public function testWriteWillOnlyFlushHeaderByDefaultToBufferDataBeforeFlushing() + { + $compressor = new Compressor(ZLIB_ENCODING_GZIP); + + $compressor->on('data', $this->expectCallableOnceWith("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00" . $this->os)); + + $compressor->write('hello'); + } + + public function testWriteWithSyncFlushWillFlushHeaderWithFirstChunkImmediately() + { + $compressor = new Compressor(ZLIB_ENCODING_GZIP, -1, ZLIB_SYNC_FLUSH); + + $compressor->on('data', $this->expectCallableOnceWith("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00" . $this->os . "\xca\x48\xcd\xc9\xc9\x07\x00\x00\x00\xff\xff")); + + $compressor->write('hello'); + } + + public function testWriteWithFinishFlushWillFlushEntireGzipHeaderAndFooterWithFirstChunkImmediately() + { + $compressor = new Compressor(ZLIB_ENCODING_GZIP, -1, ZLIB_FINISH); + + $compressor->on('data', $this->expectCallableOnceWith("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00" . $this->os . "\xcb\x48\xcd\xc9\xc9\x07\x00\x86\xa6\x10\x36" . "\x05\x00\x00\x00")); + + $compressor->write('hello'); + } + + public function testWriteAfterFinishFlushWillFlushEntireGzipWithSyncFlushWillFlushEntireGzipHeaderAndFooterAgainImmediately() + { + $compressor = new Compressor(ZLIB_ENCODING_GZIP, -1, ZLIB_FINISH); + $compressor->write('hello'); + + $compressor->on('data', $this->expectCallableOnceWith("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00" . $this->os . "\xcb\x48\xcd\xc9\xc9\x07\x00\x86\xa6\x10\x36" . "\x05\x00\x00\x00")); + + $compressor->write('hello'); + } }