Skip to content

Commit 0e1fa7c

Browse files
committed
Merge branch '6.3' into 6.4
* 6.3: fix merge do not detect the deserialization_path context value twice fix detecting the database server version [Cache] Add url decoding of password in `RedisTrait` DSN [Serializer] Remove incompatible type declaration with PHP 7.2 [Serializer] Fix test Fix denormalizing empty string into object|null parameter [PropertyInfo] Fixed promoted property type detection for `PhpStanExtractor` [Serializer] Move discrimination to abstract [Serializer] Fix deserialization_path missing using contructor [Serializer] Fix access to private when Ignore [HttpKernel] Fix logging deprecations to the "php" channel when channel "deprecation" is not defined Fix message handlers with multiple from_transports fix detecting the server version with Doctrine DBAL 4 [Serializer] Fix constructor deserialization path [Serializer] Fix XML attributes not added on empty
2 parents d5ab8c5 + 2f9f302 commit 0e1fa7c

17 files changed

+411
-45
lines changed

Encoder/XmlEncoder.php

Lines changed: 21 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -137,26 +137,22 @@ public function decode(string $data, string $format, array $context = []): mixed
137137
// todo: throw an exception if the root node name is not correctly configured (bc)
138138

139139
if ($rootNode->hasChildNodes()) {
140-
$xpath = new \DOMXPath($dom);
141-
$data = [];
142-
foreach ($xpath->query('namespace::*', $dom->documentElement) as $nsNode) {
143-
$data['@'.$nsNode->nodeName] = $nsNode->nodeValue;
140+
$data = $this->parseXml($rootNode, $context);
141+
if (\is_array($data)) {
142+
$data = $this->addXmlNamespaces($data, $rootNode, $dom);
144143
}
145144

146-
unset($data['@xmlns:xml']);
147-
148-
if (empty($data)) {
149-
return $this->parseXml($rootNode, $context);
150-
}
151-
152-
return array_merge($data, (array) $this->parseXml($rootNode, $context));
145+
return $data;
153146
}
154147

155148
if (!$rootNode->hasAttributes()) {
156149
return $rootNode->nodeValue;
157150
}
158151

159-
return array_merge($this->parseXmlAttributes($rootNode, $context), ['#' => $rootNode->nodeValue]);
152+
$data = array_merge($this->parseXmlAttributes($rootNode, $context), ['#' => $rootNode->nodeValue]);
153+
$data = $this->addXmlNamespaces($data, $rootNode, $dom);
154+
155+
return $data;
160156
}
161157

162158
public function supportsEncoding(string $format): bool
@@ -328,6 +324,19 @@ private function parseXmlValue(\DOMNode $node, array $context = []): array|strin
328324
return $value;
329325
}
330326

