-
-
Notifications
You must be signed in to change notification settings - Fork 233
Expand file tree
/
Copy pathBackedEnumNormalizerDecorator.php
More file actions
98 lines (85 loc) · 3.82 KB
/
BackedEnumNormalizerDecorator.php
File metadata and controls
98 lines (85 loc) · 3.82 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
<?php
declare(strict_types=1);
namespace App\Serializer;
use Symfony\Component\DependencyInjection\Attribute\AsDecorator;
use Symfony\Component\Serializer\Exception\NotNormalizableValueException;
use Symfony\Component\Serializer\Normalizer\BackedEnumNormalizer;
use Symfony\Component\Serializer\Normalizer\DenormalizerInterface;
use Symfony\Component\Serializer\Normalizer\NormalizerInterface;
/**
* Reproducer for https://github.com/api-platform/demo/issues/601.
*
* This decorator simulates the behavior introduced in symfony/serializer by
* Symfony PR #62574 (commit 35b1aec), which improves BackedEnumNormalizer
* error messages. That change was temporarily in v8.0.5 (then reverted) and
* will ship in Symfony 8.1.
*
* The improved normalizer distinguishes two error cases:
* 1. Type mismatch: data is not int/string → expectedTypes = [$backingType]
* 2. Invalid value: data is the right type but not a valid enum case
* → expectedTypes = null, message lists valid values
*
* Case 2 exposes a bug in api-platform/state's DeserializeProvider which does
* not handle null/empty expectedTypes, producing: "This value should be of type ."
*
* @todo Remove this decorator once Symfony 8.1 is adopted and the upstream
* API Platform bug is fixed.
*/
#[AsDecorator('serializer.normalizer.backed_enum')]
final class BackedEnumNormalizerDecorator implements NormalizerInterface, DenormalizerInterface
{
public function __construct(
private readonly BackedEnumNormalizer $inner,
) {
}
public function normalize(mixed $data, ?string $format = null, array $context = []): int|string
{
return $this->inner->normalize($data, $format, $context);
}
public function supportsNormalization(mixed $data, ?string $format = null, array $context = []): bool
{
return $this->inner->supportsNormalization($data, $format, $context);
}
public function denormalize(mixed $data, string $type, ?string $format = null, array $context = []): mixed
{
if (!is_subclass_of($type, \BackedEnum::class)) {
return $this->inner->denormalize($data, $type, $format, $context);
}
$backingType = (new \ReflectionEnum($type))->getBackingType()?->getName();
// Case 1: Type mismatch — data is not the expected backing type
if (null === $data || ('int' === $backingType && !\is_int($data)) || ('string' === $backingType && !\is_string($data))) {
throw NotNormalizableValueException::createForUnexpectedDataType(
\sprintf('The data must be of type %s.', $backingType),
$data,
[$backingType],
$context['deserialization_path'] ?? null,
true,
);
}
// Case 2: Invalid value — right type but not a valid enum case
try {
return $type::from($data);
} catch (\ValueError|\TypeError $e) {
$validValues = array_map(
static fn (\BackedEnum $case): string => \is_string($case->value)
? \sprintf("'%s'", $case->value)
: (string) $case->value,
$type::cases(),
);
throw new NotNormalizableValueException(
message: \sprintf('The data must be one of the following values: %s', implode(', ', $validValues)),
previous: $e,
path: $context['deserialization_path'] ?? null,
useMessageForUser: true,
);
}
}
public function supportsDenormalization(mixed $data, string $type, ?string $format = null, array $context = []): bool
{
return $this->inner->supportsDenormalization($data, $type, $format, $context);
}
public function getSupportedTypes(?string $format): array
{
return $this->inner->getSupportedTypes($format);
}
}