Skip to content

Commit 2f1a7ea

Browse files
Merge branch '5.4' into 6.3
* 5.4: [Serializer] Fix test Fix denormalizing empty string into object|null parameter [Serializer] Move discrimination to abstract [Serializer] Fix access to private when Ignore
2 parents bf01d35 + 1bc8d26 commit 2f1a7ea

14 files changed

+379
-33
lines changed

src/Symfony/Component/Serializer/Normalizer/AbstractObjectNormalizer.php

Lines changed: 37 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -185,9 +185,12 @@ public function normalize(mixed $object, string $format = null, array $context =
185185
}
186186

187187
$attributeContext = $this->getAttributeNormalizationContext($object, $attribute, $context);
188+
$discriminatorMapping = $this->classDiscriminatorResolver?->getMappingForMappedObject($object);
188189

189190
try {
190-
$attributeValue = $this->getAttributeValue($object, $attribute, $format, $attributeContext);
191+
$attributeValue = $attribute === $discriminatorMapping?->getTypeProperty()
192+
? $discriminatorMapping
193+
: $this->getAttributeValue($object, $attribute, $format, $attributeContext);
191194
} catch (UninitializedPropertyException $e) {
192195
if ($context[self::SKIP_UNINITIALIZED_VALUES] ?? $this->defaultContext[self::SKIP_UNINITIALIZED_VALUES] ?? true) {
193196
continue;
@@ -258,22 +261,18 @@ protected function getAttributes(object $object, ?string $format, array $context
258261
return $this->attributesCache[$key];
259262
}
260263

261-
$allowedAttributes = $this->getAllowedAttributes($object, $context, true);
262-
263-
if (false !== $allowedAttributes) {
264-
if ($context['cache_key']) {
265-
$this->attributesCache[$key] = $allowedAttributes;
266-
}
267-
268-
return $allowedAttributes;
269-
}
270-
271264
$attributes = $this->extractAttributes($object, $format, $context);
272265

273266
if ($mapping = $this->classDiscriminatorResolver?->getMappingForMappedObject($object)) {
274267
array_unshift($attributes, $mapping->getTypeProperty());
275268
}
276269

270+
$allowedAttributes = $this->getAllowedAttributes($object, $context, true);
271+
272+
if (false !== $allowedAttributes) {
273+
$attributes = array_intersect($attributes, $allowedAttributes);
274+
}
275+
277276
if ($context['cache_key'] && \stdClass::class !== $class) {
278277
$this->attributesCache[$key] = $attributes;
279278
}
@@ -364,8 +363,12 @@ public function denormalize(mixed $data, string $type, string $format = null, ar
364363
}
365364

366365
if ($attributeContext[self::DEEP_OBJECT_TO_POPULATE] ?? $this->defaultContext[self::DEEP_OBJECT_TO_POPULATE] ?? false) {
366+
$discriminatorMapping = $this->classDiscriminatorResolver?->getMappingForMappedObject($object);
367+
367368
try {
368-
$attributeContext[self::OBJECT_TO_POPULATE] = $this->getAttributeValue($object, $attribute, $format, $attributeContext);
369+
$attributeContext[self::OBJECT_TO_POPULATE] = $attribute === $discriminatorMapping?->getTypeProperty()
370+
? $discriminatorMapping
371+
: $this->getAttributeValue($object, $attribute, $format, $attributeContext);
369372
} catch (NoSuchPropertyException) {
370373
}
371374
}
@@ -432,8 +435,10 @@ private function validateAndDenormalize(array $types, string $currentClass, stri
432435
{
433436
$expectedTypes = [];
434437
$isUnionType = \count($types) > 1;
438+
$e = null;
435439
$extraAttributesException = null;
436440
$missingConstructorArgumentsException = null;
441+
$isNullable = false;
437442
foreach ($types as $type) {
438443
if (null === $data && $type->isNullable()) {
439444
return null;
@@ -456,18 +461,22 @@ private function validateAndDenormalize(array $types, string $currentClass, stri
456461
// In XML and CSV all basic datatypes are represented as strings, it is e.g. not possible to determine,
457462
// if a value is meant to be a string, float, int or a boolean value from the serialized representation.
458463
// That's why we have to transform the values, if one of these non-string basic datatypes is expected.
464+
$builtinType = $type->getBuiltinType();
459465
if (\is_string($data) && (XmlEncoder::FORMAT === $format || CsvEncoder::FORMAT === $format)) {
460466
if ('' === $data) {
461-
if (Type::BUILTIN_TYPE_ARRAY === $builtinType = $type->getBuiltinType()) {
467+
if (Type::BUILTIN_TYPE_ARRAY === $builtinType) {
462468
return [];
463469
}
464470

465-
if ($type->isNullable() && \in_array($builtinType, [Type::BUILTIN_TYPE_BOOL, Type::BUILTIN_TYPE_INT, Type::BUILTIN_TYPE_FLOAT], true)) {
466-
return null;
471+
if (Type::BUILTIN_TYPE_STRING === $builtinType) {
472+
return '';
467473
}
474+
475+
// Don't return null yet because Object-types that come first may accept empty-string too
476+
$isNullable = $isNullable ?: $type->isNullable();
468477
}
469478

470-
switch ($builtinType ?? $type->getBuiltinType()) {
479+
switch ($builtinType) {
471480
case Type::BUILTIN_TYPE_BOOL:
472481
// according to https://www.w3.org/TR/xmlschema-2/#boolean, valid representations are "false", "true", "0" and "1"
473482
if ('false' === $data || '0' === $data) {
@@ -564,24 +573,28 @@ private function validateAndDenormalize(array $types, string $currentClass, stri
564573
return $data;
565574
}
566575
} catch (NotNormalizableValueException|InvalidArgumentException $e) {
567-
if (!$isUnionType) {
576+
if (!$isUnionType && !$isNullable) {
568577
throw $e;
569578
}
570579
} catch (ExtraAttributesException $e) {
571-
if (!$isUnionType) {
580+
if (!$isUnionType && !$isNullable) {
572581
throw $e;
573582
}
574583

575584
$extraAttributesException ??= $e;
576585
} catch (MissingConstructorArgumentsException $e) {
577-
if (!$isUnionType) {
586+
if (!$isUnionType && !$isNullable) {
578587
throw $e;
579588
}
580589

581590
$missingConstructorArgumentsException ??= $e;
582591
}
583592
}
584593

594+
if ($isNullable) {
595+
return null;
596+
}
597+
585598
if ($extraAttributesException) {
586599
throw $extraAttributesException;
587600
}
@@ -590,6 +603,10 @@ private function validateAndDenormalize(array $types, string $currentClass, stri
590603
throw $missingConstructorArgumentsException;
591604
}
592605

606+
if (!$isUnionType && $e) {
607+
throw $e;
608+
}
609+
593610
if ($context[self::DISABLE_TYPE_ENFORCEMENT] ?? $this->defaultContext[self::DISABLE_TYPE_ENFORCEMENT] ?? false) {
594611
return $data;
595612
}
@@ -629,7 +646,7 @@ private function getTypes(string $currentClass, string $attribute): ?array
629646
return $this->typesCache[$key] = $types;
630647
}
631648

632-
if (null !== $this->classDiscriminatorResolver && null !== $discriminatorMapping = $this->classDiscriminatorResolver->getMappingForClass($currentClass)) {
649+
if ($discriminatorMapping = $this->classDiscriminatorResolver?->getMappingForClass($currentClass)) {
633650
if ($discriminatorMapping->getTypeProperty() === $attribute) {
634651
return $this->typesCache[$key] = [
635652
new Type(Type::BUILTIN_TYPE_STRING),

src/Symfony/Component/Serializer/Normalizer/GetSetMethodNormalizer.php

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,10 @@ public function hasCacheableSupportsMethod(): bool
7676
*/
7777
private function supports(string $class): bool
7878
{
79+
if ($this->classDiscriminatorResolver?->getMappingForClass($class)) {
80+
return true;
81+
}
82+
7983
$class = new \ReflectionClass($class);
8084
$methods = $class->getMethods(\ReflectionMethod::IS_PUBLIC);
8185
foreach ($methods as $method) {

src/Symfony/Component/Serializer/Normalizer/ObjectNormalizer.php

Lines changed: 4 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -32,9 +32,6 @@ class ObjectNormalizer extends AbstractObjectNormalizer
3232
{
3333
protected $propertyAccessor;
3434

35-
/** @var array<string, string|null> */
36-
private array $discriminatorCache = [];
37-
3835
private readonly \Closure $objectClassResolver;
3936

4037
public function __construct(ClassMetadataFactoryInterface $classMetadataFactory = null, NameConverterInterface $nameConverter = null, PropertyAccessorInterface $propertyAccessor = null, PropertyTypeExtractorInterface $propertyTypeExtractor = null, ClassDiscriminatorResolverInterface $classDiscriminatorResolver = null, callable $objectClassResolver = null, array $defaultContext = [])
@@ -130,16 +127,11 @@ protected function extractAttributes(object $object, string $format = null, arra
130127

131128
protected function getAttributeValue(object $object, string $attribute, string $format = null, array $context = []): mixed
132129
{
133-
$cacheKey = $object::class;
134-
if (!\array_key_exists($cacheKey, $this->discriminatorCache)) {
135-
$this->discriminatorCache[$cacheKey] = null;
136-
if (null !== $this->classDiscriminatorResolver) {
137-
$mapping = $this->classDiscriminatorResolver->getMappingForMappedObject($object);
138-
$this->discriminatorCache[$cacheKey] = $mapping?->getTypeProperty();
139-
}
140-
}
130+
$mapping = $this->classDiscriminatorResolver?->getMappingForMappedObject($object);
141131

142-
return $attribute === $this->discriminatorCache[$cacheKey] ? $this->classDiscriminatorResolver->getTypeForMappedObject($object) : $this->propertyAccessor->getValue($object, $attribute);
132+
return $attribute === $mapping?->getTypeProperty()
133+
? $mapping
134+
: $this->propertyAccessor->getValue($object, $attribute);
143135
}
144136

145137
/**

src/Symfony/Component/Serializer/Normalizer/PropertyNormalizer.php

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,10 @@ public function hasCacheableSupportsMethod(): bool
9292
*/
9393
private function supports(string $class): bool
9494
{
95+
if ($this->classDiscriminatorResolver?->getMappingForClass($class)) {
96+
return true;
97+
}
98+
9599
$class = new \ReflectionClass($class);
96100

97101
// We look for at least one non-static property
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <fabien@symfony.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+
namespace Symfony\Component\Serializer\Tests\Fixtures;
13+
14+
use Symfony\Component\Serializer\Normalizer\DenormalizableInterface;
15+
use Symfony\Component\Serializer\Normalizer\DenormalizerInterface;
16+
17+
/**
18+
* @author Jeroen <github.com/Jeroeny>
19+
*/
20+
class DummyString implements DenormalizableInterface
21+
{
22+
/** @var string $value */
23+
public $value;
24+
25+
public function denormalize(DenormalizerInterface $denormalizer, $data, string $format = null, array $context = [])
26+
{
27+
$this->value = $data;
28+
}
29+
}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <fabien@symfony.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+
namespace Symfony\Component\Serializer\Tests\Fixtures;
13+
14+
/**
15+
* @author Jeroen <github.com/Jeroeny>
16+
*/
17+
class DummyWithNotNormalizable
18+
{
19+
public function __construct(public NotNormalizableDummy|null $value)
20+
{
21+
}
22+
}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <fabien@symfony.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+
namespace Symfony\Component\Serializer\Tests\Fixtures;
13+
14+
/**
15+
* @author Jeroen <github.com/Jeroeny>
16+
*/
17+
class DummyWithObjectOrBool
18+
{
19+
public function __construct(public Php80WithPromotedTypedConstructor|bool $value)
20+
{
21+
}
22+
}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <fabien@symfony.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+
namespace Symfony\Component\Serializer\Tests\Fixtures;
13+
14+
/**
15+
* @author Jeroen <github.com/Jeroeny>
16+
*/
17+
class DummyWithObjectOrNull
18+
{
19+
public function __construct(public Php80WithPromotedTypedConstructor|null $value)
20+
{
21+
}
22+
}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <fabien@symfony.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+
namespace Symfony\Component\Serializer\Tests\Fixtures;
13+
14+
/**
15+
* @author Jeroen <github.com/Jeroeny>
16+
*/
17+
class DummyWithStringObject
18+
{
19+
public function __construct(public DummyString|null $value)
20+
{
21+
}
22+
}
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <fabien@symfony.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+
namespace Symfony\Component\Serializer\Tests\Fixtures;
13+
14+
use Symfony\Component\Serializer\Exception\NotNormalizableValueException;
15+
use Symfony\Component\Serializer\Normalizer\DenormalizableInterface;
16+
use Symfony\Component\Serializer\Normalizer\DenormalizerInterface;
17+
18+
/**
19+
* @author Jeroen <github.com/Jeroeny>
20+
*/
21+
class NotNormalizableDummy implements DenormalizableInterface
22+
{
23+
public function __construct()
24+
{
25+
}
26+
27+
public function denormalize(DenormalizerInterface $denormalizer, $data, string $format = null, array $context = [])
28+
{
29+
throw new NotNormalizableValueException();
30+
}
31+
}

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy