Skip to content

Commit

Permalink
Merge branch '6.4' into 7.0
Browse files Browse the repository at this point in the history
* 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
  ...
  • Loading branch information
fabpot committed Nov 25, 2023
2 parents 68d5ab1 + ff423e8 commit 5825bb1
Show file tree
Hide file tree
Showing 17 changed files with 407 additions and 45 deletions.
33 changes: 21 additions & 12 deletions Encoder/XmlEncoder.php
Original file line number Diff line number Diff line change
Expand Up @@ -137,26 +137,22 @@ public function decode(string $data, string $format, array $context = []): mixed
// todo: throw an exception if the root node name is not correctly configured (bc)

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

unset($data['@xmlns:xml']);

if (empty($data)) {
return $this->parseXml($rootNode, $context);
}

return array_merge($data, (array) $this->parseXml($rootNode, $context));
return $data;
}

if (!$rootNode->hasAttributes()) {
return $rootNode->nodeValue;
}

return array_merge($this->parseXmlAttributes($rootNode, $context), ['#' => $rootNode->nodeValue]);
$data = array_merge($this->parseXmlAttributes($rootNode, $context), ['#' => $rootNode->nodeValue]);
$data = $this->addXmlNamespaces($data, $rootNode, $dom);

return $data;
}

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

private function addXmlNamespaces(array $data, \DOMNode $node, \DOMDocument $document): array
{
$xpath = new \DOMXPath($document);

foreach ($xpath->query('namespace::*', $node) as $nsNode) {
$data['@'.$nsNode->nodeName] = $nsNode->nodeValue;
}

unset($data['@xmlns:xml']);

return $data;
}

