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

api-platform / core / 7407219156

04 Jan 2024 08:32AM UTC coverage: 35.357% (-1.9%) from 37.257%
7407219156

push

github

soyuka
cs: various fixes

0 of 10 new or added lines in 1 file covered. (0.0%)

425 existing lines in 27 files now uncovered.

10237 of 28953 relevant lines covered (35.36%)

27.07 hits per line

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

55.2
/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 as LegacyIriConverterInterface;
17
use ApiPlatform\Api\ResourceClassResolverInterface as LegacyResourceClassResolverInterface;
18
use ApiPlatform\Exception\InvalidArgumentException;
19
use ApiPlatform\Exception\ItemNotFoundException;
20
use ApiPlatform\Metadata\ApiProperty;
21
use ApiPlatform\Metadata\CollectionOperationInterface;
22
use ApiPlatform\Metadata\IriConverterInterface;
23
use ApiPlatform\Metadata\Property\Factory\PropertyMetadataFactoryInterface;
24
use ApiPlatform\Metadata\Property\Factory\PropertyNameCollectionFactoryInterface;
25
use ApiPlatform\Metadata\Resource\Factory\ResourceMetadataCollectionFactoryInterface;
26
use ApiPlatform\Metadata\ResourceAccessCheckerInterface;
27
use ApiPlatform\Metadata\ResourceClassResolverInterface;
28
use ApiPlatform\Metadata\UrlGeneratorInterface;
29
use ApiPlatform\Metadata\Util\ClassInfoTrait;
30
use ApiPlatform\Metadata\Util\CloneTrait;
31
use ApiPlatform\Symfony\Security\ResourceAccessCheckerInterface as LegacyResourceAccessCheckerInterface;
32
use Symfony\Component\PropertyAccess\Exception\NoSuchPropertyException;
33
use Symfony\Component\PropertyAccess\PropertyAccess;
34
use Symfony\Component\PropertyAccess\PropertyAccessorInterface;
35
use Symfony\Component\PropertyInfo\Type;
36
use Symfony\Component\Serializer\Encoder\CsvEncoder;
37
use Symfony\Component\Serializer\Encoder\XmlEncoder;
38
use Symfony\Component\Serializer\Exception\LogicException;
39
use Symfony\Component\Serializer\Exception\MissingConstructorArgumentsException;
40
use Symfony\Component\Serializer\Exception\NotNormalizableValueException;
41
use Symfony\Component\Serializer\Exception\RuntimeException;
42
use Symfony\Component\Serializer\Exception\UnexpectedValueException;
43
use Symfony\Component\Serializer\Mapping\Factory\ClassMetadataFactoryInterface;
44
use Symfony\Component\Serializer\NameConverter\NameConverterInterface;
45
use Symfony\Component\Serializer\Normalizer\AbstractObjectNormalizer;
46
use Symfony\Component\Serializer\Normalizer\DenormalizerInterface;
47
use Symfony\Component\Serializer\Normalizer\NormalizerInterface;
48
use Symfony\Component\Serializer\Serializer;
49

50
/**
51
 * Base item normalizer.
52
 *
53
 * @author Kévin Dunglas <dunglas@gmail.com>
54
 */
