• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

api-platform / core / 6690762601

30 Oct 2023 09:24AM UTC coverage: 67.319% (+0.02%) from 67.301%
6690762601

push

github

web-flow
ci: fix PHPUNIT (#5907)

* ci: fix phpunit


???

* Unset handler_id for symfony 6.3+

* Fix serializer configuration for PHP 8.1 (dev)

* Fix https://github.com/api-platform/api-platform/issues/2437

* Fix excepted deprecation in swagger


Trigger deprecation to fit tests. Can change test if needed


forgot semicolon


try fix deprecation

* remove copied WebTestCase to fix 8.1 dev

PR https://github.com/symfony/symfony/pull/32207 got merged

* fix no deprecation

* try tag legacy to valide


add a bc layer for reworked profiler UI

* fix warning about deprecated method


ensure method exists

* skip an exceptDeprecation, this case fails for a particular CI run

* remove uneccesary changes

* change BC deprecation system for doctrine

* fix some deprecations about validation html mode and attributes for recent sf

* fix doctrine lexer deprecations

* fix bootstrap missing

* improve doc for sf 6


f


Fix tiny bug & deprecation


--

* fix possible deprecation on 7.4

* Update ci.yml

* Update ci.yml

15610 of 23188 relevant lines covered (67.32%)

10.87 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

72.68
/src/Serializer/AbstractItemNormalizer.php
1
<?php
2

3
/*
4
 * This file is part of the API Platform project.
5
 *
6
 * (c) Kévin Dunglas <dunglas@gmail.com>
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\Serializer;
15

16
use ApiPlatform\Api\IriConverterInterface;
17
use ApiPlatform\Api\UrlGeneratorInterface;
18
use ApiPlatform\Core\Api\IriConverterInterface as LegacyIriConverterInterface;
19
use ApiPlatform\Core\Bridge\Symfony\Messenger\DataTransformer as MessengerDataTransformer;
20
use ApiPlatform\Core\DataProvider\ItemDataProviderInterface;
21
use ApiPlatform\Core\DataTransformer\DataTransformerInitializerInterface;
22
use ApiPlatform\Core\DataTransformer\DataTransformerInterface;
23
use ApiPlatform\Core\Metadata\Property\Factory\PropertyMetadataFactoryInterface as LegacyPropertyMetadataFactoryInterface;
24
use ApiPlatform\Core\Metadata\Property\PropertyMetadata;
25
use ApiPlatform\Core\Metadata\Resource\Factory\ResourceMetadataFactoryInterface;
26
use ApiPlatform\Exception\InvalidArgumentException;
27
use ApiPlatform\Exception\InvalidValueException;
28
use ApiPlatform\Exception\ItemNotFoundException;
29
use ApiPlatform\Metadata\ApiProperty;
30
use ApiPlatform\Metadata\CollectionOperationInterface;
31
use ApiPlatform\Metadata\Property\Factory\PropertyMetadataFactoryInterface;
32
use ApiPlatform\Metadata\Property\Factory\PropertyNameCollectionFactoryInterface;
33
use ApiPlatform\Metadata\Resource\Factory\ResourceMetadataCollectionFactoryInterface;
34
use ApiPlatform\Symfony\Security\ResourceAccessCheckerInterface;
35
use ApiPlatform\Util\ClassInfoTrait;
36
use ApiPlatform\Util\CloneTrait;
37
use Symfony\Component\PropertyAccess\Exception\NoSuchPropertyException;
38
use Symfony\Component\PropertyAccess\PropertyAccess;
39
use Symfony\Component\PropertyAccess\PropertyAccessorInterface;
40
use Symfony\Component\PropertyInfo\Type;
41
use Symfony\Component\Serializer\Encoder\CsvEncoder;
42
use Symfony\Component\Serializer\Encoder\XmlEncoder;
43
use Symfony\Component\Serializer\Exception\LogicException;
44
use Symfony\Component\Serializer\Exception\MissingConstructorArgumentsException;
45
use Symfony\Component\Serializer\Exception\NotNormalizableValueException;
46
use Symfony\Component\Serializer\Exception\RuntimeException;
47
use Symfony\Component\Serializer\Exception\UnexpectedValueException;
48
use Symfony\Component\Serializer\Mapping\Factory\ClassMetadataFactoryInterface;
49
use Symfony\Component\Serializer\NameConverter\AdvancedNameConverterInterface;
50
use Symfony\Component\Serializer\NameConverter\NameConverterInterface;
51
use Symfony\Component\Serializer\Normalizer\AbstractObjectNormalizer;
52
use Symfony\Component\Serializer\Normalizer\DenormalizerInterface;
53
use Symfony\Component\Serializer\Normalizer\NormalizerInterface;
54

55
/**
56
 * Base item normalizer.
57
 *
58
 * @author Kévin Dunglas <dunglas@gmail.com>
59
 */
60
abstract class AbstractItemNormalizer extends AbstractObjectNormalizer
61
{
62
    use ClassInfoTrait;
63
    use CloneTrait;
64
    use ContextTrait;
65
    use InputOutputMetadataTrait;
66
    use OperationContextTrait;
67

68
    public const IS_TRANSFORMED_TO_SAME_CLASS = 'is_transformed_to_same_class';
69

70
    /**
71
     * @var PropertyNameCollectionFactoryInterface
72
     */
73
    protected $propertyNameCollectionFactory;
74
    /**
75
     * @var LegacyPropertyMetadataFactoryInterface|PropertyMetadataFactoryInterface
76
     */
77
    protected $propertyMetadataFactory;
78
    protected $resourceMetadataFactory;
79
    /**
80
     * @var LegacyIriConverterInterface|IriConverterInterface
81
     */
82
    protected $iriConverter;
83
    protected $resourceClassResolver;
84
    protected $resourceAccessChecker;
85
    protected $propertyAccessor;
86
    protected $itemDataProvider;
87
    protected $allowPlainIdentifiers;
88
    protected $dataTransformers = [];
89
    protected $localCache = [];
90

91
    public function __construct(PropertyNameCollectionFactoryInterface $propertyNameCollectionFactory, $propertyMetadataFactory, $iriConverter, $resourceClassResolver, PropertyAccessorInterface $propertyAccessor = null, NameConverterInterface $nameConverter = null, ClassMetadataFactoryInterface $classMetadataFactory = null, ItemDataProviderInterface $itemDataProvider = null, bool $allowPlainIdentifiers = false, array $defaultContext = [], iterable $dataTransformers = [], $resourceMetadataFactory = null, ResourceAccessCheckerInterface $resourceAccessChecker = null)
92
    {
93
        if (!isset($defaultContext['circular_reference_handler'])) {
85✔
94
            $defaultContext['circular_reference_handler'] = function ($object) {
85✔
95
                return $this->iriConverter instanceof LegacyIriConverterInterface ? $this->iriConverter->getIriFromItem($object) : $this->iriConverter->getIriFromResource($object);
1✔
96
            };
85✔
97
        }
98
        if (!interface_exists(AdvancedNameConverterInterface::class) && method_exists($this, 'setCircularReferenceHandler')) {
85✔
99
            $this->setCircularReferenceHandler($defaultContext['circular_reference_handler']);
×
100
        }
101

102
        parent::__construct($classMetadataFactory, $nameConverter, null, null, \Closure::fromCallable([$this, 'getObjectClass']), $defaultContext);
85✔
103

104
        $this->propertyNameCollectionFactory = $propertyNameCollectionFactory;
85✔
105
        $this->propertyMetadataFactory = $propertyMetadataFactory;
85✔
106

107
        if ($iriConverter instanceof LegacyIriConverterInterface) {
85✔
108
            trigger_deprecation('api-platform/core', '2.7', sprintf('Use an implementation of "%s" instead of "%s".', IriConverterInterface::class, LegacyIriConverterInterface::class));
54✔
109
        }
110

111
        $this->iriConverter = $iriConverter;
85✔
112
        $this->resourceClassResolver = $resourceClassResolver;
85✔
113
        $this->propertyAccessor = $propertyAccessor ?: PropertyAccess::createPropertyAccessor();
85✔
114
        $this->itemDataProvider = $itemDataProvider;
85✔
115

116
        if (true === $allowPlainIdentifiers) {
85✔
117
            @trigger_error(sprintf('Allowing plain identifiers as argument of "%s" is deprecated since API Platform 2.7 and will not be possible anymore in API Platform 3.', self::class), \E_USER_DEPRECATED);
2✔
118
        }
119
        $this->allowPlainIdentifiers = $allowPlainIdentifiers;
85✔
120

121
        $this->dataTransformers = $dataTransformers;
85✔
122

123
        // Just skip our data transformer to trigger a proper deprecation
124
        $customDataTransformers = array_filter(\is_array($dataTransformers) ? $dataTransformers : iterator_to_array($dataTransformers), function ($dataTransformer) {
85✔
125
            return !$dataTransformer instanceof MessengerDataTransformer;
29✔
126
        });
85✔
127

128
        if (\count($customDataTransformers)) {
85✔
129
            trigger_deprecation('api-platform/core', '2.7', 'The DataTransformer pattern is deprecated, use a Provider or a Processor and either use your input or return a new output there.');
29✔
130
        }
131

132
        if ($resourceMetadataFactory && !$resourceMetadataFactory instanceof ResourceMetadataCollectionFactoryInterface) {
85✔
133
            trigger_deprecation('api-platform/core', '2.7', sprintf('Use "%s" instead of "%s".', ResourceMetadataCollectionFactoryInterface::class, ResourceMetadataFactoryInterface::class));
14✔
134
        }
135

136
        $this->resourceMetadataFactory = $resourceMetadataFactory;
85✔
137
        $this->resourceAccessChecker = $resourceAccessChecker;
85✔
138
    }
139

140
    /**
141
     * {@inheritdoc}
142
     */
143
    public function supportsNormalization($data, $format = null, array $context = []): bool
144
    {
145
        if (!\is_object($data) || is_iterable($data)) {
21✔
146
            return false;
15✔
147
        }
148

149
        $class = $this->getObjectClass($data);
15✔
150
        if (($context['output']['class'] ?? null) === $class) {
15✔
151
            return true;
×
152
        }
153

154
        return $this->resourceClassResolver->isResourceClass($class);
15✔
155
    }
156

157
    /**
158
     * {@inheritdoc}
159
     */
160
    public function hasCacheableSupportsMethod(): bool
161
    {
162
        return true;
21✔
163
    }
164

165
    /**
166
     * {@inheritdoc}
167
     *
168
     * @throws LogicException
169
     *
170
     * @return array|string|int|float|bool|\ArrayObject|null
171
     */
172
    public function normalize($object, $format = null, array $context = [])
173
    {
174
        $resourceClass = $this->getObjectClass($object);
24✔
175
        if (!($isTransformed = isset($context[self::IS_TRANSFORMED_TO_SAME_CLASS])) && $outputClass = $this->getOutputClass($resourceClass, $context)) {
24✔
176
            if (!$this->serializer instanceof NormalizerInterface) {
2✔
177
                throw new LogicException('Cannot normalize the output because the injected serializer is not a normalizer');
×
178
            }
179

180
            // Data transformers are deprecated, this is removed from 3.0
181
            if ($dataTransformer = $this->getDataTransformer($object, $outputClass, $context)) {
2✔
182
                $transformed = $dataTransformer->transform($object, $outputClass, $context);
2✔
183

184
                if ($object === $transformed) {
2✔
185
                    $context[self::IS_TRANSFORMED_TO_SAME_CLASS] = true;
×
186
                } else {
187
                    $context['api_normalize'] = true;
2✔
188
                    $context['api_resource'] = $object;
2✔
189
                    unset($context['output'], $context['resource_class']);
2✔
190
                }
191

192
                return $this->serializer->normalize($transformed, $format, $context);
2✔
193
            }
194

195
            unset($context['output'], $context['operation_name']);
×
196
            if ($this->resourceMetadataFactory instanceof ResourceMetadataCollectionFactoryInterface && !isset($context['operation'])) {
×
197
                $context['operation'] = $this->resourceMetadataFactory->create($context['resource_class'])->getOperation();
×
198
            }
199
            $context['resource_class'] = $outputClass;
×
200
            $context['api_sub_level'] = true;
×
201
            $context[self::ALLOW_EXTRA_ATTRIBUTES] = false;
×
202

203
            return $this->serializer->normalize($object, $format, $context);
×
204
        }
205

206
        if ($isTransformed) {
22✔
207
            unset($context[self::IS_TRANSFORMED_TO_SAME_CLASS]);
×
208
        }
209

210
        if ($isResourceClass = $this->resourceClassResolver->isResourceClass($resourceClass)) {
22✔
211
            $context = $this->initContext($resourceClass, $context);
22✔
212
        }
213

214
        // Never remove this, with `application/json` we don't use our AbstractCollectionNormalizer and we need
215
        // to remove the collection operation from our context or we'll introduce security issues
216
        if (isset($context['operation']) && $context['operation'] instanceof CollectionOperationInterface) {
22✔
217
            unset($context['operation_name']);
×
218
            unset($context['operation']);
×
219
            unset($context['iri']);
×
220
        }
221

222
        $iri = null;
22✔
223
        if (isset($context['iri'])) {
22✔
224
            $iri = $context['iri'];
12✔
225
        } elseif ($this->iriConverter instanceof LegacyIriConverterInterface && $isResourceClass) {
10✔
226
            $iri = $this->iriConverter->getIriFromItem($object);
5✔
227
        } elseif ($this->iriConverter instanceof IriConverterInterface) {
5✔
228
            $iri = $this->iriConverter->getIriFromResource($object, UrlGeneratorInterface::ABS_URL, $context['operation'] ?? null, $context);
5✔
229
        }
230

231
        $context['iri'] = $iri;
22✔
232
        $context['api_normalize'] = true;
22✔
233

234
        /*
235
         * When true, converts the normalized data array of a resource into an
236
         * IRI, if the normalized data array is empty.
237
         *
238
         * This is useful when traversing from a non-resource towards an attribute
239
         * which is a resource, as we do not have the benefit of {@see PropertyMetadata::isReadableLink}.
240
         *
241
         * It must not be propagated to subresources, as {@see PropertyMetadata::isReadableLink}
242
         * should take effect.
243
         */
244
        $emptyResourceAsIri = $context['api_empty_resource_as_iri'] ?? false;
22✔
245
        unset($context['api_empty_resource_as_iri']);
22✔
246

247
        if (isset($context['resources'])) {
22✔
248
            $context['resources'][$iri] = $iri;
14✔
249
        }
250

251
        $data = parent::normalize($object, $format, $context);
22✔
252
        if ($emptyResourceAsIri && \is_array($data) && 0 === \count($data)) {
21✔
253
            return $iri;
×
254
        }
255

256
        return $data;
21✔
257
    }
258

259
    /**
260
     * {@inheritdoc}
261
     *
262
     * @return bool
263
     */
264
    public function supportsDenormalization($data, $type, $format = null, array $context = [])
265
    {
266
        if (($context['input']['class'] ?? null) === $type) {
8✔
267
            return true;
×
268
        }
269

270
        return $this->localCache[$type] ?? $this->localCache[$type] = $this->resourceClassResolver->isResourceClass($type);
8✔
271
    }
272

273
    /**
274
     * {@inheritdoc}
275
     *
276
     * @return mixed
277
     */
278
    public function denormalize($data, $class, $format = null, array $context = [])
279
    {
280
        $resourceClass = $class;
33✔
281

282
        if (null !== $inputClass = $this->getInputClass($resourceClass, $context)) {
33✔
283
            if (null !== $dataTransformer = $this->getDataTransformer($data, $resourceClass, $context)) {
2✔
284
                $dataTransformerContext = $context;
2✔
285

286
                unset($context['input']);
2✔
287
                unset($context['resource_class']);
2✔
288

289
                if (!$this->serializer instanceof DenormalizerInterface) {
2✔
290
                    throw new LogicException('Cannot denormalize the input because the injected serializer is not a denormalizer');
×
291
                }
292

293
                if ($dataTransformer instanceof DataTransformerInitializerInterface) {
2✔
294
                    $context[AbstractObjectNormalizer::OBJECT_TO_POPULATE] = $dataTransformer->initialize($inputClass, $context);
1✔
295
                    $context[AbstractObjectNormalizer::DEEP_OBJECT_TO_POPULATE] = true;
1✔
296
                }
297

298
                try {
299
                    $denormalizedInput = $this->serializer->denormalize($data, $inputClass, $format, $context);
2✔
300
                } catch (NotNormalizableValueException $e) {
×
301
                    throw new UnexpectedValueException('The input data is misformatted.', $e->getCode(), $e);
×
302
                }
303

304
                if (!\is_object($denormalizedInput)) {
2✔
305
                    throw new UnexpectedValueException('Expected denormalized input to be an object.');
×
306
                }
307

308
                return $dataTransformer->transform($denormalizedInput, $resourceClass, $dataTransformerContext);
2✔
309
            }
310

311
            unset($context['input']);
×
312
            unset($context['operation']);
×
313
            unset($context['operation_name']);
×
314
            $context['resource_class'] = $inputClass;
×
315

316
            if (!$this->serializer instanceof DenormalizerInterface) {
×
317
                throw new LogicException('Cannot denormalize the input because the injected serializer is not a denormalizer');
×
318
            }
319

320
            try {
321
                return $this->serializer->denormalize($data, $inputClass, $format, $context);
×
322
            } catch (NotNormalizableValueException $e) {
×
323
                throw new UnexpectedValueException('The input data is misformatted.', $e->getCode(), $e);
×
324
            }
325
        }
326

327
        if (null === $objectToPopulate = $this->extractObjectToPopulate($class, $context, static::OBJECT_TO_POPULATE)) {
32✔
328
            $normalizedData = \is_scalar($data) ? [$data] : $this->prepareForDenormalization($data);
28✔
329
            $class = $this->getClassDiscriminatorResolvedClass($normalizedData, $class);
28✔
330
        }
331

332
        $context['api_denormalize'] = true;
32✔
333

334
        if ($this->resourceClassResolver->isResourceClass($class)) {
32✔
335
            $resourceClass = $this->resourceClassResolver->getResourceClass($objectToPopulate, $class);
32✔
336
            $context['resource_class'] = $resourceClass;
32✔
337
        }
338

339
        $supportsPlainIdentifiers = $this->supportsPlainIdentifiers();
32✔
340

341
        if (\is_string($data)) {
32✔
342
            try {
343
                return $this->iriConverter instanceof LegacyIriConverterInterface ? $this->iriConverter->getItemFromIri($data, $context + ['fetch_data' => true]) : $this->iriConverter->getResourceFromIri($data, $context + ['fetch_data' => true]);
×
344
            } catch (ItemNotFoundException $e) {
×
345
                if (!$supportsPlainIdentifiers) {
×
346
                    throw new UnexpectedValueException($e->getMessage(), $e->getCode(), $e);
×
347
                }
348
            } catch (InvalidArgumentException $e) {
×
349
                if (!$supportsPlainIdentifiers) {
×
350
                    throw new UnexpectedValueException(sprintf('Invalid IRI "%s".', $data), $e->getCode(), $e);
×
351
                }
352
            }
353
        }
354

355
        if (!\is_array($data)) {
32✔
356
            if (!$supportsPlainIdentifiers) {
×
357
                throw new UnexpectedValueException(sprintf('Expected IRI or document for resource "%s", "%s" given.', $resourceClass, \gettype($data)));
×
358
            }
359

360
            $item = $this->itemDataProvider->getItem($resourceClass, $data, null, $context + ['fetch_data' => true]);
×
361
            if (null === $item) {
×
362
                throw new ItemNotFoundException(sprintf('Item not found for resource "%s" with id "%s".', $resourceClass, $data));
×
363
            }
364

365
            return $item;
×
366
        }
367

368
        $previousObject = $this->clone($objectToPopulate);
32✔
369
        $object = parent::denormalize($data, $resourceClass, $format, $context);
32✔
370

371
        if (!$this->resourceClassResolver->isResourceClass($context['resource_class'])) {
23✔
372
            return $object;
×
373
        }
374

375
        // Bypass the post-denormalize attribute revert logic if the object could not be
376
        // cloned since we cannot possibly revert any changes made to it.
377
        if (null !== $objectToPopulate && null === $previousObject) {
23✔
378
            return $object;
1✔
379
        }
380

381
        $options = $this->getFactoryOptions($context);
22✔
382
        $propertyNames = iterator_to_array($this->propertyNameCollectionFactory->create($resourceClass, $options));
22✔
383

384
        // Revert attributes that aren't allowed to be changed after a post-denormalize check
385
        foreach (array_keys($data) as $attribute) {
22✔
386
            $attribute = $this->nameConverter ? $this->nameConverter->denormalize((string) $attribute) : $attribute;
22✔
387
            if (!\in_array($attribute, $propertyNames, true)) {
22✔
388
                continue;
×
389
            }
390

391
            if (!$this->canAccessAttributePostDenormalize($object, $previousObject, $attribute, $context)) {
22✔
392
                if (null !== $previousObject) {
2✔
393
                    $this->setValue($object, $attribute, $this->propertyAccessor->getValue($previousObject, $attribute));
1✔
394
                } else {
395
                    $propertyMetadata = $this->propertyMetadataFactory->create($resourceClass, $attribute, $options);
1✔
396
                    $this->setValue($object, $attribute, $propertyMetadata->getDefault());
1✔
397
                }
398
            }
399
        }
400

401
        return $object;
22✔
402
    }
403

404
    /**
405
     * Method copy-pasted from symfony/serializer.
406
     * Remove it after symfony/serializer version update @see https://github.com/symfony/symfony/pull/28263.
407
     *
408
     * {@inheritdoc}
409
     *
410
     * @internal
411
     *
412
     * @return object
413
     */
414
    protected function instantiateObject(array &$data, $class, array &$context, \ReflectionClass $reflectionClass, $allowedAttributes, string $format = null)
415
    {
416
        if (null !== $object = $this->extractObjectToPopulate($class, $context, static::OBJECT_TO_POPULATE)) {
32✔
417
            unset($context[static::OBJECT_TO_POPULATE]);
4✔
418

419
            return $object;
4✔
420
        }
421

422
        $class = $this->getClassDiscriminatorResolvedClass($data, $class);
28✔
423
        $reflectionClass = new \ReflectionClass($class);
28✔
424

425
        $constructor = $this->getConstructor($data, $class, $context, $reflectionClass, $allowedAttributes);
28✔
426
        if ($constructor) {
28✔
427
            $constructorParameters = $constructor->getParameters();
27✔
428

429
            $params = [];
27✔
430
            foreach ($constructorParameters as $constructorParameter) {
27✔
431
                $paramName = $constructorParameter->name;
×
432
                $key = $this->nameConverter ? $this->nameConverter->normalize($paramName, $class, $format, $context) : $paramName;
×
433

434
                $allowed = false === $allowedAttributes || (\is_array($allowedAttributes) && \in_array($paramName, $allowedAttributes, true));
×
435
                $ignored = !$this->isAllowedAttribute($class, $paramName, $format, $context);
×
436
                if ($constructorParameter->isVariadic()) {
×
437
                    if ($allowed && !$ignored && (isset($data[$key]) || \array_key_exists($key, $data))) {
×
438
                        if (!\is_array($data[$paramName])) {
×
439
                            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));
×
440
                        }
441

442
                        $params = array_merge($params, $data[$paramName]);
×
443
                    }
444
                } elseif ($allowed && !$ignored && (isset($data[$key]) || \array_key_exists($key, $data))) {
×
445
                    $params[] = $this->createConstructorArgument($data[$key], $key, $constructorParameter, $context, $format);
×
446

447
                    // Don't run set for a parameter passed to the constructor
448
                    unset($data[$key]);
×
449
                } elseif (isset($context[static::DEFAULT_CONSTRUCTOR_ARGUMENTS][$class][$key])) {
×
450
                    $params[] = $context[static::DEFAULT_CONSTRUCTOR_ARGUMENTS][$class][$key];
×
451
                } elseif ($constructorParameter->isDefaultValueAvailable()) {
×
452
                    $params[] = $constructorParameter->getDefaultValue();
×
453
                } else {
454
                    throw new MissingConstructorArgumentsException(sprintf('Cannot create an instance of %s from serialized data because its constructor requires parameter "%s" to be present.', $class, $constructorParameter->name));
×
455
                }
456
            }
457

458
            if ($constructor->isConstructor()) {
27✔
459
                return $reflectionClass->newInstanceArgs($params);
27✔
460
            }
461

462
            return $constructor->invokeArgs(null, $params);
×
463
        }
464

465
        return new $class();
1✔
466
    }
