1 | <?php |
||||||
2 | |||||||
3 | /* |
||||||
4 | * This file is part of the API Platform project. |
||||||
5 | * |
||||||
6 | * (c) Kévin Dunglas <[email protected]> |
||||||
7 | * |
||||||
8 | * For the full copyright and license information, please view the LICENSE |
||||||
9 | * file that was distributed with this source code. |
||||||
10 | */ |
||||||
11 | |||||||
12 | declare(strict_types=1); |
||||||
13 | |||||||
14 | namespace ApiPlatform\Core\Serializer; |
||||||
15 | |||||||
16 | use ApiPlatform\Core\Api\IriConverterInterface; |
||||||
17 | use ApiPlatform\Core\Api\ResourceClassResolverInterface; |
||||||
18 | use ApiPlatform\Core\DataProvider\ItemDataProviderInterface; |
||||||
19 | use ApiPlatform\Core\DataTransformer\DataTransformerInterface; |
||||||
20 | use ApiPlatform\Core\Exception\InvalidArgumentException; |
||||||
21 | use ApiPlatform\Core\Exception\InvalidValueException; |
||||||
22 | use ApiPlatform\Core\Exception\ItemNotFoundException; |
||||||
23 | use ApiPlatform\Core\Metadata\Property\Factory\PropertyMetadataFactoryInterface; |
||||||
24 | use ApiPlatform\Core\Metadata\Property\Factory\PropertyNameCollectionFactoryInterface; |
||||||
25 | use ApiPlatform\Core\Metadata\Property\PropertyMetadata; |
||||||
26 | use ApiPlatform\Core\Metadata\Resource\Factory\ResourceMetadataFactoryInterface; |
||||||
27 | use ApiPlatform\Core\Util\ClassInfoTrait; |
||||||
28 | use Symfony\Component\PropertyAccess\Exception\NoSuchPropertyException; |
||||||
29 | use Symfony\Component\PropertyAccess\PropertyAccess; |
||||||
30 | use Symfony\Component\PropertyAccess\PropertyAccessorInterface; |
||||||
31 | use Symfony\Component\PropertyInfo\Type; |
||||||
32 | use Symfony\Component\Serializer\Exception\LogicException; |
||||||
33 | use Symfony\Component\Serializer\Exception\MissingConstructorArgumentsException; |
||||||
34 | use Symfony\Component\Serializer\Exception\RuntimeException; |
||||||
35 | use Symfony\Component\Serializer\Exception\UnexpectedValueException; |
||||||
36 | use Symfony\Component\Serializer\Mapping\Factory\ClassMetadataFactoryInterface; |
||||||
37 | use Symfony\Component\Serializer\NameConverter\AdvancedNameConverterInterface; |
||||||
38 | use Symfony\Component\Serializer\NameConverter\NameConverterInterface; |
||||||
39 | use Symfony\Component\Serializer\Normalizer\AbstractObjectNormalizer; |
||||||
40 | use Symfony\Component\Serializer\Normalizer\DenormalizerInterface; |
||||||
41 | use Symfony\Component\Serializer\Normalizer\NormalizerInterface; |
||||||
42 | |||||||
43 | /** |
||||||
44 | * Base item normalizer. |
||||||
45 | * |
||||||
46 | * @author Kévin Dunglas <[email protected]> |
||||||
47 | */ |
||||||
48 | abstract class AbstractItemNormalizer extends AbstractObjectNormalizer |
||||||
49 | { |
||||||
50 | use ClassInfoTrait; |
||||||
51 | use ContextTrait; |
||||||
52 | use InputOutputMetadataTrait; |
||||||
53 | |||||||
54 | protected $propertyNameCollectionFactory; |
||||||
55 | protected $propertyMetadataFactory; |
||||||
56 | protected $iriConverter; |
||||||
57 | protected $resourceClassResolver; |
||||||
58 | protected $propertyAccessor; |
||||||
59 | protected $itemDataProvider; |
||||||
60 | protected $allowPlainIdentifiers; |
||||||
61 | protected $dataTransformers = []; |
||||||
62 | protected $localCache = []; |
||||||
63 | |||||||
64 | public function __construct(PropertyNameCollectionFactoryInterface $propertyNameCollectionFactory, PropertyMetadataFactoryInterface $propertyMetadataFactory, IriConverterInterface $iriConverter, ResourceClassResolverInterface $resourceClassResolver, PropertyAccessorInterface $propertyAccessor = null, NameConverterInterface $nameConverter = null, ClassMetadataFactoryInterface $classMetadataFactory = null, ItemDataProviderInterface $itemDataProvider = null, bool $allowPlainIdentifiers = false, array $defaultContext = [], iterable $dataTransformers = [], ResourceMetadataFactoryInterface $resourceMetadataFactory = null) |
||||||
65 | { |
||||||
66 | if (!isset($defaultContext['circular_reference_handler'])) { |
||||||
67 | $defaultContext['circular_reference_handler'] = function ($object) { |
||||||
68 | return $this->iriConverter->getIriFromItem($object); |
||||||
69 | }; |
||||||
70 | } |
||||||
71 | if (!interface_exists(AdvancedNameConverterInterface::class)) { |
||||||
72 | $this->setCircularReferenceHandler($defaultContext['circular_reference_handler']); |
||||||
0 ignored issues
–
show
|
|||||||
73 | } |
||||||
74 | |||||||
75 | parent::__construct($classMetadataFactory, $nameConverter, null, null, \Closure::fromCallable([$this, 'getObjectClass']), $defaultContext); |
||||||
76 | |||||||
77 | $this->propertyNameCollectionFactory = $propertyNameCollectionFactory; |
||||||
78 | $this->propertyMetadataFactory = $propertyMetadataFactory; |
||||||
79 | $this->iriConverter = $iriConverter; |
||||||
80 | $this->resourceClassResolver = $resourceClassResolver; |
||||||
81 | $this->propertyAccessor = $propertyAccessor ?: PropertyAccess::createPropertyAccessor(); |
||||||
82 | $this->itemDataProvider = $itemDataProvider; |
||||||
83 | $this->allowPlainIdentifiers = $allowPlainIdentifiers; |
||||||
84 | $this->dataTransformers = $dataTransformers; |
||||||
85 | $this->resourceMetadataFactory = $resourceMetadataFactory; |
||||||
86 | } |
||||||
87 | |||||||
88 | /** |
||||||
89 | * {@inheritdoc} |
||||||
90 | */ |
||||||
91 | public function supportsNormalization($data, $format = null) |
||||||
92 | { |
||||||
93 | if (!\is_object($data) || $data instanceof \Traversable) { |
||||||
94 | return false; |
||||||
95 | } |
||||||
96 | |||||||
97 | return $this->resourceClassResolver->isResourceClass($this->getObjectClass($data)); |
||||||
98 | } |
||||||
99 | |||||||
100 | /** |
||||||
101 | * {@inheritdoc} |
||||||
102 | */ |
||||||
103 | public function hasCacheableSupportsMethod(): bool |
||||||
104 | { |
||||||
105 | return true; |
||||||
106 | } |
||||||
107 | |||||||
108 | /** |
||||||
109 | * {@inheritdoc} |
||||||
110 | * |
||||||
111 | * @throws LogicException |
||||||
112 | */ |
||||||
113 | public function normalize($object, $format = null, array $context = []) |
||||||
114 | { |
||||||
115 | if ($object !== $transformed = $this->transformOutput($object, $context)) { |
||||||
116 | if (!$this->serializer instanceof NormalizerInterface) { |
||||||
117 | throw new LogicException('Cannot normalize the output because the injected serializer is not a normalizer'); |
||||||
118 | } |
||||||
119 | |||||||
120 | $context['api_normalize'] = true; |
||||||
121 | $context['api_resource'] = $object; |
||||||
122 | unset($context['output']); |
||||||
123 | unset($context['resource_class']); |
||||||
124 | |||||||
125 | return $this->serializer->normalize($transformed, $format, $context); |
||||||
126 | } |
||||||
127 | |||||||
128 | $resourceClass = $this->resourceClassResolver->getResourceClass($object, $context['resource_class'] ?? null); |
||||||
129 | $context = $this->initContext($resourceClass, $context); |
||||||
130 | $iri = $context['iri'] ?? $this->iriConverter->getIriFromItem($object); |
||||||
131 | $context['iri'] = $iri; |
||||||
132 | $context['api_normalize'] = true; |
||||||
133 | |||||||
134 | /* |
||||||
135 | * When true, converts the normalized data array of a resource into an |
||||||
136 | * IRI, if the normalized data array is empty. |
||||||
137 | * |
||||||
138 | * This is useful when traversing from a non-resource towards an attribute |
||||||
139 | * which is a resource, as we do not have the benefit of {@see PropertyMetadata::isReadableLink}. |
||||||
140 | * |
||||||
141 | * It must not be propagated to subresources, as {@see PropertyMetadata::isReadableLink} |
||||||
142 | * should take effect. |
||||||
143 | */ |
||||||
144 | $emptyResourceAsIri = $context['api_empty_resource_as_iri'] ?? false; |
||||||
145 | unset($context['api_empty_resource_as_iri']); |
||||||
146 | |||||||
147 | if (isset($context['resources'])) { |
||||||
148 | $context['resources'][$iri] = $iri; |
||||||
149 | } |
||||||
150 | |||||||
151 | $data = parent::normalize($object, $format, $context); |
||||||
152 | if ($emptyResourceAsIri && \is_array($data) && 0 === \count($data)) { |
||||||
153 | return $iri; |
||||||
154 | } |
||||||
155 | |||||||
156 | return $data; |
||||||
157 | } |
||||||
158 | |||||||
159 | /** |
||||||
160 | * {@inheritdoc} |
||||||
161 | */ |
||||||
162 | public function supportsDenormalization($data, $type, $format = null) |
||||||
163 | { |
||||||
164 | return $this->localCache[$type] ?? $this->localCache[$type] = $this->resourceClassResolver->isResourceClass($type); |
||||||
165 | } |
||||||
166 | |||||||
167 | /** |
||||||
168 | * {@inheritdoc} |
||||||
169 | */ |
||||||
170 | public function denormalize($data, $class, $format = null, array $context = []) |
||||||
171 | { |
||||||
172 | $resourceClass = $this->resourceClassResolver->getResourceClass(null, $class); |
||||||
173 | $context['api_denormalize'] = true; |
||||||
174 | $context['resource_class'] = $resourceClass; |
||||||
175 | |||||||
176 | if (null !== ($inputClass = $this->getInputClass($resourceClass, $context)) && null !== ($dataTransformer = $this->getDataTransformer($data, $resourceClass, $context))) { |
||||||
177 | $dataTransformerContext = $context; |
||||||
178 | |||||||
179 | unset($context['input']); |
||||||
180 | unset($context['resource_class']); |
||||||
181 | |||||||
182 | if (!$this->serializer instanceof DenormalizerInterface) { |
||||||
183 | throw new LogicException('Cannot denormalize the input because the injected serializer is not a denormalizer'); |
||||||
184 | } |
||||||
185 | $denormalizedInput = $this->serializer->denormalize($data, $inputClass, $format, $context); |
||||||
186 | if (!\is_object($denormalizedInput)) { |
||||||
187 | throw new \UnexpectedValueException('Expected denormalized input to be an object.'); |
||||||
188 | } |
||||||
189 | |||||||
190 | return $dataTransformer->transform($denormalizedInput, $resourceClass, $dataTransformerContext); |
||||||
191 | } |
||||||
192 | |||||||
193 | $supportsPlainIdentifiers = $this->supportsPlainIdentifiers(); |
||||||
194 | |||||||
195 | if (\is_string($data)) { |
||||||
196 | try { |
||||||
197 | return $this->iriConverter->getItemFromIri($data, $context + ['fetch_data' => true]); |
||||||
198 | } catch (ItemNotFoundException $e) { |
||||||
199 | if (!$supportsPlainIdentifiers) { |
||||||
200 | throw new UnexpectedValueException($e->getMessage(), $e->getCode(), $e); |
||||||
201 | } |
||||||
202 | } catch (InvalidArgumentException $e) { |
||||||
203 | if (!$supportsPlainIdentifiers) { |
||||||
204 | throw new UnexpectedValueException(sprintf('Invalid IRI "%s".', $data), $e->getCode(), $e); |
||||||
205 | } |
||||||
206 | } |
||||||
207 | } |
||||||
208 | |||||||
209 | if (!\is_array($data)) { |
||||||
210 | if (!$supportsPlainIdentifiers) { |
||||||
211 | throw new UnexpectedValueException(sprintf('Expected IRI or document for resource "%s", "%s" given.', $resourceClass, \gettype($data))); |
||||||
212 | } |
||||||
213 | |||||||
214 | $item = $this->itemDataProvider->getItem($resourceClass, $data, null, $context + ['fetch_data' => true]); |
||||||
0 ignored issues
–
show
The method
getItem() does not exist on null .
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces. This is most likely a typographical error or the method has been renamed.
Loading history...
|
|||||||
215 | if (null === $item) { |
||||||
216 | throw new ItemNotFoundException(sprintf('Item not found for resource "%s" with id "%s".', $resourceClass, $data)); |
||||||
217 | } |
||||||
218 | |||||||
219 | return $item; |
||||||
220 | } |
||||||
221 | |||||||
222 | return parent::denormalize($data, $resourceClass, $format, $context); |
||||||
223 | } |
||||||
224 | |||||||
225 | /** |
||||||
226 | * Method copy-pasted from symfony/serializer. |
||||||
227 | * Remove it after symfony/serializer version update @link https://github.com/symfony/symfony/pull/28263. |
||||||
228 | * |
||||||
229 | * {@inheritdoc} |
||||||
230 | * |
||||||
231 | * @internal |
||||||
232 | */ |
||||||
233 | protected function instantiateObject(array &$data, $class, array &$context, \ReflectionClass $reflectionClass, $allowedAttributes, string $format = null) |
||||||
234 | { |
||||||
235 | if (null !== $object = $this->extractObjectToPopulate($class, $context, static::OBJECT_TO_POPULATE)) { |
||||||
236 | unset($context[static::OBJECT_TO_POPULATE]); |
||||||
237 | |||||||
238 | return $object; |
||||||
239 | } |
||||||
240 | |||||||
241 | if ($this->classDiscriminatorResolver && $mapping = $this->classDiscriminatorResolver->getMappingForClass($class)) { |
||||||
242 | if (!isset($data[$mapping->getTypeProperty()])) { |
||||||
243 | throw new RuntimeException(sprintf('Type property "%s" not found for the abstract object "%s"', $mapping->getTypeProperty(), $class)); |
||||||
244 | } |
||||||
245 | |||||||
246 | $type = $data[$mapping->getTypeProperty()]; |
||||||
247 | if (null === ($mappedClass = $mapping->getClassForType($type))) { |
||||||
248 | throw new RuntimeException(sprintf('The type "%s" has no mapped class for the abstract object "%s"', $type, $class)); |
||||||
249 | } |
||||||
250 | |||||||
251 | $class = $mappedClass; |
||||||
252 | $reflectionClass = new \ReflectionClass($class); |
||||||
253 | } |
||||||
254 | |||||||
255 | $constructor = $this->getConstructor($data, $class, $context, $reflectionClass, $allowedAttributes); |
||||||
256 | if ($constructor) { |
||||||
257 | $constructorParameters = $constructor->getParameters(); |
||||||
258 | |||||||
259 | $params = []; |
||||||
260 | foreach ($constructorParameters as $constructorParameter) { |
||||||
261 | $paramName = $constructorParameter->name; |
||||||
262 | $key = $this->nameConverter ? $this->nameConverter->normalize($paramName, $class, $format, $context) : $paramName; |
||||||
0 ignored issues
–
show
The call to
Symfony\Component\Serial...rInterface::normalize() has too many arguments starting with $class .
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue. If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress. Please note the @ignore annotation hint above.
Loading history...
|
|||||||
263 | |||||||
264 | $allowed = false === $allowedAttributes || (\is_array($allowedAttributes) && \in_array($paramName, $allowedAttributes, true)); |
||||||
265 | $ignored = !$this->isAllowedAttribute($class, $paramName, $format, $context); |
||||||
266 | if ($constructorParameter->isVariadic()) { |
||||||
267 | if ($allowed && !$ignored && (isset($data[$key]) || \array_key_exists($key, $data))) { |
||||||
268 | if (!\is_array($data[$paramName])) { |
||||||
269 | throw new RuntimeException(sprintf('Cannot create an instance of %s from serialized data because the variadic parameter %s can only accept an array.', $class, $constructorParameter->name)); |
||||||
270 | } |
||||||
271 | |||||||
272 | $params = array_merge($params, $data[$paramName]); |
||||||
273 | } |
||||||
274 | } elseif ($allowed && !$ignored && (isset($data[$key]) || \array_key_exists($key, $data))) { |
||||||
275 | $params[] = $this->createConstructorArgument($data[$key], $key, $constructorParameter, $context, $format); |
||||||
276 | |||||||
277 | // Don't run set for a parameter passed to the constructor |
||||||
278 | unset($data[$key]); |
||||||
279 | } elseif (isset($context[static::DEFAULT_CONSTRUCTOR_ARGUMENTS][$class][$key])) { |
||||||
280 | $params[] = $context[static::DEFAULT_CONSTRUCTOR_ARGUMENTS][$class][$key]; |
||||||
281 | } elseif ($constructorParameter->isDefaultValueAvailable()) { |
||||||
282 | $params[] = $constructorParameter->getDefaultValue(); |
||||||
283 | } else { |
||||||
284 | throw new MissingConstructorArgumentsException( |
||||||
285 | sprintf( |
||||||
286 | 'Cannot create an instance of %s from serialized data because its constructor requires parameter "%s" to be present.', |
||||||
287 | $class, |
||||||
288 | $constructorParameter->name |
||||||
289 | ) |
||||||
290 | ); |
||||||
291 | } |
||||||
292 | } |
||||||
293 | |||||||
294 | if ($constructor->isConstructor()) { |
||||||
295 | return $reflectionClass->newInstanceArgs($params); |
||||||
296 | } |
||||||
297 | |||||||
298 | return $constructor->invokeArgs(null, $params); |
||||||
299 | } |
||||||
300 | |||||||
301 | return new $class(); |
||||||
302 | } |
||||||
303 | |||||||
304 | /** |
||||||
305 | * {@inheritdoc} |
||||||
306 | */ |
||||||
307 | protected function createConstructorArgument($parameterData, string $key, \ReflectionParameter $constructorParameter, array &$context, string $format = null) |
||||||
0 ignored issues
–
show
The parameter
$key is not used and could be removed.
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
This check looks for parameters that have been defined for a function or method, but which are not used in the method body.
Loading history...
|
|||||||
308 | { |
||||||
309 | return $this->createAttributeValue($constructorParameter->name, $parameterData, $format, $context); |
||||||
310 | } |
||||||
311 | |||||||
312 | /** |
||||||
313 | * {@inheritdoc} |
||||||
314 | * |
||||||
315 | * Unused in this context. |
||||||
316 | */ |
||||||
317 | protected function extractAttributes($object, $format = null, array $context = []) |
||||||
318 | { |
||||||
319 | return []; |
||||||
320 | } |
||||||
321 | |||||||
322 | /** |
||||||
323 | * {@inheritdoc} |
||||||
324 | */ |
||||||
325 | protected function getAllowedAttributes($classOrObject, array $context, $attributesAsString = false) |
||||||
326 | { |
||||||
327 | $options = $this->getFactoryOptions($context); |
||||||
328 | $propertyNames = $this->propertyNameCollectionFactory->create($context['resource_class'], $options); |
||||||
329 | |||||||
330 | $allowedAttributes = []; |
||||||
331 | foreach ($propertyNames as $propertyName) { |
||||||
332 | $propertyMetadata = $this->propertyMetadataFactory->create($context['resource_class'], $propertyName, $options); |
||||||
333 | |||||||
334 | if ( |
||||||
335 | $this->isAllowedAttribute($classOrObject, $propertyName, null, $context) && |
||||||
336 | ( |
||||||
337 | isset($context['api_normalize']) && $propertyMetadata->isReadable() || |
||||||
338 | isset($context['api_denormalize']) && ($propertyMetadata->isWritable() || !\is_object($classOrObject) && $propertyMetadata->isInitializable()) |
||||||
339 | ) |
||||||
340 | ) { |
||||||
341 | $allowedAttributes[] = $propertyName; |
||||||
342 | } |
||||||
343 | } |
||||||
344 | |||||||
345 | return $allowedAttributes; |
||||||
346 | } |
||||||
347 | |||||||
348 | /** |
||||||
349 | * {@inheritdoc} |
||||||
350 | */ |
||||||
351 | protected function setAttributeValue($object, $attribute, $value, $format = null, array $context = []) |
||||||
352 | { |
||||||
353 | $this->setValue($object, $attribute, $this->createAttributeValue($attribute, $value, $format, $context)); |
||||||
354 | } |
||||||
355 | |||||||
356 | /** |
||||||
357 | * Validates the type of the value. Allows using integers as floats for JSON formats. |
||||||
358 | * |
||||||
359 | * @throws InvalidArgumentException |
||||||
360 | */ |
||||||
361 | protected function validateType(string $attribute, Type $type, $value, string $format = null) |
||||||
362 | { |
||||||
363 | $builtinType = $type->getBuiltinType(); |
||||||
364 | if (Type::BUILTIN_TYPE_FLOAT === $builtinType && null !== $format && false !== strpos($format, 'json')) { |
||||||
365 | $isValid = \is_float($value) || \is_int($value); |
||||||
366 | } else { |
||||||
367 | $isValid = \call_user_func('is_'.$builtinType, $value); |
||||||
368 | } |
||||||
369 | |||||||
370 | if (!$isValid) { |
||||||
371 | throw new InvalidArgumentException(sprintf( |
||||||
372 | 'The type of the "%s" attribute must be "%s", "%s" given.', $attribute, $builtinType, \gettype($value) |
||||||
373 | )); |
||||||
374 | } |
||||||
375 | } |
||||||
376 | |||||||
377 | /** |
||||||
378 | * Denormalizes a collection of objects. |
||||||
379 | * |
||||||
380 | * @throws InvalidArgumentException |
||||||
381 | */ |
||||||
382 | protected function denormalizeCollection(string $attribute, PropertyMetadata $propertyMetadata, Type $type, string $className, $value, ?string $format, array $context): array |
||||||
383 | { |
||||||
384 | if (!\is_array($value)) { |
||||||
385 | throw new InvalidArgumentException(sprintf( |
||||||
386 | 'The type of the "%s" attribute must be "array", "%s" given.', $attribute, \gettype($value) |
||||||
387 | )); |
||||||
388 | } |
||||||
389 | |||||||
390 | $collectionKeyType = $type->getCollectionKeyType(); |
||||||
391 | $collectionKeyBuiltinType = null === $collectionKeyType ? null : $collectionKeyType->getBuiltinType(); |
||||||
392 | |||||||
393 | $values = []; |
||||||
394 | foreach ($value as $index => $obj) { |
||||||
395 | if (null !== $collectionKeyBuiltinType && !\call_user_func('is_'.$collectionKeyBuiltinType, $index)) { |
||||||
396 | throw new InvalidArgumentException(sprintf( |
||||||
397 | 'The type of the key "%s" must be "%s", "%s" given.', |
||||||
398 | $index, $collectionKeyBuiltinType, \gettype($index)) |
||||||
399 | ); |
||||||
400 | } |
||||||
401 | |||||||
402 | $values[$index] = $this->denormalizeRelation($attribute, $propertyMetadata, $className, $obj, $format, $this->createChildContext($context, $attribute, $format)); |
||||||
403 | } |
||||||
404 | |||||||
405 | return $values; |
||||||
406 | } |
||||||
407 | |||||||
408 | /** |
||||||
409 | * Denormalizes a relation. |
||||||
410 | * |
||||||
411 | * @throws LogicException |
||||||
412 | * @throws UnexpectedValueException |
||||||
413 | * @throws ItemNotFoundException |
||||||
414 | * |
||||||
415 | * @return object|null |
||||||
416 | */ |
||||||
417 | protected function denormalizeRelation(string $attributeName, PropertyMetadata $propertyMetadata, string $className, $value, ?string $format, array $context) |
||||||
418 | { |
||||||
419 | $supportsPlainIdentifiers = $this->supportsPlainIdentifiers(); |
||||||
420 | |||||||
421 | if (\is_string($value)) { |
||||||
422 | try { |
||||||
423 | return $this->iriConverter->getItemFromIri($value, $context + ['fetch_data' => true]); |
||||||
424 | } catch (ItemNotFoundException $e) { |
||||||
425 | if (!$supportsPlainIdentifiers) { |
||||||
426 | throw new UnexpectedValueException($e->getMessage(), $e->getCode(), $e); |
||||||
427 | } |
||||||
428 | } catch (InvalidArgumentException $e) { |
||||||
429 | if (!$supportsPlainIdentifiers) { |
||||||
430 | throw new UnexpectedValueException(sprintf('Invalid IRI "%s".', $value), $e->getCode(), $e); |
||||||
431 | } |
||||||
432 | } |
||||||
433 | } |
||||||
434 | |||||||
435 | if ($propertyMetadata->isWritableLink()) { |
||||||
436 | $context['api_allow_update'] = true; |
||||||
437 | |||||||
438 | if (!$this->serializer instanceof DenormalizerInterface) { |
||||||
439 | throw new LogicException(sprintf('The injected serializer must be an instance of "%s".', DenormalizerInterface::class)); |
||||||
440 | } |
||||||
441 | |||||||
442 | try { |
||||||
443 | $item = $this->serializer->denormalize($value, $className, $format, $context); |
||||||
444 | if (!\is_object($item) && null !== $item) { |
||||||
445 | throw new \UnexpectedValueException('Expected item to be an object or null.'); |
||||||
446 | } |
||||||
447 | |||||||
448 | return $item; |
||||||
449 | } catch (InvalidValueException $e) { |
||||||
450 | if (!$supportsPlainIdentifiers) { |
||||||
451 | throw $e; |
||||||
452 | } |
||||||
453 | } |
||||||
454 | } |
||||||
455 | |||||||
456 | if (!\is_array($value)) { |
||||||
457 | if (!$supportsPlainIdentifiers) { |
||||||
458 | throw new UnexpectedValueException(sprintf( |
||||||
459 | 'Expected IRI or nested document for attribute "%s", "%s" given.', $attributeName, \gettype($value) |
||||||
460 | )); |
||||||
461 | } |
||||||
462 | |||||||
463 | $item = $this->itemDataProvider->getItem($className, $value, null, $context + ['fetch_data' => true]); |
||||||
464 | if (null === $item) { |
||||||
465 | throw new ItemNotFoundException(sprintf('Item not found for resource "%s" with id "%s".', $className, $value)); |
||||||
466 | } |
||||||
467 | |||||||
468 | return $item; |
||||||
469 | } |
||||||
470 | |||||||
471 | throw new UnexpectedValueException(sprintf('Nested documents for attribute "%s" are not allowed. Use IRIs instead.', $attributeName)); |
||||||
472 | } |
||||||
473 | |||||||
474 | /** |
||||||
475 | * Gets a valid context for property name collection / property metadata factories. |
||||||
476 | */ |
||||||
477 | protected function getFactoryOptions(array $context): array |
||||||
478 | { |
||||||
479 | $options = []; |
||||||
480 | |||||||
481 | if (isset($context[self::GROUPS])) { |
||||||
482 | /* @see https://github.com/symfony/symfony/blob/v4.2.6/src/Symfony/Component/PropertyInfo/Extractor/SerializerExtractor.php */ |
||||||
483 | $options['serializer_groups'] = $context[self::GROUPS]; |
||||||
484 | } |
||||||
485 | |||||||
486 | if (isset($context['collection_operation_name'])) { |
||||||
487 | $options['collection_operation_name'] = $context['collection_operation_name']; |
||||||
488 | } |
||||||
489 | |||||||
490 | if (isset($context['item_operation_name'])) { |
||||||
491 | $options['item_operation_name'] = $context['item_operation_name']; |
||||||
492 | } |
||||||
493 | |||||||
494 | return $options; |
||||||
495 | } |
||||||
496 | |||||||
497 | /** |
||||||
498 | * Creates the context to use when serializing a relation. |
||||||
499 | * |
||||||
500 | * @deprecated since version 2.1, to be removed in 3.0. |
||||||
501 | */ |
||||||
502 | protected function createRelationSerializationContext(string $resourceClass, array $context): array |
||||||
0 ignored issues
–
show
The parameter
$resourceClass is not used and could be removed.
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
This check looks for parameters that have been defined for a function or method, but which are not used in the method body.
Loading history...
|
|||||||
503 | { |
||||||
504 | @trigger_error(sprintf('The method %s() is deprecated since 2.1 and will be removed in 3.0.', __METHOD__), E_USER_DEPRECATED); |
||||||
505 | |||||||
506 | return $context; |
||||||
507 | } |
||||||
508 | |||||||
509 | /** |
||||||
510 | * {@inheritdoc} |
||||||
511 | * |
||||||
512 | * @throws NoSuchPropertyException |
||||||
513 | * @throws LogicException |
||||||
514 | */ |
||||||
515 | protected function getAttributeValue($object, $attribute, $format = null, array $context = []) |
||||||
516 | { |
||||||
517 | $context['api_attribute'] = $attribute; |
||||||
518 | $propertyMetadata = $this->propertyMetadataFactory->create($context['resource_class'], $attribute, $this->getFactoryOptions($context)); |
||||||
519 | |||||||
520 | try { |
||||||
521 | $attributeValue = $this->propertyAccessor->getValue($object, $attribute); |
||||||
522 | } catch (NoSuchPropertyException $e) { |
||||||
523 | if (!$propertyMetadata->hasChildInherited()) { |
||||||
524 | throw $e; |
||||||
525 | } |
||||||
526 | |||||||
527 | $attributeValue = null; |
||||||
528 | } |
||||||
529 | |||||||
530 | $type = $propertyMetadata->getType(); |
||||||
531 | |||||||
532 | if ( |
||||||
533 | is_iterable($attributeValue) && |
||||||
534 | $type && |
||||||
535 | $type->isCollection() && |
||||||
536 | ($collectionValueType = $type->getCollectionValueType()) && |
||||||
537 | ($className = $collectionValueType->getClassName()) && |
||||||
538 | $this->resourceClassResolver->isResourceClass($className) |
||||||
539 | ) { |
||||||
540 | $resourceClass = $this->resourceClassResolver->getResourceClass($attributeValue, $className); |
||||||
541 | $childContext = $this->createChildContext($context, $attribute, $format); |
||||||
542 | $childContext['resource_class'] = $resourceClass; |
||||||
543 | unset($childContext['iri']); |
||||||
544 | |||||||
545 | return $this->normalizeCollectionOfRelations($propertyMetadata, $attributeValue, $resourceClass, $format, $childContext); |
||||||
546 | } |
||||||
547 | |||||||
548 | if ( |
||||||
549 | $type && |
||||||
550 | ($className = $type->getClassName()) && |
||||||
551 | $this->resourceClassResolver->isResourceClass($className) |
||||||
552 | ) { |
||||||
553 | $resourceClass = $this->resourceClassResolver->getResourceClass($attributeValue, $className); |
||||||
554 | $childContext = $this->createChildContext($context, $attribute, $format); |
||||||
555 | $childContext['resource_class'] = $resourceClass; |
||||||
556 | unset($childContext['iri']); |
||||||
557 | |||||||
558 | return $this->normalizeRelation($propertyMetadata, $attributeValue, $resourceClass, $format, $childContext); |
||||||
559 | } |
||||||
560 | |||||||
561 | if (!$this->serializer instanceof NormalizerInterface) { |
||||||
562 | throw new LogicException(sprintf('The injected serializer must be an instance of "%s".', NormalizerInterface::class)); |
||||||
563 | } |
||||||
564 | |||||||
565 | unset($context['resource_class']); |
||||||
566 | |||||||
567 | return $this->serializer->normalize($attributeValue, $format, $context); |
||||||
568 | } |
||||||
569 | |||||||
570 | /** |
||||||
571 | * Normalizes a collection of relations (to-many). |
||||||
572 | * |
||||||
573 | * @param iterable $attributeValue |
||||||
574 | */ |
||||||
575 | protected function normalizeCollectionOfRelations(PropertyMetadata $propertyMetadata, $attributeValue, string $resourceClass, ?string $format, array $context): array |
||||||
576 | { |
||||||
577 | $value = []; |
||||||
578 | foreach ($attributeValue as $index => $obj) { |
||||||
579 | $value[$index] = $this->normalizeRelation($propertyMetadata, $obj, $resourceClass, $format, $context); |
||||||
580 | } |
||||||
581 | |||||||
582 | return $value; |
||||||
583 | } |
||||||
584 | |||||||
585 | /** |
||||||
586 | * Normalizes a relation as an object if is a Link or as an URI. |
||||||
587 | * |
||||||
588 | * @throws LogicException |
||||||
589 | * |
||||||
590 | * @return string|array |
||||||
591 | */ |
||||||
592 | protected function normalizeRelation(PropertyMetadata $propertyMetadata, $relatedObject, string $resourceClass, ?string $format, array $context) |
||||||
593 | { |
||||||
594 | if (null === $relatedObject || !empty($context['attributes']) || $propertyMetadata->isReadableLink()) { |
||||||
595 | if (!$this->serializer instanceof NormalizerInterface) { |
||||||
596 | throw new LogicException(sprintf('The injected serializer must be an instance of "%s".', NormalizerInterface::class)); |
||||||
597 | } |
||||||
598 | |||||||
599 | return $this->serializer->normalize($relatedObject, $format, $context); |
||||||
0 ignored issues
–
show
|
|||||||
600 | } |
||||||
601 | |||||||
602 | $iri = $this->iriConverter->getIriFromItem($relatedObject); |
||||||
603 | if (isset($context['resources'])) { |
||||||
604 | $context['resources'][$iri] = $iri; |
||||||
605 | } |
||||||
606 | if (isset($context['resources_to_push']) && $propertyMetadata->getAttribute('push', false)) { |
||||||
607 | $context['resources_to_push'][$iri] = $iri; |
||||||
608 | } |
||||||
609 | |||||||
610 | return $iri; |
||||||
611 | } |
||||||
612 | |||||||
613 | /** |
||||||
614 | * Finds the first supported data transformer if any. |
||||||
615 | * |
||||||
616 | * @param object|array $data object on normalize / array on denormalize |
||||||
617 | */ |
||||||
618 | protected function getDataTransformer($data, string $to, array $context = []): ?DataTransformerInterface |
||||||
619 | { |
||||||
620 | foreach ($this->dataTransformers as $dataTransformer) { |
||||||
621 | if ($dataTransformer->supportsTransformation($data, $to, $context)) { |
||||||
622 | return $dataTransformer; |
||||||
623 | } |
||||||
624 | } |
||||||
625 | |||||||
626 | return null; |
||||||
627 | } |
||||||
628 | |||||||
629 | /** |
||||||
630 | * For a given resource, it returns an output representation if any |
||||||
631 | * If not, the resource is returned. |
||||||
632 | */ |
||||||
633 | protected function transformOutput($object, array $context = []) |
||||||
634 | { |
||||||
635 | $outputClass = $this->getOutputClass($this->getObjectClass($object), $context); |
||||||
636 | if (null !== $outputClass && null !== $dataTransformer = $this->getDataTransformer($object, $outputClass, $context)) { |
||||||
637 | return $dataTransformer->transform($object, $outputClass, $context); |
||||||
638 | } |
||||||
639 | |||||||
640 | return $object; |
||||||
641 | } |
||||||
642 | |||||||
643 | private function createAttributeValue($attribute, $value, $format = null, array $context = []) |
||||||
644 | { |
||||||
645 | $propertyMetadata = $this->propertyMetadataFactory->create($context['resource_class'], $attribute, $this->getFactoryOptions($context)); |
||||||
646 | $type = $propertyMetadata->getType(); |
||||||
647 | |||||||
648 | if (null === $type) { |
||||||
649 | // No type provided, blindly return the value |
||||||
650 | return $value; |
||||||
651 | } |
||||||
652 | |||||||
653 | if (null === $value && $type->isNullable()) { |
||||||
654 | return $value; |
||||||
655 | } |
||||||
656 | |||||||
657 | if ( |
||||||
658 | $type->isCollection() && |
||||||
659 | null !== ($collectionValueType = $type->getCollectionValueType()) && |
||||||
660 | null !== ($className = $collectionValueType->getClassName()) && |
||||||
661 | $this->resourceClassResolver->isResourceClass($className) |
||||||
662 | ) { |
||||||
663 | $resourceClass = $this->resourceClassResolver->getResourceClass(null, $className); |
||||||
664 | $context['resource_class'] = $resourceClass; |
||||||
665 | |||||||
666 | return $this->denormalizeCollection($attribute, $propertyMetadata, $type, $resourceClass, $value, $format, $context); |
||||||
667 | } |
||||||
668 | |||||||
669 | if ( |
||||||
670 | null !== ($className = $type->getClassName()) && |
||||||
671 | $this->resourceClassResolver->isResourceClass($className) |
||||||
672 | ) { |
||||||
673 | $resourceClass = $this->resourceClassResolver->getResourceClass(null, $className); |
||||||
674 | $childContext = $this->createChildContext($context, $attribute, $format); |
||||||
675 | $childContext['resource_class'] = $resourceClass; |
||||||
676 | |||||||
677 | return $this->denormalizeRelation($attribute, $propertyMetadata, $resourceClass, $value, $format, $childContext); |
||||||
678 | } |
||||||
679 | |||||||
680 | if ( |
||||||
681 | $type->isCollection() && |
||||||
682 | null !== ($collectionValueType = $type->getCollectionValueType()) && |
||||||
683 | null !== ($className = $collectionValueType->getClassName()) |
||||||
684 | ) { |
||||||
685 | if (!$this->serializer instanceof DenormalizerInterface) { |
||||||
686 | throw new LogicException(sprintf('The injected serializer must be an instance of "%s".', DenormalizerInterface::class)); |
||||||
687 | } |
||||||
688 | |||||||
689 | unset($context['resource_class']); |
||||||
690 | |||||||
691 | return $this->serializer->denormalize($value, $className.'[]', $format, $context); |
||||||
692 | } |
||||||
693 | |||||||
694 | if (null !== $className = $type->getClassName()) { |
||||||
695 | if (!$this->serializer instanceof DenormalizerInterface) { |
||||||
696 | throw new LogicException(sprintf('The injected serializer must be an instance of "%s".', DenormalizerInterface::class)); |
||||||
697 | } |
||||||
698 | |||||||
699 | unset($context['resource_class']); |
||||||
700 | |||||||
701 | return $this->serializer->denormalize($value, $className, $format, $context); |
||||||
702 | } |
||||||
703 | |||||||
704 | if ($context[static::DISABLE_TYPE_ENFORCEMENT] ?? false) { |
||||||
705 | return $value; |
||||||
706 | } |
||||||
707 | |||||||
708 | $this->validateType($attribute, $type, $value, $format); |
||||||
709 | |||||||
710 | return $value; |
||||||
711 | } |
||||||
712 | |||||||
713 | /** |
||||||
714 | * Sets a value of the object using the PropertyAccess component. |
||||||
715 | * |
||||||
716 | * @param object $object |
||||||
717 | */ |
||||||
718 | private function setValue($object, string $attributeName, $value) |
||||||
719 | { |
||||||
720 | try { |
||||||
721 | $this->propertyAccessor->setValue($object, $attributeName, $value); |
||||||
722 | } catch (NoSuchPropertyException $exception) { |
||||||
723 | // Properties not found are ignored |
||||||
724 | } |
||||||
725 | } |
||||||
726 | |||||||
727 | private function supportsPlainIdentifiers(): bool |
||||||
728 | { |
||||||
729 | return $this->allowPlainIdentifiers && null !== $this->itemDataProvider; |
||||||
730 | } |
||||||
731 | } |
||||||
732 |
This function has been deprecated. The supplier of the function has supplied an explanatory message.
The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.