55
abstract class AbstractItemNormalizer extends AbstractObjectNormalizer
56
{
57
    use ClassInfoTrait;
58
    use CloneTrait;
59
    use ContextTrait;
60
    use InputOutputMetadataTrait;
61
    use OperationContextTrait;
62

63
    protected PropertyAccessorInterface $propertyAccessor;
64
    protected array $localCache = [];
65
    protected array $localFactoryOptionsCache = [];
66
    protected $resourceAccessChecker;
67

68
    /**
69
     * @param LegacyResourceAccessCheckerInterface|ResourceAccessCheckerInterface $resourceAccessChecker
70
     */
71
    public function __construct(protected PropertyNameCollectionFactoryInterface $propertyNameCollectionFactory, protected PropertyMetadataFactoryInterface $propertyMetadataFactory, protected LegacyIriConverterInterface|IriConverterInterface $iriConverter, protected LegacyResourceClassResolverInterface|ResourceClassResolverInterface $resourceClassResolver, PropertyAccessorInterface $propertyAccessor = null, NameConverterInterface $nameConverter = null, ClassMetadataFactoryInterface $classMetadataFactory = null, array $defaultContext = [], ResourceMetadataCollectionFactoryInterface $resourceMetadataCollectionFactory = null, $resourceAccessChecker = null, protected ?TagCollectorInterface $tagCollector = null)
72
    {
73
        if (!isset($defaultContext['circular_reference_handler'])) {
176✔
74
            $defaultContext['circular_reference_handler'] = fn ($object): ?string => $this->iriConverter->getIriFromResource($object);
176✔
75
        }
76

77
        parent::__construct($classMetadataFactory, $nameConverter, null, null, \Closure::fromCallable($this->getObjectClass(...)), $defaultContext);
176✔
78
        $this->propertyAccessor = $propertyAccessor ?: PropertyAccess::createPropertyAccessor();
176✔
79
        $this->resourceAccessChecker = $resourceAccessChecker;
176✔
80
        $this->resourceMetadataCollectionFactory = $resourceMetadataCollectionFactory;
176✔
81
    }
82

83
    /**
84
     * {@inheritdoc}
85
     */
86
    public function supportsNormalization(mixed $data, string $format = null, array $context = []): bool
87
    {
88
        if (!\is_object($data) || is_iterable($data)) {
68✔
89
            return false;
32✔
90
        }
91

92
        $class = $context['force_resource_class'] ?? $this->getObjectClass($data);
44✔
93
        if (($context['output']['class'] ?? null) === $class) {
44✔
94
            return true;
4✔
95
        }
96

97
        return $this->resourceClassResolver->isResourceClass($class);
40✔
98
    }
99

100
    public function getSupportedTypes(?string $format): array
101
    {
102
        return [
80✔
103
            'object' => true,
80✔
104
        ];
80✔
105
    }
106

107
    /**
108
     * {@inheritdoc}
109
     */
110
    public function hasCacheableSupportsMethod(): bool
111
    {
112
        if (method_exists(Serializer::class, 'getSupportedTypes')) {
×
UNCOV
113
            trigger_deprecation(
×
UNCOV
114
                'api-platform/core',
×
115
                '3.1',
×
UNCOV
116
                'The "%s()" method is deprecated, use "getSupportedTypes()" instead.',
×
UNCOV
117
                __METHOD__
×
UNCOV
118
            );
×
119
        }
120

UNCOV
121
        return true;
×
122
    }
123

124
    /**
125
     * {@inheritdoc}
126
     *
127
     * @throws LogicException
128
     */
129
    public function normalize(mixed $object, string $format = null, array $context = []): array|string|int|float|bool|\ArrayObject|null
130
    {
131
        $resourceClass = $context['force_resource_class'] ?? $this->getObjectClass($object);
68✔
132
        if ($outputClass = $this->getOutputClass($context)) {
68✔
133
            if (!$this->serializer instanceof NormalizerInterface) {
4✔
UNCOV
134
                throw new LogicException('Cannot normalize the output because the injected serializer is not a normalizer');
×
135
            }
136

137
            unset($context['output'], $context['operation'], $context['operation_name']);
4✔
138
            $context['resource_class'] = $outputClass;
4✔
139
            $context['api_sub_level'] = true;
4✔
140
            $context[self::ALLOW_EXTRA_ATTRIBUTES] = false;
4✔
141

142
            return $this->serializer->normalize($object, $format, $context);
4✔
143
        }
144

145
        // Never remove this, with `application/json` we don't use our AbstractCollectionNormalizer and we need
146
        // to remove the collection operation from our context or we'll introduce security issues
147
        if (isset($context['operation']) && $context['operation'] instanceof CollectionOperationInterface) {
68✔
UNCOV
148
            unset($context['operation_name']);
×
UNCOV
149
            unset($context['operation']);
×
UNCOV
150
            unset($context['iri']);
×
151
        }
152

153
        if ($this->resourceClassResolver->isResourceClass($resourceClass)) {
68✔
154
            $context = $this->initContext($resourceClass, $context);
64✔
155
        }
156

157
        $context['api_normalize'] = true;
68✔
158
        $iri = $context['iri'] ??= $this->iriConverter->getIriFromResource($object, UrlGeneratorInterface::ABS_URL, $context['operation'] ?? null, $context);
68✔
159

160
        /*
161
         * When true, converts the normalized data array of a resource into an
162
         * IRI, if the normalized data array is empty.
163
         *
164
         * This is useful when traversing from a non-resource towards an attribute
165
         * which is a resource, as we do not have the benefit of {@see ApiProperty::isReadableLink}.
166
         *
167
         * It must not be propagated to resources, as {@see ApiProperty::isReadableLink}
168
         * should take effect.
169
         */
170
        $emptyResourceAsIri = $context['api_empty_resource_as_iri'] ?? false;
68✔
171
        unset($context['api_empty_resource_as_iri']);
68✔
172

173
        if (!$this->tagCollector && isset($context['resources'])) {
68✔
UNCOV
174
            $context['resources'][$iri] = $iri;
×
175
        }
176

177
        $context['object'] = $object;
68✔
178
        $context['format'] = $format;
68✔
179

180
        $data = parent::normalize($object, $format, $context);
68✔
181

182
        $context['data'] = $data;
64✔
183
        unset($context['property_metadata']);
64✔
184
        unset($context['api_attribute']);
64✔
185

186
        if ($emptyResourceAsIri && \is_array($data) && 0 === \count($data)) {
64✔
187
            $context['data'] = $iri;
×
188

UNCOV
189
            if ($this->tagCollector) {
×
UNCOV
190
                $this->tagCollector->collect($context);
×
191
            }
192

UNCOV
193
            return $iri;
×
194
        }
195

196
        if ($this->tagCollector) {
64✔
197
            $this->tagCollector->collect($context);
36✔
198
        }
199

200
        return $data;
64✔
201
    }
202

203
    /**
204
     * {@inheritdoc}
205
     */
206
    public function supportsDenormalization(mixed $data, string $type, string $format = null, array $context = []): bool
207
    {
208
        if (($context['input']['class'] ?? null) === $type) {
12✔
UNCOV
209
            return true;
×
210
        }
211

212
        return $this->localCache[$type] ?? $this->localCache[$type] = $this->resourceClassResolver->isResourceClass($type);
12✔
213
    }
214

215
    /**
216
     * {@inheritdoc}
217
     */
218
    public function denormalize(mixed $data, string $class, string $format = null, array $context = []): mixed
219
    {
220
        $resourceClass = $class;
28✔
221

222
        if ($inputClass = $this->getInputClass($context)) {
28✔
UNCOV
223
            if (!$this->serializer instanceof DenormalizerInterface) {
×
UNCOV
224
                throw new LogicException('Cannot denormalize the input because the injected serializer is not a denormalizer');
×
225
            }
226

227
            unset($context['input'], $context['operation'], $context['operation_name']);
×
UNCOV
228
            $context['resource_class'] = $inputClass;
×
229

230
            try {
UNCOV
231
                return $this->serializer->denormalize($data, $inputClass, $format, $context);
×
UNCOV
232
            } catch (NotNormalizableValueException $e) {
×
UNCOV
233
                throw new UnexpectedValueException('The input data is misformatted.', $e->getCode(), $e);
×
234
            }
235
        }
236

237
        if (null === $objectToPopulate = $this->extractObjectToPopulate($resourceClass, $context, static::OBJECT_TO_POPULATE)) {
28✔
238
            $normalizedData = \is_scalar($data) ? [$data] : $this->prepareForDenormalization($data);
28✔
239
            $class = $this->getClassDiscriminatorResolvedClass($normalizedData, $class, $context);
28✔
240
        }
241

242
        $context['api_denormalize'] = true;
28✔
243

244
        if ($this->resourceClassResolver->isResourceClass($class)) {
28✔
245
            $resourceClass = $this->resourceClassResolver->getResourceClass($objectToPopulate, $class);
28✔
246
            $context['resource_class'] = $resourceClass;
28✔
247
        }
248

249
        if (\is_string($data)) {
28✔
250
            try {
UNCOV
251
                return $this->iriConverter->getResourceFromIri($data, $context + ['fetch_data' => true]);
×
UNCOV
252
            } catch (ItemNotFoundException $e) {
×
UNCOV
253
                throw new UnexpectedValueException($e->getMessage(), $e->getCode(), $e);
×
254
            } catch (InvalidArgumentException $e) {
×
UNCOV
255
                throw new UnexpectedValueException(sprintf('Invalid IRI "%s".', $data), $e->getCode(), $e);
×
256
            }
257
        }
258

259
        if (!\is_array($data)) {
28✔
UNCOV
260
            throw NotNormalizableValueException::createForUnexpectedDataType(sprintf('The type of the "%s" resource must be "array" (nested document) or "string" (IRI), "%s" given.', $resourceClass, \gettype($data)), $data, [Type::BUILTIN_TYPE_ARRAY, Type::BUILTIN_TYPE_STRING], $context['deserialization_path'] ?? null);
×
261
        }
262

263
        $previousObject = $this->clone($objectToPopulate);
28✔
264
        $object = parent::denormalize($data, $class, $format, $context);
28✔
265

266
        if (!$this->resourceClassResolver->isResourceClass($class)) {
16✔
267
            return $object;
×
268
        }
269

270
        // Bypass the post-denormalize attribute revert logic if the object could not be
271
        // cloned since we cannot possibly revert any changes made to it.
272
        if (null !== $objectToPopulate && null === $previousObject) {
16✔
UNCOV
273
            return $object;
×
274
        }
275

276
        $options = $this->getFactoryOptions($context);
16✔
277
        $propertyNames = iterator_to_array($this->propertyNameCollectionFactory->create($resourceClass, $options));
16✔
278

279
        // Revert attributes that aren't allowed to be changed after a post-denormalize check
280
        foreach (array_keys($data) as $attribute) {
16✔
281
            $attribute = $this->nameConverter ? $this->nameConverter->denormalize((string) $attribute) : $attribute;
16✔
282
            if (!\in_array($attribute, $propertyNames, true)) {
16✔
UNCOV
283
                continue;
×
284
            }
285

286
            if (!$this->canAccessAttributePostDenormalize($object, $previousObject, $attribute, $context)) {
16✔
UNCOV
287
                if (null !== $previousObject) {
×
UNCOV
288
                    $this->setValue($object, $attribute, $this->propertyAccessor->getValue($previousObject, $attribute));
×
289
                } else {
UNCOV
290
                    $propertyMetadata = $this->propertyMetadataFactory->create($resourceClass, $attribute, $options);
×
UNCOV
291
                    $this->setValue($object, $attribute, $propertyMetadata->getDefault());
×
292
                }
293
            }
294
        }
295

296
        return $object;
16✔
297
    }
298

299
    /**
300
     * Method copy-pasted from symfony/serializer.
301
     * Remove it after symfony/serializer version update @see https://github.com/symfony/symfony/pull/28263.
302
     *
303
     * {@inheritdoc}
304
     *
305
     * @internal
306
     */
307
    protected function instantiateObject(array &$data, string $class, array &$context, \ReflectionClass $reflectionClass, array|bool $allowedAttributes, string $format = null): object
308
    {
309
        if (null !== $object = $this->extractObjectToPopulate($class, $context, static::OBJECT_TO_POPULATE)) {
28✔
UNCOV
310
            unset($context[static::OBJECT_TO_POPULATE]);
×
311

UNCOV
312
            return $object;
×
313
        }
314

315
        $class = $this->getClassDiscriminatorResolvedClass($data, $class, $context);
28✔
316
        $reflectionClass = new \ReflectionClass($class);
28✔
317

318
        $constructor = $this->getConstructor($data, $class, $context, $reflectionClass, $allowedAttributes);
28✔
319
        if ($constructor) {
28✔
320
            $constructorParameters = $constructor->getParameters();
28✔
321

322
            $params = [];
28✔
323
            foreach ($constructorParameters as $constructorParameter) {
28✔
324
                $paramName = $constructorParameter->name;
×
325
                $key = $this->nameConverter ? $this->nameConverter->normalize($paramName, $class, $format, $context) : $paramName;
×
326

UNCOV
327
                $allowed = false === $allowedAttributes || (\is_array($allowedAttributes) && \in_array($paramName, $allowedAttributes, true));
×
UNCOV
328
                $ignored = !$this->isAllowedAttribute($class, $paramName, $format, $context);
×
329
                if ($constructorParameter->isVariadic()) {
×
UNCOV
330
                    if ($allowed && !$ignored && (isset($data[$key]) || \array_key_exists($key, $data))) {
×
331
                        if (!\is_array($data[$paramName])) {
×
332
                            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));
×
333
                        }
334

335
                        $params[] = $data[$paramName];
×
336
                    }
337
                } elseif ($allowed && !$ignored && (isset($data[$key]) || \array_key_exists($key, $data))) {
×
338
                    $constructorContext = $context;
×
UNCOV
339
                    $constructorContext['deserialization_path'] = $context['deserialization_path'] ?? $key;
×
340
                    try {
UNCOV
341
                        $params[] = $this->createConstructorArgument($data[$key], $key, $constructorParameter, $constructorContext, $format);
×
UNCOV
342
                    } catch (NotNormalizableValueException $exception) {
×
UNCOV
343
                        if (!isset($context['not_normalizable_value_exceptions'])) {
×
344
                            throw $exception;
×
345
                        }
346
                        $context['not_normalizable_value_exceptions'][] = $exception;
×
347
                    }
348

349
                    // Don't run set for a parameter passed to the constructor
350
                    unset($data[$key]);
×
351
                } elseif (isset($context[static::DEFAULT_CONSTRUCTOR_ARGUMENTS][$class][$key])) {
×
UNCOV
352
                    $params[] = $context[static::DEFAULT_CONSTRUCTOR_ARGUMENTS][$class][$key];
×
UNCOV
353
                } elseif ($constructorParameter->isDefaultValueAvailable()) {
×
354
                    $params[] = $constructorParameter->getDefaultValue();
×
355
                } else {
UNCOV
356
                    if (!isset($context['not_normalizable_value_exceptions'])) {
×
UNCOV
357
                        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), 0, null, [$constructorParameter->name]);
×
358
                    }
359

360
                    $exception = NotNormalizableValueException::createForUnexpectedDataType(sprintf('Failed to create object because the class misses the "%s" property.', $constructorParameter->name), $data, ['unknown'], $context['deserialization_path'] ?? null, true);
×
UNCOV
361
                    $context['not_normalizable_value_exceptions'][] = $exception;
×
362
                }