467

468
    protected function getClassDiscriminatorResolvedClass(array &$data, string $class): string
469
    {
470
        if (null === $this->classDiscriminatorResolver || (null === $mapping = $this->classDiscriminatorResolver->getMappingForClass($class))) {
28✔
471
            return $class;
28✔
472
        }
473

474
        if (!isset($data[$mapping->getTypeProperty()])) {
×
475
            throw new RuntimeException(sprintf('Type property "%s" not found for the abstract object "%s"', $mapping->getTypeProperty(), $class));
×
476
        }
477

478
        $type = $data[$mapping->getTypeProperty()];
×
479
        if (null === ($mappedClass = $mapping->getClassForType($type))) {
×
480
            throw new RuntimeException(sprintf('The type "%s" has no mapped class for the abstract object "%s"', $type, $class));
×
481
        }
482

483
        return $mappedClass;
×
484
    }
485

486
    /**
487
     * {@inheritdoc}
488
     */
489
    protected function createConstructorArgument($parameterData, string $key, \ReflectionParameter $constructorParameter, array &$context, string $format = null)
490
    {
491
        return $this->createAttributeValue($constructorParameter->name, $parameterData, $format, $context);
×
492
    }
493

494
    /**
495
     * {@inheritdoc}
496
     *
497
     * Unused in this context.
498
     *
499
     * @return string[]
500
     */