/**
* Parse the data and convert it to DOMElements.
*
Expand Down
1 change: 1 addition & 0 deletions Normalizer/AbstractNormalizer.php
Original file line number Diff line number Diff line change
Expand Up @@ -320,6 +320,7 @@ protected function instantiateObject(array &$data, string $class, array &$contex
$missingConstructorArguments = [];
$params = [];
$unsetKeys = [];

foreach ($constructorParameters as $constructorParameter) {
$paramName = $constructorParameter->name;
$attributeContext = $this->getAttributeDenormalizationContext($class, $paramName, $context);
Expand Down
56 changes: 36 additions & 20 deletions Normalizer/AbstractObjectNormalizer.php
Original file line number Diff line number Diff line change
Expand Up @@ -176,7 +176,9 @@ public function normalize(mixed $object, string $format = null, array $context =
$attributeContext = $this->getAttributeNormalizationContext($object, $attribute, $context);

try {
$attributeValue = $this->getAttributeValue($object, $attribute, $format, $attributeContext);
$attributeValue = $attribute === $this->classDiscriminatorResolver?->getMappingForMappedObject($object)?->getTypeProperty()
? $this->classDiscriminatorResolver?->getTypeForMappedObject($object)
: $this->getAttributeValue($object, $attribute, $format, $attributeContext);
} catch (UninitializedPropertyException $e) {
if ($context[self::SKIP_UNINITIALIZED_VALUES] ?? $this->defaultContext[self::SKIP_UNINITIALIZED_VALUES] ?? true) {
continue;
Expand Down Expand Up @@ -244,22 +246,18 @@ protected function getAttributes(object $object, ?string $format, array $context
return $this->attributesCache[$key];
}

$allowedAttributes = $this->getAllowedAttributes($object, $context, true);

if (false !== $allowedAttributes) {
if ($context['cache_key']) {
$this->attributesCache[$key] = $allowedAttributes;
}

return $allowedAttributes;
}

$attributes = $this->extractAttributes($object, $format, $context);

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

$allowedAttributes = $this->getAllowedAttributes($object, $context, true);

if (false !== $allowedAttributes) {
$attributes = array_intersect($attributes, $allowedAttributes);
}

if ($context['cache_key'] && \stdClass::class !== $class) {
$this->attributesCache[$key] = $attributes;
}
Expand Down Expand Up @@ -340,8 +338,12 @@ public function denormalize(mixed $data, string $type, string $format = null, ar
}

if ($attributeContext[self::DEEP_OBJECT_TO_POPULATE] ?? $this->defaultContext[self::DEEP_OBJECT_TO_POPULATE] ?? false) {
$discriminatorMapping = $this->classDiscriminatorResolver?->getMappingForMappedObject($object);

try {
$attributeContext[self::OBJECT_TO_POPULATE] = $this->getAttributeValue($object, $attribute, $format, $attributeContext);
$attributeContext[self::OBJECT_TO_POPULATE] = $attribute === $discriminatorMapping?->getTypeProperty()
? $discriminatorMapping
: $this->getAttributeValue($object, $attribute, $format, $attributeContext);
} catch (NoSuchPropertyException) {
}
}
Expand Down Expand Up @@ -405,8 +407,10 @@ private function validateAndDenormalize(array $types, string $currentClass, stri
{
$expectedTypes = [];
$isUnionType = \count($types) > 1;
$e = null;
$extraAttributesException = null;
$missingConstructorArgumentsException = null;
$isNullable = false;
foreach ($types as $type) {
if (null === $data && $type->isNullable()) {
return null;
Expand All @@ -429,18 +433,22 @@ private function validateAndDenormalize(array $types, string $currentClass, stri
// In XML and CSV all basic datatypes are represented as strings, it is e.g. not possible to determine,
// if a value is meant to be a string, float, int or a boolean value from the serialized representation.
// That's why we have to transform the values, if one of these non-string basic datatypes is expected.
$builtinType = $type->getBuiltinType();
if (\is_string($data) && (XmlEncoder::FORMAT === $format || CsvEncoder::FORMAT === $format)) {
if ('' === $data) {
if (Type::BUILTIN_TYPE_ARRAY === $builtinType = $type->getBuiltinType()) {
if (Type::BUILTIN_TYPE_ARRAY === $builtinType) {
return [];
}

if ($type->isNullable() && \in_array($builtinType, [Type::BUILTIN_TYPE_BOOL, Type::BUILTIN_TYPE_INT, Type::BUILTIN_TYPE_FLOAT], true)) {
return null;
if (Type::BUILTIN_TYPE_STRING === $builtinType) {
return '';
}

// Don't return null yet because Object-types that come first may accept empty-string too
$isNullable = $isNullable ?: $type->isNullable();
}

switch ($builtinType ?? $type->getBuiltinType()) {
switch ($builtinType) {
case Type::BUILTIN_TYPE_BOOL:
// according to https://www.w3.org/TR/xmlschema-2/#boolean, valid representations are "false", "true", "0" and "1"
if ('false' === $data || '0' === $data) {
Expand Down Expand Up @@ -537,24 +545,28 @@ private function validateAndDenormalize(array $types, string $currentClass, stri
return $data;
}
} catch (NotNormalizableValueException|InvalidArgumentException $e) {
if (!$isUnionType) {
if (!$isUnionType && !$isNullable) {
throw $e;
}
} catch (ExtraAttributesException $e) {
if (!$isUnionType) {
if (!$isUnionType && !$isNullable) {
throw $e;
}

$extraAttributesException ??= $e;
} catch (MissingConstructorArgumentsException $e) {
if (!$isUnionType) {
if (!$isUnionType && !$isNullable) {
throw $e;
}

$missingConstructorArgumentsException ??= $e;
}
}

if ($isNullable) {
return null;
}

if ($extraAttributesException) {
throw $extraAttributesException;
}
Expand All @@ -563,6 +575,10 @@ private function validateAndDenormalize(array $types, string $currentClass, stri
throw $missingConstructorArgumentsException;
}

if (!$isUnionType && $e) {
throw $e;
}

if ($context[self::DISABLE_TYPE_ENFORCEMENT] ?? $this->defaultContext[self::DISABLE_TYPE_ENFORCEMENT] ?? false) {
return $data;
}
Expand Down Expand Up @@ -602,7 +618,7 @@ private function getTypes(string $currentClass, string $attribute): ?array
return $this->typesCache[$key] = $types;
}

if (null !== $this->classDiscriminatorResolver && null !== $discriminatorMapping = $this->classDiscriminatorResolver->getMappingForClass($currentClass)) {
if ($discriminatorMapping = $this->classDiscriminatorResolver?->getMappingForClass($currentClass)) {
if ($discriminatorMapping->getTypeProperty() === $attribute) {
return $this->typesCache[$key] = [
new Type(Type::BUILTIN_TYPE_STRING),
Expand Down
4 changes: 4 additions & 0 deletions Normalizer/GetSetMethodNormalizer.php
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,10 @@ public function supportsDenormalization(mixed $data, string $type, string $forma
*/
private function supports(string $class): bool
{
if ($this->classDiscriminatorResolver?->getMappingForClass($class)) {
return true;
}

$class = new \ReflectionClass($class);
$methods = $class->getMethods(\ReflectionMethod::IS_PUBLIC);
foreach ($methods as $method) {
Expand Down
16 changes: 4 additions & 12 deletions Normalizer/ObjectNormalizer.php
Original file line number Diff line number Diff line change
Expand Up @@ -30,9 +30,6 @@ final class ObjectNormalizer extends AbstractObjectNormalizer
{
protected PropertyAccessorInterface $propertyAccessor;

/** @var array<string, string|null> */
private array $discriminatorCache = [];

private readonly \Closure $objectClassResolver;

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

protected function getAttributeValue(object $object, string $attribute, string $format = null, array $context = []): mixed
{
$cacheKey = $object::class;
if (!\array_key_exists($cacheKey, $this->discriminatorCache)) {
$this->discriminatorCache[$cacheKey] = null;
if (null !== $this->classDiscriminatorResolver) {
$mapping = $this->classDiscriminatorResolver->getMappingForMappedObject($object);
$this->discriminatorCache[$cacheKey] = $mapping?->getTypeProperty();
}
}
$mapping = $this->classDiscriminatorResolver?->getMappingForMappedObject($object);

return $attribute === $this->discriminatorCache[$cacheKey] ? $this->classDiscriminatorResolver->getTypeForMappedObject($object) : $this->propertyAccessor->getValue($object, $attribute);
return $attribute === $mapping?->getTypeProperty()
? $mapping
: $this->propertyAccessor->getValue($object, $attribute);
}

protected function setAttributeValue(object $object, string $attribute, mixed $value, string $format = null, array $context = []): void
Expand Down
4 changes: 4 additions & 0 deletions Normalizer/PropertyNormalizer.php
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,10 @@ public function supportsDenormalization(mixed $data, string $type, string $forma
*/
private function supports(string $class): bool
{
if ($this->classDiscriminatorResolver?->getMappingForClass($class)) {
return true;
}

$class = new \ReflectionClass($class);

// We look for at least one non-static property
Expand Down
11 changes: 11 additions & 0 deletions Tests/Encoder/XmlEncoderTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -472,6 +472,17 @@ public function testDecodeWithNamespace()
$array = $this->getNamespacedArray();

$this->assertEquals($array, $this->encoder->decode($source, 'xml'));

$source = '<?xml version="1.0"?>'."\n".
'<response xmlns="http://www.w3.org/2005/Atom" xmlns:app="http://www.w3.org/2007/app" app:foo="bar">'.
'</response>'."\n";

$this->assertEquals([
'@xmlns' => 'http://www.w3.org/2005/Atom',
'@xmlns:app' => 'http://www.w3.org/2007/app',
'@app:foo' => 'bar',
'#' => '',
], $this->encoder->decode($source, 'xml'));
}

public function testDecodeScalarWithAttribute()
Expand Down
29 changes: 29 additions & 0 deletions Tests/Fixtures/DummyString.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
<?php

/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <[email protected]>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

namespace Symfony\Component\Serializer\Tests\Fixtures;

use Symfony\Component\Serializer\Normalizer\DenormalizableInterface;
use Symfony\Component\Serializer\Normalizer\DenormalizerInterface;

/**
* @author Jeroen <github.com/Jeroeny>
*/
class DummyString implements DenormalizableInterface
{
/** @var string $value */
public $value;

public function denormalize(DenormalizerInterface $denormalizer, $data, string $format = null, array $context = []): void
{
$this->value = $data;
}
}
22 changes: 22 additions & 0 deletions Tests/Fixtures/DummyWithNotNormalizable.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
<?php

/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <[email protected]>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

namespace Symfony\Component\Serializer\Tests\Fixtures;

/**
* @author Jeroen <github.com/Jeroeny>
*/
class DummyWithNotNormalizable
{
public function __construct(public NotNormalizableDummy|null $value)
{
}
}
22 changes: 22 additions & 0 deletions Tests/Fixtures/DummyWithObjectOrBool.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
<?php

/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <[email protected]>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

namespace Symfony\Component\Serializer\Tests\Fixtures;

/**
* @author Jeroen <github.com/Jeroeny>
*/
class DummyWithObjectOrBool
{
public function __construct(public Php80WithPromotedTypedConstructor|bool $value)
{
}
}
22 changes: 22 additions & 0 deletions Tests/Fixtures/DummyWithObjectOrNull.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
<?php

/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <[email protected]>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

namespace Symfony\Component\Serializer\Tests\Fixtures;

/**
* @author Jeroen <github.com/Jeroeny>
*/
class DummyWithObjectOrNull
{
public function __construct(public Php80WithPromotedTypedConstructor|null $value)
{
}
}
22 changes: 22 additions & 0 deletions Tests/Fixtures/DummyWithStringObject.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
<?php

/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <[email protected]>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

namespace Symfony\Component\Serializer\Tests\Fixtures;

/**
* @author Jeroen <github.com/Jeroeny>
*/
class DummyWithStringObject
{
public function __construct(public DummyString|null $value)
{
}
}
Loading

0 comments on commit 5825bb1

Please sign in to comment.