Skip to content

Commit 5825bb1

Browse files
committed
Merge branch '6.4' into 7.0
* 6.4: (26 commits) [AssetMapper] Fix: also download files referenced by url() in CSS [AssetMapper] Fix eager imports are not deduplicated [Mime] Add `TemplatedEmail::$locale` to the serialized props fix tests add return types to test fixtures fix merge [Cache] Get TRUNCATE statement from platform 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 [AssetMapper] Adding an option (true by default) to not publish dot files [AssetMapper] Fixing out-of-date test on Windows [HttpKernel] Fix logging deprecations to the "php" channel when channel "deprecation" is not defined ...
2 parents 68d5ab1 + ff423e8 commit 5825bb1

17 files changed

+407
-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
@@ -320,6 +320,7 @@ protected function instantiateObject(array &$data, string $class, array &$contex
320320
$missingConstructorArguments = [];
321321
$params = [];
322322
$unsetKeys = [];
323+
323324
foreach ($constructorParameters as $constructorParameter) {
324325
$paramName = $constructorParameter->name;
325326
$attributeContext = $this->getAttributeDenormalizationContext($class, $paramName, $context);

Normalizer/AbstractObjectNormalizer.php

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

178178
try {
179-
$attributeValue = $this->getAttributeValue($object, $attribute, $format, $attributeContext);
179+
$attributeValue = $attribute === $this->classDiscriminatorResolver?->getMappingForMappedObject($object)?->getTypeProperty()
180+
? $this->classDiscriminatorResolver?->getTypeForMappedObject($object)
181+
: $this->getAttributeValue($object, $attribute, $format, $attributeContext);
180182
} catch (UninitializedPropertyException $e) {
181183
if ($context[self::SKIP_UNINITIALIZED_VALUES] ?? $this->defaultContext[self::SKIP_UNINITIALIZED_VALUES] ?? true) {
182184
continue;
@@ -244,22 +246,18 @@ protected function getAttributes(object $object, ?string $format, array $context
244246
return $this->attributesCache[$key];
245247
}
246248

247-
$allowedAttributes = $this->getAllowedAttributes($object, $context, true);
248-
249-
if (false !== $allowedAttributes) {
250-
if ($context['cache_key']) {
251-
$this->attributesCache[$key] = $allowedAttributes;
252-
}
253-
254-
return $allowedAttributes;
255-
}
256-
257249
$attributes = $this->extractAttributes($object, $format, $context);
258250

259251
if ($mapping = $this->classDiscriminatorResolver?->getMappingForMappedObject($object)) {
260252
array_unshift($attributes, $mapping->getTypeProperty());
261253
}
262254

255+
$allowedAttributes = $this->getAllowedAttributes($object, $context, true);
256+
257+
if (false !== $allowedAttributes) {
258+
$attributes = array_intersect($attributes, $allowedAttributes);
259+
}
260+
263261
if ($context['cache_key'] && \stdClass::class !== $class) {
264262
$this->attributesCache[$key] = $attributes;
265263
}
@@ -340,8 +338,12 @@ public function denormalize(mixed $data, string $type, string $format = null, ar
340338
}
341339

342340
if ($attributeContext[self::DEEP_OBJECT_TO_POPULATE] ?? $this->defaultContext[self::DEEP_OBJECT_TO_POPULATE] ?? false) {
341+
$discriminatorMapping = $this->classDiscriminatorResolver?->getMappingForMappedObject($object);
342+
343343
try {
344-
$attributeContext[self::OBJECT_TO_POPULATE] = $this->getAttributeValue($object, $attribute, $format, $attributeContext);
344+
$attributeContext[self::OBJECT_TO_POPULATE] = $attribute === $discriminatorMapping?->getTypeProperty()
345+
? $discriminatorMapping
346+
: $this->getAttributeValue($object, $attribute, $format, $attributeContext);
345347
} catch (NoSuchPropertyException) {
346348
}
347349
}
@@ -405,8 +407,10 @@ private function validateAndDenormalize(array $types, string $currentClass, stri
405407
{
406408
$expectedTypes = [];
407409
$isUnionType = \count($types) > 1;
410+
$e = null;
408411
$extraAttributesException = null;
409412
$missingConstructorArgumentsException = null;
413+
$isNullable = false;
410414
foreach ($types as $type) {
411415
if (null === $data && $type->isNullable()) {
412416
return null;
@@ -429,18 +433,22 @@ private function validateAndDenormalize(array $types, string $currentClass, stri
429433
// In XML and CSV all basic datatypes are represented as strings, it is e.g. not possible to determine,
430434
// if a value is meant to be a string, float, int or a boolean value from the serialized representation.
431435
// That's why we have to transform the values, if one of these non-string basic datatypes is expected.
436+
$builtinType = $type->getBuiltinType();
432437
if (\is_string($data) && (XmlEncoder::FORMAT === $format || CsvEncoder::FORMAT === $format)) {
433438
if ('' === $data) {
434-
if (Type::BUILTIN_TYPE_ARRAY === $builtinType = $type->getBuiltinType()) {
439+
if (Type::BUILTIN_TYPE_ARRAY === $builtinType) {
435440
return [];
436441
}
437442

438-
if ($type->isNullable() && \in_array($builtinType, [Type::BUILTIN_TYPE_BOOL, Type::BUILTIN_TYPE_INT, Type::BUILTIN_TYPE_FLOAT], true)) {
439-
return null;
443+
if (Type::BUILTIN_TYPE_STRING === $builtinType) {
444+
return '';
440445
}
446+
447+
// Don't return null yet because Object-types that come first may accept empty-string too
448+
$isNullable = $isNullable ?: $type->isNullable();
441449
}
442450

443-
switch ($builtinType ?? $type->getBuiltinType()) {
451+
switch ($builtinType) {
444452
case Type::BUILTIN_TYPE_BOOL:
445453
// according to https://www.w3.org/TR/xmlschema-2/#boolean, valid representations are "false", "true", "0" and "1"
446454
if ('false' === $data || '0' === $data) {
@@ -537,24 +545,28 @@ private function validateAndDenormalize(array $types, string $currentClass, stri
537545
return $data;
538546
}
539547
} catch (NotNormalizableValueException|InvalidArgumentException $e) {
540-
if (!$isUnionType) {
548+
if (!$isUnionType && !$isNullable) {
541549
throw $e;
542550
}
543551
} catch (ExtraAttributesException $e) {
544-
if (!$isUnionType) {
552+
if (!$isUnionType && !$isNullable) {
545553
throw $e;
546554
}
547555

548556
$extraAttributesException ??= $e;
549557
} catch (MissingConstructorArgumentsException $e) {
550-
if (!$isUnionType) {
558+
if (!$isUnionType && !$isNullable) {
551559
throw $e;
552560
}
553561

554562
$missingConstructorArgumentsException ??= $e;
555563
}
556564
}
557565

566+
if ($isNullable) {
567+
return null;
568+
}
569+
558570
if ($extraAttributesException) {
559571
throw $extraAttributesException;
560572
}
@@ -563,6 +575,10 @@ private function validateAndDenormalize(array $types, string $currentClass, stri
563575
throw $missingConstructorArgumentsException;
564576
}
565577

578+
if (!$isUnionType && $e) {
579+
throw $e;
580+
}
581+
566582
if ($context[self::DISABLE_TYPE_ENFORCEMENT] ?? $this->defaultContext[self::DISABLE_TYPE_ENFORCEMENT] ?? false) {
567583
return $data;
568584
}
@@ -602,7 +618,7 @@ private function getTypes(string $currentClass, string $attribute): ?array
602618
return $this->typesCache[$key] = $types;
603619
}
604620

605-
if (null !== $this->classDiscriminatorResolver && null !== $discriminatorMapping = $this->classDiscriminatorResolver->getMappingForClass($currentClass)) {
621+
if ($discriminatorMapping = $this->classDiscriminatorResolver?->getMappingForClass($currentClass)) {
606622
if ($discriminatorMapping->getTypeProperty() === $attribute) {
607623
return $this->typesCache[$key] = [
608624
new Type(Type::BUILTIN_TYPE_STRING),

Normalizer/GetSetMethodNormalizer.php

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,10 @@ public function supportsDenormalization(mixed $data, string $type, string $forma
5858
*/
5959
private function supports(string $class): bool
6060
{
61+
if ($this->classDiscriminatorResolver?->getMappingForClass($class)) {
62+
return true;
63+
}
64+
6165
$class = new \ReflectionClass($class);
6266
$methods = $class->getMethods(\ReflectionMethod::IS_PUBLIC);
6367
foreach ($methods as $method) {

Normalizer/ObjectNormalizer.php

Lines changed: 4 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -30,9 +30,6 @@ final class ObjectNormalizer extends AbstractObjectNormalizer
3030
{
3131
protected PropertyAccessorInterface $propertyAccessor;
3232

33-
/** @var array<string, string|null> */
34-
private array $discriminatorCache = [];
35-
3633
private readonly \Closure $objectClassResolver;
3734

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

119116
protected function getAttributeValue(object $object, string $attribute, string $format = null, array $context = []): mixed
120117
{
121-
$cacheKey = $object::class;
122-
if (!\array_key_exists($cacheKey, $this->discriminatorCache)) {
123-
$this->discriminatorCache[$cacheKey] = null;
124-
if (null !== $this->classDiscriminatorResolver) {
125-
$mapping = $this->classDiscriminatorResolver->getMappingForMappedObject($object);
126-
$this->discriminatorCache[$cacheKey] = $mapping?->getTypeProperty();
127-
}
128-
}
118+
$mapping = $this->classDiscriminatorResolver?->getMappingForMappedObject($object);
129119

130-
return $attribute === $this->discriminatorCache[$cacheKey] ? $this->classDiscriminatorResolver->getTypeForMappedObject($object) : $this->propertyAccessor->getValue($object, $attribute);
120+
return $attribute === $mapping?->getTypeProperty()
121+
? $mapping
122+
: $this->propertyAccessor->getValue($object, $attribute);
131123
}
132124

133125
protected function setAttributeValue(object $object, string $attribute, mixed $value, string $format = null, array $context = []): void

Normalizer/PropertyNormalizer.php

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,10 @@ public function supportsDenormalization(mixed $data, string $type, string $forma
7474
*/
7575
private function supports(string $class): bool
7676
{
77+
if ($this->classDiscriminatorResolver?->getMappingForClass($class)) {
78+
return true;
79+
}
80+
7781
$class = new \ReflectionClass($class);
7882

7983
// 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 = []): void
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)