501
    protected function extractAttributes($object, $format = null, array $context = [])
502
    {
503
        return [];
×
504
    }
505

506
    /**
507
     * {@inheritdoc}
508
     *
509
     * @return array|bool
510
     */
511
    protected function getAllowedAttributes($classOrObject, array $context, $attributesAsString = false)
512
    {
513
        if (!$this->resourceClassResolver->isResourceClass($context['resource_class'])) {
50✔
514
            return parent::getAllowedAttributes($classOrObject, $context, $attributesAsString);
×
515
        }
516

517
        $resourceClass = $this->resourceClassResolver->getResourceClass(null, $context['resource_class']); // fix for abstract classes and interfaces
50✔
518
        $options = $this->getFactoryOptions($context);
50✔
519
        $propertyNames = $this->propertyNameCollectionFactory->create($resourceClass, $options);
50✔
520

521
        $allowedAttributes = [];
50✔
522
        foreach ($propertyNames as $propertyName) {
50✔
523
            $propertyMetadata = $this->propertyMetadataFactory->create($resourceClass, $propertyName, $options);
50✔
524

525
            if (
526
                $this->isAllowedAttribute($classOrObject, $propertyName, null, $context) &&
50✔
527
                (
528
                    isset($context['api_normalize']) && $propertyMetadata->isReadable() ||
50✔
529
                    isset($context['api_denormalize']) && ($propertyMetadata->isWritable() || !\is_object($classOrObject) && $propertyMetadata->isInitializable())
50✔
530
                )
531
            ) {
532
                $allowedAttributes[] = $propertyName;
50✔
533
            }
534
        }
535

536
        return $allowedAttributes;
50✔
537
    }
