Skip to content

Commit

Permalink
feat: implemented Serializer
Browse files Browse the repository at this point in the history
  • Loading branch information
petrknap committed Apr 1, 2024
1 parent 9634cb6 commit 0de404f
Show file tree
Hide file tree
Showing 11 changed files with 332 additions and 3 deletions.
15 changes: 14 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -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;

$array = [
'type' => 'image/png',
'data' => base64_decode('iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAAAXNSR0IArs4c6QAAAA1JREFUGFdj+L+U4T8ABu8CpCYJ1DQAAAAASUVORK5CYII='),
];
$serialized = Binary::serialize($array);
$unserialized = Binary::unserialize($serialized);

printf('Array was serialized into `%s` %s.', base64_encode($serialized), $unserialized === $array ? 'successfully' : 'unsuccessfully');
```

---

Run `composer require petrknap/binary` to install it.
Expand Down
19 changes: 19 additions & 0 deletions src/Binary.php
Original file line number Diff line number Diff line change
Expand Up @@ -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(),
);
}
}
7 changes: 6 additions & 1 deletion src/Coder.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
2 changes: 2 additions & 0 deletions src/CoderInterface.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ interface CoderInterface
{
public const CHECKSUM_ALGORITHM = 'crc32';

public function withData(string $data): static;

public function getData(): string;

/**
Expand Down
37 changes: 37 additions & 0 deletions src/Exception/CouldNotSerializeData.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
<?php

declare(strict_types=1);

namespace PetrKnap\Binary\Exception;

use PetrKnap\Binary\SerializerInterface;
use RuntimeException;
use Throwable;

final class CouldNotSerializeData extends RuntimeException implements SerializerException
{
public function __construct(
private readonly SerializerInterface $serializer,
private readonly mixed $data,
?Throwable $reason = null,
) {
parent::__construct(
sprintf(
'%s could not serialize %s',
$serializer::class,
is_object($data) ? $data::class : gettype($data),
),
previous: $reason,
);
}

public function getSerializer(): SerializerInterface
{
return $this->serializer;
}

public function getData(): mixed
{
return $this->data;
}
}
37 changes: 37 additions & 0 deletions src/Exception/CouldNotUnserializeData.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
<?php

declare(strict_types=1);

namespace PetrKnap\Binary\Exception;

use PetrKnap\Binary\SerializerInterface;
use RuntimeException;
use Throwable;

final class CouldNotUnserializeData extends RuntimeException implements SerializerException
{
public function __construct(
private readonly SerializerInterface $serializer,
private readonly string $data,
?Throwable $reason = null,
) {
parent::__construct(
sprintf(
'%s could not unserialize string(%d)',
$serializer::class,
strlen($data)
),
previous: $reason,
);
}

public function getSerializer(): SerializerInterface
{
return $this->serializer;
}

public function getData(): string
{
return $this->data;
}
}
12 changes: 12 additions & 0 deletions src/Exception/SerializerException.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<?php

declare(strict_types=1);

namespace PetrKnap\Binary\Exception;

use PetrKnap\Binary\SerializerInterface;

interface SerializerException extends BinaryException
{
public function getSerializer(): SerializerInterface;
}
56 changes: 56 additions & 0 deletions src/Serializer.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
<?php

declare(strict_types=1);

namespace PetrKnap\Binary;

use Throwable;

class Serializer implements SerializerInterface
{
public function __construct(
protected readonly EncoderInterface $encoder,
protected readonly DecoderInterface $decoder,
) {
}

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);
}

/**
* Alternative to {@see unserialize()}
*
* @throws Throwable
*/
protected function doUnserialize(string $serialized): mixed
{
return unserialize($serialized);
}
}
22 changes: 22 additions & 0 deletions src/SerializerInterface.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
<?php

declare(strict_types=1);

namespace PetrKnap\Binary;

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;
}
3 changes: 2 additions & 1 deletion tests/ReadmeTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,8 @@ public static function getPathToMarkdownFile(): string
public static function getExpectedOutputsOfPhpExamples(): iterable
{
return [
'coders' => 'Data was coded into `a8vMFCssyD2Rs5BB0Evt6tJv10J_b2Aoui0tcXT69aaPP9oIyB-fLeAHAA` successfully.',
'coder' => 'Data was coded into `a8vMFCssyD2Rs5BB0Evt6tJv10J_b2Aoui0tcXT69aaPP9oIyB-fLeAHAA` successfully.',
'serializer' => 'Array was serialized into `S7QysqoutjKxUiqpLEhVsi62srRSysxNTE/VL8hLB/GBUimJJYkgpoWxlVJngJ87L5cUFwMDA6+nh0sQkGYEYQ42ICkveqQTxCkOcndiWHdO5iVYlYtjiER48o/9Ux7aM7C9Z1qixnnFBCjB4Onq57LOKaFJyboWAA==` successfully.',
];
}
}
125 changes: 125 additions & 0 deletions tests/SerializerTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
<?php declare(strict_types=1);

namespace PetrKnap\Binary;

use PHPUnit\Framework\Attributes\Depends;
use PHPUnit\Framework\MockObject\MockObject;
use PHPUnit\Framework\TestCase;
use stdClass;

final class SerializerTest extends TestCase
{
private EncoderInterface&MockObject $internalEncoder;
private DecoderInterface&MockObject $internalDecoder;
private SerializerInterface&MockObject $internalSerializer;
private Serializer $serializer;

public function setUp(): void
{
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);
}
};
}

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')
->willReturn($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($encoded),
);
}

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),
);
}
}

0 comments on commit 0de404f

Please sign in to comment.