diff --git a/README.md b/README.md index 4257dc0..0d559f5 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ -# Library for work with binaries +# Library for work with binary data and objects -Library for basic work with binary data in PHP. -See the examples below for more information, or check out [`CoderInterface`](./src/CoderInterface.php) and [`SerializerInterface`](./src/SerializerInterface.php). +Simple library for work with binary data and objects in PHP. +See the examples below for more information, or check out [`Encoder`](./src/Encoder.php), [`Decoder`](./src/Decoder.php) and [`Serializer`](./src/Serializer.php). ```php use PetrKnap\Binary\Binary; diff --git a/composer.json b/composer.json index 18d3087..a7a3d11 100755 --- a/composer.json +++ b/composer.json @@ -14,7 +14,7 @@ "allow-plugins": false, "sort-packages": true }, - "description": "Library for work with binaries", + "description": "Library for work with binary data and objects", "funding": [ { "type": "other", @@ -23,21 +23,23 @@ ], "homepage": "https://github.com/petrknap/php-binary", "keywords": [ - "binary", - "helper", "base64", - "encoder", - "decoder", + "binary", "checksum", - "zlib", "compression", - "decompression" + "decoder", + "decompression", + "encoder", + "helper", + "igbinary", + "serializer", + "zlib" ], "license": "LGPL-3.0-or-later", "name": "petrknap/binary", "require": { - "petrknap/shorts": "^1.4", - "php": ">=8.1" + "php": ">=8.1", + "petrknap/shorts": "^1.5" }, "require-dev": { "ext-mbstring": "*", diff --git a/src/Binary.php b/src/Binary.php index 6b16bfa..4002fcd 100644 --- a/src/Binary.php +++ b/src/Binary.php @@ -18,20 +18,11 @@ public static function decode(string $data): Decoder public static function serialize(mixed $data): string { - return self::getSerializer()->serialize(serializable: $data); + return (new Serializer())->serialize(serializable: $data); } public static function unserialize(string $data): mixed { - return self::getSerializer()->unserialize(serialized: $data); - } - - private static function getSerializer(): Serializer - { - static $serializer; - return $serializer ??= new Serializer( - new Encoder(), - new Decoder(), - ); + return (new Serializer())->unserialize(serialized: $data); } } diff --git a/src/Coder.php b/src/Coder.php index 362e3c0..ea14e4e 100644 --- a/src/Coder.php +++ b/src/Coder.php @@ -4,18 +4,17 @@ namespace PetrKnap\Binary; +use PetrKnap\Shorts\Exception; + /** + * @internal please use subclass + * * @phpstan-consistent-constructor override {@see self::create()} if not * - * @implements CoderInterface + * @implements CoderInterface */ abstract class Coder implements CoderInterface { - protected const BASE64_URL_SAFE_MAP = [ - ['+', '/', '='], - ['-', '_', ''], - ]; - public function __construct( protected readonly string $data = '', ) { diff --git a/src/Coder/Base64.php b/src/Coder/Base64.php new file mode 100644 index 0000000..a9462af --- /dev/null +++ b/src/Coder/Base64.php @@ -0,0 +1,47 @@ +urlSafe = $urlSafe ?? self::URL_SAFE; + return parent::encode($decoded); + } + + protected function doEncode(string $decoded): string + { + $encoded = base64_encode($decoded); + if ($this->urlSafe) { + $encoded = str_replace(self::URL_REPLACE_TABLE[0], self::URL_REPLACE_TABLE[1], $encoded); + } + return $encoded; + } + + protected function doDecode(string $encoded): string + { + $decoded = base64_decode( + str_replace(self::URL_REPLACE_TABLE[1], self::URL_REPLACE_TABLE[0], $encoded), + strict: true, + ); + if ($decoded === false) { + throw new Exception\CouldNotDecodeData(__METHOD__, $encoded); + } + return $decoded; + } +} diff --git a/src/Coder/Checksum.php b/src/Coder/Checksum.php new file mode 100644 index 0000000..485e87f --- /dev/null +++ b/src/Coder/Checksum.php @@ -0,0 +1,60 @@ +algorithm = $algorithm ?? self::ALGORITHM; + return parent::encode($decoded); + } + + public function decode(string $encoded, ?string $algorithm = null): string + { + $this->algorithm = $algorithm ?? self::ALGORITHM; + return parent::decode($encoded); + } + + protected function doEncode(string $decoded): string + { + $checksum = hash($this->algorithm, $decoded, binary: true); + return $decoded . $checksum; + } + + protected function doDecode(string $encoded): string + { + $checksumLength = mb_strlen(hash($this->algorithm, '', binary: true), encoding: '8bit'); + $dataLength = mb_strlen($encoded, encoding: '8bit') - $checksumLength; + $decoded = mb_strcut($encoded, 0, $dataLength, encoding: '8bit'); + if ($this->doEncode($decoded) !== $encoded) { + throw new Exception\CouldNotDecodeData(__METHOD__, $encoded); + } + return $decoded; + } +} diff --git a/src/Coder/Coder.php b/src/Coder/Coder.php new file mode 100644 index 0000000..865b5a1 --- /dev/null +++ b/src/Coder/Coder.php @@ -0,0 +1,44 @@ +doEncode($decoded); + } catch (Throwable $reason) { + if ($reason instanceof Exception\CouldNotEncodeData) { + throw $reason; + } + throw new Exception\CouldNotEncodeData(__METHOD__, $decoded, $reason); + } + } + + public function decode(string $encoded): string + { + try { + return $this->doDecode($encoded); + } catch (Throwable $reason) { + if ($reason instanceof Exception\CouldNotDecodeData) { + throw $reason; + } + throw new Exception\CouldNotDecodeData(__METHOD__, $encoded, $reason); + } + } + + /** + * @throws Throwable + */ + abstract protected function doEncode(string $decoded): string; + + /** + * @throws Throwable + */ + abstract protected function doDecode(string $encoded): string; +} diff --git a/src/Coder/CoderInterface.php b/src/Coder/CoderInterface.php new file mode 100644 index 0000000..d1c6d87 --- /dev/null +++ b/src/Coder/CoderInterface.php @@ -0,0 +1,18 @@ + + */ +final class CouldNotDecodeData extends CouldNotProcessData implements CoderException +{ +} diff --git a/src/Coder/Exception/CouldNotEncodeData.php b/src/Coder/Exception/CouldNotEncodeData.php new file mode 100644 index 0000000..e5a362b --- /dev/null +++ b/src/Coder/Exception/CouldNotEncodeData.php @@ -0,0 +1,14 @@ + + */ +final class CouldNotEncodeData extends CouldNotProcessData implements CoderException +{ +} diff --git a/src/Coder/Zlib.php b/src/Coder/Zlib.php new file mode 100644 index 0000000..e712be6 --- /dev/null +++ b/src/Coder/Zlib.php @@ -0,0 +1,60 @@ +encoding = $encoding ?? self::ENCODING; + $this->level = $level ?? self::LEVEL; + return parent::encode($decoded); + } + + public function decode(string $encoded, ?int $maxLength = null): string + { + $this->maxLength = $maxLength ?? self::MAX_LENGTH; + return parent::decode($encoded); + } + + protected function doEncode(string $decoded): string + { + $encoded = zlib_encode($decoded, $this->encoding, $this->level); + if ($encoded === false) { + throw new Exception\CouldNotEncodeData(__METHOD__, $decoded); + } + return $encoded; + } + + protected function doDecode(string $encoded): string + { + $decoded = zlib_decode($encoded, $this->maxLength); + if ($decoded === false) { + throw new Exception\CouldNotDecodeData(__METHOD__, $encoded); + } + return $decoded; + } +} diff --git a/src/CoderInterface.php b/src/CoderInterface.php index 80301e5..48379a1 100644 --- a/src/CoderInterface.php +++ b/src/CoderInterface.php @@ -4,41 +4,37 @@ namespace PetrKnap\Binary; +use PetrKnap\Shorts\Exception; + /** - * @template TExceptionCouldNotCodeData of Exception\CouldNotCodeData + * @internal please use subinterface + * + * @template TExceptionCouldNotProcessData of Exception\CouldNotProcessData */ interface CoderInterface { - public const CHECKSUM_ALGORITHM = 'crc32'; - public function withData(string $data): static; public function getData(): string; /** - * {@see base64_encode()}/{@see base64_decode()} the data - * - * @link https://en.wikipedia.org/wiki/Base64 + * @see Coder\Base64 * - * @throws TExceptionCouldNotCodeData + * @throws TExceptionCouldNotProcessData */ public function base64(): static; /** - * Encodes/decodes the data {@see hash()} into the data + * @see Coder\Checksum * - * @link https://en.wikipedia.org/wiki/Checksum - * - * @throws TExceptionCouldNotCodeData + * @throws TExceptionCouldNotProcessData */ - public function checksum(string $algorithm = self::CHECKSUM_ALGORITHM): static; + public function checksum(?string $algorithm = null): static; /** - * {@see zlib_encode()}/{@see zlib_decode()} the data - * - * @link https://en.wikipedia.org/wiki/Zlib + * @see Coder\zlib * - * @throws TExceptionCouldNotCodeData + * @throws TExceptionCouldNotProcessData */ public function zlib(): static; } diff --git a/src/Decoder.php b/src/Decoder.php index 307c6b5..fa03409 100644 --- a/src/Decoder.php +++ b/src/Decoder.php @@ -4,51 +4,28 @@ namespace PetrKnap\Binary; -use Throwable; - class Decoder extends Coder implements DecoderInterface { public function base64(): static { - try { - $decoded = base64_decode( - str_replace(self::BASE64_URL_SAFE_MAP[1], self::BASE64_URL_SAFE_MAP[0], $this->data), - strict: true, - ); - if ($decoded === false) { - throw new Exception\CouldNotDecodeData($this, __METHOD__); - } - return static::create($this, $decoded); - } catch (Throwable $reason) { - throw new Exception\CouldNotDecodeData($this, __METHOD__, $reason); - } + return static::create($this, (new Coder\Base64())->decode( + $this->data, + )); } - public function checksum(string $algorithm = self::CHECKSUM_ALGORITHM): static + public function checksum(?string $algorithm = null): static { - try { - $checksumLength = mb_strlen(hash($algorithm, '', binary: true), encoding: '8bit'); - $dataLength = mb_strlen($this->data, encoding: '8bit') - $checksumLength; - $data = mb_strcut($this->data, 0, $dataLength, encoding: '8bit'); - if ((new Encoder($data))->checksum($algorithm)->getData() !== $this->data) { - throw new Exception\CouldNotDecodeData($this, __METHOD__); - } - return static::create($this, $data); - } catch (Throwable $reason) { - throw new Exception\CouldNotDecodeData($this, __METHOD__, $reason); - } + return static::create($this, (new Coder\Checksum())->decode( + $this->data, + algorithm: $algorithm, + )); } - public function zlib(int $maxLength = self::ZLIB_MAX_LENGTH): static + public function zlib(?int $maxLength = null): static { - try { - $decoded = zlib_decode($this->data, $maxLength); - if ($decoded === false) { - throw new Exception\CouldNotDecodeData($this, __METHOD__); - } - return static::create($this, $decoded); - } catch (Throwable $reason) { - throw new Exception\CouldNotDecodeData($this, __METHOD__, $reason); - } + return static::create($this, (new Coder\Zlib())->decode( + $this->data, + maxLength: $maxLength, + )); } } diff --git a/src/DecoderInterface.php b/src/DecoderInterface.php index aa0201b..76fb8b8 100644 --- a/src/DecoderInterface.php +++ b/src/DecoderInterface.php @@ -5,11 +5,9 @@ namespace PetrKnap\Binary; /** - * @extends CoderInterface + * @extends CoderInterface */ interface DecoderInterface extends CoderInterface { - public const ZLIB_MAX_LENGTH = 0; - - public function zlib(int $maxLength = self::ZLIB_MAX_LENGTH): static; + public function zlib(?int $maxLength = null): static; } diff --git a/src/Encoder.php b/src/Encoder.php index cd907f5..914ff51 100644 --- a/src/Encoder.php +++ b/src/Encoder.php @@ -4,43 +4,30 @@ namespace PetrKnap\Binary; -use Throwable; - class Encoder extends Coder implements EncoderInterface { - public function base64(bool $urlSafe = self::BASE64_URL_SAFE): static + public function base64(?bool $urlSafe = null): static { - try { - $encoded = base64_encode($this->data); - if ($urlSafe) { - $encoded = str_replace(self::BASE64_URL_SAFE_MAP[0], self::BASE64_URL_SAFE_MAP[1], $encoded); - } - return static::create($this, $encoded); - } catch (Throwable $reason) { - throw new Exception\CouldNotEncodeData($this, __METHOD__, $reason); - } + return static::create($this, (new Coder\Base64())->encode( + $this->data, + urlSafe: $urlSafe, + )); } - public function checksum(string $algorithm = self::CHECKSUM_ALGORITHM): static + public function checksum(?string $algorithm = null): static { - try { - $checksum = hash($algorithm, $this->data, binary: true); - return static::create($this, $this->data . $checksum); - } catch (Throwable $reason) { - throw new Exception\CouldNotEncodeData($this, __METHOD__, $reason); - } + return static::create($this, (new Coder\Checksum())->encode( + $this->data, + algorithm: $algorithm, + )); } - public function zlib(int $encoding = self::ZLIB_ENCODING, int $level = self::ZLIB_LEVEL): static + public function zlib(?int $encoding = null, ?int $level = null): static { - try { - $encoded = zlib_encode($this->data, $encoding, $level); - if ($encoded === false) { - throw new Exception\CouldNotEncodeData($this, __METHOD__); - } - return static::create($this, $encoded); - } catch (Throwable $reason) { - throw new Exception\CouldNotEncodeData($this, __METHOD__, $reason); - } + return static::create($this, (new Coder\Zlib())->encode( + $this->data, + encoding: $encoding, + level: $level, + )); } } diff --git a/src/EncoderInterface.php b/src/EncoderInterface.php index f274793..2f85f8e 100644 --- a/src/EncoderInterface.php +++ b/src/EncoderInterface.php @@ -5,15 +5,11 @@ namespace PetrKnap\Binary; /** - * @extends CoderInterface + * @extends CoderInterface */ interface EncoderInterface extends CoderInterface { - public const BASE64_URL_SAFE = false; - public const ZLIB_ENCODING = ZLIB_ENCODING_RAW; - public const ZLIB_LEVEL = -1; + public function base64(?bool $urlSafe = null): static; - public function base64(bool $urlSafe = self::BASE64_URL_SAFE): static; - - public function zlib(int $encoding = self::ZLIB_ENCODING, int $level = self::ZLIB_LEVEL): static; + public function zlib(?int $encoding = null, ?int $level = null): static; } diff --git a/src/Exception/CoderException.php b/src/Exception/CoderException.php deleted file mode 100644 index 0f944d3..0000000 --- a/src/Exception/CoderException.php +++ /dev/null @@ -1,20 +0,0 @@ - - */ -abstract class CouldNotCodeData extends RuntimeException implements CoderException -{ - /** - * @param TCoder $coder - */ - public function __construct( - private readonly CoderInterface $coder, - string $method, - ?Throwable $reason = null, - ) { - parent::__construct( - sprintf( - '%s could not code string(%d)', - $method, - strlen($this->coder->getData()), - ), - previous: $reason - ); - } - - public function getCoder(): CoderInterface - { - return $this->coder; - } -} diff --git a/src/Exception/CouldNotDecodeData.php b/src/Exception/CouldNotDecodeData.php deleted file mode 100644 index aafc648..0000000 --- a/src/Exception/CouldNotDecodeData.php +++ /dev/null @@ -1,14 +0,0 @@ - - */ -final class CouldNotDecodeData extends CouldNotCodeData implements DecoderException -{ -} diff --git a/src/Exception/CouldNotEncodeData.php b/src/Exception/CouldNotEncodeData.php deleted file mode 100644 index 23309a9..0000000 --- a/src/Exception/CouldNotEncodeData.php +++ /dev/null @@ -1,14 +0,0 @@ - - */ -final class CouldNotEncodeData extends CouldNotCodeData implements EncoderException -{ -} diff --git a/src/Exception/CouldNotSerializeData.php b/src/Exception/CouldNotSerializeData.php deleted file mode 100644 index 5793f2c..0000000 --- a/src/Exception/CouldNotSerializeData.php +++ /dev/null @@ -1,37 +0,0 @@ -serializer; - } - - public function getData(): mixed - { - return $this->data; - } -} diff --git a/src/Exception/CouldNotUnserializeData.php b/src/Exception/CouldNotUnserializeData.php deleted file mode 100644 index 0449b02..0000000 --- a/src/Exception/CouldNotUnserializeData.php +++ /dev/null @@ -1,37 +0,0 @@ -serializer; - } - - public function getData(): string - { - return $this->data; - } -} diff --git a/src/Exception/DecoderException.php b/src/Exception/DecoderException.php deleted file mode 100644 index b06d5a1..0000000 --- a/src/Exception/DecoderException.php +++ /dev/null @@ -1,14 +0,0 @@ - - */ -interface DecoderException extends CoderException -{ -} diff --git a/src/Exception/EncoderException.php b/src/Exception/EncoderException.php deleted file mode 100644 index 9535dc2..0000000 --- a/src/Exception/EncoderException.php +++ /dev/null @@ -1,14 +0,0 @@ - - */ -interface EncoderException extends CoderException -{ -} diff --git a/src/Exception/SerializerException.php b/src/Exception/SerializerException.php deleted file mode 100644 index 40acedf..0000000 --- a/src/Exception/SerializerException.php +++ /dev/null @@ -1,12 +0,0 @@ -coder = $coder ?? new Coder\Zlib(); + $this->serializer = $serializer ?? new Serializer\Php(); } - public function serialize(mixed $serializable): string - { - try { - $serialized = $this->doSerialize($serializable); - return $this->encoder->withData($serialized)->zlib()->getData(); - } catch (Throwable $reason) { - throw new Exception\CouldNotSerializeData($this, $serializable, $reason); - } - } - - public function unserialize(string $serialized): mixed - { - try { - $serialized = $this->decoder->withData($serialized)->zlib()->getData(); - return $this->doUnserialize($serialized); - } catch (Throwable $reason) { - throw new Exception\CouldNotUnserializeData($this, $serialized, $reason); - } - } - - /** - * Alternative to {@see serialize()} - * - * @throws Throwable - */ protected function doSerialize(mixed $serializable): string { - return serialize($serializable); + return $this->coder->encode($this->serializer->serialize($serializable)); } - /** - * Alternative to {@see unserialize()} - * - * @throws Throwable - */ protected function doUnserialize(string $serialized): mixed { - return unserialize($serialized); + return $this->serializer->unserialize($this->coder->decode($serialized)); } } diff --git a/src/Serializer/Exception/CouldNotSerializeData.php b/src/Serializer/Exception/CouldNotSerializeData.php new file mode 100644 index 0000000..083fd45 --- /dev/null +++ b/src/Serializer/Exception/CouldNotSerializeData.php @@ -0,0 +1,14 @@ + + */ +final class CouldNotSerializeData extends CouldNotProcessData implements SerializerException +{ +} diff --git a/src/Serializer/Exception/CouldNotUnserializeData.php b/src/Serializer/Exception/CouldNotUnserializeData.php new file mode 100644 index 0000000..3074ea7 --- /dev/null +++ b/src/Serializer/Exception/CouldNotUnserializeData.php @@ -0,0 +1,14 @@ + + */ +final class CouldNotUnserializeData extends CouldNotProcessData implements SerializerException +{ +} diff --git a/src/Serializer/Exception/SerializerException.php b/src/Serializer/Exception/SerializerException.php new file mode 100644 index 0000000..8b6d769 --- /dev/null +++ b/src/Serializer/Exception/SerializerException.php @@ -0,0 +1,11 @@ +doSerialize($serializable); + } catch (Throwable $reason) { + if ($reason instanceof Exception\CouldNotSerializeData) { + throw $reason; + } + throw new Exception\CouldNotSerializeData(__METHOD__, $serializable, $reason); + } + } + + public function unserialize(string $serialized): mixed + { + try { + return $this->doUnserialize($serialized); + } catch (Throwable $reason) { + if ($reason instanceof Exception\CouldNotUnserializeData) { + throw $reason; + } + throw new Exception\CouldNotUnserializeData(__METHOD__, $serialized, $reason); + } + } + + /** + * @throws Throwable + */ + abstract protected function doSerialize(mixed $serializable): string; + + /** + * @throws Throwable + */ + abstract protected function doUnserialize(string $serialized): mixed; +} diff --git a/src/SerializerInterface.php b/src/Serializer/SerializerInterface.php similarity index 71% rename from src/SerializerInterface.php rename to src/Serializer/SerializerInterface.php index 7f9a6b7..56b4694 100644 --- a/src/SerializerInterface.php +++ b/src/Serializer/SerializerInterface.php @@ -2,20 +2,16 @@ declare(strict_types=1); -namespace PetrKnap\Binary; +namespace PetrKnap\Binary\Serializer; interface SerializerInterface { /** - * {@see serialize()} the serializable - * * @throws Exception\CouldNotSerializeData */ public function serialize(mixed $serializable): string; /** - * {@see unserialize()} the serialized - * * @throws Exception\CouldNotUnserializeData */ public function unserialize(string $serialized): mixed; diff --git a/tests/Coder/Base64Test.php b/tests/Coder/Base64Test.php new file mode 100644 index 0000000..ac4db96 --- /dev/null +++ b/tests/Coder/Base64Test.php @@ -0,0 +1,57 @@ + [$data, CoderTestCase::DATA_BASE64, false], + 'URL safe' => [$data, '2jmj7l5rSw0yVb_vlWAYkK_YBwnaOaPuXmtLDTJVv--VYBiQr9gHCdo5o-5ea0sNMlW_75VgGJCv2AcJ', true], + ]; + } + + #[DataProvider('data')] + public function testEncodes(string $decoded, string $encoded, bool $urlSafe): void + { + self::assertSame( + $encoded, + (new Base64())->encode( + $decoded, + urlSafe: $urlSafe, + ), + ); + } + + #[DataProvider('data')] + public function testDecodes(string $decoded, string $encoded): void + { + self::assertSame( + $decoded, + (new Base64())->decode( + $encoded, + ), + ); + } + + #[DataProvider('dataDecodeThrows')] + public function testDecodeThrows(string $data): void + { + self::expectException(Exception\CouldNotDecodeData::class); + + (new Base64())->decode( + $data, + ); + } + + public static function dataDecodeThrows(): array + { + return [ + 'wrong data' => ['?'], + ]; + } +} diff --git a/tests/Coder/ChecksumTest.php b/tests/Coder/ChecksumTest.php new file mode 100644 index 0000000..838f44c --- /dev/null +++ b/tests/Coder/ChecksumTest.php @@ -0,0 +1,90 @@ + [$data, $data . hex2bin('95a41ef8'), 'crc32'], + 'sha1' => [$data, $data . hex2bin('d0dc1cf9bf61884f8e7982e0b1b87954bd9ee9c7'), 'sha1'], + ]; + } + + #[DataProvider('data')] + public function testEncodes(string $decoded, string $encoded, string $algorithm): void + { + self::assertSame( + $encoded, + self::getChecksum()->encode( + $decoded, + algorithm: $algorithm, + ), + ); + } + + #[DataProvider('dataEncodeThrows')] + public function testEncodeThrows(string $algorithm): void + { + self::expectException(Exception\CouldNotEncodeData::class); + + self::getChecksum()->encode( + self::getDecodedData(), + algorithm: $algorithm, + ); + } + + public static function dataEncodeThrows(): array + { + return [ + 'wrong algorithm' => ['?'], + ]; + } + + #[DataProvider('data')] + public function testDecodes(string $decoded, string $encoded, string $algorithm): void + { + self::assertSame( + $decoded, + self::getChecksum()->decode( + $encoded, + algorithm: $algorithm, + ), + ); + } + + #[DataProvider('dataDecodeThrows')] + public function testDecodeThrows(string $data, string $algorithm): void + { + self::expectException(Exception\CouldNotDecodeData::class); + + self::getChecksum()->decode( + $data, + algorithm: $algorithm, + ); + } + + public static function dataDecodeThrows(): array + { + return [ + 'wrong algorithm' => [self::getEncodedData(), '?'], + 'short data' => ['?', Checksum::ALGORITHM], + 'wrong data' => [self::getDecodedData(), Checksum::ALGORITHM], + 'wrong checksum' => ['?' . self::getEncodedData(), Checksum::ALGORITHM], + ]; + } + + private static function getChecksum(): Checksum + { + try { + return new Checksum(); + } catch (MissingRequirement $reason) { + self::markTestSkipped($reason->getMessage()); + } + } +} diff --git a/tests/Coder/CoderTestCase.php b/tests/Coder/CoderTestCase.php new file mode 100644 index 0000000..4693e0e --- /dev/null +++ b/tests/Coder/CoderTestCase.php @@ -0,0 +1,26 @@ + [$data, base64_decode('u2W5+F1ctjevUej+91MTJCasv8HOeYtIMQA='), ZLIB_ENCODING_RAW], + 'deflate' => [$data, base64_decode('eJy7Zbn4XVy2N69R6P73UxMkJqy/wc55i0gxAF0bG4s='), ZLIB_ENCODING_DEFLATE], + 'gzip' => [$data, base64_decode('H4sIAAAAAAAAA7tlufhdXLY3r1Ho/vdTEyQmrL/BznmLSDEAqyp39zwAAAA='), ZLIB_ENCODING_GZIP], + ]; + } + + #[DataProvider('data')] + public function testEncodes(string $decoded, string $encoded, int $encoding): void + { + self::assertSame( + $encoded, + self::getZlib()->encode( + $decoded, + encoding: $encoding, + ), + ); + } + + #[DataProvider('dataEncodeThrows')] + public function testEncodeThrows(?int $encoding, ?int $level): void + { + self::expectException(Exception\CouldNotEncodeData::class); + + self::getZlib()->encode( + self::getDecodedData(), + encoding: $encoding, + level: $level, + ); + } + + public static function dataEncodeThrows(): array + { + return [ + 'wrong encoding' => [0, null], + 'wrong level' => [null, -2], + ]; + } + + #[DataProvider('data')] + public function testDecodes(string $decoded, string $encoded): void + { + self::assertSame( + $decoded, + self::getZlib()->decode( + $encoded, + ), + ); + } + + #[DataProvider('dataDecodeThrows')] + public function testDecodeThrows(string $data, ?int $maxLength): void + { + self::expectException(Exception\CouldNotDecodeData::class); + + self::getZlib()->decode( + $data, + maxLength: $maxLength, + ); + } + + public static function dataDecodeThrows(): array + { + return [ + 'wrong data' => ['AwA=', null], + 'wrong maximal length' => [base64_decode('AwA='), -1], + ]; + } + + private static function getZlib(): Zlib + { + try { + return new Zlib(); + } catch (MissingRequirement $reason) { + self::markTestSkipped($reason->getMessage()); + } + } +} diff --git a/tests/CoderTestCase.php b/tests/CoderTestCase.php deleted file mode 100644 index ac6609c..0000000 --- a/tests/CoderTestCase.php +++ /dev/null @@ -1,38 +0,0 @@ - [$data, self::DATA_BASE64, false], - 'URL safe' => [$data, '2jmj7l5rSw0yVb_vlWAYkK_YBwnaOaPuXmtLDTJVv--VYBiQr9gHCdo5o-5ea0sNMlW_75VgGJCv2AcJ', true], - ]; - } - - public static function dataChecksum(): array - { - $data = base64_decode(self::DATA_BASE64); - return [ - 'crc32' => [$data, $data . hex2bin('95a41ef8'), 'crc32'], - 'sha1' => [$data, $data . hex2bin('d0dc1cf9bf61884f8e7982e0b1b87954bd9ee9c7'), 'sha1'], - ]; - } - - public static function dataZlib(): array - { - $data = base64_decode(self::DATA_BASE64); - return [ - 'RAW' => [$data, base64_decode('u2W5+F1ctjevUej+91MTJCasv8HOeYtIMQA='), ZLIB_ENCODING_RAW, EncoderInterface::ZLIB_LEVEL], - 'deflate' => [$data, base64_decode('eJy7Zbn4XVy2N69R6P73UxMkJqy/wc55i0gxAF0bG4s='), ZLIB_ENCODING_DEFLATE, EncoderInterface::ZLIB_LEVEL], - 'gzip' => [$data, base64_decode('H4sIAAAAAAAAA7tlufhdXLY3r1Ho/vdTEyQmrL/BznmLSDEAqyp39zwAAAA='), ZLIB_ENCODING_GZIP, EncoderInterface::ZLIB_LEVEL], - ]; - } -} diff --git a/tests/DecoderTest.php b/tests/DecoderTest.php index 448e015..ff69e3a 100644 --- a/tests/DecoderTest.php +++ b/tests/DecoderTest.php @@ -2,84 +2,31 @@ namespace PetrKnap\Binary; -use PHPUnit\Framework\Attributes\DataProvider; +use PHPUnit\Framework\TestCase; -final class DecoderTest extends CoderTestCase +final class DecoderTest extends TestCase { - #[DataProvider('dataBase64')] - public function testDecodesBase64(string $decoded, string $encoded): void + public function testDecodesBase64(): void { self::assertSame( - $decoded, - (new Decoder($encoded))->base64()->getData(), + Coder\Base64Test::getDecodedData(), + (new Decoder(Coder\Base64Test::getEncodedData()))->base64()->getData(), ); } - #[DataProvider('dataBase64Throws')] - public function testBase64Throws(string $data): void - { - self::expectException(Exception\CouldNotDecodeData::class); - - (new Decoder($data))->base64(); - } - - public static function dataBase64Throws(): array - { - return [ - 'wrong data' => ['?'], - ]; - } - - #[DataProvider('dataChecksum')] - public function testDecodesChecksum(string $decoded, string $encoded, string $algorithm): void + public function testDecodesChecksum(): void { self::assertSame( - $decoded, - (new Decoder($encoded))->checksum($algorithm)->getData(), + Coder\ChecksumTest::getDecodedData(), + (new Decoder(Coder\ChecksumTest::getEncodedData()))->checksum()->getData(), ); } - #[DataProvider('dataChecksumThrows')] - public function testChecksumThrows(string $data, string $algorithm): void - { - self::expectException(Exception\CouldNotDecodeData::class); - - (new Decoder($data))->checksum($algorithm); - } - - public static function dataChecksumThrows(): array - { - $dataSet = self::dataChecksum()[CoderInterface::CHECKSUM_ALGORITHM]; - return [ - 'wrong algorithm' => [$dataSet[1], '?'], - 'short data' => ['?', CoderInterface::CHECKSUM_ALGORITHM], - 'wrong data' => [$dataSet[0], CoderInterface::CHECKSUM_ALGORITHM], - 'wrong checksum' => ['?' . $dataSet[1], CoderInterface::CHECKSUM_ALGORITHM], - ]; - } - - #[DataProvider('dataZlib')] - public function testDecodesZlib(string $decoded, string $encoded): void + public function testDecodesZlib(): void { self::assertSame( - $decoded, - (new Decoder($encoded))->zlib()->getData(), + Coder\ZlibTest::getDecodedData(), + (new Decoder(Coder\ZlibTest::getEncodedData()))->zlib()->getData(), ); } - - #[DataProvider('dataZlibThrows')] - public function testZlibThrows(string $data, int $maxLength): void - { - self::expectException(Exception\CouldNotDecodeData::class); - - (new Decoder($data))->zlib($maxLength); - } - - public static function dataZlibThrows(): array - { - return [ - 'wrong data' => ['AwA=', DecoderInterface::ZLIB_MAX_LENGTH], - 'wrong maximal length' => [base64_decode('AwA='), -1], - ]; - } } diff --git a/tests/EncoderTest.php b/tests/EncoderTest.php index f9418de..21574e6 100644 --- a/tests/EncoderTest.php +++ b/tests/EncoderTest.php @@ -2,65 +2,31 @@ namespace PetrKnap\Binary; -use PHPUnit\Framework\Attributes\DataProvider; +use PHPUnit\Framework\TestCase; -final class EncoderTest extends CoderTestCase +final class EncoderTest extends TestCase { - #[DataProvider('dataBase64')] - public function testEncodesBase64(string $decoded, string $encoded, bool $urlSafe): void + public function testEncodesBase64(): void { self::assertSame( - $encoded, - (new Encoder($decoded))->base64($urlSafe)->getData(), + Coder\Base64Test::getEncodedData(), + (new Encoder(Coder\Base64Test::getDecodedData()))->base64()->getData(), ); } - #[DataProvider('dataChecksum')] - public function testEncodesChecksum(string $decoded, string $encoded, string $algorithm): void + public function testEncodesChecksum(): void { self::assertSame( - $encoded, - (new Encoder($decoded))->checksum($algorithm)->getData(), + Coder\ChecksumTest::getEncodedData(), + (new Encoder(Coder\ChecksumTest::getDecodedData()))->checksum()->getData(), ); } - #[DataProvider('dataChecksumThrows')] - public function testChecksumThrows(string $algorithm): void - { - self::expectException(Exception\CouldNotEncodeData::class); - - (new Encoder(''))->checksum($algorithm); - } - - public static function dataChecksumThrows(): array - { - return [ - 'wrong algorithm' => ['?'], - ]; - } - - #[DataProvider('dataZlib')] - public function testEncodesZlib(string $decoded, string $encoded, int $encoding, int $level): void + public function testEncodesZlib(): void { self::assertSame( - base64_encode($encoded), - base64_encode((new Encoder($decoded))->zlib(encoding: $encoding, level: $level)->getData()), + Coder\ZlibTest::getEncodedData(), + (new Encoder(Coder\ZlibTest::getDecodedData()))->zlib()->getData(), ); } - - #[DataProvider('dataZlibThrows')] - public function testZlibThrows(int $encoding, int $level): void - { - self::expectException(Exception\CouldNotEncodeData::class); - - (new Encoder('data'))->zlib($encoding, $level); - } - - public static function dataZlibThrows(): array - { - return [ - 'wrong encoding' => [0, EncoderInterface::ZLIB_LEVEL], - 'wrong level' => [EncoderInterface::ZLIB_ENCODING, -2], - ]; - } } diff --git a/tests/IgbinarySerializerTest.php b/tests/IgbinarySerializerTest.php deleted file mode 100644 index 29b8856..0000000 --- a/tests/IgbinarySerializerTest.php +++ /dev/null @@ -1,33 +0,0 @@ -getMessage()); - } - } - - public function testSerializesData(): void - { - SerializerTest::doTestSerializesData( - new IgbinarySerializer( - new Encoder(), - new Decoder(), - ), - ); - } -} diff --git a/tests/Serializer/IgbinaryTest.php b/tests/Serializer/IgbinaryTest.php new file mode 100644 index 0000000..73d65b9 --- /dev/null +++ b/tests/Serializer/IgbinaryTest.php @@ -0,0 +1,22 @@ +getMessage()); + } + } +} diff --git a/tests/Serializer/PhpTest.php b/tests/Serializer/PhpTest.php new file mode 100644 index 0000000..6814f97 --- /dev/null +++ b/tests/Serializer/PhpTest.php @@ -0,0 +1,16 @@ +array = []; + $serializable->binary = 0b0; + $serializable->float = .0; + $serializable->int = 0; + $serializable->null = null; + $serializable->string = ''; + + return $serializable; + } + + abstract public static function getSerialized(): string; + + abstract public static function getSerializer(): SerializerInterface; + + public function testSerializesSerializable(): void + { + self::assertEquals( + static::getSerialized(), + static::getSerializer()->serialize(static::getSerializable()), + ); + } + + public function testSerializeThrowsOnNonserializable(): void + { + self::expectException(Exception\CouldNotSerializeData::class); + + static::getSerializer()->serialize(new class () { + }); + } + + public function testUnserializesSerialized(): void + { + self::assertEquals( + static::getSerializable(), + static::getSerializer()->unserialize(static::getSerialized()), + ); + } + + public function testSerializeThrowsOnNonserialized(): void + { + self::expectException(Exception\CouldNotUnserializeData::class); + + static::getSerializer()->unserialize('?' . static::getSerialized()); + } +} diff --git a/tests/SerializerTest.php b/tests/SerializerTest.php index 155fcea..81f8967 100644 --- a/tests/SerializerTest.php +++ b/tests/SerializerTest.php @@ -2,129 +2,21 @@ namespace PetrKnap\Binary; -use PHPUnit\Framework\MockObject\MockObject; -use PHPUnit\Framework\TestCase; -use stdClass; +use PetrKnap\Shorts\Exception\MissingRequirement; -final class SerializerTest extends TestCase +final class SerializerTest extends Serializer\SerializerTestCase { - private EncoderInterface&MockObject $internalEncoder; - private DecoderInterface&MockObject $internalDecoder; - private SerializerInterface&MockObject $internalSerializer; - private Serializer $serializer; - - protected function setUp(): void + public static function getSerialized(): string { - parent::setUp(); - - $this->internalEncoder = self::createMock(EncoderInterface::class); - $this->internalDecoder = self::createMock(DecoderInterface::class); - $this->internalSerializer = self::createMock(SerializerInterface::class); - - $this->serializer = new class ( - $this->internalEncoder, - $this->internalDecoder, - $this->internalSerializer, - ) extends Serializer { - public function __construct( - EncoderInterface $encoder, - DecoderInterface $decoder, - private readonly SerializerInterface $serializer, - ) { - parent::__construct($encoder, $decoder); - } - protected function doSerialize(mixed $serializable): string - { - return $this->serializer->serialize($serializable); - } - protected function doUnserialize(string $serialized): mixed - { - return $this->serializer->unserialize($serialized); - } - }; + return base64_decode('NYtJCoAwEAT/0i8IuCA9R+/6hhFRAiFCJh4k+HdDwGNVV6+cCMv7HNQMHFmMA6Ep6QNROpbXqsbmo6aqPJ205AiXZsjeuCN8zP/aE/EOAbJI+1pOPp6o4AjI+wE='); } - public static function doTestSerializesData(SerializerInterface $serializer): void + public static function getSerializer(): Serializer\SerializerInterface { - $data = new stdClass(); - $data->array = []; - $data->binary = 0b0; - $data->float = .0; - $data->int = 0; - $data->null = null; - $data->string = ''; - - self::assertEquals( - $data, - $serializer->unserialize( - $serializer->serialize( - $data, - ), - ), - ); - } - - public function testSerializesData(): void - { - self::doTestSerializesData( - new Serializer( - new Encoder(), - new Decoder(), - ), - ); - } - - public function testCallsDoSerializeAndUsesEncoder(): void - { - $serializable = (string) 0b01; - $serialized = (string) 0b10; - $encoded = (string) 0b11; - - $this->internalSerializer->expects(self::once()) - ->method('serialize') - ->with($serializable) - ->willReturn($serialized); - $this->internalEncoder->expects(self::once()) - ->method('withData') - ->with($serialized) - ->willReturn($this->internalEncoder); - $this->internalEncoder->expects(self::once()) - ->method('zlib') - ->willReturn($this->internalEncoder); - $this->internalEncoder->expects(self::once()) - ->method('getData') - ->willReturn($encoded); - - self::assertSame( - $encoded, - $this->serializer->serialize($serializable), - ); - } - - public function testUsesDecoderAndCallsDoUnserialize(): void - { - $serialized = (string) 0b01; - $decoded = (string) 0b10; - $serializable = (string) 0b11; - - $this->internalDecoder->expects(self::once()) - ->method('withData') - ->with($serialized) - ->willReturn($this->internalDecoder); - $this->internalDecoder->expects(self::once()) - ->method('zlib') - ->willReturn($this->internalDecoder); - $this->internalDecoder->expects(self::once()) - ->method('getData') - ->willReturn($decoded); - $this->internalSerializer->expects(self::once()) - ->method('unserialize') - ->with($decoded) - ->willReturn($serializable); - - self::assertSame( - $serializable, - $this->serializer->unserialize($serialized), - ); + try { + return new Serializer(); + } catch (MissingRequirement $reason) { + self::markTestSkipped($reason->getMessage()); + } } }