538

539
    /**
540
     * {@inheritdoc}
541
     *
542
     * @return bool
543
     */
544
    protected function isAllowedAttribute($classOrObject, $attribute, $format = null, array $context = [])
545
    {
546
        if (!parent::isAllowedAttribute($classOrObject, $attribute, $format, $context)) {
50✔
547
            return false;
1✔
548
        }
549

550
        return $this->canAccessAttribute(\is_object($classOrObject) ? $classOrObject : null, $attribute, $context);
50✔
551
    }
552

553
    /**
554
     * Check if access to the attribute is granted.
555
     *
556
     * @param object $object
557
     */
558
    protected function canAccessAttribute($object, string $attribute, array $context = []): bool
559
    {
560
        if (!$this->resourceClassResolver->isResourceClass($context['resource_class'])) {
50✔
561
            return true;
×
562
        }
563

564
        $options = $this->getFactoryOptions($context);
50✔
565
        /** @var PropertyMetadata|ApiProperty */
566
        $propertyMetadata = $this->propertyMetadataFactory->create($context['resource_class'], $attribute, $options);
50✔
567
        $security = $propertyMetadata instanceof PropertyMetadata ? $propertyMetadata->getAttribute('security') : $propertyMetadata->getSecurity();
50✔
568
        if ($this->resourceAccessChecker && $security) {
50✔
569
            return $this->resourceAccessChecker->isGranted($context['resource_class'], $security, [
4✔
570
                'object' => $object,
4✔
571
            ]);
4✔
572
        }
573

574
        return true;
50✔
575
    }