363
            }
364

365
            if (\count($context['not_normalizable_value_exceptions'] ?? []) > 0) {
28✔
UNCOV
366
                return $reflectionClass->newInstanceWithoutConstructor();
×
367
            }
368

369
            if ($constructor->isConstructor()) {
28✔
370
                return $reflectionClass->newInstanceArgs($params);
28✔
371
            }
372

UNCOV
373
            return $constructor->invokeArgs(null, $params);
×
374
        }
375

UNCOV
376
        return new $class();
×
377
    }
378

379
    protected function getClassDiscriminatorResolvedClass(array $data, string $class, array $context = []): string
380
    {
381
        if (null === $this->classDiscriminatorResolver || (null === $mapping = $this->classDiscriminatorResolver->getMappingForClass($class))) {
28✔
382
            return $class;
28✔
383
        }
384

385
        if (!isset($data[$mapping->getTypeProperty()])) {
×
UNCOV
386
            throw NotNormalizableValueException::createForUnexpectedDataType(sprintf('Type property "%s" not found for the abstract object "%s".', $mapping->getTypeProperty(), $class), null, ['string'], isset($context['deserialization_path']) ? $context['deserialization_path'].'.'.$mapping->getTypeProperty() : $mapping->getTypeProperty());
×
387
        }
388

UNCOV
389
        $type = $data[$mapping->getTypeProperty()];
×
UNCOV
390
        if (null === ($mappedClass = $mapping->getClassForType($type))) {
×
UNCOV
391
            throw NotNormalizableValueException::createForUnexpectedDataType(sprintf('The type "%s" is not a valid value.', $type), $type, ['string'], isset($context['deserialization_path']) ? $context['deserialization_path'].'.'.$mapping->getTypeProperty() : $mapping->getTypeProperty(), true);
×
392
        }
393

UNCOV
394
        return $mappedClass;
×
395
    }
