From 5abda62ce017ce7c3492e8d1fc503f726df72902 Mon Sep 17 00:00:00 2001 From: Petr Knap <8299754+petrknap@users.noreply.github.com> Date: Mon, 1 Apr 2024 10:01:50 +0200 Subject: [PATCH] feat: implemented `Serializer` --- README.md | 15 ++- src/Binary.php | 19 ++++ src/Coder.php | 7 +- src/CoderInterface.php | 2 + src/Exception/CouldNotSerializeData.php | 37 +++++++ src/Exception/CouldNotUnserializeData.php | 37 +++++++ src/Exception/SerializerException.php | 12 +++ src/Serializer.php | 56 ++++++++++ src/SerializerInterface.php | 22 ++++ tests/ReadmeTest.php | 3 +- tests/SerializerTest.php | 124 ++++++++++++++++++++++ 11 files changed, 331 insertions(+), 3 deletions(-) create mode 100644 src/Exception/CouldNotSerializeData.php create mode 100644 src/Exception/CouldNotUnserializeData.php create mode 100644 src/Exception/SerializerException.php create mode 100644 src/Serializer.php create mode 100644 src/SerializerInterface.php create mode 100644 tests/SerializerTest.php diff --git a/README.md b/README.md index d26b4a9..4257dc0 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ # Library for work with binaries Library for basic work with binary data in PHP. -See the sample below for more information, or check out [`CoderInterface`](./src/CoderInterface.php). +See the examples below for more information, or check out [`CoderInterface`](./src/CoderInterface.php) and [`SerializerInterface`](./src/SerializerInterface.php). ```php use PetrKnap\Binary\Binary; @@ -13,6 +13,19 @@ $decoded = Binary::decode($encoded)->base64()->zlib()->checksum()->getData(); printf('Data was coded into `%s` %s.', $encoded, $decoded === $data ? 'successfully' : 'unsuccessfully'); ``` +```php +use PetrKnap\Binary\Binary; + +$data = [ + 'type' => 'image/png', + 'data' => base64_decode('iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAAAXNSR0IArs4c6QAAAA1JREFUGFdj+L+U4T8ABu8CpCYJ1DQAAAAASUVORK5CYII='), +]; +$serialized = Binary::serialize($data); +$unserialized = Binary::unserialize($serialized); + +printf('Data was serialized into `%s` %s.', base64_encode($serialized), $unserialized === $data ? 'successfully' : 'unsuccessfully'); +``` + --- Run `composer require petrknap/binary` to install it. diff --git a/src/Binary.php b/src/Binary.php index 7b52b6e..6b16bfa 100644 --- a/src/Binary.php +++ b/src/Binary.php @@ -15,4 +15,23 @@ public static function decode(string $data): Decoder { return new Decoder($data); } + + public static function serialize(mixed $data): string + { + return self::getSerializer()->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(), + ); + } } diff --git a/src/Coder.php b/src/Coder.php index a304a44..362e3c0 100644 --- a/src/Coder.php +++ b/src/Coder.php @@ -17,10 +17,15 @@ abstract class Coder implements CoderInterface ]; public function __construct( - protected readonly string $data, + protected readonly string $data = '', ) { } + public function withData(string $data): static + { + return static::create($this, $data); + } + public function getData(): string { return $this->data; diff --git a/src/CoderInterface.php b/src/CoderInterface.php index fd324cc..80301e5 100644 --- a/src/CoderInterface.php +++ b/src/CoderInterface.php @@ -11,6 +11,8 @@ interface CoderInterface { public const CHECKSUM_ALGORITHM = 'crc32'; + public function withData(string $data): static; + public function getData(): string; /** diff --git a/src/Exception/CouldNotSerializeData.php b/src/Exception/CouldNotSerializeData.php new file mode 100644 index 0000000..5793f2c --- /dev/null +++ b/src/Exception/CouldNotSerializeData.php @@ -0,0 +1,37 @@ +serializer; + } + + public function getData(): mixed + { + return $this->data; + } +} diff --git a/src/Exception/CouldNotUnserializeData.php b/src/Exception/CouldNotUnserializeData.php new file mode 100644 index 0000000..0449b02 --- /dev/null +++ b/src/Exception/CouldNotUnserializeData.php @@ -0,0 +1,37 @@ +serializer; + } + + public function getData(): string + { + return $this->data; + } +} diff --git a/src/Exception/SerializerException.php b/src/Exception/SerializerException.php new file mode 100644 index 0000000..40acedf --- /dev/null +++ b/src/Exception/SerializerException.php @@ -0,0 +1,12 @@ +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); + } + + /** + * Alternative to {@see unserialize()} + * + * @throws Throwable + */ + protected function doUnserialize(string $serialized): mixed + { + return unserialize($serialized); + } +} diff --git a/src/SerializerInterface.php b/src/SerializerInterface.php new file mode 100644 index 0000000..7f9a6b7 --- /dev/null +++ b/src/SerializerInterface.php @@ -0,0 +1,22 @@ + 'Data was coded into `a8vMFCssyD2Rs5BB0Evt6tJv10J_b2Aoui0tcXT69aaPP9oIyB-fLeAHAA` successfully.', + 'coder' => 'Data was coded into `a8vMFCssyD2Rs5BB0Evt6tJv10J_b2Aoui0tcXT69aaPP9oIyB-fLeAHAA` successfully.', + 'serializer' => 'Data was serialized into `S7QysqoutjKxUiqpLEhVsi62srRSysxNTE/VL8hLB/GBUimJJYkgpoWxlVJngJ87L5cUFwMDA6+nh0sQkGYEYQ42ICkveqQTxCkOcndiWHdO5iVYlYtjiER48o/9Ux7aM7C9Z1qixnnFBCjB4Onq57LOKaFJyboWAA==` successfully.', ]; } } diff --git a/tests/SerializerTest.php b/tests/SerializerTest.php new file mode 100644 index 0000000..2e9420a --- /dev/null +++ b/tests/SerializerTest.php @@ -0,0 +1,124 @@ +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); + } + }; + } + + public function testSerializesData(): void + { + $data = new stdClass(); + $data->array = []; + $data->binary = 0b0; + $data->float = .0; + $data->int = 0; + $data->null = null; + $data->string = ''; + $serializer = new Serializer( + new Encoder(), + new Decoder(), + ); + + self::assertEquals( + $data, + $serializer->unserialize( + $serializer->serialize( + $data, + ), + ), + ); + } + + 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), + ); + } +}