576

577
    /**
578
     * Check if access to the attribute is granted.
579
     *
580
     * @param object      $object
581
     * @param object|null $previousObject
582
     */
583
    protected function canAccessAttributePostDenormalize($object, $previousObject, string $attribute, array $context = []): bool
584
    {
585
        $options = $this->getFactoryOptions($context);
22✔
586
        /** @var PropertyMetadata|ApiProperty */
587
        $propertyMetadata = $this->propertyMetadataFactory->create($context['resource_class'], $attribute, $options);
22✔
588
        $security = $propertyMetadata instanceof PropertyMetadata ? $propertyMetadata->getAttribute('security_post_denormalize') : $propertyMetadata->getSecurityPostDenormalize();
22✔
589
        if ($this->resourceAccessChecker && $security) {
22✔
590
            return $this->resourceAccessChecker->isGranted($context['resource_class'], $security, [
2✔
591
                'object' => $object,
2✔
592
                'previous_object' => $previousObject,
2✔
593
            ]);
2✔
594
        }
595

596
        return true;
22✔
597
    }
598

599
    /**
600
     * {@inheritdoc}
601
     */
602
    protected function setAttributeValue($object, $attribute, $value, $format = null, array $context = [])
603
    {
604
        $this->setValue($object, $attribute, $this->createAttributeValue($attribute, $value, $format, $context));
32✔
605
    }
606

607
    /**
608
     * Validates the type of the value. Allows using integers as floats for JSON formats.
609
     *
610
     * @param mixed $value
611
     *
612
     * @throws InvalidArgumentException
613
     */
614
    protected function validateType(string $attribute, Type $type, $value, string $format = null)
615
    {
616
        $builtinType = $type->getBuiltinType();
16✔
617
        if (Type::BUILTIN_TYPE_FLOAT === $builtinType && null !== $format && false !== strpos($format, 'json')) {
16✔
618
            $isValid = \is_float($value) || \is_int($value);
1✔
619
        } else {
620
            $isValid = \call_user_func('is_'.$builtinType, $value);
15✔
621
        }
622

623
        if (!$isValid) {
16✔
624
            throw new UnexpectedValueException(sprintf('The type of the "%s" attribute must be "%s", "%s" given.', $attribute, $builtinType, \gettype($value)));
1✔
625
        }
626
    }
627

628
    /**
629
     * Denormalizes a collection of objects.
630
     *
631
     * @param ApiProperty|PropertyMetadata $propertyMetadata
632
     * @param mixed                        $value
633
     *
634
     * @throws InvalidArgumentException
635
     */
636
    protected function denormalizeCollection(string $attribute, $propertyMetadata, Type $type, string $className, $value, ?string $format, array $context): array
637
    {
638
        if (!\is_array($value)) {
7✔
639
            throw new InvalidArgumentException(sprintf('The type of the "%s" attribute must be "array", "%s" given.', $attribute, \gettype($value)));
1✔
640
        }
641

642
        $collectionKeyType = method_exists(Type::class, 'getCollectionKeyTypes') ? ($type->getCollectionKeyTypes()[0] ?? null) : $type->getCollectionKeyType();
6✔
643
        $collectionKeyBuiltinType = null === $collectionKeyType ? null : $collectionKeyType->getBuiltinType();
6✔
644
        $childContext = $this->createChildContext($this->createOperationContext($context, $className), $attribute, $format);
6✔
645

646
        $values = [];
6✔
647
        foreach ($value as $index => $obj) {
6✔
648
            if (null !== $collectionKeyBuiltinType && !\call_user_func('is_'.$collectionKeyBuiltinType, $index)) {
6✔
649
                throw new InvalidArgumentException(sprintf('The type of the key "%s" must be "%s", "%s" given.', $index, $collectionKeyBuiltinType, \gettype($index)));
2✔
650
            }
651

652
            $values[$index] = $this->denormalizeRelation($attribute, $propertyMetadata, $className, $obj, $format, $childContext);
4✔
653
        }
654

655
        return $values;
4✔
656
    }
657

658
    /**
659
     * Denormalizes a relation.
660
     *
661
     * @param ApiProperty|PropertyMetadata $propertyMetadata
662
     * @param mixed                        $value
663
     *
664
     * @throws LogicException
665
     * @throws UnexpectedValueException
666
     * @throws ItemNotFoundException
667
     *
668
     * @return object|null
669
     */
670
    protected function denormalizeRelation(string $attributeName, $propertyMetadata, string $className, $value, ?string $format, array $context)
