From 93d2d01e878beca484985b0d7137f94eb70ccfcd Mon Sep 17 00:00:00 2001 From: Timm Friebe Date: Sun, 3 Aug 2025 11:20:46 +0200 Subject: [PATCH 1/2] Abstract algorithm options into an `Options` class --- .../io/streams/compress/Algorithm.class.php | 4 +- .../php/io/streams/compress/Brotli.class.php | 8 +- .../php/io/streams/compress/Bzip2.class.php | 8 +- .../php/io/streams/compress/Gzip.class.php | 8 +- .../php/io/streams/compress/None.class.php | 4 +- .../php/io/streams/compress/Options.class.php | 59 +++++++++++++ .../unittest/AlgorithmsTest.class.php | 12 +-- .../compress/unittest/OptionsTest.class.php | 87 +++++++++++++++++++ 8 files changed, 168 insertions(+), 22 deletions(-) create mode 100755 src/main/php/io/streams/compress/Options.class.php create mode 100755 src/test/php/io/streams/compress/unittest/OptionsTest.class.php diff --git a/src/main/php/io/streams/compress/Algorithm.class.php b/src/main/php/io/streams/compress/Algorithm.class.php index 8ceaff4..991d49f 100755 --- a/src/main/php/io/streams/compress/Algorithm.class.php +++ b/src/main/php/io/streams/compress/Algorithm.class.php @@ -21,7 +21,7 @@ public abstract function extension(): string; public abstract function level(int $select): int; /** Compresses data */ - public abstract function compress(string $data, int $level= Compression::DEFAULT): string; + public abstract function compress(string $data, $options= null): string; /** Decompresses bytes */ public abstract function decompress(string $bytes): string; @@ -30,7 +30,7 @@ public abstract function decompress(string $bytes): string; public abstract function open(InputStream $in): InputStream; /** Opens an output stream for writing */ - public abstract function create(OutputStream $out, int $level= Compression::DEFAULT): OutputStream; + public abstract function create(OutputStream $out, $options= null): OutputStream; /** @return string */ public function hashCode() { return crc32($this->name()); } diff --git a/src/main/php/io/streams/compress/Brotli.class.php b/src/main/php/io/streams/compress/Brotli.class.php index 67977e5..f53350b 100755 --- a/src/main/php/io/streams/compress/Brotli.class.php +++ b/src/main/php/io/streams/compress/Brotli.class.php @@ -23,8 +23,8 @@ public function level(int $select): int { } /** Compresses data */ - public function compress(string $data, int $level= Compression::DEFAULT): string { - return brotli_compress($data, $this->level($level)); + public function compress(string $data, $options= null): string { + return brotli_compress($data, $this->level(Options::from($options)->level)); } /** Decompresses bytes */ @@ -38,7 +38,7 @@ public function open(InputStream $in): InputStream { } /** Opens an output stream for writing */ - public function create(OutputStream $out, int $level= Compression::DEFAULT): OutputStream { - return new BrotliOutputStream($out, $this->level($level)); + public function create(OutputStream $out, $options= null): OutputStream { + return new BrotliOutputStream($out, $this->level(Options::from($options)->level)); } } \ No newline at end of file diff --git a/src/main/php/io/streams/compress/Bzip2.class.php b/src/main/php/io/streams/compress/Bzip2.class.php index 75644c6..90f825c 100755 --- a/src/main/php/io/streams/compress/Bzip2.class.php +++ b/src/main/php/io/streams/compress/Bzip2.class.php @@ -24,8 +24,8 @@ public function level(int $select): int { } /** Compresses data */ - public function compress(string $data, int $level= Compression::DEFAULT): string { - return bzcompress($data, $this->level($level)); + public function compress(string $data, $options= null): string { + return bzcompress($data, $this->level(Options::from($options)->level)); } /** Decompresses bytes */ @@ -43,7 +43,7 @@ public function open(InputStream $in): InputStream { } /** Opens an output stream for writing */ - public function create(OutputStream $out, int $level= Compression::DEFAULT): OutputStream { - return new Bzip2OutputStream($out, $this->level($level)); + public function create(OutputStream $out, $options= null): OutputStream { + return new Bzip2OutputStream($out, $this->level(Options::from($options)->level)); } } \ No newline at end of file diff --git a/src/main/php/io/streams/compress/Gzip.class.php b/src/main/php/io/streams/compress/Gzip.class.php index fef8119..4c1f047 100755 --- a/src/main/php/io/streams/compress/Gzip.class.php +++ b/src/main/php/io/streams/compress/Gzip.class.php @@ -24,8 +24,8 @@ public function level(int $select): int { } /** Compresses data */ - public function compress(string $data, int $level= Compression::DEFAULT): string { - return gzcompress($data, $this->level($level)); + public function compress(string $data, $options= null): string { + return gzcompress($data, $this->level(Options::from($options)->level)); } /** Decompresses bytes */ @@ -44,7 +44,7 @@ public function open(InputStream $in): InputStream { } /** Opens an output stream for writing */ - public function create(OutputStream $out, int $level= Compression::DEFAULT): OutputStream { - return new GzipOutputStream($out, $this->level($level)); + public function create(OutputStream $out, $options= null): OutputStream { + return new GzipOutputStream($out, $this->level(Options::from($options)->level)); } } \ No newline at end of file diff --git a/src/main/php/io/streams/compress/None.class.php b/src/main/php/io/streams/compress/None.class.php index 85f5adf..a996380 100755 --- a/src/main/php/io/streams/compress/None.class.php +++ b/src/main/php/io/streams/compress/None.class.php @@ -20,7 +20,7 @@ public function extension(): string { return ''; } public function level(int $select): int { return 0; } /** Compresses data */ - public function compress(string $data, int $level= Compression::DEFAULT): string { return $data; } + public function compress(string $data, $options= null): string { return $data; } /** Decompresses bytes */ public function decompress(string $bytes): string { return $bytes; } @@ -31,7 +31,7 @@ public function open(InputStream $in): InputStream { } /** Opens an output stream for writing */ - public function create(OutputStream $out, int $level= Compression::DEFAULT): OutputStream { + public function create(OutputStream $out, $options= null): OutputStream { return $out; } } \ No newline at end of file diff --git a/src/main/php/io/streams/compress/Options.class.php b/src/main/php/io/streams/compress/Options.class.php new file mode 100755 index 0000000..8507afb --- /dev/null +++ b/src/main/php/io/streams/compress/Options.class.php @@ -0,0 +1,59 @@ +level= $level ?? Compression::DEFAULT; + $this->length= $length; + } + + /** @param ?int|[:var]|self $arg */ + public static function from($arg): self { + if (null === $arg) { + return new self(); + } else if ($arg instanceof self) { + return $arg; + } else if (is_array($arg)) { + return new self( + $arg['level'] ?? null, + $arg['length'] ?? null + ); + } else { + return new self($arg); + } + } + + /** @return string */ + public function toString() { + switch ($this->level) { + case Compression::FASTEST: $level= 'FASTEST'; break; + case Compression::DEFAULT: $level= 'DEFAULT'; break; + case Compression::STRONGEST: $level= 'STRONGEST'; break; + default: $level= $this->level; + } + + return sprintf( + '%s(level: %s, length: %s)', + nameof($this), + $level, + null === $this->length ? 'null' : $this->length + ); + } +} \ No newline at end of file diff --git a/src/test/php/io/streams/compress/unittest/AlgorithmsTest.class.php b/src/test/php/io/streams/compress/unittest/AlgorithmsTest.class.php index ebb0680..7580def 100755 --- a/src/test/php/io/streams/compress/unittest/AlgorithmsTest.class.php +++ b/src/test/php/io/streams/compress/unittest/AlgorithmsTest.class.php @@ -15,10 +15,10 @@ public function name(): string { return 'test'; } public function token(): string { return 'x-test'; } public function extension(): string { return '.test'; } public function level(int $select): int { return $select; } - public function compress(string $data, int $level= Compression::DEFAULT): string { return $data; } + public function compress(string $data, $options= null): string { return $data; } public function decompress(string $bytes): string { return $bytes; } public function open(InputStream $in): InputStream { return $in; } - public function create(OutputStream $out, int $method= Compression::DEFAULT): OutputStream { return $out; } + public function create(OutputStream $out, $options= null): OutputStream { return $out; } }; $this->additional= new class() extends Algorithm { public function supported(): bool { return true; } @@ -26,10 +26,10 @@ public function name(): string { return 'add'; } public function token(): string { return 'x-add'; } public function extension(): string { return '.add'; } public function level(int $select): int { return $select; } - public function compress(string $data, int $level= Compression::DEFAULT): string { return $data; } + public function compress(string $data, $options= null): string { return $data; } public function decompress(string $bytes): string { return $bytes; } public function open(InputStream $in): InputStream { return $in; } - public function create(OutputStream $out, int $method= Compression::DEFAULT): OutputStream { return $out; } + public function create(OutputStream $out, $options= null): OutputStream { return $out; } }; $this->unsupported= new class() extends Algorithm { public function supported(): bool { return false; } @@ -37,10 +37,10 @@ public function name(): string { return 'lzw'; } public function token(): string { return 'compress'; } public function extension(): string { return '.lz'; } public function level(int $select): int { return $select; } - public function compress(string $data, int $level= Compression::DEFAULT): string { return $data; } + public function compress(string $data, $options= null): string { return $data; } public function decompress(string $bytes): string { return $bytes; } public function open(InputStream $in): InputStream { return $in; } - public function create(OutputStream $out, int $method= Compression::DEFAULT): OutputStream { return $out; } + public function create(OutputStream $out, $options= null): OutputStream { return $out; } }; } diff --git a/src/test/php/io/streams/compress/unittest/OptionsTest.class.php b/src/test/php/io/streams/compress/unittest/OptionsTest.class.php new file mode 100755 index 0000000..96a7e47 --- /dev/null +++ b/src/test/php/io/streams/compress/unittest/OptionsTest.class.php @@ -0,0 +1,87 @@ + -1], Compression::DEFAULT, null]; + yield [['level' => Compression::FASTEST], Compression::FASTEST, null]; + yield [['level' => Compression::FASTEST, 'length' => self::LENGTH], Compression::FASTEST, self::LENGTH]; + } + + #[Test] + public function can_create() { + new Options(); + } + + #[Test] + public function default_level() { + Assert::equals(Compression::DEFAULT, (new Options())->level); + } + + #[Test] + public function default_length() { + Assert::null((new Options())->length); + } + + #[Test, Values([Compression::FASTEST, Compression::DEFAULT, Compression::STRONGEST])] + public function level($level) { + Assert::equals($level, (new Options($level))->level); + } + + #[Test] + public function length() { + Assert::equals(self::LENGTH, (new Options(null, self::LENGTH))->length); + } + + #[Test] + public function from_level() { + Assert::equals(new Options(Compression::FASTEST, null), Options::from(Compression::FASTEST)); + } + + #[Test] + public function from_null() { + Assert::equals(new Options(Compression::DEFAULT, null), Options::from(null)); + } + + #[Test] + public function from_options() { + $options= new Options(Compression::FASTEST, self::LENGTH); + Assert::equals($options, Options::from($options)); + } + + #[Test, Values(from: 'maps')] + public function from($map, $level, $length) { + Assert::equals(new Options($level, $length), Options::from($map)); + } + + #[Test] + public function string_representation() { + Assert::equals( + 'io.streams.compress.Options(level: DEFAULT, length: null)', + (new Options())->toString() + ); + } + + #[Test] + public function string_representation_with_length() { + Assert::equals( + 'io.streams.compress.Options(level: DEFAULT, length: 6100)', + (new Options(null, self::LENGTH))->toString() + ); + } + + #[Test] + public function string_representation_with_level() { + Assert::equals( + 'io.streams.compress.Options(level: 22, length: null)', + (new Options(22))->toString() + ); + } +} \ No newline at end of file From 67629f08a6418be780ce99a6b65a4e220f533f7a Mon Sep 17 00:00:00 2001 From: Timm Friebe Date: Sun, 3 Aug 2025 11:45:27 +0200 Subject: [PATCH 2/2] Bump major version, explain BC break for implementers --- ChangeLog.md | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/ChangeLog.md b/ChangeLog.md index eaf8bdc..bd36fd4 100755 --- a/ChangeLog.md +++ b/ChangeLog.md @@ -3,6 +3,16 @@ Compression streams ChangeLog ## ?.?.? / ????-??-?? +## 2.0.0 / ????-??-?? + +* **Heads up:** Algorithm implementations must change their `compress` + and `create` signatures from `$level= -1` to `$options= null`, and + can use `Options::from($options)->level` to access the given level + in a backwards-compatible fashion. + (@thekid) +* Merged PR #12: Abstract algorithm options into an `Options` class + (@thekid) + ## 1.4.0 / 2025-07-31 * Merged PR #9: Add `Algorithm::compress()` and `Algorithm::decompress()`