396

397
    protected function createConstructorArgument($parameterData, string $key, \ReflectionParameter $constructorParameter, array &$context, string $format = null): mixed
398
    {
UNCOV
399
        return $this->createAndValidateAttributeValue($constructorParameter->name, $parameterData, $format, $context);
×
400
    }
401

402
    /**
403
     * {@inheritdoc}
404
     *
405
     * Unused in this context.
406
     *
407
     * @return string[]
408
     */
409
    protected function extractAttributes($object, $format = null, array $context = []): array
410
    {
UNCOV
411
        return [];
×
412
    }
413

414
    /**
415
     * {@inheritdoc}
416
     */
417
    protected function getAllowedAttributes(string|object $classOrObject, array $context, bool $attributesAsString = false): array|bool
418
    {
419
        if (!$this->resourceClassResolver->isResourceClass($context['resource_class'])) {
80✔
420
            return parent::getAllowedAttributes($classOrObject, $context, $attributesAsString);
4✔
421
        }
422

423
        $resourceClass = $this->resourceClassResolver->getResourceClass(null, $context['resource_class']); // fix for abstract classes and interfaces
76✔
424
        $options = $this->getFactoryOptions($context);
76✔
425
        $propertyNames = $this->propertyNameCollectionFactory->create($resourceClass, $options);
76✔
426

427
        $allowedAttributes = [];
76✔
428
        foreach ($propertyNames as $propertyName) {
76✔
429
            $propertyMetadata = $this->propertyMetadataFactory->create($resourceClass, $propertyName, $options);
76✔
430

431
            if (
432
                $this->isAllowedAttribute($classOrObject, $propertyName, null, $context)
76✔
433
                && (isset($context['api_normalize']) && $propertyMetadata->isReadable()
76✔
434
                    || isset($context['api_denormalize']) && ($propertyMetadata->isWritable() || !\is_object($classOrObject) && $propertyMetadata->isInitializable())
76✔
435
                )
436
            ) {
437
                $allowedAttributes[] = $propertyName;
76✔
438
            }
439
        }
440

441
        return $allowedAttributes;
76✔
442
    }
443

444
    /**
445
     * {@inheritdoc}
446
     */
447
    protected function isAllowedAttribute(object|string $classOrObject, string $attribute, string $format = null, array $context = []): bool
448
    {
449
        if (!parent::isAllowedAttribute($classOrObject, $attribute, $format, $context)) {
80✔
UNCOV
450
            return false;
×
451
        }
452

453
        return $this->canAccessAttribute(\is_object($classOrObject) ? $classOrObject : null, $attribute, $context);
80✔
454
    }
455

456
    /**
457
     * Check if access to the attribute is granted.
458
     */
459
    protected function canAccessAttribute(?object $object, string $attribute, array $context = []): bool
460
    {
461
        if (!$this->resourceClassResolver->isResourceClass($context['resource_class'])) {
80✔
462
            return true;
4✔
463
        }
464

465
        $options = $this->getFactoryOptions($context);
76✔
466
        $propertyMetadata = $this->propertyMetadataFactory->create($context['resource_class'], $attribute, $options);
76✔
467
        $security = $propertyMetadata->getSecurity();
76✔
468
        if (null !== $this->resourceAccessChecker && $security) {
76✔
UNCOV
469
            return $this->resourceAccessChecker->isGranted($context['resource_class'], $security, [
×
UNCOV
470
                'object' => $object,
×
UNCOV
471
            ]);
×
472
        }
473

474
        return true;
76✔
475
    }
476

477
    /**
478
     * Check if access to the attribute is granted.
479
     */
480
    protected function canAccessAttributePostDenormalize(?object $object, ?object $previousObject, string $attribute, array $context = []): bool
481
    {
482
        $options = $this->getFactoryOptions($context);
16✔
483
        $propertyMetadata = $this->propertyMetadataFactory->create($context['resource_class'], $attribute, $options);
16✔
484
        $security = $propertyMetadata->getSecurityPostDenormalize();
16✔
485
        if ($this->resourceAccessChecker && $security) {
16✔
UNCOV
486
            return $this->resourceAccessChecker->isGranted($context['resource_class'], $security, [
×
UNCOV
487
                'object' => $object,
×
UNCOV
488
                'previous_object' => $previousObject,
×
UNCOV
489
            ]);
×
490
        }
491

492
        return true;
16✔
493
    }
494

495
    /**
496
     * {@inheritdoc}
497
     */
498
    protected function setAttributeValue(object $object, string $attribute, mixed $value, string $format = null, array $context = []): void
499
    {
500
        try {
501
            $this->setValue($object, $attribute, $this->createAttributeValue($attribute, $value, $format, $context));
28✔
502
        } catch (NotNormalizableValueException $exception) {
12✔
503
            // Only throw if collecting denormalization errors is disabled.
504
            if (!isset($context['not_normalizable_value_exceptions'])) {
8✔
505
                throw $exception;
8✔
506
            }
507
        }
508
    }