671
    {
672
        $supportsPlainIdentifiers = $this->supportsPlainIdentifiers();
8✔
673

674
        if (\is_string($value)) {
8✔
675
            try {
676
                return $this->iriConverter instanceof LegacyIriConverterInterface ? $this->iriConverter->getItemFromIri($value, $context + ['fetch_data' => true]) : $this->iriConverter->getResourceFromIri($value, $context + ['fetch_data' => true]);
1✔
677
            } catch (ItemNotFoundException $e) {
×
678
                if (!$supportsPlainIdentifiers) {
×
679
                    throw new UnexpectedValueException($e->getMessage(), $e->getCode(), $e);
×
680
                }
681
            } catch (InvalidArgumentException $e) {
×
682
                if (!$supportsPlainIdentifiers) {
×
683
                    throw new UnexpectedValueException(sprintf('Invalid IRI "%s".', $value), $e->getCode(), $e);
×
684
                }
685
            }
686
        }
687

688
        if ($propertyMetadata->isWritableLink()) {
7✔
689
            $context['api_allow_update'] = true;
2✔
690

691
            if (!$this->serializer instanceof DenormalizerInterface) {
2✔
692
                throw new LogicException(sprintf('The injected serializer must be an instance of "%s".', DenormalizerInterface::class));
×
693
            }
694

695
            try {
696
                $item = $this->serializer->denormalize($value, $className, $format, $context);
2✔
697
                if (!\is_object($item) && null !== $item) {
2✔
698
                    throw new \UnexpectedValueException('Expected item to be an object or null.');
×
699
                }
700

701
                return $item;
2✔
702
            } catch (InvalidValueException $e) {
×
703
                if (!$supportsPlainIdentifiers) {
×
704
                    throw $e;
×
705
                }
706
            }
707
        }
708

709
        if (!\is_array($value)) {
5✔
710
            if (!$supportsPlainIdentifiers) {
4✔
711
                throw new UnexpectedValueException(sprintf('Expected IRI or nested document for attribute "%s", "%s" given.', $attributeName, \gettype($value)));
2✔
712
            }
713

714
            $item = $this->itemDataProvider->getItem($className, $value, null, $context + ['fetch_data' => true]);
2✔
715
            if (null === $item) {
2✔
716
                throw new ItemNotFoundException(sprintf('Item not found for resource "%s" with id "%s".', $className, $value));
1✔
717
            }
718

719
            return $item;
1✔
720
        }
721

722
        throw new UnexpectedValueException(sprintf('Nested documents for attribute "%s" are not allowed. Use IRIs instead.', $attributeName));
1✔
723
    }
724

725
    /**
726
     * Gets the options for the property name collection / property metadata factories.
727
     */
728
    protected function getFactoryOptions(array $context): array
729
    {
730
        $options = [];
50✔
731

732
        if (isset($context[self::GROUPS])) {
50✔
733
            /* @see https://github.com/symfony/symfony/blob/v4.2.6/src/Symfony/Component/PropertyInfo/Extractor/SerializerExtractor.php */
734
            $options['serializer_groups'] = (array) $context[self::GROUPS];
×
735
        }
736

737
        if (isset($context['resource_class']) && $this->resourceClassResolver->isResourceClass($context['resource_class']) && $this->resourceMetadataFactory instanceof ResourceMetadataCollectionFactoryInterface) {
50✔
738
            $resourceClass = $this->resourceClassResolver->getResourceClass(null, $context['resource_class']); // fix for abstract classes and interfaces
7✔
739
            // This is a hot spot, we should avoid calling this here but in many cases we can't
740
            $operation = $context['root_operation'] ?? $context['operation'] ?? $this->resourceMetadataFactory->create($resourceClass)->getOperation($context['root_operation_name'] ?? $context['operation_name'] ?? null);
7✔
741
            $options['normalization_groups'] = $operation->getNormalizationContext()['groups'] ?? null;
7✔
742
            $options['denormalization_groups'] = $operation->getDenormalizationContext()['groups'] ?? null;
7✔
743
        }
744

745
        if (isset($context['operation_name'])) {
50✔
746
            $options['operation_name'] = $context['operation_name'];
5✔
747
        }
748

749
        // Preserve this context here since its deprecated in 2.7 and removed in 3.0.
750
        if (isset($context['collection_operation_name'])) {
50✔
751
            $options['collection_operation_name'] = $context['collection_operation_name'];
×
752
        }
753

754
        if (isset($context['item_operation_name'])) {
50✔
755
            $options['item_operation_name'] = $context['item_operation_name'];
×
756
        }
757

758
        return $options;
50✔
759
    }
760

761
    /**
762
     * Creates the context to use when serializing a relation.
763
     *
764
     * @deprecated since version 2.1, to be removed in 3.0.
765
     */
766
    protected function createRelationSerializationContext(string $resourceClass, array $context): array
767
    {
768
        @trigger_error(sprintf('The method %s() is deprecated since 2.1 and will be removed in 3.0.', __METHOD__), \E_USER_DEPRECATED);
×
769

770
        return $context;
×
771
    }
772

773
    /**
774
     * {@inheritdoc}
775
     *
776
     * @throws UnexpectedValueException
777
     * @throws LogicException
778
     *
779
     * @return mixed
780
     */
781
    protected function getAttributeValue($object, $attribute, $format = null, array $context = [])
