Skip to content

Commit 3cde0d0

Browse files
authored
Merge pull request #12 from petrknap/rebinariable
Implemented helper for two-way object to binary string conversion
2 parents b2c28ce + f0c21e6 commit 3cde0d0

11 files changed

+160
-52
lines changed

composer.json

+1
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@
5353
"scripts": {
5454
"test": "phpunit --colors=always --testdox tests",
5555
"validate": [
56+
"composer outdated \"petrknap/*\" --major-only --strict --ansi --no-interaction",
5657
"phpcs --colors --standard=PSR12 --exclude=Generic.Files.LineLength src tests",
5758
"phpstan analyse --level max src",
5859
"phpstan analyse --level 5 tests",

src/BinariableInterface.php

+1-7
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,6 @@
66

77
use Stringable;
88

9-
interface BinariableInterface extends Stringable
9+
interface BinariableInterface extends Serializer\OneWaySelfSerializerInterface, Stringable
1010
{
11-
/**
12-
* @return string binary representation of this instance
13-
*
14-
* @throws Exception\CouldNotConvertToBinary
15-
*/
16-
public function toBinary(): string;
1711
}

src/BinariableTrait.php

+1-6
Original file line numberDiff line numberDiff line change
@@ -4,19 +4,14 @@
44

55
namespace PetrKnap\Binary;
66

7-
use PetrKnap\Shorts\Exception\NotImplemented;
8-
97
trait BinariableTrait
108
{
119
/**
1210
* Unfortunately PHP uses string for binary data, so the magic clashes.
1311
*/
1412
public function __toString(): string
1513
{
16-
if (!($this instanceof BinariableInterface)) {
17-
NotImplemented::throw(BinariableInterface::class);
18-
}
19-
14+
/** @var BinariableInterface $this */
2015
$binary = $this->toBinary();
2116
trigger_error(
2217
'Returned binary string',

src/Exception/BinariableException.php

-9
This file was deleted.

src/Exception/CouldNotConvertToBinary.php

-14
This file was deleted.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace PetrKnap\Binary\Serializer;
6+
7+
interface OneWaySelfSerializerInterface
8+
{
9+
/**
10+
* @return string binary representation of this instance
11+
*
12+
* @throws Exception\CouldNotSerializeData
13+
*/
14+
public function toBinary(): string;
15+
}
+15
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace PetrKnap\Binary\Serializer;
6+
7+
interface SelfSerializerInterface extends OneWaySelfSerializerInterface
8+
{
9+
/**
10+
* @param string $data binary representation of an instance
11+
*
12+
* @throws Exception\CouldNotUnserializeData
13+
*/
14+
public static function fromBinary(string $data): self;
15+
}
+35
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace PetrKnap\Binary\Serializer;
6+
7+
use PetrKnap\Binary\Serializer;
8+
9+
/**
10+
* If your {@see self::__construct()} argument is an instance of {@see SelfSerializerInterface} then
11+
* accept it as an union type `YourClass|string` and call {@see SelfSerializerInterface::fromBinary()} if it is a string.
12+
*/
13+
trait SelfSerializerTrait
14+
{
15+
/**
16+
* References to {@see self::__construct()} arguments (`&$argument`)
17+
*/
18+
private readonly array $referencesToConstructorArgs;
19+
20+
public function toBinary(): string
21+
{
22+
return (new Serializer())->serialize(array_map(
23+
static fn (mixed $constructorArg): mixed => match ($constructorArg instanceof SelfSerializerInterface) {
24+
true => $constructorArg->toBinary(),
25+
false => $constructorArg,
26+
},
27+
$this->referencesToConstructorArgs,
28+
));
29+
}
30+
31+
public static function fromBinary(string $data): self
32+
{
33+
return new self(...(new Serializer())->unserialize($data));
34+
}
35+
}

tests/BinariableTest.php

+8-16
Original file line numberDiff line numberDiff line change
@@ -8,47 +8,39 @@
88

99
final class BinariableTest extends TestCase
1010
{
11-
public const DATA = b'data';
12-
13-
public function testExplicitConversionWorks(): void
14-
{
15-
self::assertSame(
16-
self::DATA,
17-
self::getBinariable()->toBinary(),
18-
);
19-
}
11+
public const BINARY = b'binary';
2012

2113
public function testNativeConversionWorks(): void
2214
{
2315
self::assertSame(
24-
self::DATA,
25-
(string) self::getBinariable(),
16+
self::BINARY,
17+
(string) self::getInstance(),
2618
);
2719
}
2820

2921
public function testNativeComparisonWorks(): void
3022
{
31-
self::assertTrue(self::DATA == self::getBinariable());
23+
self::assertTrue(self::BINARY == self::getInstance());
3224
}
3325

3426
public function testUnionTypingWorks(): void
3527
{
3628
$function = static fn (BinariableInterface|string $parameter): string => (string) $parameter;
3729

3830
self::assertSame(
39-
self::DATA,
40-
$function(self::getBinariable()),
31+
self::BINARY,
32+
$function(self::getInstance()),
4133
);
4234
}
4335

44-
private function getBinariable(): BinariableInterface
36+
private static function getInstance(): BinariableInterface
4537
{
4638
return new class () implements BinariableInterface {
4739
use BinariableTrait;
4840

4941
public function toBinary(): string
5042
{
51-
return BinariableTest::DATA;
43+
return BinariableTest::BINARY;
5244
}
5345
};
5446
}
+26
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace PetrKnap\Binary\Serializer;
6+
7+
use PHPUnit\Framework\TestCase;
8+
9+
final class SelfSerializerTest extends TestCase
10+
{
11+
public function testSerializationWorks(): void
12+
{
13+
self::assertEquals(
14+
SelfSerializingDataObject::BINARY_B64,
15+
base64_encode(SelfSerializingDataObject::getInstance()->toBinary()),
16+
);
17+
}
18+
19+
public function testUnserializationWorks(): void
20+
{
21+
self::assertEquals(
22+
SelfSerializingDataObject::getInstance(),
23+
SelfSerializingDataObject::fromBinary(base64_decode(SelfSerializingDataObject::BINARY_B64)),
24+
);
25+
}
26+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace PetrKnap\Binary\Serializer;
6+
7+
use stdClass;
8+
9+
final class SelfSerializingDataObject implements SelfSerializerInterface
10+
{
11+
use SelfSerializerTrait;
12+
13+
public const BINARY_B64 = 'S7QysarOtDKwzrQyBGN/KwsrpeKSFOecxOJiJStDq+piK2MrpbT8fCVrMCspsUjJujbTygjINTQxtVLy3mK0cdW5LUYbgJgpyTm7Xptbe+mjSSLP5xx5lKSq7bz6VZJ2svbSLX9qVLMNtDU3epzcJbZkz41Td454nazuvngs9Pk2iVyGpG0qz99U5ea9Xdqv1mkzv/hJ4KRV2z6/Cve8dfKW9/Y/KwPPml24dudFYdH0m0+D1fM+ro+sfHxFVt/XO9W5Njo4jNeNTXUBc8/av2uyGZWAHjG29rOuBQA=';
14+
15+
public readonly ?self $innerInstance;
16+
17+
private function __construct(
18+
public readonly int $scalar,
19+
public readonly stdClass $serializable,
20+
self|string|null $innerInstance = null,
21+
public mixed $variable = null,
22+
) {
23+
$this->innerInstance = match (is_string($innerInstance)) {
24+
true => self::fromBinary($innerInstance),
25+
false => $innerInstance,
26+
};
27+
$this->referencesToConstructorArgs = [
28+
$this->scalar,
29+
$this->serializable,
30+
$this->innerInstance,
31+
&$this->variable,
32+
];
33+
}
34+
35+
public static function getInstance(): self
36+
{
37+
$stdClass = new stdClass();
38+
$stdClass->foo = 'bar';
39+
40+
$instance = new self(
41+
1,
42+
$stdClass,
43+
new self(
44+
2,
45+
$stdClass,
46+
new self(
47+
3,
48+
$stdClass,
49+
variable: 'value',
50+
),
51+
),
52+
);
53+
54+
$instance->innerInstance->innerInstance->variable = 'changed';
55+
56+
return $instance;
57+
}
58+
}

0 commit comments

Comments
 (0)