509

510
    /**
511
     * Validates the type of the value. Allows using integers as floats for JSON formats.
512
     *
513
     * @throws NotNormalizableValueException
514
     */
515
    protected function validateType(string $attribute, Type $type, mixed $value, string $format = null, array $context = []): void
516
    {
517
        $builtinType = $type->getBuiltinType();
16✔
518
        if (Type::BUILTIN_TYPE_FLOAT === $builtinType && null !== $format && str_contains($format, 'json')) {
16✔
519
            $isValid = \is_float($value) || \is_int($value);
×
520
        } else {
521
            $isValid = \call_user_func('is_'.$builtinType, $value);
16✔
522
        }
523

524
        if (!$isValid) {
16✔
UNCOV
525
            throw NotNormalizableValueException::createForUnexpectedDataType(sprintf('The type of the "%s" attribute must be "%s", "%s" given.', $attribute, $builtinType, \gettype($value)), $value, [$builtinType], $context['deserialization_path'] ?? null);
×
526
        }
527
    }
528

529
    /**
530
     * Denormalizes a collection of objects.
531
     *
532
     * @throws NotNormalizableValueException
533
     */
534
    protected function denormalizeCollection(string $attribute, ApiProperty $propertyMetadata, Type $type, string $className, mixed $value, ?string $format, array $context): array
535
    {
536
        if (!\is_array($value)) {
12✔
537
            throw NotNormalizableValueException::createForUnexpectedDataType(sprintf('The type of the "%s" attribute must be "array", "%s" given.', $attribute, \gettype($value)), $value, [Type::BUILTIN_TYPE_ARRAY], $context['deserialization_path'] ?? null);
4✔
538
        }
539

540
        $collectionKeyType = $type->getCollectionKeyTypes()[0] ?? null;
8✔
541
        $collectionKeyBuiltinType = $collectionKeyType?->getBuiltinType();
8✔
542
        $childContext = $this->createChildContext($this->createOperationContext($context, $className), $attribute, $format);
8✔
543
        $values = [];
8✔
544
        foreach ($value as $index => $obj) {
8✔
545
            if (null !== $collectionKeyBuiltinType && !\call_user_func('is_'.$collectionKeyBuiltinType, $index)) {
8✔
546
                throw NotNormalizableValueException::createForUnexpectedDataType(sprintf('The type of the key "%s" must be "%s", "%s" given.', $index, $collectionKeyBuiltinType, \gettype($index)), $index, [$collectionKeyBuiltinType], ($context['deserialization_path'] ?? false) ? sprintf('key(%s)', $context['deserialization_path']) : null, true);
4✔
547
            }
548

549
            $values[$index] = $this->denormalizeRelation($attribute, $propertyMetadata, $className, $obj, $format, $childContext);
4✔
550
        }
551

552
        return $values;
4✔
553
    }
554

555
    /**
556
     * Denormalizes a relation.
557
     *
558
     * @throws LogicException
559
     * @throws UnexpectedValueException
560
     * @throws NotNormalizableValueException
561
     */
562
    protected function denormalizeRelation(string $attributeName, ApiProperty $propertyMetadata, string $className, mixed $value, ?string $format, array $context): ?object
563
    {
UNCOV
564
        if (\is_string($value)) {
×
565
            try {
566
                return $this->iriConverter->getResourceFromIri($value, $context + ['fetch_data' => true]);
×
567
            } catch (ItemNotFoundException $e) {
×
568
                if (!isset($context['not_normalizable_value_exceptions'])) {
×
569
                    throw new UnexpectedValueException($e->getMessage(), $e->getCode(), $e);
×
570
                }
571
                $context['not_normalizable_value_exceptions'][] = NotNormalizableValueException::createForUnexpectedDataType(
×
572
                    $e->getMessage(),
×
573
                    $value,
×
UNCOV
574
                    [$className],
×
575
                    $context['deserialization_path'] ?? null,
×
576
                    true,
×
577
                    $e->getCode(),
×
578
                    $e
×
UNCOV
579
                );
×
580

581
                return null;
×
582
            } catch (InvalidArgumentException $e) {
×
583
                if (!isset($context['not_normalizable_value_exceptions'])) {
×
584
                    throw new UnexpectedValueException(sprintf('Invalid IRI "%s".', $value), $e->getCode(), $e);
×
585
                }
586
                $context['not_normalizable_value_exceptions'][] = NotNormalizableValueException::createForUnexpectedDataType(
×
587
                    $e->getMessage(),
×
588
                    $value,
×
UNCOV
589
                    [$className],
×
590
                    $context['deserialization_path'] ?? null,
×
UNCOV
591
                    true,
×
UNCOV
592
                    $e->getCode(),
×
UNCOV
593
                    $e
×
594
                );
×
595

UNCOV
596
                return null;
×
597
            }
598
        }
599

UNCOV
600
        if ($propertyMetadata->isWritableLink()) {
×
601
            $context['api_allow_update'] = true;
×
602

603
            if (!$this->serializer instanceof DenormalizerInterface) {
×
UNCOV
604
                throw new LogicException(sprintf('The injected serializer must be an instance of "%s".', DenormalizerInterface::class));
×
605
            }
606

UNCOV
607
            $item = $this->serializer->denormalize($value, $className, $format, $context);
×
UNCOV
608
            if (!\is_object($item) && null !== $item) {
×
609
                throw new \UnexpectedValueException('Expected item to be an object or null.');
×
610
            }
611

UNCOV
612
            return $item;
×
613
        }
614

UNCOV
615
        if (!\is_array($value)) {
×
UNCOV
616
            throw NotNormalizableValueException::createForUnexpectedDataType(sprintf('The type of the "%s" attribute must be "array" (nested document) or "string" (IRI), "%s" given.', $attributeName, \gettype($value)), $value, [Type::BUILTIN_TYPE_ARRAY, Type::BUILTIN_TYPE_STRING], $context['deserialization_path'] ?? null, true);
×
617
        }
618

UNCOV
619
        throw NotNormalizableValueException::createForUnexpectedDataType(sprintf('Nested documents for attribute "%s" are not allowed. Use IRIs instead.', $attributeName), $value, [Type::BUILTIN_TYPE_ARRAY, Type::BUILTIN_TYPE_STRING], $context['deserialization_path'] ?? null, true);
×
620
    }
621

