1 | <?php |
||||
2 | |||||
3 | declare(strict_types=1); |
||||
4 | |||||
5 | namespace JMS\Serializer\GraphNavigator; |
||||
6 | |||||
7 | use JMS\Serializer\Accessor\AccessorStrategyInterface; |
||||
8 | use JMS\Serializer\Construction\ObjectConstructorInterface; |
||||
9 | use JMS\Serializer\DeserializationContext; |
||||
10 | use JMS\Serializer\EventDispatcher\EventDispatcher; |
||||
11 | use JMS\Serializer\EventDispatcher\EventDispatcherInterface; |
||||
12 | use JMS\Serializer\EventDispatcher\ObjectEvent; |
||||
13 | use JMS\Serializer\EventDispatcher\PreDeserializeEvent; |
||||
14 | use JMS\Serializer\Exception\ExpressionLanguageRequiredException; |
||||
15 | use JMS\Serializer\Exception\LogicException; |
||||
16 | use JMS\Serializer\Exception\NotAcceptableException; |
||||
17 | use JMS\Serializer\Exception\RuntimeException; |
||||
18 | use JMS\Serializer\Exception\SkipHandlerException; |
||||
19 | use JMS\Serializer\Exclusion\ExpressionLanguageExclusionStrategy; |
||||
20 | use JMS\Serializer\Expression\ExpressionEvaluatorInterface; |
||||
21 | use JMS\Serializer\GraphNavigator; |
||||
22 | use JMS\Serializer\GraphNavigatorInterface; |
||||
23 | use JMS\Serializer\Handler\HandlerRegistryInterface; |
||||
24 | use JMS\Serializer\Metadata\ClassMetadata; |
||||
25 | use JMS\Serializer\NullAwareVisitorInterface; |
||||
26 | use JMS\Serializer\Visitor\DeserializationVisitorInterface; |
||||
27 | use Metadata\MetadataFactoryInterface; |
||||
28 | |||||
29 | /** |
||||
30 | * Handles traversal along the object graph. |
||||
31 | * |
||||
32 | * This class handles traversal along the graph, and calls different methods |
||||
33 | * on visitors, or custom handlers to process its nodes. |
||||
34 | * |
||||
35 | * @author Johannes M. Schmitt <[email protected]> |
||||
36 | */ |
||||
37 | final class DeserializationGraphNavigator extends GraphNavigator implements GraphNavigatorInterface |
||||
38 | { |
||||
39 | /** |
||||
40 | * @var DeserializationVisitorInterface |
||||
41 | */ |
||||
42 | protected $visitor; |
||||
43 | |||||
44 | /** |
||||
45 | * @var DeserializationContext |
||||
46 | */ |
||||
47 | protected $context; |
||||
48 | |||||
49 | /** |
||||
50 | * @var ExpressionLanguageExclusionStrategy |
||||
51 | */ |
||||
52 | private $expressionExclusionStrategy; |
||||
53 | |||||
54 | /** |
||||
55 | * @var EventDispatcherInterface |
||||
56 | */ |
||||
57 | private $dispatcher; |
||||
58 | |||||
59 | /** |
||||
60 | * @var MetadataFactoryInterface |
||||
61 | */ |
||||
62 | 141 | private $metadataFactory; |
|||
63 | |||||
64 | /** |
||||
65 | * @var HandlerRegistryInterface |
||||
66 | */ |
||||
67 | private $handlerRegistry; |
||||
68 | |||||
69 | /** |
||||
70 | 141 | * @var ObjectConstructorInterface |
|||
71 | 141 | */ |
|||
72 | 141 | private $objectConstructor; |
|||
73 | 141 | /** |
|||
74 | 141 | * @var AccessorStrategyInterface |
|||
75 | 141 | */ |
|||
76 | 1 | private $accessor; |
|||
77 | |||||
78 | 141 | public function __construct( |
|||
79 | MetadataFactoryInterface $metadataFactory, |
||||
80 | HandlerRegistryInterface $handlerRegistry, |
||||
81 | ObjectConstructorInterface $objectConstructor, |
||||
82 | AccessorStrategyInterface $accessor, |
||||
83 | ?EventDispatcherInterface $dispatcher = null, |
||||
84 | ?ExpressionEvaluatorInterface $expressionEvaluator = null |
||||
85 | ) { |
||||
86 | $this->dispatcher = $dispatcher ?: new EventDispatcher(); |
||||
87 | 136 | $this->metadataFactory = $metadataFactory; |
|||
88 | $this->handlerRegistry = $handlerRegistry; |
||||
89 | $this->objectConstructor = $objectConstructor; |
||||
90 | $this->accessor = $accessor; |
||||
91 | 136 | if ($expressionEvaluator) { |
|||
92 | $this->expressionExclusionStrategy = new ExpressionLanguageExclusionStrategy($expressionEvaluator); |
||||
93 | } |
||||
94 | } |
||||
95 | |||||
96 | 136 | /** |
|||
97 | 13 | * Called for each node of the graph that is being traversed. |
|||
98 | * |
||||
99 | * @param mixed $data the data depends on the direction, and type of visitor |
||||
100 | 136 | * @param array|null $type array has the format ["name" => string, "params" => array] |
|||
101 | 136 | * |
|||
102 | 15 | * @return mixed the return value depends on the direction, and type of visitor |
|||
103 | */ |
||||
104 | 122 | public function accept($data, ?array $type = null) |
|||
105 | 51 | { |
|||
106 | // If the type was not given, we infer the most specific type from the |
||||
107 | 117 | // input data in serialization mode. |
|||
108 | 117 | if (null === $type) { |
|||
109 | 17 | throw new RuntimeException('The type must be given for all properties when deserializing.'); |
|||
110 | } |
||||
111 | 113 | ||||
112 | 113 | // Sometimes data can convey null but is not of a null type. |
|||
113 | 13 | // Visitors can have the power to add this custom null evaluation |
|||
114 | if ($this->visitor instanceof NullAwareVisitorInterface && true === $this->visitor->isNull($data)) { |
||||
115 | 105 | $type = ['name' => 'NULL', 'params' => []]; |
|||
116 | 99 | } |
|||
117 | 25 | ||||
118 | switch ($type['name']) { |
||||
119 | 93 | case 'NULL': |
|||
120 | 36 | return $this->visitor->visitNull($data, $type); |
|||
0 ignored issues
–
show
|
|||||
121 | |||||
122 | 81 | case 'string': |
|||
123 | return $this->visitor->visitString($data, $type); |
||||
124 | |||||
125 | case 'int': |
||||
126 | case 'integer': |
||||
127 | 81 | return $this->visitor->visitInteger($data, $type); |
|||
128 | |||||
129 | case 'bool': |
||||
130 | case 'boolean': |
||||
131 | 81 | return $this->visitor->visitBoolean($data, $type); |
|||
132 | |||||
133 | case 'double': |
||||
134 | case 'float': |
||||
135 | return $this->visitor->visitDouble($data, $type); |
||||
136 | |||||
137 | case 'array': |
||||
138 | case 'iterable': |
||||
139 | case 'list': |
||||
140 | 81 | return $this->visitor->visitArray($data, $type); |
|||
141 | 26 | ||||
142 | 26 | case 'resource': |
|||
143 | throw new RuntimeException('Resources are not supported in serialized data.'); |
||||
144 | 26 | ||||
145 | default: |
||||
146 | $this->context->increaseDepth(); |
||||
147 | |||||
148 | 70 | // Trigger pre-serialization callbacks, and listeners if they exist. |
|||
149 | // Dispatch pre-serialization event before handling data to have ability change type in listener |
||||
150 | 70 | if ($this->dispatcher->hasListeners('serializer.pre_deserialize', $type['name'], $this->format)) { |
|||
151 | $this->dispatcher->dispatch('serializer.pre_deserialize', $type['name'], $this->format, $event = new PreDeserializeEvent($this->context, $data, $type)); |
||||
152 | $type = $event->getType(); |
||||
153 | $data = $event->getData(); |
||||
154 | 70 | } |
|||
155 | 14 | ||||
156 | // First, try whether a custom handler exists for the given type. This is done |
||||
157 | // before loading metadata because the type name might not be a class, but |
||||
158 | 68 | // could also simply be an artifical type. |
|||
159 | if (null !== $handler = $this->handlerRegistry->getHandler(GraphNavigatorInterface::DIRECTION_DESERIALIZATION, $type['name'], $this->format)) { |
||||
160 | try { |
||||
161 | $rs = \call_user_func($handler, $this->visitor, $data, $type, $this->context); |
||||
0 ignored issues
–
show
It seems like
$handler can also be of type object ; however, parameter $callback of call_user_func() does only seem to accept callable , maybe add an additional type check?
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
Loading history...
|
|||||
162 | $this->context->decreaseDepth(); |
||||
163 | |||||
164 | 68 | return $rs; |
|||
165 | } catch (SkipHandlerException $e) { |
||||
166 | 68 | // Skip handler, fallback to default behavior |
|||
167 | } |
||||
168 | 68 | } |
|||
169 | 68 | ||||
170 | 68 | $metadata = $this->metadataFactory->getMetadataForClass($type['name']); |
|||
171 | \assert($metadata instanceof ClassMetadata); |
||||
172 | |||||
173 | if ($metadata->usingExpression && !$this->expressionExclusionStrategy) { |
||||
174 | 68 | throw new ExpressionLanguageRequiredException(sprintf('To use conditional exclude/expose in %s you must configure the expression language.', $metadata->name)); |
|||
175 | } |
||||
176 | |||||
177 | if (!empty($metadata->discriminatorMap) && $type['name'] === $metadata->discriminatorBaseClass) { |
||||
178 | 68 | $metadata = $this->resolveMetadata($data, $metadata); |
|||
179 | 18 | } |
|||
180 | |||||
181 | if (null !== $this->exclusionStrategy && $this->exclusionStrategy->shouldSkipClass($metadata, $this->context)) { |
||||
182 | 64 | $this->context->decreaseDepth(); |
|||
183 | |||||
184 | 64 | return null; |
|||
185 | 64 | } |
|||
186 | 4 | ||||
187 | $this->context->pushClassMetadata($metadata); |
||||
188 | |||||
189 | 64 | $object = $this->objectConstructor->construct($this->visitor, $metadata, $data, $type, $this->context); |
|||
190 | |||||
191 | if (null === $object) { |
||||
192 | 67 | $this->context->popClassMetadata(); |
|||
193 | 67 | $this->context->decreaseDepth(); |
|||
194 | |||||
195 | 67 | return $this->visitor->visitNull($data, $type); |
|||
0 ignored issues
–
show
Are you sure the usage of
$this->visitor->visitNull($data, $type) targeting JMS\Serializer\Visitor\D...rInterface::visitNull() seems to always return null.
This check looks for function or method calls that always return null and whose return value is used. class A
{
function getObject()
{
return null;
}
}
$a = new A();
if ($a->getObject()) {
The method The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.
Loading history...
|
|||||
196 | } |
||||
197 | |||||
198 | $this->visitor->startVisitingObject($metadata, $object, $type); |
||||
199 | 14 | foreach ($metadata->propertyMetadata as $propertyMetadata) { |
|||
200 | if (null !== $this->exclusionStrategy && $this->exclusionStrategy->shouldSkipProperty($propertyMetadata, $this->context)) { |
||||
201 | 14 | continue; |
|||
202 | } |
||||
203 | 12 | ||||
204 | if (null !== $this->expressionExclusionStrategy && $this->expressionExclusionStrategy->shouldSkipProperty($propertyMetadata, $this->context)) { |
||||
205 | continue; |
||||
206 | } |
||||
207 | |||||
208 | if ($propertyMetadata->readOnly) { |
||||
209 | continue; |
||||
210 | } |
||||
211 | |||||
212 | 12 | $this->context->pushPropertyMetadata($propertyMetadata); |
|||
213 | try { |
||||
214 | $v = $this->visitor->visitProperty($propertyMetadata, $data); |
||||
215 | 67 | $this->accessor->setValue($object, $v, $propertyMetadata, $this->context); |
|||
216 | } catch (NotAcceptableException $e) { |
||||
217 | 67 | if (true === $propertyMetadata->hasDefault) { |
|||
218 | 67 | $cloned = clone $propertyMetadata; |
|||
219 | $cloned->setter = null; |
||||
220 | 67 | $this->accessor->setValue($object, $cloned->defaultValue, $cloned, $this->context); |
|||
221 | 4 | } |
|||
222 | } |
||||
223 | |||||
224 | 67 | $this->context->popPropertyMetadata(); |
|||
225 | 1 | } |
|||
226 | |||||
227 | 67 | $rs = $this->visitor->endVisitingObject($metadata, $data, $type); |
|||
228 | $this->afterVisitingObject($metadata, $rs, $type); |
||||
229 | |||||
230 | return $rs; |
||||
231 | } |
||||
232 | } |
||||
233 | |||||
234 | /** |
||||
235 | * @param mixed $data |
||||
236 | */ |
||||
237 | private function resolveMetadata($data, ClassMetadata $metadata): ?ClassMetadata |
||||
238 | { |
||||
239 | $typeValue = $this->visitor->visitDiscriminatorMapProperty($data, $metadata); |
||||
240 | |||||
241 | if (!isset($metadata->discriminatorMap[$typeValue])) { |
||||
242 | throw new LogicException(sprintf( |
||||
243 | 'The type value "%s" does not exist in the discriminator map of class "%s". Available types: %s', |
||||
244 | $typeValue, |
||||
245 | $metadata->name, |
||||
246 | implode(', ', array_keys($metadata->discriminatorMap)), |
||||
247 | )); |
||||
248 | } |
||||
249 | |||||
250 | return $this->metadataFactory->getMetadataForClass($metadata->discriminatorMap[$typeValue]); |
||||
0 ignored issues
–
show
|
|||||
251 | } |
||||
252 | |||||
253 | private function afterVisitingObject(ClassMetadata $metadata, object $object, array $type): void |
||||
254 | { |
||||
255 | $this->context->decreaseDepth(); |
||||
256 | $this->context->popClassMetadata(); |
||||
257 | |||||
258 | foreach ($metadata->postDeserializeMethods as $method) { |
||||
259 | $method->invoke($object); |
||||
260 | } |
||||
261 | |||||
262 | if ($this->dispatcher->hasListeners('serializer.post_deserialize', $metadata->name, $this->format)) { |
||||
263 | $this->dispatcher->dispatch('serializer.post_deserialize', $metadata->name, $this->format, new ObjectEvent($this->context, $object, $type)); |
||||
264 | } |
||||
265 | } |
||||
266 | } |
||||
267 |
This check looks for function or method calls that always return null and whose return value is used.
The method
getObject()
can return nothing but null, so it makes no sense to use the return value.The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.