782
    {
783
        $context['api_attribute'] = $attribute;
21✔
784
        /** @var ApiProperty|PropertyMetadata */
785
        $propertyMetadata = $this->propertyMetadataFactory->create($context['resource_class'], $attribute, $this->getFactoryOptions($context));
21✔
786

787
        try {
788
            $attributeValue = $this->propertyAccessor->getValue($object, $attribute);
21✔
789
        } catch (NoSuchPropertyException $e) {
3✔
790
            // BC to be removed in 3.0
791
            if ($propertyMetadata instanceof PropertyMetadata && !$propertyMetadata->hasChildInherited()) {
3✔
792
                throw $e;
×
793
            }
794
            if ($propertyMetadata instanceof ApiProperty) {
3✔
795
                throw $e;
1✔
796
            }
797

798
            $attributeValue = null;
2✔
799
        }
800

801
        if ($context['api_denormalize'] ?? false) {
20✔
802
            return $attributeValue;
×
803
        }
804

805
        $type = $propertyMetadata instanceof PropertyMetadata ? $propertyMetadata->getType() : ($propertyMetadata->getBuiltinTypes()[0] ?? null);
20✔
806

807
        if (
808
            $type &&
20✔
809
            $type->isCollection() &&
20✔
810
            ($collectionValueType = method_exists(Type::class, 'getCollectionValueTypes') ? ($type->getCollectionValueTypes()[0] ?? null) : $type->getCollectionValueType()) &&
20✔
811
            ($className = $collectionValueType->getClassName()) &&
20✔
812
            $this->resourceClassResolver->isResourceClass($className)
20✔
813
        ) {
814
            if (!is_iterable($attributeValue)) {
5✔
815
                throw new UnexpectedValueException('Unexpected non-iterable value for to-many relation.');
×
816
            }
817

818
            $resourceClass = $this->resourceClassResolver->getResourceClass($attributeValue, $className);
5✔
819
            $childContext = $this->createChildContext($this->createOperationContext($context, $resourceClass), $attribute, $format);
5✔
820

821
            return $this->normalizeCollectionOfRelations($propertyMetadata, $attributeValue, $resourceClass, $format, $childContext);
5✔
822
        }
823

824
        if (
825
            $type &&
20✔
826
            ($className = $type->getClassName()) &&
20✔
827
            $this->resourceClassResolver->isResourceClass($className)
20✔
828
        ) {
829
            if (!\is_object($attributeValue) && null !== $attributeValue) {
8✔
830
                throw new UnexpectedValueException('Unexpected non-object value for to-one relation.');
×
831
            }
832

833
            $resourceClass = $this->resourceClassResolver->getResourceClass($attributeValue, $className);
8✔
834
            $childContext = $this->createChildContext($this->createOperationContext($context, $resourceClass), $attribute, $format);
8✔
835

836
            return $this->normalizeRelation($propertyMetadata, $attributeValue, $resourceClass, $format, $childContext);
8✔
837
        }
838

839
        if (!$this->serializer instanceof NormalizerInterface) {
19✔
840
            throw new LogicException(sprintf('The injected serializer must be an instance of "%s".', NormalizerInterface::class));
×
841
        }
842

843
        unset($context['resource_class']);
19✔
844

845
        if ($type && $type->getClassName()) {
19✔
846
            $childContext = $this->createChildContext($context, $attribute, $format);
3✔
847
            unset($childContext['iri'], $childContext['uri_variables']);
3✔
848

849
            if ($propertyMetadata instanceof PropertyMetadata) {
3✔
850
                $childContext['output']['iri'] = $propertyMetadata->getIri() ?? false;
×
851
            } else {
852
                $childContext['output']['gen_id'] = $propertyMetadata->getGenId() ?? false;
3✔
853
            }
854

855
            return $this->serializer->normalize($attributeValue, $format, $childContext);
3✔
856
        }
857

858
        return $this->serializer->normalize($attributeValue, $format, $context);
19✔
859
    }
860

861
    /**
862
     * Normalizes a collection of relations (to-many).
863
     *
864
     * @param ApiProperty|PropertyMetadata $propertyMetadata
865
     * @param iterable                     $attributeValue
866
     *
867
     * @throws UnexpectedValueException
868
     */
869
    protected function normalizeCollectionOfRelations($propertyMetadata, $attributeValue, string $resourceClass, ?string $format, array $context): array
870
    {
871
        $value = [];
5✔
872
        foreach ($attributeValue as $index => $obj) {
5✔
873
            if (!\is_object($obj) && null !== $obj) {
2✔
874
                throw new UnexpectedValueException('Unexpected non-object element in to-many relation.');
×
875
            }
876

877
            $value[$index] = $this->normalizeRelation($propertyMetadata, $obj, $resourceClass, $format, $context);
2✔
878
        }
879

880
        return $value;
5✔
881
    }
882

883
    /**
884
     * Normalizes a relation.
885
     *
886
     * @param ApiProperty|PropertyMetadata $propertyMetadata
887
     * @param object|null                  $relatedObject
888
     *
889
     * @throws LogicException
890
     * @throws UnexpectedValueException
891
     *
892
     * @return string|array|\ArrayObject|null IRI or normalized object data
893
     */
894
    protected function normalizeRelation($propertyMetadata, $relatedObject, string $resourceClass, ?string $format, array $context)
895
    {
896
        if (null === $relatedObject || !empty($context['attributes']) || $propertyMetadata->isReadableLink()) {
8✔
897
            if (!$this->serializer instanceof NormalizerInterface) {
5✔
898
                throw new LogicException(sprintf('The injected serializer must be an instance of "%s".', NormalizerInterface::class));
×
899
            }
900

901
            $relatedContext = $this->createOperationContext($context, $resourceClass);
5✔
902
            $normalizedRelatedObject = $this->serializer->normalize($relatedObject, $format, $relatedContext);
5✔
903
            if (!\is_string($normalizedRelatedObject) && !\is_array($normalizedRelatedObject) && !$normalizedRelatedObject instanceof \ArrayObject && null !== $normalizedRelatedObject) {
5✔
904
                throw new UnexpectedValueException('Expected normalized relation to be an IRI, array, \ArrayObject or null');
×
905
            }
906

907
            return $normalizedRelatedObject;
5✔
908
        }
909

910
        $iri = $this->iriConverter instanceof LegacyIriConverterInterface ? $this->iriConverter->getIriFromItem($relatedObject) : $this->iriConverter->getIriFromResource($relatedObject);
3✔
911

912
        if (isset($context['resources'])) {
3✔
913
            $context['resources'][$iri] = $iri;
1✔
914
        }
915

916
        $push = $propertyMetadata instanceof PropertyMetadata ? $propertyMetadata->getAttribute('push', false) : ($propertyMetadata->getPush() ?? false);
3✔
917
        if (isset($context['resources_to_push']) && $push) {
3✔
918
            $context['resources_to_push'][$iri] = $iri;
×
919
        }
920

921
        return $iri;
3✔
922
    }
923

924
    /**
925
     * Finds the first supported data transformer if any.
926
     *
927
     * @param object|array $data object on normalize / array on denormalize
928
     */
929
    protected function getDataTransformer($data, string $to, array $context = []): ?DataTransformerInterface
930
    {
931
        foreach ($this->dataTransformers as $dataTransformer) {
4✔
932
            if ($dataTransformer->supportsTransformation($data, $to, $context)) {
4✔
933
                return $dataTransformer;
4✔
934
            }
935
        }
936

937
        return null;
×
938
    }
939

940
    /**
941
     * For a given resource, it returns an output representation if any
942
     * If not, the resource is returned.
943
     *
944
     * @param mixed $object
945
     */
946
    protected function transformOutput($object, array $context = [], string $outputClass = null)
947
    {
948
    }
×
949

950
    private function createAttributeValue($attribute, $value, $format = null, array $context = [])