622
    /**
623
     * Gets the options for the property name collection / property metadata factories.
624
     */
625
    protected function getFactoryOptions(array $context): array
626
    {
627
        $options = [];
80✔
628
        if (isset($context[self::GROUPS])) {
80✔
629
            /* @see https://github.com/symfony/symfony/blob/v4.2.6/src/Symfony/Component/PropertyInfo/Extractor/SerializerExtractor.php */
630
            $options['serializer_groups'] = (array) $context[self::GROUPS];
8✔
631
        }
632

633
        $operationCacheKey = ($context['resource_class'] ?? '').($context['operation_name'] ?? '').($context['api_normalize'] ?? '');
80✔
634
        if ($operationCacheKey && isset($this->localFactoryOptionsCache[$operationCacheKey])) {
80✔
635
            return $options + $this->localFactoryOptionsCache[$operationCacheKey];
80✔
636
        }
637

638
        // This is a hot spot
639
        if (isset($context['resource_class'])) {
80✔
640
            // Note that the groups need to be read on the root operation
641
            if ($operation = ($context['root_operation'] ?? null)) {
80✔
642
                $options['normalization_groups'] = $operation->getNormalizationContext()['groups'] ?? null;
8✔
643
                $options['denormalization_groups'] = $operation->getDenormalizationContext()['groups'] ?? null;
8✔
644
                $options['operation_name'] = $operation->getName();
8✔
645
            }
646
        }
647

648
        return $options + $this->localFactoryOptionsCache[$operationCacheKey] = $options;
80✔
649
    }
650

651
    /**
652
     * {@inheritdoc}
653
     *
654
     * @throws UnexpectedValueException
655
     */
656
    protected function getAttributeValue(object $object, string $attribute, string $format = null, array $context = []): mixed
657
    {
658
        $context['api_attribute'] = $attribute;
64✔
659
        $context['property_metadata'] = $propertyMetadata = $this->propertyMetadataFactory->create($context['resource_class'], $attribute, $this->getFactoryOptions($context));
64✔
660

661
        if ($context['api_denormalize'] ?? false) {
64✔
UNCOV
662
            return $this->propertyAccessor->getValue($object, $attribute);
×
663
        }
664

665
        $types = $propertyMetadata->getBuiltinTypes() ?? [];
64✔
666

667
        foreach ($types as $type) {
64✔
668
            if (
669
                $type->isCollection()
52✔
670
                && ($collectionValueType = $type->getCollectionValueTypes()[0] ?? null)
52✔
671
                && ($className = $collectionValueType->getClassName())
52✔
672
                && $this->resourceClassResolver->isResourceClass($className)
52✔
673
            ) {
674
                $childContext = $this->createChildContext($this->createOperationContext($context, $className), $attribute, $format);
16✔
675

676
                // @see ApiPlatform\Hal\Serializer\ItemNormalizer:getComponents logic for intentional duplicate content
677
                // @see ApiPlatform\JsonApi\Serializer\ItemNormalizer:getComponents logic for intentional duplicate content
678
                if ('jsonld' === $format && $itemUriTemplate = $propertyMetadata->getUriTemplate()) {
16✔
679
                    $operation = $this->resourceMetadataCollectionFactory->create($className)->getOperation(
×
UNCOV
680
                        operationName: $itemUriTemplate,
×
UNCOV
681
                        forceCollection: true,
×
UNCOV
682
                        httpOperation: true
×
UNCOV
683
                    );
×
684

685
                    return $this->iriConverter->getIriFromResource($object, UrlGeneratorInterface::ABS_PATH, $operation, $childContext);
×
686
                }
687

688
                $attributeValue = $this->propertyAccessor->getValue($object, $attribute);
16✔
689

690
                if (!is_iterable($attributeValue)) {
16✔
UNCOV
691
                    throw new UnexpectedValueException('Unexpected non-iterable value for to-many relation.');
×
692
                }
693

694
                $resourceClass = $this->resourceClassResolver->getResourceClass($attributeValue, $className);
16✔
695

696
                $data = $this->normalizeCollectionOfRelations($propertyMetadata, $attributeValue, $resourceClass, $format, $childContext);
16✔
697
                $context['data'] = $data;
16✔
698
                $context['type'] = $type;
16✔
699

700
                if ($this->tagCollector) {
16✔
701
                    $this->tagCollector->collect($context);
16✔
702
                }
703

704
                return $data;
16✔
705
            }
706

707
            if (
708
                ($className = $type->getClassName())
52✔
709
                && $this->resourceClassResolver->isResourceClass($className)
52✔
710
            ) {
711
                $childContext = $this->createChildContext($this->createOperationContext($context, $className), $attribute, $format);
24✔
712
                unset($childContext['iri'], $childContext['uri_variables'], $childContext['item_uri_template']);
24✔
713

714
                if ('jsonld' === $format && $uriTemplate = $propertyMetadata->getUriTemplate()) {
24✔
UNCOV
715
                    $operation = $this->resourceMetadataCollectionFactory->create($className)->getOperation(
×
UNCOV
716
                        operationName: $uriTemplate,
×
UNCOV
717
                        httpOperation: true
×
UNCOV
718
                    );
×
719

720
                    return $this->iriConverter->getIriFromResource($object, UrlGeneratorInterface::ABS_PATH, $operation, $childContext);
×
721
                }
722

723
                $attributeValue = $this->propertyAccessor->getValue($object, $attribute);
24✔
724

725
                if (!\is_object($attributeValue) && null !== $attributeValue) {
24✔
UNCOV
726
                    throw new UnexpectedValueException('Unexpected non-object value for to-one relation.');
×
727
                }
728

729
                $resourceClass = $this->resourceClassResolver->getResourceClass($attributeValue, $className);
24✔
730

731
                $data = $this->normalizeRelation($propertyMetadata, $attributeValue, $resourceClass, $format, $childContext);
24✔
732
                $context['data'] = $data;
24✔
733
                $context['type'] = $type;
24✔
734

735
                if ($this->tagCollector) {
24✔
736
                    $this->tagCollector->collect($context);
12✔
737
                }
738

739
                return $data;
24✔
740
            }
741

742
            if (!$this->serializer instanceof NormalizerInterface) {
52✔
UNCOV
743
                throw new LogicException(sprintf('The injected serializer must be an instance of "%s".', NormalizerInterface::class));
×
744
            }
745

746
            unset(
52✔
747
                $context['resource_class'],
52✔
748
                $context['force_resource_class'],
52✔
749
            );
52✔
750

751
            // Anonymous resources
752
            if ($className) {
52✔
753
                $childContext = $this->createChildContext($this->createOperationContext($context, $className), $attribute, $format);
16✔
754
                $childContext['output']['gen_id'] = $propertyMetadata->getGenId() ?? true;
16✔
755

756
                $attributeValue = $this->propertyAccessor->getValue($object, $attribute);
16✔
757

758
                return $this->serializer->normalize($attributeValue, $format, $childContext);
16✔
759
            }
760

761
            if ('array' === $type->getBuiltinType()) {
48✔
762
                $childContext = $this->createChildContext($context, $attribute, $format);
20✔
763
                $childContext['output']['gen_id'] = $propertyMetadata->getGenId() ?? true;
20✔
764

765
                $attributeValue = $this->propertyAccessor->getValue($object, $attribute);
20✔
766

767
                return $this->serializer->normalize($attributeValue, $format, $childContext);
20✔
768
            }
769
        }
770

771
        if (!$this->serializer instanceof NormalizerInterface) {
60✔
UNCOV
772
            throw new LogicException(sprintf('The injected serializer must be an instance of "%s".', NormalizerInterface::class));
×
773
        }
774

775
        unset($context['resource_class']);
60✔
776
        unset($context['force_resource_class']);
60✔
777

778
        $attributeValue = $this->propertyAccessor->getValue($object, $attribute);
60✔
779

780
        return $this->serializer->normalize($attributeValue, $format, $context);
56✔
781
    }