327+
private function addXmlNamespaces(array $data, \DOMNode $node, \DOMDocument $document): array
328+
{
329+
$xpath = new \DOMXPath($document);
330+
331+
foreach ($xpath->query('namespace::*', $node) as $nsNode) {
332+
$data['@'.$nsNode->nodeName] = $nsNode->nodeValue;
333+
}
334+
335+
unset($data['@xmlns:xml']);
336+
337+
return $data;
338+
}
339+
331340
/**
332341
* Parse the data and convert it to DOMElements.
333342
*

Normalizer/AbstractNormalizer.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -342,6 +342,7 @@ protected function instantiateObject(array &$data, string $class, array &$contex
342342
$missingConstructorArguments = [];
343343
$params = [];
344344
$unsetKeys = [];
345+
345346
foreach ($constructorParameters as $constructorParameter) {
346347
$paramName = $constructorParameter->name;
347348
$attributeContext = $this->getAttributeDenormalizationContext($class, $paramName, $context);

Normalizer/AbstractObjectNormalizer.php

Lines changed: 36 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -187,7 +187,9 @@ public function normalize(mixed $object, string $format = null, array $context =
187187
$attributeContext = $this->getAttributeNormalizationContext($object, $attribute, $context);
188188

189189
try {
190-
$attributeValue = $this->getAttributeValue($object, $attribute, $format, $attributeContext);
190+
$attributeValue = $attribute === $this->classDiscriminatorResolver?->getMappingForMappedObject($object)?->getTypeProperty()
191+
? $this->classDiscriminatorResolver?->getTypeForMappedObject($object)
192+
: $this->getAttributeValue($object, $attribute, $format, $attributeContext);
191193
} catch (UninitializedPropertyException $e) {
192194
if ($context[self::SKIP_UNINITIALIZED_VALUES] ?? $this->defaultContext[self::SKIP_UNINITIALIZED_VALUES] ?? true) {
193195
continue;
@@ -258,22 +260,18 @@ protected function getAttributes(object $object, ?string $format, array $context
258260
return $this->attributesCache[$key];
259261
}
260262

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-
271263
$attributes = $this->extractAttributes($object, $format, $context);
272264

273265
if ($mapping = $this->classDiscriminatorResolver?->getMappingForMappedObject($object)) {
274266
array_unshift($attributes, $mapping->getTypeProperty());
275267
}
276268

269+
$allowedAttributes = $this->getAllowedAttributes($object, $context, true);
270+
271+
if (false !== $allowedAttributes) {
272+
$attributes = array_intersect($attributes, $allowedAttributes);
273+
}
274+
277275
if ($context['cache_key'] && \stdClass::class !== $class) {
278276
$this->attributesCache[$key] = $attributes;
279277
}
@@ -364,8 +362,12 @@ public function denormalize(mixed $data, string $type, string $format = null, ar
364362
}
365363

366364
if ($attributeContext[self::DEEP_OBJECT_TO_POPULATE] ?? $this->defaultContext[self::DEEP_OBJECT_TO_POPULATE] ?? false) {
365+
$discriminatorMapping = $this->classDiscriminatorResolver?->getMappingForMappedObject($object);
366+
367367
try {
368-
$attributeContext[self::OBJECT_TO_POPULATE] = $this->getAttributeValue($object, $attribute, $format, $attributeContext);
368+
$attributeContext[self::OBJECT_TO_POPULATE] = $attribute === $discriminatorMapping?->getTypeProperty()
369+
? $discriminatorMapping
370+
: $this->getAttributeValue($object, $attribute, $format, $attributeContext);
369371
} catch (NoSuchPropertyException) {
370372
}
371373
}
@@ -432,8 +434,10 @@ private function validateAndDenormalize(array $types, string $currentClass, stri
432434
{
433435
$expectedTypes = [];
434436
$isUnionType = \count($types) > 1;
437+
$e = null;
435438
$extraAttributesException = null;
436439
$missingConstructorArgumentsException = null;
440+
$isNullable = false;
437441
foreach ($types as $type) {
438442
if (null === $data && $type->isNullable()) {
439443
return null;
@@ -456,18 +460,22 @@ private function validateAndDenormalize(array $types, string $currentClass, stri
456460
// In XML and CSV all basic datatypes are represented as strings, it is e.g. not possible to determine,
457461
// if a value is meant to be a string, float, int or a boolean value from the serialized representation.
458462
// That's why we have to transform the values, if one of these non-string basic datatypes is expected.
463+
$builtinType = $type->getBuiltinType();
459464
if (\is_string($data) && (XmlEncoder::FORMAT === $format || CsvEncoder::FORMAT === $format)) {
460465
if ('' === $data) {
461-
if (Type::BUILTIN_TYPE_ARRAY === $builtinType = $type->getBuiltinType()) {
466+
if (Type::BUILTIN_TYPE_ARRAY === $builtinType) {
462467
return [];
463468
}
464469

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

470-
switch ($builtinType ?? $type->getBuiltinType()) {
478+
switch ($builtinType) {
471479
case Type::BUILTIN_TYPE_BOOL:
472480
// according to https://www.w3.org/TR/xmlschema-2/#boolean, valid representations are "false", "true", "0" and "1"
473481
if ('false' === $data || '0' === $data) {
@@ -564,24 +572,28 @@ private function validateAndDenormalize(array $types, string $currentClass, stri
564572
return $data;
565573
}
566574
} catch (NotNormalizableValueException|InvalidArgumentException $e) {
567-
if (!$isUnionType) {
575+
if (!$isUnionType && !$isNullable) {
568576
throw $e;
569577
}
570578
} catch (ExtraAttributesException $e) {
571-
if (!$isUnionType) {
579+
if (!$isUnionType && !$isNullable) {
572580
throw $e;
573581
}
574582

575583
$extraAttributesException ??= $e;
576584
} catch (MissingConstructorArgumentsException $e) {
577-
if (!$isUnionType) {
585+
if (!$isUnionType && !$isNullable) {
578586
throw $e;
579587
}
580588

581589
$missingConstructorArgumentsException ??= $e;
582590
}
583591
}
584592

593+
if ($isNullable) {
594+
return null;
595+
}
596+
585597
if ($extraAttributesException) {
586598
throw $extraAttributesException;
587599
}
@@ -590,6 +602,10 @@ private function validateAndDenormalize(array $types, string $currentClass, stri
590602
throw $missingConstructorArgumentsException;
591603
}
592604

605+
if (!$isUnionType && $e) {
606+
throw $e;
607+
}
608+
593609
if ($context[self::DISABLE_TYPE_ENFORCEMENT] ?? $this->defaultContext[self::DISABLE_TYPE_ENFORCEMENT] ?? false) {
594610
return $data;
595611
}
@@ -629,7 +645,7 @@ private function getTypes(string $currentClass, string $attribute): ?array
629645
return $this->typesCache[$key] = $types;
630646
}
631647

632-
if (null !== $this->classDiscriminatorResolver && null !== $discriminatorMapping = $this->classDiscriminatorResolver->getMappingForClass($currentClass)) {
648+
if ($discriminatorMapping = $this->classDiscriminatorResolver?->getMappingForClass($currentClass)) {
633649
if ($discriminatorMapping->getTypeProperty() === $attribute) {
634650
return $this->typesCache[$key] = [
635651
new Type(Type::BUILTIN_TYPE_STRING),

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) {

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
/**

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

Tests/Encoder/XmlEncoderTest.php

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -472,6 +472,17 @@ public function testDecodeWithNamespace()
472472
$array = $this->getNamespacedArray();
473473

474474
$this->assertEquals($array, $this->encoder->decode($source, 'xml'));
475+
476+
$source = '<?xml version="1.0"?>'."\n".
477+
'<response xmlns="http://www.w3.org/2005/Atom" xmlns:app="http://www.w3.org/2007/app" app:foo="bar">'.
478+
'</response>'."\n";
479+
480+
$this->assertEquals([
481+
'@xmlns' => 'http://www.w3.org/2005/Atom',
482+
'@xmlns:app' => 'http://www.w3.org/2007/app',
483+
'@app:foo' => 'bar',
484+
'#' => '',
485+
], $this->encoder->decode($source, 'xml'));
475486
}
476487

477488
public function testDecodeScalarWithAttribute()

Tests/Fixtures/DummyString.php

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 <[email protected]>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
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 <[email protected]>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
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 <[email protected]>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
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+
}

0 commit comments

Comments
 (0)