951
    {
952
        if (!$this->resourceClassResolver->isResourceClass($context['resource_class'])) {
32✔
953
            return $value;
×
954
        }
955

956
        /** @var ApiProperty|PropertyMetadata */
957
        $propertyMetadata = $this->propertyMetadataFactory->create($context['resource_class'], $attribute, $this->getFactoryOptions($context));
32✔
958
        $type = $propertyMetadata instanceof PropertyMetadata ? $propertyMetadata->getType() : ($propertyMetadata->getBuiltinTypes()[0] ?? null);
32✔
959

960
        if (null === $type) {
32✔
961
            // No type provided, blindly return the value
962
            return $value;
4✔
963
        }
964

965
        if (null === $value && $type->isNullable()) {
28✔
966
            return $value;
1✔
967
        }
968

969
        $collectionValueType = method_exists(Type::class, 'getCollectionValueTypes') ? ($type->getCollectionValueTypes()[0] ?? null) : $type->getCollectionValueType();
27✔
970

971
        /* From @see AbstractObjectNormalizer::validateAndDenormalize() */
972
        // Fix a collection that contains the only one element
973
        // This is special to xml format only
974
        if ('xml' === $format && null !== $collectionValueType && (!\is_array($value) || !\is_int(key($value)))) {
27✔
975
            $value = [$value];
1✔
976
        }
977

978
        if (
979
            $type->isCollection() &&
27✔
980
            null !== $collectionValueType &&
27✔
981
            null !== ($className = $collectionValueType->getClassName()) &&
27✔
982
            $this->resourceClassResolver->isResourceClass($className)
27✔
983
        ) {
984
            $resourceClass = $this->resourceClassResolver->getResourceClass(null, $className);
7✔
985
            $context['resource_class'] = $resourceClass;
7✔
986

987
            return $this->denormalizeCollection($attribute, $propertyMetadata, $type, $resourceClass, $value, $format, $context);
7✔
988
        }
989

990
        if (
991
            null !== ($className = $type->getClassName()) &&
23✔
992
            $this->resourceClassResolver->isResourceClass($className)
23✔
993
        ) {
994
            $resourceClass = $this->resourceClassResolver->getResourceClass(null, $className);
9✔
995
            $childContext = $this->createChildContext($this->createOperationContext($context, $resourceClass), $attribute, $format);
9✔
996

997
            return $this->denormalizeRelation($attribute, $propertyMetadata, $resourceClass, $value, $format, $childContext);
9✔
998
        }
999

1000
        if (
1001
            $type->isCollection() &&
17✔
1002
            null !== $collectionValueType &&
17✔
1003
            null !== ($className = $collectionValueType->getClassName())
17✔
1004
        ) {
1005
            if (!$this->serializer instanceof DenormalizerInterface) {
×
1006
                throw new LogicException(sprintf('The injected serializer must be an instance of "%s".', DenormalizerInterface::class));
×
1007
            }
1008

1009
            unset($context['resource_class']);
×
1010

1011
            return $this->serializer->denormalize($value, $className.'[]', $format, $context);
×
1012
        }
1013

1014
        if (null !== $className = $type->getClassName()) {
17✔
1015
            if (!$this->serializer instanceof DenormalizerInterface) {
×
1016
                throw new LogicException(sprintf('The injected serializer must be an instance of "%s".', DenormalizerInterface::class));
×
1017
            }
1018

1019
            unset($context['resource_class']);
×
1020

1021
            return $this->serializer->denormalize($value, $className, $format, $context);
×
1022
        }
1023

1024
        /* From @see AbstractObjectNormalizer::validateAndDenormalize() */
1025
        // In XML and CSV all basic datatypes are represented as strings, it is e.g. not possible to determine,
1026
        // if a value is meant to be a string, float, int or a boolean value from the serialized representation.
1027
        // That's why we have to transform the values, if one of these non-string basic datatypes is expected.
1028
        if (\is_string($value) && (XmlEncoder::FORMAT === $format || CsvEncoder::FORMAT === $format)) {
17✔
1029
            if ('' === $value && $type->isNullable() && \in_array($type->getBuiltinType(), [Type::BUILTIN_TYPE_BOOL, Type::BUILTIN_TYPE_INT, Type::BUILTIN_TYPE_FLOAT], true)) {
1✔
1030
                return null;
×
1031
            }
1032

1033
            switch ($type->getBuiltinType()) {
1✔
1034
                case Type::BUILTIN_TYPE_BOOL:
1035
                    // according to https://www.w3.org/TR/xmlschema-2/#boolean, valid representations are "false", "true", "0" and "1"
1036
                    if ('false' === $value || '0' === $value) {
1✔
1037
                        $value = false;
1✔
1038
                    } elseif ('true' === $value || '1' === $value) {
1✔
1039
                        $value = true;
1✔
1040
                    } else {
1041
                        throw new NotNormalizableValueException(sprintf('The type of the "%s" attribute for class "%s" must be bool ("%s" given).', $attribute, $className, $value));
×
1042
                    }
1043
                    break;
1✔
1044
                case Type::BUILTIN_TYPE_INT:
1045
                    if (ctype_digit($value) || ('-' === $value[0] && ctype_digit(substr($value, 1)))) {
1✔
1046
                        $value = (int) $value;
1✔
1047
                    } else {
1048
                        throw new NotNormalizableValueException(sprintf('The type of the "%s" attribute for class "%s" must be int ("%s" given).', $attribute, $className, $value));
×
1049
                    }
1050
                    break;
1✔
1051
                case Type::BUILTIN_TYPE_FLOAT:
1052
                    if (is_numeric($value)) {
1✔
1053
                        return (float) $value;
1✔
1054
                    }
1055

1056
                    switch ($value) {
1057
                        case 'NaN':
1✔
1058
                            return \NAN;
1✔
1059
                        case 'INF':
1✔
1060
                            return \INF;
1✔
1061
                        case '-INF':
1✔
1062
                            return -\INF;
1✔
1063
                        default:
1064
                            throw new NotNormalizableValueException(sprintf('The type of the "%s" attribute for class "%s" must be float ("%s" given).', $attribute, $className, $value));
×
1065
                    }
1066
            }
1067
        }
1068

1069
        if ($context[static::DISABLE_TYPE_ENFORCEMENT] ?? false) {
17✔
1070
            return $value;
1✔
1071
        }
1072

1073
        $this->validateType($attribute, $type, $value, $format);
16✔
1074

1075
        return $value;
15✔
1076
    }
1077

1078
    /**
1079
     * Sets a value of the object using the PropertyAccess component.
1080
     *
1081
     * @param object $object
1082
     * @param mixed  $value
1083
     */
1084
    private function setValue($object, string $attributeName, $value)
1085
    {
1086
        try {
1087
            $this->propertyAccessor->setValue($object, $attributeName, $value);
23✔
1088
        } catch (NoSuchPropertyException $exception) {
1✔
1089
            // Properties not found are ignored
1090
        }
1091
    }
1092

1093
    /**
1094
     * TODO: to remove in 3.0.
1095
     *
1096
     * @deprecated since 2.7
1097
     */
1098
    private function supportsPlainIdentifiers(): bool
1099
    {
1100
        return $this->allowPlainIdentifiers && null !== $this->itemDataProvider;
32✔
1101
    }
1102
}
1103

1104
class_alias(AbstractItemNormalizer::class, \ApiPlatform\Core\Serializer\AbstractItemNormalizer::class);
×
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc