Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
78.31% |
65 / 83 |
|
57.14% |
4 / 7 |
CRAP | |
0.00% |
0 / 1 |
NotificationNormalizer | |
78.31% |
65 / 83 |
|
57.14% |
4 / 7 |
73.49 | |
0.00% |
0 / 1 |
normalize | |
73.17% |
30 / 41 |
|
0.00% |
0 / 1 |
35.12 | |||
isUninitializedValueError | |
100.00% |
3 / 3 |
|
100.00% |
1 / 1 |
3 | |||
updateData | |
66.67% |
4 / 6 |
|
0.00% |
0 / 1 |
4.59 | |||
isMaxDepthReached | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
getCacheKey | |
100.00% |
12 / 12 |
|
100.00% |
1 / 1 |
3 | |||
supportsNormalization | |
100.00% |
9 / 9 |
|
100.00% |
1 / 1 |
8 | |||
getObjectSimpleValue | |
54.55% |
6 / 11 |
|
0.00% |
0 / 1 |
9.38 |
1 | <?php |
2 | |
3 | namespace App\Domain\Notification\Serializer; |
4 | |
5 | use App\Domain\Documentation\Model\Document; |
6 | use App\Domain\Registry\Model\Contractor; |
7 | use App\Domain\Registry\Model\Mesurement; |
8 | use App\Domain\Registry\Model\Proof; |
9 | use App\Domain\Registry\Model\Request; |
10 | use App\Domain\Registry\Model\Treatment; |
11 | use App\Domain\Registry\Model\Violation; |
12 | use Symfony\Component\PropertyAccess\Exception\AccessException; |
13 | use Symfony\Component\Serializer\Exception\LogicException; |
14 | use Symfony\Component\Serializer\Normalizer\NormalizerInterface; |
15 | use Symfony\Component\Serializer\Normalizer\ObjectNormalizer; |
16 | |
17 | /* @phpstan-ignore class.extendsFinalByPhpDoc */ |
18 | class NotificationNormalizer extends ObjectNormalizer |
19 | { |
20 | private $maxDepthHandler; |
21 | private $objectClassResolver; |
22 | |
23 | public function normalize($object, $format = null, array $context = []): array|string|int|float|bool|\ArrayObject|null |
24 | { |
25 | if (!isset($context['cache_key'])) { |
26 | $context['cache_key'] = $this->getCacheKey($format, $context); |
27 | } |
28 | |
29 | $this->validateCallbackContext($context); |
30 | |
31 | if ($this->isCircularReference($object, $context)) { |
32 | return $this->handleCircularReference($object, $format, $context); |
33 | } |
34 | |
35 | $data = []; |
36 | $stack = []; |
37 | $attributes = $this->getAttributes($object, $format, $context); |
38 | $class = $this->objectClassResolver ? ($this->objectClassResolver)($object) : \get_class($object); |
39 | $attributesMetadata = $this->classMetadataFactory ? $this->classMetadataFactory->getMetadataFor($class)->getAttributesMetadata() : null; |
40 | if (isset($context[self::MAX_DEPTH_HANDLER])) { |
41 | $maxDepthHandler = $context[self::MAX_DEPTH_HANDLER]; |
42 | if (!\is_callable($maxDepthHandler)) { |
43 | throw new \Symfony\Component\PropertyAccess\Exception\InvalidArgumentException(sprintf('The "%s" given in the context is not callable.', self::MAX_DEPTH_HANDLER)); |
44 | } |
45 | } else { |
46 | // already validated in constructor resp by type declaration of setMaxDepthHandler |
47 | $maxDepthHandler = $this->defaultContext[self::MAX_DEPTH_HANDLER] ?? $this->maxDepthHandler; |
48 | } |
49 | |
50 | foreach ($attributes as $attribute) { |
51 | $maxDepthReached = false; |
52 | if (null !== $attributesMetadata && ($maxDepthReached = $this->isMaxDepthReached($attributesMetadata, $class, $attribute, $context)) && !$maxDepthHandler) { |
53 | continue; |
54 | } |
55 | |
56 | try { |
57 | $attributeValue = $this->getAttributeValue($object, $attribute, $format, $context); |
58 | } catch (AccessException $e) { |
59 | if (sprintf('The property "%s::$%s" is not initialized.', \get_class($object), $attribute) === $e->getMessage()) { |
60 | continue; |
61 | } |
62 | if (($p = $e->getPrevious()) && 'Error' === \get_class($p) && $this->isUninitializedValueError($p)) { |
63 | continue; |
64 | } |
65 | |
66 | throw $e; |
67 | } catch (\Error $e) { |
68 | if ($this->isUninitializedValueError($e)) { |
69 | continue; |
70 | } |
71 | |
72 | throw $e; |
73 | } |
74 | |
75 | if ($maxDepthReached) { |
76 | $attributeValue = $maxDepthHandler($attributeValue, $object, $attribute, $format, $context); |
77 | } |
78 | |
79 | $attributeValue = $this->applyCallbacks($attributeValue, $object, $attribute, $format, $context); |
80 | |
81 | if (null !== $attributeValue && !is_scalar($attributeValue)) { |
82 | $stack[$attribute] = $attributeValue; |
83 | } |
84 | |
85 | $data = $this->updateData($data, $attribute, $attributeValue, $class, $format, $context); |
86 | } |
87 | |
88 | foreach ($stack as $attribute => $attributeValue) { |
89 | // if (!$this->serializer instanceof NormalizerInterface) { |
90 | // throw new LogicException(sprintf('Cannot normalize attribute "%s" because the injected serializer is not a normalizer.', $attribute)); |
91 | // } |
92 | |
93 | $data = $this->updateData($data, $attribute, $this->getObjectSimpleValue($attributeValue), $class, $format, $context); |
94 | } |
95 | |
96 | if (isset($context[self::PRESERVE_EMPTY_OBJECTS]) && !\count($data)) { |
97 | return new \ArrayObject(); |
98 | } |
99 | |
100 | return $data; |
101 | } |
102 | |
103 | private function isUninitializedValueError(\Error $e): bool |
104 | { |
105 | return \PHP_VERSION_ID >= 70400 |
106 | && str_starts_with($e->getMessage(), 'Typed property') |
107 | && str_ends_with($e->getMessage(), 'must not be accessed before initialization'); |
108 | } |
109 | |
110 | /** |
111 | * Sets an attribute and apply the name converter if necessary. |
112 | */ |
113 | private function updateData(array $data, string $attribute, $attributeValue, string $class, ?string $format, array $context): array |
114 | { |
115 | if (null === $attributeValue && ($context[self::SKIP_NULL_VALUES] ?? $this->defaultContext[self::SKIP_NULL_VALUES] ?? false)) { |
116 | return $data; |
117 | } |
118 | |
119 | if ($this->nameConverter) { |
120 | $attribute = $this->nameConverter->normalize($attribute/* , $class, $format, $context */); |
121 | } |
122 | |
123 | $data[$attribute] = $attributeValue; |
124 | |
125 | return $data; |
126 | } |
127 | |
128 | private function isMaxDepthReached(array $attributesMetadata, string $class, string $attribute, array &$context): bool |
129 | { |
130 | return true; |
131 | } |
132 | |
133 | private function getCacheKey(?string $format, array $context): bool|string |
134 | { |
135 | foreach ($context[self::EXCLUDE_FROM_CACHE_KEY] ?? $this->defaultContext[self::EXCLUDE_FROM_CACHE_KEY] as $key) { |
136 | unset($context[$key]); |
137 | } |
138 | unset($context[self::EXCLUDE_FROM_CACHE_KEY]); |
139 | unset($context[self::OBJECT_TO_POPULATE]); |
140 | unset($context['cache_key']); // avoid artificially different keys |
141 | |
142 | try { |
143 | return md5($format . serialize([ |
144 | 'context' => $context, |
145 | 'ignored' => $context[self::IGNORED_ATTRIBUTES] ?? $this->defaultContext[self::IGNORED_ATTRIBUTES], |
146 | 'attributes' => $context[self::ATTRIBUTES] ?? $this->defaultContext[self::ATTRIBUTES], |
147 | ])); |
148 | } catch (\Exception $exception) { |
149 | // The context cannot be serialized, skip the cache |
150 | return false; |
151 | } |
152 | } |
153 | |
154 | public function supportsNormalization($data, $format = null, array $context = []): bool |
155 | { |
156 | return null == $format && ($data instanceof Treatment |
157 | || $data instanceof Contractor |
158 | || $data instanceof Mesurement |
159 | || $data instanceof Proof |
160 | || $data instanceof Request |
161 | || $data instanceof Document |
162 | || $data instanceof Violation |
163 | ) |
164 | ; |
165 | } |
166 | |
167 | public static function getObjectSimpleValue($object) |
168 | { |
169 | if (is_object($object)) { |
170 | if (method_exists($object, 'getId')) { |
171 | return $object->getId(); |
172 | } elseif (method_exists($object, '__toString')) { |
173 | return $object->__toString(); |
174 | } elseif (method_exists($object, 'format')) { |
175 | return $object->format(DATE_ATOM); |
176 | } |
177 | |
178 | return ''; |
179 | } |
180 | |
181 | if (is_array($object)) { |
182 | return join(', ', $object); |
183 | } |
184 | |
185 | return $object; |
186 | } |
187 | } |