782

783
    /**
784
     * Normalizes a collection of relations (to-many).
785
     *
786
     * @throws UnexpectedValueException
787
     */
788
    protected function normalizeCollectionOfRelations(ApiProperty $propertyMetadata, iterable $attributeValue, string $resourceClass, ?string $format, array $context): array
789
    {
790
        $value = [];
16✔
791
        foreach ($attributeValue as $index => $obj) {
16✔
792
            if (!\is_object($obj) && null !== $obj) {
×
793
                throw new UnexpectedValueException('Unexpected non-object element in to-many relation.');
×
794
            }
795

796
            // update context, if concrete object class deviates from general relation class (e.g. in case of polymorphic resources)
797
            $objResourceClass = $this->resourceClassResolver->getResourceClass($obj, $resourceClass);
×
UNCOV
798
            $context['resource_class'] = $objResourceClass;
×
UNCOV
799
            if ($this->resourceMetadataCollectionFactory) {
×
UNCOV
800
                $context['operation'] = $this->resourceMetadataCollectionFactory->create($objResourceClass)->getOperation();
×
801
            }
802

UNCOV
803
            $value[$index] = $this->normalizeRelation($propertyMetadata, $obj, $resourceClass, $format, $context);
×
804
        }
805

806
        return $value;
16✔
807
    }
808

809
    /**
810
     * Normalizes a relation.
811
     *
812
     * @throws LogicException
813
     * @throws UnexpectedValueException
814
     */
815
    protected function normalizeRelation(ApiProperty $propertyMetadata, ?object $relatedObject, string $resourceClass, ?string $format, array $context): \ArrayObject|array|string|null
816
    {
817
        if (null === $relatedObject || !empty($context['attributes']) || $propertyMetadata->isReadableLink()) {
24✔
818
            if (!$this->serializer instanceof NormalizerInterface) {
16✔
819
                throw new LogicException(sprintf('The injected serializer must be an instance of "%s".', NormalizerInterface::class));
×
820
            }
821

822
            $relatedContext = $this->createOperationContext($context, $resourceClass);
16✔
823
            $normalizedRelatedObject = $this->serializer->normalize($relatedObject, $format, $relatedContext);
16✔
824
            if (!\is_string($normalizedRelatedObject) && !\is_array($normalizedRelatedObject) && !$normalizedRelatedObject instanceof \ArrayObject && null !== $normalizedRelatedObject) {
16✔
UNCOV
825
                throw new UnexpectedValueException('Expected normalized relation to be an IRI, array, \ArrayObject or null');
×
826
            }
827

828
            return $normalizedRelatedObject;
16✔
829
        }
830

831
        $context['iri'] = $iri = $this->iriConverter->getIriFromResource(resource: $relatedObject, context: $context);
8✔
832
        $context['data'] = $iri;
8✔
833
        $context['object'] = $relatedObject;
8✔
834
        unset($context['property_metadata']);
8✔
835
        unset($context['api_attribute']);
8✔
836

837
        if ($this->tagCollector) {
8✔
UNCOV
838
            $this->tagCollector->collect($context);
×
839
        } elseif (isset($context['resources'])) {
8✔
UNCOV
840
            $context['resources'][$iri] = $iri;
×
841
        }
842

843
        $push = $propertyMetadata->getPush() ?? false;
8✔
844
        if (isset($context['resources_to_push']) && $push) {
8✔
UNCOV
845
            $context['resources_to_push'][$iri] = $iri;
×
846
        }
847

848
        return $iri;
8✔
849
    }
850

851
    private function createAttributeValue(string $attribute, mixed $value, string $format = null, array &$context = []): mixed
852
    {
853
        try {
854
            return $this->createAndValidateAttributeValue($attribute, $value, $format, $context);
28✔
855
        } catch (NotNormalizableValueException $exception) {
12✔
856
            if (!isset($context['not_normalizable_value_exceptions'])) {
8✔
857
                throw $exception;
8✔
858
            }
UNCOV
859
            $context['not_normalizable_value_exceptions'][] = $exception;
×
860

UNCOV
861
            throw $exception;
×
862
        }
863
    }
864

865
    private function createAndValidateAttributeValue(string $attribute, mixed $value, string $format = null, array $context = []): mixed
