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