Skip to content

Commit 6e05bfb

Browse files
authored
Merge pull request #2 from KurtThiemann/custom-property-serializer
Add custom serializers and deserializers for individual properties
2 parents 169a1d5 + e6d869d commit 6e05bfb

16 files changed

+266
-14
lines changed

README.md

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -176,6 +176,18 @@ protected array $otherExamples;
176176
// ^ items in the array will not be converted
177177
```
178178

179+
#### Serializer and Deserializer
180+
A custom Serializer and Deserializer can be specified for a property.
181+
This can be useful if you want to serialize a specific property in a different way.
182+
183+
```php
184+
#[Serialize(serializer: new Base64Serializer(), deserializer: new Base64Deserializer(TestClass::class))]
185+
protected TestClass $example;
186+
```
187+
188+
Note that the custom Deserializer is responsible for returning the correct type.
189+
If an incompatible type is returned, an IncorrectTypeException is thrown.
190+
179191
### Exceptions
180192
The following exceptions may be thrown during serialization or deserialization:
181193
- [MissingPropertyException](#missingpropertyexception)

src/ArrayDeserializer.php

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
use ReflectionNamedType;
1414
use ReflectionProperty;
1515
use ReflectionUnionType;
16+
use TypeError;
1617

1718
/**
1819
* Deserializes arrays into objects using the Serialize attribute.
@@ -26,7 +27,7 @@
2627
* @see Serialize
2728
* @template T
2829
*/
29-
class ArrayDeserializer
30+
class ArrayDeserializer implements DeserializerInterface
3031
{
3132

3233
/**
@@ -49,10 +50,13 @@ public function __construct(
4950
* @throws UnsupportedTypeException if the type of the property is unsupported
5051
*/
5152
public function deserialize(
52-
array $data,
53+
mixed $data,
5354
string $path = "",
5455
): object
5556
{
57+
if (!is_array($data)) {
58+
throw new InvalidArgumentException("Data must be an array.");
59+
}
5660
try {
5761
$reflectionClass = new ReflectionClass($this->class);
5862
$result = new $this->class;
@@ -112,6 +116,15 @@ protected function deserializeProperty(
112116
}
113117

114118
$value = $data[$name];
119+
if ($customDeserializer = $attribute->getDeserializer()) {
120+
$value = $customDeserializer->deserialize($value, $path);
121+
try {
122+
$property->setValue($result, $value);
123+
} catch (TypeError) {
124+
throw new IncorrectTypeException($path . "." . $name, $type, $value);
125+
}
126+
return;
127+
}
115128

116129
$nullable = $attribute->allowsNull() ?? $type?->allowsNull() ?? true;
117130
if ($value === null) {

src/ArraySerializer.php

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@
2525
* @see Serialize
2626
* @see JsonSerializer
2727
*/
28-
class ArraySerializer
28+
class ArraySerializer implements SerializerInterface
2929
{
3030
/**
3131
* Create a serializer from an object
@@ -67,7 +67,9 @@ public function serialize(object $item): array
6767
$name = $attribute->getName() ?? $property->getName();
6868
$value = $property->getValue($item);
6969

70-
if ($value instanceof JsonSerializable) {
70+
if ($customSerializer = $attribute->getSerializer()) {
71+
$value = $customSerializer->serialize($value);
72+
} else if ($value instanceof JsonSerializable) {
7173
$value = $value->jsonSerialize();
7274
} elseif (is_object($value)) {
7375
$value = $this->serialize($value);
@@ -78,4 +80,4 @@ public function serialize(object $item): array
7880

7981
return $serializedProperties;
8082
}
81-
}
83+
}

src/DeserializerInterface.php

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
<?php
2+
3+
namespace Aternos\Serializer;
4+
5+
use Aternos\Serializer\Exceptions\IncorrectTypeException;
6+
use Aternos\Serializer\Exceptions\MissingPropertyException;
7+
use Aternos\Serializer\Exceptions\UnsupportedTypeException;
8+
9+
/**
10+
* @template T
11+
*/
12+
interface DeserializerInterface
13+
{
14+
/**
15+
* Create a deserializer for a class
16+
*
17+
* @param class-string<T> $class the class to deserialize into
18+
*/
19+
public function __construct(string $class);
20+
21+
/**
22+
* Deserialize the data into an object
23+
*
24+
* @return T
25+
* @throws IncorrectTypeException if the type of the property is incorrect
26+
* @throws MissingPropertyException if a required property is missing
27+
* @throws UnsupportedTypeException if the type of the property is unsupported
28+
*/
29+
public function deserialize(mixed $data, string $path = ""): object;
30+
}

src/Json/JsonDeserializer.php

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,11 @@
33
namespace Aternos\Serializer\Json;
44

55
use Aternos\Serializer\ArrayDeserializer;
6+
use Aternos\Serializer\DeserializerInterface;
67
use Aternos\Serializer\Exceptions\IncorrectTypeException;
78
use Aternos\Serializer\Exceptions\MissingPropertyException;
89
use Aternos\Serializer\Exceptions\UnsupportedTypeException;
10+
use InvalidArgumentException;
911
use JsonException;
1012

1113
/**
@@ -20,7 +22,7 @@
2022
* @see Serialize
2123
* @template T
2224
*/
23-
class JsonDeserializer
25+
class JsonDeserializer implements DeserializerInterface
2426
{
2527
protected ArrayDeserializer $arrayDeserializer;
2628

@@ -45,7 +47,7 @@ public function __construct(
4547
* @throws UnsupportedTypeException if the type of the property is unsupported
4648
* @throws JsonException if the data is invalid json
4749
*/
48-
public function deserialize(array|string $data, string $path = ""): object
50+
public function deserialize(mixed $data, string $path = ""): object
4951
{
5052
if (is_string($data)) {
5153
$data = json_decode($data, true, flags: JSON_THROW_ON_ERROR);
@@ -54,6 +56,10 @@ public function deserialize(array|string $data, string $path = ""): object
5456
}
5557
}
5658

59+
if (!is_array($data)) {
60+
throw new InvalidArgumentException("Data must be a string or an array.");
61+
}
62+
5763
return $this->arrayDeserializer->deserialize($data, $path);
5864
}
59-
}
65+
}

src/Json/JsonSerializer.php

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
use Aternos\Serializer\ArraySerializer;
66
use Aternos\Serializer\Exceptions\IncorrectTypeException;
77
use Aternos\Serializer\Exceptions\MissingPropertyException;
8+
use Aternos\Serializer\SerializerInterface;
89

910
/**
1011
* A class that serializes objects using the Serialize attribute.
@@ -18,7 +19,7 @@
1819
* @see ArraySerializer
1920
* @see PropertyJsonSerializer
2021
*/
21-
class JsonSerializer
22+
class JsonSerializer implements SerializerInterface
2223
{
2324
protected ArraySerializer $arraySerializer;
2425

@@ -38,4 +39,4 @@ public function serialize(object $item): string
3839
{
3940
return json_encode($this->arraySerializer->serialize($item));
4041
}
41-
}
42+
}

src/Serialize.php

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,12 +26,16 @@ public static function getAttribute(ReflectionProperty $property): ?self
2626
* @param bool|null $required Whether the field is required
2727
* @param bool|null $allowNull Whether the field can be null
2828
* @param class-string|null $itemType The type of the items in the array
29+
* @param SerializerInterface|null $serializer A custom serializer for this field
30+
* @param DeserializerInterface|null $deserializer A custom deserializer for this field
2931
*/
3032
public function __construct(
3133
protected ?string $name = null,
3234
protected ?bool $required = null,
3335
protected ?bool $allowNull = null,
3436
protected ?string $itemType = null,
37+
protected ?SerializerInterface $serializer = null,
38+
protected ?DeserializerInterface $deserializer = null,
3539
)
3640
{
3741
}
@@ -58,4 +62,20 @@ public function getItemType(): ?string
5862
{
5963
return $this->itemType;
6064
}
61-
}
65+
66+
/**
67+
* @return SerializerInterface|null
68+
*/
69+
public function getSerializer(): ?SerializerInterface
70+
{
71+
return $this->serializer;
72+
}
73+
74+
/**
75+
* @return DeserializerInterface|null
76+
*/
77+
public function getDeserializer(): ?DeserializerInterface
78+
{
79+
return $this->deserializer;
80+
}
81+
}

src/SerializerInterface.php

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
<?php
2+
3+
namespace Aternos\Serializer;
4+
5+
interface SerializerInterface
6+
{
7+
/**
8+
* Serialize an object into a scalar value or an array.
9+
*
10+
* @param object $item
11+
* @return int|float|string|array|null
12+
*/
13+
public function serialize(object $item): int|float|string|array|null;
14+
}

tests/src/Base64Deserializer.php

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
<?php
2+
3+
namespace Aternos\Serializer\Test\Src;
4+
5+
use Aternos\Serializer\DeserializerInterface;
6+
7+
class Base64Deserializer implements DeserializerInterface
8+
{
9+
10+
/**
11+
* @inheritDoc
12+
*/
13+
public function __construct(protected string $class)
14+
{
15+
}
16+
17+
/**
18+
* @inheritDoc
19+
*/
20+
public function deserialize(mixed $data, string $path = ""): object
21+
{
22+
return unserialize(base64_decode($data));
23+
}
24+
}

tests/src/Base64Serializer.php

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
<?php
2+
3+
namespace Aternos\Serializer\Test\Src;
4+
5+
use Aternos\Serializer\SerializerInterface;
6+
7+
class Base64Serializer implements SerializerInterface
8+
{
9+
/**
10+
* @inheritDoc
11+
*/
12+
public function serialize(object $item): int|float|string|array|null
13+
{
14+
return base64_encode(serialize($item));
15+
}
16+
}

0 commit comments

Comments
 (0)