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
17/* @phpstan-ignore class.extendsFinalByPhpDoc */
18class 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}