866
    {
867
        $propertyMetadata = $this->propertyMetadataFactory->create($context['resource_class'], $attribute, $this->getFactoryOptions($context));
28✔
868
        $types = $propertyMetadata->getBuiltinTypes() ?? [];
28✔
869
        $isMultipleTypes = \count($types) > 1;
28✔
870

871
        foreach ($types as $type) {
28✔
872
            if (null === $value && ($type->isNullable() || ($context[static::DISABLE_TYPE_ENFORCEMENT] ?? false))) {
28✔
UNCOV
873
                return $value;
×
874
            }
875

876
            $collectionValueType = $type->getCollectionValueTypes()[0] ?? null;
28✔
877

878
            /* From @see AbstractObjectNormalizer::validateAndDenormalize() */
879
            // Fix a collection that contains the only one element
880
            // This is special to xml format only
881
            if ('xml' === $format && null !== $collectionValueType && (!\is_array($value) || !\is_int(key($value)))) {
28✔
UNCOV
882
                $value = [$value];
×
883
            }
884

885
            if (
886
                $type->isCollection()
28✔
887
                && null !== $collectionValueType
28✔
888
                && null !== ($className = $collectionValueType->getClassName())
28✔
889
                && $this->resourceClassResolver->isResourceClass($className)
28✔
890
            ) {
891
                $resourceClass = $this->resourceClassResolver->getResourceClass(null, $className);
12✔
892
                $context['resource_class'] = $resourceClass;
12✔
893

894
                return $this->denormalizeCollection($attribute, $propertyMetadata, $type, $resourceClass, $value, $format, $context);
12✔
895
            }
896

897
            if (
898
                null !== ($className = $type->getClassName())
20✔
899
                && $this->resourceClassResolver->isResourceClass($className)
20✔
900
            ) {
901
                $resourceClass = $this->resourceClassResolver->getResourceClass(null, $className);
8✔
902
                $childContext = $this->createChildContext($this->createOperationContext($context, $resourceClass), $attribute, $format);
8✔
903

904
                return $this->denormalizeRelation($attribute, $propertyMetadata, $resourceClass, $value, $format, $childContext);
8✔
905
            }
906

907
            if (
908
                $type->isCollection()
16✔
909
                && null !== $collectionValueType
16✔
910
                && null !== ($className = $collectionValueType->getClassName())
16✔
911
                && \is_array($value)
16✔
912
            ) {
913
                if (!$this->serializer instanceof DenormalizerInterface) {
×
UNCOV
914
                    throw new LogicException(sprintf('The injected serializer must be an instance of "%s".', DenormalizerInterface::class));
×
915
                }
916

917
                unset($context['resource_class']);
×
918

UNCOV
919
                return $this->serializer->denormalize($value, $className.'[]', $format, $context);
×
920
            }
921

922
            if (null !== $className = $type->getClassName()) {
16✔
923
                if (!$this->serializer instanceof DenormalizerInterface) {
×
UNCOV
924
                    throw new LogicException(sprintf('The injected serializer must be an instance of "%s".', DenormalizerInterface::class));
×
925
                }
926

UNCOV
927
                unset($context['resource_class']);
×
928

UNCOV
929
                return $this->serializer->denormalize($value, $className, $format, $context);
×
930
            }
931

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

941
                switch ($type->getBuiltinType()) {
×
942
                    case Type::BUILTIN_TYPE_BOOL:
943
                        // according to http://www.w3.org/TR/xmlschema-2/#boolean, valid representations are "false", "true", "0" and "1"
944
                        if ('false' === $value || '0' === $value) {
×
945
                            $value = false;
×
UNCOV
946
                        } elseif ('true' === $value || '1' === $value) {
×
947
                            $value = true;
×
948
                        } else {
949
                            // union/intersect types: try the next type, if not valid, an exception will be thrown at the end
UNCOV
950
                            if ($isMultipleTypes) {
×
951
                                break 2;
×
952
                            }
UNCOV
953
                            throw NotNormalizableValueException::createForUnexpectedDataType(sprintf('The type of the "%s" attribute for class "%s" must be bool ("%s" given).', $attribute, $className, $value), $value, [Type::BUILTIN_TYPE_BOOL], $context['deserialization_path'] ?? null);
×
954
                        }
955
                        break;
×
956
                    case Type::BUILTIN_TYPE_INT:
UNCOV
957
                        if (ctype_digit($value) || ('-' === $value[0] && ctype_digit(substr($value, 1)))) {
×
958
                            $value = (int) $value;
×
959
                        } else {
960
                            // union/intersect types: try the next type, if not valid, an exception will be thrown at the end
UNCOV
961
                            if ($isMultipleTypes) {
×
962
                                break 2;
×
963
                            }
UNCOV
964
                            throw NotNormalizableValueException::createForUnexpectedDataType(sprintf('The type of the "%s" attribute for class "%s" must be int ("%s" given).', $attribute, $className, $value), $value, [Type::BUILTIN_TYPE_INT], $context['deserialization_path'] ?? null);
×
965
                        }
UNCOV
966
                        break;
×
967
                    case Type::BUILTIN_TYPE_FLOAT:
968
                        if (is_numeric($value)) {
×
969
                            return (float) $value;
×
970
                        }
971

972
                        switch ($value) {
UNCOV
973
                            case 'NaN':
×
UNCOV
974
                                return \NAN;
×
975
                            case 'INF':
×
976
                                return \INF;
×
UNCOV
977
                            case '-INF':
×
978
                                return -\INF;
×
979
                            default:
980
                                // union/intersect types: try the next type, if not valid, an exception will be thrown at the end
UNCOV
981
                                if ($isMultipleTypes) {
×
UNCOV
982
                                    break 3;
×
983
                                }
984
                                throw NotNormalizableValueException::createForUnexpectedDataType(sprintf('The type of the "%s" attribute for class "%s" must be float ("%s" given).', $attribute, $className, $value), $value, [Type::BUILTIN_TYPE_FLOAT], $context['deserialization_path'] ?? null);
×
985
                        }
986
                }
987
            }
988

989
            if ($context[static::DISABLE_TYPE_ENFORCEMENT] ?? false) {
16✔
UNCOV
990
                return $value;
×
991
            }
992

993
            try {
994
                $this->validateType($attribute, $type, $value, $format, $context);
16✔
995

996
                break;
16✔
UNCOV
997
            } catch (NotNormalizableValueException $e) {
×
998
                // union/intersect types: try the next type
UNCOV
999
                if (!$isMultipleTypes) {
×
UNCOV
1000
                    throw $e;
×
1001
                }
1002
            }
1003
        }
1004

1005
        return $value;
16✔
1006
    }
1007

1008
    /**
1009
     * Sets a value of the object using the PropertyAccess component.
1010
     */
1011
    private function setValue(object $object, string $attributeName, mixed $value): void
1012
    {
1013
        try {
1014
            $this->propertyAccessor->setValue($object, $attributeName, $value);
16✔
1015
        } catch (NoSuchPropertyException) {
4✔
1016
            // Properties not found are ignored
1017
        }
1018
    }
1019
}
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