Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
78.31% covered (warning)
78.31%
65 / 83
57.14% covered (warning)
57.14%
4 / 7
CRAP
0.00% covered (danger)
0.00%
0 / 1
NotificationNormalizer
78.31% covered (warning)
78.31%
65 / 83
57.14% covered (warning)
57.14%
4 / 7
73.49
0.00% covered (danger)
0.00%
0 / 1
 normalize
73.17% covered (warning)
73.17%
30 / 41
0.00% covered (danger)
0.00%
0 / 1
35.12
 isUninitializedValueError
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
3
 updateData
66.67% covered (warning)
66.67%
4 / 6
0.00% covered (danger)
0.00%
0 / 1
4.59
 isMaxDepthReached
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getCacheKey
100.00% covered (success)
100.00%
12 / 12
100.00% covered (success)
100.00%
1 / 1
3
 supportsNormalization
100.00% covered (success)
100.00%
9 / 9
100.00% covered (success)
100.00%
1 / 1
8
 getObjectSimpleValue
54.55% covered (warning)
54.55%
6 / 11
0.00% covered (danger)
0.00%
0 / 1
9.38
1<?php
2
3namespace App\Domain\Notification\Serializer;
4
5use App\Domain\Documentation\Model\Document;
6use App\Domain\Registry\Model\Contractor;
7use App\Domain\Registry\Model\Mesurement;
8use App\Domain\Registry\Model\Proof;
9use App\Domain\Registry\Model\Request;
10use App\Domain\Registry\Model\Treatment;
11use App\Domain\Registry\Model\Violation;
12use Symfony\Component\PropertyAccess\Exception\AccessException;
13use Symfony\Component\Serializer\Exception\LogicException;
14use Symfony\Component\Serializer\Normalizer\NormalizerInterface;
15use Symfony\Component\Serializer\Normalizer\ObjectNormalizer;
16
17class 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}