From 29dc0cf71eba7aaad9f6329bbc0773fd8ded2ae0 Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Wed, 19 May 2021 15:18:37 +0200 Subject: [PATCH 001/297] Bump Symfony 6 to PHP 8 --- composer.json | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/composer.json b/composer.json index 60a0567f9..a21c01c19 100644 --- a/composer.json +++ b/composer.json @@ -16,10 +16,9 @@ } ], "require": { - "php": ">=7.2.5", + "php": ">=8.0.2", "symfony/deprecation-contracts": "^2.1", - "symfony/polyfill-ctype": "~1.8", - "symfony/polyfill-php80": "^1.15" + "symfony/polyfill-ctype": "~1.8" }, "require-dev": { "doctrine/annotations": "^1.12", From faf6f02ce2ab7b3ed29fabcc5af8c6b120d1d5a3 Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Thu, 20 May 2021 14:59:02 +0200 Subject: [PATCH 002/297] Bump symfony/* deps to ^5.4|^6.0 --- composer.json | 40 ++++++++++++++++++++-------------------- 1 file changed, 20 insertions(+), 20 deletions(-) diff --git a/composer.json b/composer.json index 863eedad8..27775ccfa 100644 --- a/composer.json +++ b/composer.json @@ -23,31 +23,31 @@ "require-dev": { "doctrine/annotations": "^1.12", "phpdocumentor/reflection-docblock": "^3.2|^4.0|^5.0", - "symfony/cache": "^4.4|^5.0|^6.0", - "symfony/config": "^4.4|^5.0|^6.0", - "symfony/dependency-injection": "^4.4|^5.0|^6.0", - "symfony/error-handler": "^4.4|^5.0|^6.0", - "symfony/filesystem": "^4.4|^5.0|^6.0", - "symfony/form": "^4.4|^5.0|^6.0", - "symfony/http-foundation": "^4.4|^5.0|^6.0", - "symfony/http-kernel": "^4.4|^5.0|^6.0", - "symfony/mime": "^4.4|^5.0|^6.0", - "symfony/property-access": "^4.4.9|^5.0.9|^6.0", - "symfony/property-info": "^5.3|^6.0", - "symfony/uid": "^5.1|^6.0", - "symfony/validator": "^4.4|^5.0|^6.0", - "symfony/var-dumper": "^4.4|^5.0|^6.0", - "symfony/var-exporter": "^4.4|^5.0|^6.0", - "symfony/yaml": "^4.4|^5.0|^6.0" + "symfony/cache": "^5.4|^6.0", + "symfony/config": "^5.4|^6.0", + "symfony/dependency-injection": "^5.4|^6.0", + "symfony/error-handler": "^5.4|^6.0", + "symfony/filesystem": "^5.4|^6.0", + "symfony/form": "^5.4|^6.0", + "symfony/http-foundation": "^5.4|^6.0", + "symfony/http-kernel": "^5.4|^6.0", + "symfony/mime": "^5.4|^6.0", + "symfony/property-access": "^5.4|^6.0", + "symfony/property-info": "^5.4|^6.0", + "symfony/uid": "^5.4|^6.0", + "symfony/validator": "^5.4|^6.0", + "symfony/var-dumper": "^5.4|^6.0", + "symfony/var-exporter": "^5.4|^6.0", + "symfony/yaml": "^5.4|^6.0" }, "conflict": { "doctrine/annotations": "<1.12", "phpdocumentor/reflection-docblock": "<3.2.2", "phpdocumentor/type-resolver": "<1.4.0", - "symfony/dependency-injection": "<4.4", - "symfony/property-access": "<4.4", - "symfony/property-info": "<4.4", - "symfony/yaml": "<4.4" + "symfony/dependency-injection": "<5.4", + "symfony/property-access": "<5.4", + "symfony/property-info": "<5.4", + "symfony/yaml": "<5.4" }, "suggest": { "psr/cache-implementation": "For using the metadata cache.", From dca2510fdde45a54d6afe4d3ee5056374894f520 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=A9my=20Deruss=C3=A9?= Date: Wed, 19 May 2021 20:52:47 +0200 Subject: [PATCH 003/297] Remove constraint for PHP < 8 --- Encoder/CsvEncoder.php | 4 ---- Encoder/JsonDecode.php | 2 +- Encoder/JsonEncode.php | 2 +- Mapping/Loader/AnnotationLoader.php | 8 +++----- Normalizer/ObjectNormalizer.php | 16 ++++++---------- Normalizer/PropertyNormalizer.php | 15 ++++++--------- Tests/Annotation/ContextTest.php | 4 ---- Tests/Annotation/DiscriminatorMapTest.php | 12 ------------ Tests/Encoder/CsvEncoderTest.php | 6 ------ .../AnnotationLoaderWithAttributesTest.php | 3 --- Tests/Normalizer/ObjectNormalizerTest.php | 6 ------ Tests/Normalizer/PropertyNormalizerTest.php | 3 --- 12 files changed, 17 insertions(+), 64 deletions(-) diff --git a/Encoder/CsvEncoder.php b/Encoder/CsvEncoder.php index 909e8fef0..e73c0ae29 100644 --- a/Encoder/CsvEncoder.php +++ b/Encoder/CsvEncoder.php @@ -53,10 +53,6 @@ class CsvEncoder implements EncoderInterface, DecoderInterface public function __construct(array $defaultContext = []) { $this->defaultContext = array_merge($this->defaultContext, $defaultContext); - - if (\PHP_VERSION_ID < 70400 && '' === $this->defaultContext[self::ESCAPE_CHAR_KEY]) { - $this->defaultContext[self::ESCAPE_CHAR_KEY] = '\\'; - } } /** diff --git a/Encoder/JsonDecode.php b/Encoder/JsonDecode.php index 7ba4bffde..01593edbe 100644 --- a/Encoder/JsonDecode.php +++ b/Encoder/JsonDecode.php @@ -84,7 +84,7 @@ public function decode(string $data, string $format, array $context = []) throw new NotEncodableValueException($e->getMessage(), 0, $e); } - if (\PHP_VERSION_ID >= 70300 && (\JSON_THROW_ON_ERROR & $options)) { + if (\JSON_THROW_ON_ERROR & $options) { return $decodedData; } diff --git a/Encoder/JsonEncode.php b/Encoder/JsonEncode.php index 88a837c60..d817aa0ec 100644 --- a/Encoder/JsonEncode.php +++ b/Encoder/JsonEncode.php @@ -46,7 +46,7 @@ public function encode($data, string $format, array $context = []) throw new NotEncodableValueException($e->getMessage(), 0, $e); } - if (\PHP_VERSION_ID >= 70300 && (\JSON_THROW_ON_ERROR & $options)) { + if (\JSON_THROW_ON_ERROR & $options) { return $encodedJson; } diff --git a/Mapping/Loader/AnnotationLoader.php b/Mapping/Loader/AnnotationLoader.php index bd0f049c7..48f351415 100644 --- a/Mapping/Loader/AnnotationLoader.php +++ b/Mapping/Loader/AnnotationLoader.php @@ -155,11 +155,9 @@ public function loadClassMetadata(ClassMetadataInterface $classMetadata) */ public function loadAnnotations(object $reflector): iterable { - if (\PHP_VERSION_ID >= 80000) { - foreach ($reflector->getAttributes() as $attribute) { - if (self::KNOWN_ANNOTATIONS[$attribute->getName()] ?? false) { - yield $attribute->newInstance(); - } + foreach ($reflector->getAttributes() as $attribute) { + if (self::KNOWN_ANNOTATIONS[$attribute->getName()] ?? false) { + yield $attribute->newInstance(); } } diff --git a/Normalizer/ObjectNormalizer.php b/Normalizer/ObjectNormalizer.php index c830359d2..458b38f9d 100644 --- a/Normalizer/ObjectNormalizer.php +++ b/Normalizer/ObjectNormalizer.php @@ -107,20 +107,16 @@ protected function extractAttributes(object $object, string $format = null, arra } } - $checkPropertyInitialization = \PHP_VERSION_ID >= 70400; - // properties foreach ($reflClass->getProperties() as $reflProperty) { $isPublic = $reflProperty->isPublic(); - if ($checkPropertyInitialization) { - if (!$isPublic) { - $reflProperty->setAccessible(true); - } - if (!$reflProperty->isInitialized($object)) { - unset($attributes[$reflProperty->name]); - continue; - } + if (!$isPublic) { + $reflProperty->setAccessible(true); + } + if (!$reflProperty->isInitialized($object)) { + unset($attributes[$reflProperty->name]); + continue; } if (!$isPublic) { diff --git a/Normalizer/PropertyNormalizer.php b/Normalizer/PropertyNormalizer.php index 39e750275..8a0a3efee 100644 --- a/Normalizer/PropertyNormalizer.php +++ b/Normalizer/PropertyNormalizer.php @@ -101,18 +101,15 @@ protected function extractAttributes(object $object, string $format = null, arra { $reflectionObject = new \ReflectionObject($object); $attributes = []; - $checkPropertyInitialization = \PHP_VERSION_ID >= 70400; do { foreach ($reflectionObject->getProperties() as $property) { - if ($checkPropertyInitialization) { - if (!$property->isPublic()) { - $property->setAccessible(true); - } - - if (!$property->isInitialized($object)) { - continue; - } + if (!$property->isPublic()) { + $property->setAccessible(true); + } + + if (!$property->isInitialized($object)) { + continue; } if (!$this->isAllowedAttribute($reflectionObject->getName(), $property->name, $format, $context)) { diff --git a/Tests/Annotation/ContextTest.php b/Tests/Annotation/ContextTest.php index ad79c893b..0cb064868 100644 --- a/Tests/Annotation/ContextTest.php +++ b/Tests/Annotation/ContextTest.php @@ -77,9 +77,6 @@ public function provideTestThrowsOnNonArrayContextData(): iterable yield 'non-array denormalization context' => [['normalizationContext' => 'not_an_array']]; } - /** - * @requires PHP 8 - */ public function testInvalidGroupOption() { $this->expectException(InvalidArgumentException::class); @@ -128,7 +125,6 @@ public function testAsContextArg() } /** - * @requires PHP 8 * @dataProvider provideValidInputs */ public function testValidInputs(callable $factory, string $expectedDump) diff --git a/Tests/Annotation/DiscriminatorMapTest.php b/Tests/Annotation/DiscriminatorMapTest.php index fd9cf68a2..000011125 100644 --- a/Tests/Annotation/DiscriminatorMapTest.php +++ b/Tests/Annotation/DiscriminatorMapTest.php @@ -23,9 +23,6 @@ class DiscriminatorMapTest extends TestCase { use ExpectDeprecationTrait; - /** - * @requires PHP 8 - */ public function testGetTypePropertyAndMapping() { $annotation = new DiscriminatorMap(...['typeProperty' => 'type', 'mapping' => [ @@ -67,9 +64,6 @@ public function testExceptionWithoutTypeProperty() new DiscriminatorMap(['mapping' => ['foo' => 'FooClass']]); } - /** - * @requires PHP 8 - */ public function testExceptionWithEmptyTypeProperty() { $this->expectException(InvalidArgumentException::class); @@ -85,9 +79,6 @@ public function testExceptionWithEmptyTypePropertyLegacy() new DiscriminatorMap(['typeProperty' => '', 'mapping' => ['foo' => 'FooClass']]); } - /** - * @requires PHP 8 - */ public function testExceptionWithoutMappingProperty() { $this->expectException(InvalidArgumentException::class); @@ -103,9 +94,6 @@ public function testExceptionWithoutMappingPropertyLegacy() new DiscriminatorMap(['typeProperty' => 'type']); } - /** - * @requires PHP 8 - */ public function testExceptionWitEmptyMappingProperty() { $this->expectException(InvalidArgumentException::class); diff --git a/Tests/Encoder/CsvEncoderTest.php b/Tests/Encoder/CsvEncoderTest.php index c71a173d4..c66366a98 100644 --- a/Tests/Encoder/CsvEncoderTest.php +++ b/Tests/Encoder/CsvEncoderTest.php @@ -59,9 +59,6 @@ public function testTrueFalseValues() ], $this->encoder->decode($csv, 'csv', [CsvEncoder::AS_COLLECTION_KEY => false])); } - /** - * @requires PHP 7.4 - */ public function testDoubleQuotesAndSlashes() { $this->assertSame($csv = <<<'CSV' @@ -74,9 +71,6 @@ public function testDoubleQuotesAndSlashes() $this->assertSame($data, $this->encoder->decode($csv, 'csv', [CsvEncoder::AS_COLLECTION_KEY => false])); } - /** - * @requires PHP 7.4 - */ public function testSingleSlash() { $this->assertSame($csv = "0\n\\\n", $this->encoder->encode($data = ['\\'], 'csv')); diff --git a/Tests/Mapping/Loader/AnnotationLoaderWithAttributesTest.php b/Tests/Mapping/Loader/AnnotationLoaderWithAttributesTest.php index c5836f26f..b0db50a9a 100644 --- a/Tests/Mapping/Loader/AnnotationLoaderWithAttributesTest.php +++ b/Tests/Mapping/Loader/AnnotationLoaderWithAttributesTest.php @@ -13,9 +13,6 @@ use Symfony\Component\Serializer\Mapping\Loader\AnnotationLoader; -/** - * @requires PHP 8 - */ class AnnotationLoaderWithAttributesTest extends AnnotationLoaderTest { protected function createLoader(): AnnotationLoader diff --git a/Tests/Normalizer/ObjectNormalizerTest.php b/Tests/Normalizer/ObjectNormalizerTest.php index 860c16f60..c1126910b 100644 --- a/Tests/Normalizer/ObjectNormalizerTest.php +++ b/Tests/Normalizer/ObjectNormalizerTest.php @@ -119,9 +119,6 @@ public function testNormalize() ); } - /** - * @requires PHP 7.4 - */ public function testNormalizeObjectWithUninitializedProperties() { $obj = new Php74Dummy(); @@ -131,9 +128,6 @@ public function testNormalizeObjectWithUninitializedProperties() ); } - /** - * @requires PHP 7.4 - */ public function testNormalizeObjectWithUninitializedPrivateProperties() { $obj = new Php74DummyPrivate(); diff --git a/Tests/Normalizer/PropertyNormalizerTest.php b/Tests/Normalizer/PropertyNormalizerTest.php index b2a76656d..4fd54388e 100644 --- a/Tests/Normalizer/PropertyNormalizerTest.php +++ b/Tests/Normalizer/PropertyNormalizerTest.php @@ -86,9 +86,6 @@ public function testNormalize() ); } - /** - * @requires PHP 7.4 - */ public function testNormalizeObjectWithUninitializedProperties() { $obj = new Php74Dummy(); From 2c5e8e4e6e22979e3732f7b227b7da7d4079e7cb Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Wed, 2 Jun 2021 18:09:43 +0200 Subject: [PATCH 004/297] Update phpunit.xml.dist files for phpunit >= 9.3 --- phpunit.xml.dist | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/phpunit.xml.dist b/phpunit.xml.dist index 2d99ce1d4..fd66cdfcc 100644 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -1,7 +1,7 @@ - - + + ./ - - ./Tests - ./vendor - - - + + + ./Tests + ./vendor + + From a773359926a48d7e3afed7e1a88dc7c8b453f5d3 Mon Sep 17 00:00:00 2001 From: "Alexander M. Turek" Date: Mon, 21 Jun 2021 11:50:22 +0200 Subject: [PATCH 005/297] Remove code for old libxml versions Signed-off-by: Alexander M. Turek --- Encoder/XmlEncoder.php | 6 ------ 1 file changed, 6 deletions(-) diff --git a/Encoder/XmlEncoder.php b/Encoder/XmlEncoder.php index cc49630b6..5964cd7a9 100644 --- a/Encoder/XmlEncoder.php +++ b/Encoder/XmlEncoder.php @@ -115,18 +115,12 @@ public function decode(string $data, string $format, array $context = []) } $internalErrors = libxml_use_internal_errors(true); - if (\LIBXML_VERSION < 20900) { - $disableEntities = libxml_disable_entity_loader(true); - } libxml_clear_errors(); $dom = new \DOMDocument(); $dom->loadXML($data, $context[self::LOAD_OPTIONS] ?? $this->defaultContext[self::LOAD_OPTIONS]); libxml_use_internal_errors($internalErrors); - if (\LIBXML_VERSION < 20900) { - libxml_disable_entity_loader($disableEntities); - } if ($error = libxml_get_last_error()) { libxml_clear_errors(); From ab0ddac0f8bfb2bf2009f3c3903d32e900f36dfb Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Thu, 1 Jul 2021 11:27:18 +0200 Subject: [PATCH 006/297] [Serializer] add union types --- Annotation/Context.php | 5 +- Annotation/DiscriminatorMap.php | 23 +------ Annotation/Groups.php | 19 ++---- Annotation/MaxDepth.php | 19 +----- Annotation/SerializedName.php | 19 +----- .../CompiledClassMetadataCacheWarmer.php | 2 +- Encoder/ChainEncoder.php | 2 +- Encoder/CsvEncoder.php | 2 +- Encoder/EncoderInterface.php | 2 +- Encoder/JsonEncode.php | 4 +- Encoder/JsonEncoder.php | 2 +- Encoder/XmlEncoder.php | 17 ++--- Encoder/YamlEncoder.php | 2 +- .../ClassDiscriminatorFromClassMetadata.php | 6 +- Mapping/ClassDiscriminatorMapping.php | 5 +- .../ClassDiscriminatorResolverInterface.php | 10 +-- Mapping/Factory/CacheClassMetadataFactory.php | 4 +- Mapping/Factory/ClassMetadataFactory.php | 4 +- .../Factory/ClassMetadataFactoryInterface.php | 8 +-- Mapping/Factory/ClassResolverTrait.php | 8 +-- .../Factory/CompiledClassMetadataFactory.php | 4 +- Normalizer/AbstractNormalizer.php | 27 +++----- Normalizer/AbstractObjectNormalizer.php | 32 ++++------ Normalizer/ArrayDenormalizer.php | 4 +- .../ConstraintViolationListNormalizer.php | 6 +- .../ContextAwareDenormalizerInterface.php | 2 +- .../ContextAwareNormalizerInterface.php | 2 +- Normalizer/CustomNormalizer.php | 8 +-- Normalizer/DataUriNormalizer.php | 8 +-- Normalizer/DateIntervalNormalizer.php | 8 +-- Normalizer/DateTimeNormalizer.php | 8 +-- Normalizer/DateTimeZoneNormalizer.php | 8 +-- Normalizer/DenormalizableInterface.php | 2 +- Normalizer/DenormalizerInterface.php | 4 +- Normalizer/FormErrorNormalizer.php | 4 +- Normalizer/GetSetMethodNormalizer.php | 6 +- Normalizer/JsonSerializableNormalizer.php | 8 +-- Normalizer/MimeMessageNormalizer.php | 8 +-- Normalizer/NormalizerInterface.php | 4 +- Normalizer/ObjectNormalizer.php | 4 +- Normalizer/ProblemNormalizer.php | 4 +- Normalizer/PropertyNormalizer.php | 12 ++-- Normalizer/UidNormalizer.php | 8 +-- Normalizer/UnwrappingDenormalizer.php | 4 +- Serializer.php | 18 +++--- SerializerInterface.php | 4 +- Tests/Annotation/DiscriminatorMapTest.php | 63 ------------------- Tests/Annotation/GroupsTest.php | 32 ---------- Tests/Annotation/MaxDepthTest.php | 41 +----------- Tests/Annotation/SerializedNameTest.php | 21 ------- Tests/Fixtures/AbstractNormalizerDummy.php | 10 +-- Tests/Fixtures/DenormalizableDummy.php | 2 +- Tests/Fixtures/Dummy.php | 2 +- .../Fixtures/NormalizableTraversableDummy.php | 2 +- Tests/Fixtures/ScalarDummy.php | 2 +- .../Fixtures/StaticConstructorNormalizer.php | 2 +- .../Normalizer/UnwrappinDenormalizerTest.php | 6 +- 57 files changed, 143 insertions(+), 410 deletions(-) diff --git a/Annotation/Context.php b/Annotation/Context.php index 3d6464790..d4b2ffcac 100644 --- a/Annotation/Context.php +++ b/Annotation/Context.php @@ -35,7 +35,7 @@ final class Context * * @throws InvalidArgumentException */ - public function __construct(array $options = [], array $context = [], array $normalizationContext = [], array $denormalizationContext = [], $groups = []) + public function __construct(array $options = [], array $context = [], array $normalizationContext = [], array $denormalizationContext = [], string|array $groups = []) { if (!$context) { if (!array_intersect((array_keys($options)), ['normalizationContext', 'groups', 'context', 'value', 'denormalizationContext'])) { @@ -48,9 +48,6 @@ public function __construct(array $options = [], array $context = [], array $nor $context = $options['value'] ?? $options['context'] ?? []; } } - if (!\is_string($groups) && !\is_array($groups)) { - throw new \TypeError(sprintf('"%s": Expected parameter $groups to be a string or an array of strings, got "%s".', __METHOD__, get_debug_type($groups))); - } $normalizationContext = $options['normalizationContext'] ?? $normalizationContext; $denormalizationContext = $options['denormalizationContext'] ?? $denormalizationContext; diff --git a/Annotation/DiscriminatorMap.php b/Annotation/DiscriminatorMap.php index d01287bfd..79c27c7bc 100644 --- a/Annotation/DiscriminatorMap.php +++ b/Annotation/DiscriminatorMap.php @@ -25,32 +25,11 @@ #[\Attribute(\Attribute::TARGET_CLASS)] class DiscriminatorMap { - /** - * @var string - */ private $typeProperty; - - /** - * @var array - */ private $mapping; - /** - * @param string $typeProperty - * - * @throws InvalidArgumentException - */ - public function __construct($typeProperty, array $mapping = null) + public function __construct(string $typeProperty, array $mapping) { - if (\is_array($typeProperty)) { - trigger_deprecation('symfony/serializer', '5.3', 'Passing an array as first argument to "%s" is deprecated. Use named arguments instead.', __METHOD__); - - $mapping = $typeProperty['mapping'] ?? null; - $typeProperty = $typeProperty['typeProperty'] ?? null; - } elseif (!\is_string($typeProperty)) { - throw new \TypeError(sprintf('"%s": Argument $typeProperty was expected to be a string or array, got "%s".', __METHOD__, get_debug_type($typeProperty))); - } - if (empty($typeProperty)) { throw new InvalidArgumentException(sprintf('Parameter "typeProperty" of annotation "%s" cannot be empty.', static::class)); } diff --git a/Annotation/Groups.php b/Annotation/Groups.php index 0a251f786..198a79eb3 100644 --- a/Annotation/Groups.php +++ b/Annotation/Groups.php @@ -32,27 +32,18 @@ class Groups /** * @param string|string[] $groups - * - * @throws InvalidArgumentException */ - public function __construct($groups) + public function __construct(string|array $groups) { - if (\is_string($groups)) { - $groups = (array) $groups; - } elseif (!\is_array($groups)) { - throw new \TypeError(sprintf('"%s": Parameter $groups is expected to be a string or an array of strings, got "%s".', __METHOD__, get_debug_type($groups))); - } elseif (isset($groups['value'])) { - trigger_deprecation('symfony/serializer', '5.3', 'Passing an array of properties as first argument to "%s" is deprecated. Use named arguments instead.', __METHOD__); + $groups = (array) $groups; - $groups = (array) $groups['value']; - } if (empty($groups)) { throw new InvalidArgumentException(sprintf('Parameter of annotation "%s" cannot be empty.', static::class)); } foreach ($groups as $group) { - if (!\is_string($group)) { - throw new InvalidArgumentException(sprintf('Parameter of annotation "%s" must be a string or an array of strings.', static::class)); + if (!\is_string($group) || '' === $group) { + throw new InvalidArgumentException(sprintf('Parameter of annotation "%s" must be a string or an array of non-empty strings.', static::class)); } } @@ -60,8 +51,6 @@ public function __construct($groups) } /** - * Gets groups. - * * @return string[] */ public function getGroups() diff --git a/Annotation/MaxDepth.php b/Annotation/MaxDepth.php index e0cc41ebd..73c8a9e17 100644 --- a/Annotation/MaxDepth.php +++ b/Annotation/MaxDepth.php @@ -25,26 +25,11 @@ #[\Attribute(\Attribute::TARGET_METHOD | \Attribute::TARGET_PROPERTY)] class MaxDepth { - /** - * @var int - */ private $maxDepth; - /** - * @param int $maxDepth - */ - public function __construct($maxDepth) + public function __construct(int $maxDepth) { - if (\is_array($maxDepth)) { - trigger_deprecation('symfony/serializer', '5.3', 'Passing an array as first argument to "%s" is deprecated. Use named arguments instead.', __METHOD__); - - if (!isset($maxDepth['value'])) { - throw new InvalidArgumentException(sprintf('Parameter of annotation "%s" should be set.', static::class)); - } - $maxDepth = $maxDepth['value']; - } - - if (!\is_int($maxDepth) || $maxDepth <= 0) { + if ($maxDepth <= 0) { throw new InvalidArgumentException(sprintf('Parameter of annotation "%s" must be a positive integer.', static::class)); } diff --git a/Annotation/SerializedName.php b/Annotation/SerializedName.php index 874d955f1..64e888906 100644 --- a/Annotation/SerializedName.php +++ b/Annotation/SerializedName.php @@ -25,26 +25,11 @@ #[\Attribute(\Attribute::TARGET_METHOD | \Attribute::TARGET_PROPERTY)] final class SerializedName { - /** - * @var string - */ private $serializedName; - /** - * @param string $serializedName - */ - public function __construct($serializedName) + public function __construct(string $serializedName) { - if (\is_array($serializedName)) { - trigger_deprecation('symfony/serializer', '5.3', 'Passing an array as first argument to "%s" is deprecated. Use named arguments instead.', __METHOD__); - - if (!isset($serializedName['value'])) { - throw new InvalidArgumentException(sprintf('Parameter of annotation "%s" should be set.', static::class)); - } - $serializedName = $serializedName['value']; - } - - if (!\is_string($serializedName) || empty($serializedName)) { + if (empty($serializedName)) { throw new InvalidArgumentException(sprintf('Parameter of annotation "%s" must be a non-empty string.', static::class)); } diff --git a/CacheWarmer/CompiledClassMetadataCacheWarmer.php b/CacheWarmer/CompiledClassMetadataCacheWarmer.php index 360640e13..081bcff67 100644 --- a/CacheWarmer/CompiledClassMetadataCacheWarmer.php +++ b/CacheWarmer/CompiledClassMetadataCacheWarmer.php @@ -40,7 +40,7 @@ public function __construct(array $classesToCompile, ClassMetadataFactoryInterfa /** * {@inheritdoc} */ - public function warmUp($cacheDir) + public function warmUp(string $cacheDir) { $metadatas = []; diff --git a/Encoder/ChainEncoder.php b/Encoder/ChainEncoder.php index 48722d21c..0c2a53753 100644 --- a/Encoder/ChainEncoder.php +++ b/Encoder/ChainEncoder.php @@ -35,7 +35,7 @@ public function __construct(array $encoders = []) /** * {@inheritdoc} */ - final public function encode($data, string $format, array $context = []) + final public function encode(mixed $data, string $format, array $context = []) { return $this->getEncoder($format, $context)->encode($data, $format, $context); } diff --git a/Encoder/CsvEncoder.php b/Encoder/CsvEncoder.php index e73c0ae29..8d1f1f99b 100644 --- a/Encoder/CsvEncoder.php +++ b/Encoder/CsvEncoder.php @@ -58,7 +58,7 @@ public function __construct(array $defaultContext = []) /** * {@inheritdoc} */ - public function encode($data, string $format, array $context = []) + public function encode(mixed $data, string $format, array $context = []) { $handle = fopen('php://temp,', 'w+'); diff --git a/Encoder/EncoderInterface.php b/Encoder/EncoderInterface.php index 0ce4636be..5b4c5ea43 100644 --- a/Encoder/EncoderInterface.php +++ b/Encoder/EncoderInterface.php @@ -29,7 +29,7 @@ interface EncoderInterface * * @throws UnexpectedValueException */ - public function encode($data, string $format, array $context = []); + public function encode(mixed $data, string $format, array $context = []); /** * Checks whether the serializer can encode to given format. diff --git a/Encoder/JsonEncode.php b/Encoder/JsonEncode.php index d817aa0ec..4da793ffd 100644 --- a/Encoder/JsonEncode.php +++ b/Encoder/JsonEncode.php @@ -32,11 +32,9 @@ public function __construct(array $defaultContext = []) } /** - * Encodes PHP data to a JSON string. - * * {@inheritdoc} */ - public function encode($data, string $format, array $context = []) + public function encode(mixed $data, string $format, array $context = []) { $options = $context[self::OPTIONS] ?? $this->defaultContext[self::OPTIONS]; diff --git a/Encoder/JsonEncoder.php b/Encoder/JsonEncoder.php index cf4a89ca1..c10be44b1 100644 --- a/Encoder/JsonEncoder.php +++ b/Encoder/JsonEncoder.php @@ -32,7 +32,7 @@ public function __construct(JsonEncode $encodingImpl = null, JsonDecode $decodin /** * {@inheritdoc} */ - public function encode($data, string $format, array $context = []) + public function encode(mixed $data, string $format, array $context = []) { return $this->encodingImpl->encode($data, self::FORMAT, $context); } diff --git a/Encoder/XmlEncoder.php b/Encoder/XmlEncoder.php index 5964cd7a9..7a885e711 100644 --- a/Encoder/XmlEncoder.php +++ b/Encoder/XmlEncoder.php @@ -80,7 +80,7 @@ public function __construct(array $defaultContext = []) /** * {@inheritdoc} */ - public function encode($data, string $format, array $context = []) + public function encode(mixed $data, string $format, array $context = []) { $encoderIgnoredNodeTypes = $context[self::ENCODER_IGNORED_NODE_TYPES] ?? $this->defaultContext[self::ENCODER_IGNORED_NODE_TYPES]; $ignorePiNode = \in_array(\XML_PI_NODE, $encoderIgnoredNodeTypes, true); @@ -217,10 +217,7 @@ final protected function appendCData(\DOMNode $node, string $val): bool return true; } - /** - * @param \DOMDocumentFragment $fragment - */ - final protected function appendDocumentFragment(\DOMNode $node, $fragment): bool + final protected function appendDocumentFragment(\DOMNode $node, \DOMDocumentFragment $fragment): bool { if ($fragment instanceof \DOMDocumentFragment) { $node->appendChild($fragment); @@ -357,11 +354,9 @@ private function parseXmlValue(\DOMNode $node, array $context = []) /** * Parse the data and convert it to DOMElements. * - * @param array|object $data - * * @throws NotEncodableValueException */ - private function buildXml(\DOMNode $parentNode, $data, string $xmlRootNodeName = null): bool + private function buildXml(\DOMNode $parentNode, mixed $data, string $xmlRootNodeName = null): bool { $append = true; $removeEmptyTags = $this->context[self::REMOVE_EMPTY_TAGS] ?? $this->defaultContext[self::REMOVE_EMPTY_TAGS] ?? false; @@ -431,10 +426,8 @@ private function buildXml(\DOMNode $parentNode, $data, string $xmlRootNodeName = /** * Selects the type of node to create and appends it to the parent. - * - * @param array|object $data */ - private function appendNode(\DOMNode $parentNode, $data, string $nodeName, string $key = null): bool + private function appendNode(\DOMNode $parentNode, mixed $data, string $nodeName, string $key = null): bool { $node = $this->dom->createElement($nodeName); if (null !== $key) { @@ -462,7 +455,7 @@ private function needsCdataWrapping(string $val): bool * * @throws NotEncodableValueException */ - private function selectNodeType(\DOMNode $node, $val): bool + private function selectNodeType(\DOMNode $node, mixed $val): bool { if (\is_array($val)) { return $this->buildXml($node, $val); diff --git a/Encoder/YamlEncoder.php b/Encoder/YamlEncoder.php index c688c2283..651cfc70f 100644 --- a/Encoder/YamlEncoder.php +++ b/Encoder/YamlEncoder.php @@ -54,7 +54,7 @@ public function __construct(Dumper $dumper = null, Parser $parser = null, array /** * {@inheritdoc} */ - public function encode($data, string $format, array $context = []) + public function encode(mixed $data, string $format, array $context = []) { $context = array_merge($this->defaultContext, $context); diff --git a/Mapping/ClassDiscriminatorFromClassMetadata.php b/Mapping/ClassDiscriminatorFromClassMetadata.php index 23554ffda..2e46fecc8 100644 --- a/Mapping/ClassDiscriminatorFromClassMetadata.php +++ b/Mapping/ClassDiscriminatorFromClassMetadata.php @@ -44,7 +44,7 @@ public function getMappingForClass(string $class): ?ClassDiscriminatorMapping /** * {@inheritdoc} */ - public function getMappingForMappedObject($object): ?ClassDiscriminatorMapping + public function getMappingForMappedObject(object|string $object): ?ClassDiscriminatorMapping { if ($this->classMetadataFactory->hasMetadataFor($object)) { $metadata = $this->classMetadataFactory->getMetadataFor($object); @@ -65,7 +65,7 @@ public function getMappingForMappedObject($object): ?ClassDiscriminatorMapping /** * {@inheritdoc} */ - public function getTypeForMappedObject($object): ?string + public function getTypeForMappedObject(object|string $object): ?string { if (null === $mapping = $this->getMappingForMappedObject($object)) { return null; @@ -74,7 +74,7 @@ public function getTypeForMappedObject($object): ?string return $mapping->getMappedObjectType($object); } - private function resolveMappingForMappedObject($object) + private function resolveMappingForMappedObject(object|string $object) { $reflectionClass = new \ReflectionClass($object); if ($parentClass = $reflectionClass->getParentClass()) { diff --git a/Mapping/ClassDiscriminatorMapping.php b/Mapping/ClassDiscriminatorMapping.php index 0c314c7ba..d38088fad 100644 --- a/Mapping/ClassDiscriminatorMapping.php +++ b/Mapping/ClassDiscriminatorMapping.php @@ -47,10 +47,7 @@ public function getClassForType(string $type): ?string return $this->typesMapping[$type] ?? null; } - /** - * @param object|string $object - */ - public function getMappedObjectType($object): ?string + public function getMappedObjectType(object|string $object): ?string { foreach ($this->typesMapping as $type => $typeClass) { if (is_a($object, $typeClass)) { diff --git a/Mapping/ClassDiscriminatorResolverInterface.php b/Mapping/ClassDiscriminatorResolverInterface.php index 22d76fafb..83a12d845 100644 --- a/Mapping/ClassDiscriminatorResolverInterface.php +++ b/Mapping/ClassDiscriminatorResolverInterface.php @@ -20,13 +20,7 @@ interface ClassDiscriminatorResolverInterface { public function getMappingForClass(string $class): ?ClassDiscriminatorMapping; - /** - * @param object|string $object - */ - public function getMappingForMappedObject($object): ?ClassDiscriminatorMapping; + public function getMappingForMappedObject(object|string $object): ?ClassDiscriminatorMapping; - /** - * @param object|string $object - */ - public function getTypeForMappedObject($object): ?string; + public function getTypeForMappedObject(object|string $object): ?string; } diff --git a/Mapping/Factory/CacheClassMetadataFactory.php b/Mapping/Factory/CacheClassMetadataFactory.php index a85bdb184..4d1fe154c 100644 --- a/Mapping/Factory/CacheClassMetadataFactory.php +++ b/Mapping/Factory/CacheClassMetadataFactory.php @@ -43,7 +43,7 @@ public function __construct(ClassMetadataFactoryInterface $decorated, CacheItemP /** * {@inheritdoc} */ - public function getMetadataFor($value) + public function getMetadataFor(string|object $value) { $class = $this->getClass($value); @@ -67,7 +67,7 @@ public function getMetadataFor($value) /** * {@inheritdoc} */ - public function hasMetadataFor($value) + public function hasMetadataFor(mixed $value) { return $this->decorated->hasMetadataFor($value); } diff --git a/Mapping/Factory/ClassMetadataFactory.php b/Mapping/Factory/ClassMetadataFactory.php index b55c070fb..5516e7eea 100644 --- a/Mapping/Factory/ClassMetadataFactory.php +++ b/Mapping/Factory/ClassMetadataFactory.php @@ -38,7 +38,7 @@ public function __construct(LoaderInterface $loader) /** * {@inheritdoc} */ - public function getMetadataFor($value) + public function getMetadataFor(string|object $value) { $class = $this->getClass($value); @@ -67,7 +67,7 @@ public function getMetadataFor($value) /** * {@inheritdoc} */ - public function hasMetadataFor($value) + public function hasMetadataFor(mixed $value) { return \is_object($value) || (\is_string($value) && (class_exists($value) || interface_exists($value, false))); } diff --git a/Mapping/Factory/ClassMetadataFactoryInterface.php b/Mapping/Factory/ClassMetadataFactoryInterface.php index 7ef91a823..5d0cfeacd 100644 --- a/Mapping/Factory/ClassMetadataFactoryInterface.php +++ b/Mapping/Factory/ClassMetadataFactoryInterface.php @@ -34,20 +34,16 @@ interface ClassMetadataFactoryInterface * {@link \Symfony\Component\Serializer\Mapping\Loader\LoaderInterface::loadClassMetadata()} method for further * configuration. At last, the new object is returned. * - * @param string|object $value - * * @return ClassMetadataInterface * * @throws InvalidArgumentException */ - public function getMetadataFor($value); + public function getMetadataFor(string|object $value); /** * Checks if class has metadata. * - * @param mixed $value - * * @return bool */ - public function hasMetadataFor($value); + public function hasMetadataFor(mixed $value); } diff --git a/Mapping/Factory/ClassResolverTrait.php b/Mapping/Factory/ClassResolverTrait.php index 0a65da5d5..05ce80922 100644 --- a/Mapping/Factory/ClassResolverTrait.php +++ b/Mapping/Factory/ClassResolverTrait.php @@ -25,11 +25,9 @@ trait ClassResolverTrait /** * Gets a class name for a given class or instance. * - * @param object|string $value - * * @throws InvalidArgumentException If the class does not exist */ - private function getClass($value): string + private function getClass(object|string $value): string { if (\is_string($value)) { if (!class_exists($value) && !interface_exists($value, false)) { @@ -39,10 +37,6 @@ private function getClass($value): string return ltrim($value, '\\'); } - if (!\is_object($value)) { - throw new InvalidArgumentException(sprintf('Cannot create metadata for non-objects. Got: "%s".', get_debug_type($value))); - } - return \get_class($value); } } diff --git a/Mapping/Factory/CompiledClassMetadataFactory.php b/Mapping/Factory/CompiledClassMetadataFactory.php index 17daf9e66..1753cc584 100644 --- a/Mapping/Factory/CompiledClassMetadataFactory.php +++ b/Mapping/Factory/CompiledClassMetadataFactory.php @@ -44,7 +44,7 @@ public function __construct(string $compiledClassMetadataFile, ClassMetadataFact /** * {@inheritdoc} */ - public function getMetadataFor($value) + public function getMetadataFor(string|object $value) { $className = \is_object($value) ? \get_class($value) : $value; @@ -72,7 +72,7 @@ public function getMetadataFor($value) /** * {@inheritdoc} */ - public function hasMetadataFor($value) + public function hasMetadataFor(mixed $value) { $className = \is_object($value) ? \get_class($value) : $value; diff --git a/Normalizer/AbstractNormalizer.php b/Normalizer/AbstractNormalizer.php index a8698baaf..0aec8c6f9 100644 --- a/Normalizer/AbstractNormalizer.php +++ b/Normalizer/AbstractNormalizer.php @@ -202,11 +202,9 @@ protected function isCircularReference(object $object, array &$context) * * @final * - * @return mixed - * * @throws CircularReferenceException */ - protected function handleCircularReference(object $object, string $format = null, array $context = []) + protected function handleCircularReference(object $object, string $format = null, array $context = []): mixed { $circularReferenceHandler = $context[self::CIRCULAR_REFERENCE_HANDLER] ?? $this->defaultContext[self::CIRCULAR_REFERENCE_HANDLER]; if ($circularReferenceHandler) { @@ -219,14 +217,13 @@ protected function handleCircularReference(object $object, string $format = null /** * Gets attributes to normalize using groups. * - * @param string|object $classOrObject - * @param bool $attributesAsString If false, return an array of {@link AttributeMetadataInterface} + * @param bool $attributesAsString If false, return an array of {@link AttributeMetadataInterface} * * @throws LogicException if the 'allow_extra_attributes' context variable is false and no class metadata factory is provided * * @return string[]|AttributeMetadataInterface[]|bool */ - protected function getAllowedAttributes($classOrObject, array $context, bool $attributesAsString = false) + protected function getAllowedAttributes(string|object $classOrObject, array $context, bool $attributesAsString = false) { $allowExtraAttributes = $context[self::ALLOW_EXTRA_ATTRIBUTES] ?? $this->defaultContext[self::ALLOW_EXTRA_ATTRIBUTES]; if (!$this->classMetadataFactory) { @@ -274,11 +271,9 @@ protected function getGroups(array $context): array /** * Is this attribute allowed? * - * @param object|string $classOrObject - * * @return bool */ - protected function isAllowedAttribute($classOrObject, string $attribute, string $format = null, array $context = []) + protected function isAllowedAttribute(object|string $classOrObject, string $attribute, string $format = null, array $context = []) { $ignoredAttributes = $context[self::IGNORED_ATTRIBUTES] ?? $this->defaultContext[self::IGNORED_ATTRIBUTES]; if (\in_array($attribute, $ignoredAttributes)) { @@ -302,11 +297,9 @@ protected function isAllowedAttribute($classOrObject, string $attribute, string * Normalizes the given data to an array. It's particularly useful during * the denormalization process. * - * @param object|array $data - * * @return array */ - protected function prepareForDenormalization($data) + protected function prepareForDenormalization(object|array|null $data) { return (array) $data; } @@ -315,11 +308,9 @@ protected function prepareForDenormalization($data) * Returns the method to use to construct an object. This method must be either * the object constructor or static. * - * @param array|bool $allowedAttributes - * * @return \ReflectionMethod|null */ - protected function getConstructor(array &$data, string $class, array &$context, \ReflectionClass $reflectionClass, $allowedAttributes) + protected function getConstructor(array &$data, string $class, array &$context, \ReflectionClass $reflectionClass, array|bool $allowedAttributes) { return $reflectionClass->getConstructor(); } @@ -332,14 +323,12 @@ protected function getConstructor(array &$data, string $class, array &$context, * is removed from the context before being returned to avoid side effects * when recursively normalizing an object graph. * - * @param array|bool $allowedAttributes - * * @return object * * @throws RuntimeException * @throws MissingConstructorArgumentsException */ - protected function instantiateObject(array &$data, string $class, array &$context, \ReflectionClass $reflectionClass, $allowedAttributes, string $format = null) + protected function instantiateObject(array &$data, string $class, array &$context, \ReflectionClass $reflectionClass, array|bool $allowedAttributes, string $format = null) { if (null !== $object = $this->extractObjectToPopulate($class, $context, self::OBJECT_TO_POPULATE)) { unset($context[self::OBJECT_TO_POPULATE]); @@ -416,7 +405,7 @@ protected function instantiateObject(array &$data, string $class, array &$contex /** * @internal */ - protected function denormalizeParameter(\ReflectionClass $class, \ReflectionParameter $parameter, string $parameterName, $parameterData, array $context, string $format = null) + protected function denormalizeParameter(\ReflectionClass $class, \ReflectionParameter $parameter, string $parameterName, mixed $parameterData, array $context, string $format = null): mixed { try { if (($parameterType = $parameter->getType()) instanceof \ReflectionNamedType && !$parameterType->isBuiltin()) { diff --git a/Normalizer/AbstractObjectNormalizer.php b/Normalizer/AbstractObjectNormalizer.php index e629189c4..54afeb93e 100644 --- a/Normalizer/AbstractObjectNormalizer.php +++ b/Normalizer/AbstractObjectNormalizer.php @@ -125,7 +125,7 @@ public function __construct(ClassMetadataFactoryInterface $classMetadataFactory /** * {@inheritdoc} */ - public function supportsNormalization($data, string $format = null) + public function supportsNormalization(mixed $data, string $format = null) { return \is_object($data) && !$data instanceof \Traversable; } @@ -133,7 +133,7 @@ public function supportsNormalization($data, string $format = null) /** * {@inheritdoc} */ - public function normalize($object, string $format = null, array $context = []) + public function normalize(mixed $object, string $format = null, array $context = []) { if (!isset($context['cache_key'])) { $context['cache_key'] = $this->getCacheKey($format, $context); @@ -217,7 +217,7 @@ public function normalize($object, string $format = null, array $context = []) /** * Computes the normalization context merged with current one. Metadata always wins over global context, as more specific. */ - private function getAttributeNormalizationContext($object, string $attribute, array $context): array + private function getAttributeNormalizationContext(object $object, string $attribute, array $context): array { if (null === $metadata = $this->getAttributeMetadata($object, $attribute)) { return $context; @@ -238,7 +238,7 @@ private function getAttributeDenormalizationContext(string $class, string $attri return array_merge($context, $metadata->getDenormalizationContextForGroups($this->getGroups($context))); } - private function getAttributeMetadata($objectOrClass, string $attribute): ?AttributeMetadataInterface + private function getAttributeMetadata(object|string $objectOrClass, string $attribute): ?AttributeMetadataInterface { if (!$this->classMetadataFactory) { return null; @@ -250,7 +250,7 @@ private function getAttributeMetadata($objectOrClass, string $attribute): ?Attri /** * {@inheritdoc} */ - protected function instantiateObject(array &$data, string $class, array &$context, \ReflectionClass $reflectionClass, $allowedAttributes, string $format = null) + protected function instantiateObject(array &$data, string $class, array &$context, \ReflectionClass $reflectionClass, array|bool $allowedAttributes, string $format = null) { if ($this->classDiscriminatorResolver && $mapping = $this->classDiscriminatorResolver->getMappingForClass($class)) { if (!isset($data[$mapping->getTypeProperty()])) { @@ -324,7 +324,7 @@ abstract protected function getAttributeValue(object $object, string $attribute, /** * {@inheritdoc} */ - public function supportsDenormalization($data, string $type, string $format = null) + public function supportsDenormalization(mixed $data, string $type, string $format = null) { return class_exists($type) || (interface_exists($type, false) && $this->classDiscriminatorResolver && null !== $this->classDiscriminatorResolver->getMappingForClass($type)); } @@ -332,7 +332,7 @@ public function supportsDenormalization($data, string $type, string $format = nu /** * {@inheritdoc} */ - public function denormalize($data, string $type, string $format = null, array $context = []) + public function denormalize(mixed $data, string $type, string $format = null, array $context = []) { if (!isset($context['cache_key'])) { $context['cache_key'] = $this->getCacheKey($format, $context); @@ -386,19 +386,15 @@ public function denormalize($data, string $type, string $format = null, array $c /** * Sets attribute value. */ - abstract protected function setAttributeValue(object $object, string $attribute, $value, string $format = null, array $context = []); + abstract protected function setAttributeValue(object $object, string $attribute, mixed $value, string $format = null, array $context = []); /** * Validates the submitted data and denormalizes it. * - * @param mixed $data - * - * @return mixed - * * @throws NotNormalizableValueException * @throws LogicException */ - private function validateAndDenormalize(string $currentClass, string $attribute, $data, ?string $format, array $context) + private function validateAndDenormalize(string $currentClass, string $attribute, mixed $data, ?string $format, array $context): mixed { if (null === $types = $this->getTypes($currentClass, $attribute)) { return $data; @@ -534,7 +530,7 @@ private function validateAndDenormalize(string $currentClass, string $attribute, /** * @internal */ - protected function denormalizeParameter(\ReflectionClass $class, \ReflectionParameter $parameter, string $parameterName, $parameterData, array $context, string $format = null) + protected function denormalizeParameter(\ReflectionClass $class, \ReflectionParameter $parameter, string $parameterName, mixed $parameterData, array $context, string $format = null): mixed { if ($parameter->isVariadic() || null === $this->propertyTypeExtractor || null === $this->propertyTypeExtractor->getTypes($class->getName(), $parameterName)) { return parent::denormalizeParameter($class, $parameter, $parameterName, $parameterData, $context, $format); @@ -582,10 +578,8 @@ private function getTypes(string $currentClass, string $attribute): ?array /** * Sets an attribute and apply the name converter if necessary. - * - * @param mixed $attributeValue */ - private function updateData(array $data, string $attribute, $attributeValue, string $class, ?string $format, array $context): array + private function updateData(array $data, string $attribute, mixed $attributeValue, string $class, ?string $format, array $context): array { if (null === $attributeValue && ($context[self::SKIP_NULL_VALUES] ?? $this->defaultContext[self::SKIP_NULL_VALUES] ?? false)) { return $data; @@ -653,10 +647,8 @@ protected function createChildContext(array $parentContext, string $attribute, ? * Builds the cache key for the attributes cache. * * The key must be different for every option in the context that could change which attributes should be handled. - * - * @return bool|string */ - private function getCacheKey(?string $format, array $context) + private function getCacheKey(?string $format, array $context): bool|string { foreach ($context[self::EXCLUDE_FROM_CACHE_KEY] ?? $this->defaultContext[self::EXCLUDE_FROM_CACHE_KEY] as $key) { unset($context[$key]); diff --git a/Normalizer/ArrayDenormalizer.php b/Normalizer/ArrayDenormalizer.php index 77c746c75..332f4e5a6 100644 --- a/Normalizer/ArrayDenormalizer.php +++ b/Normalizer/ArrayDenormalizer.php @@ -34,7 +34,7 @@ class ArrayDenormalizer implements ContextAwareDenormalizerInterface, Denormaliz * * @throws NotNormalizableValueException */ - public function denormalize($data, string $type, string $format = null, array $context = []): array + public function denormalize(mixed $data, string $type, string $format = null, array $context = []): array { if (null === $this->denormalizer) { throw new BadMethodCallException('Please set a denormalizer before calling denormalize()!'); @@ -63,7 +63,7 @@ public function denormalize($data, string $type, string $format = null, array $c /** * {@inheritdoc} */ - public function supportsDenormalization($data, string $type, string $format = null, array $context = []): bool + public function supportsDenormalization(mixed $data, string $type, string $format = null, array $context = []): bool { if (null === $this->denormalizer) { throw new BadMethodCallException(sprintf('The nested denormalizer needs to be set to allow "%s()" to be used.', __METHOD__)); diff --git a/Normalizer/ConstraintViolationListNormalizer.php b/Normalizer/ConstraintViolationListNormalizer.php index 2546ffd0c..3ca313466 100644 --- a/Normalizer/ConstraintViolationListNormalizer.php +++ b/Normalizer/ConstraintViolationListNormalizer.php @@ -33,7 +33,7 @@ class ConstraintViolationListNormalizer implements NormalizerInterface, Cacheabl private $defaultContext; private $nameConverter; - public function __construct($defaultContext = [], NameConverterInterface $nameConverter = null) + public function __construct(array $defaultContext = [], NameConverterInterface $nameConverter = null) { $this->defaultContext = $defaultContext; $this->nameConverter = $nameConverter; @@ -44,7 +44,7 @@ public function __construct($defaultContext = [], NameConverterInterface $nameCo * * @return array */ - public function normalize($object, string $format = null, array $context = []) + public function normalize(mixed $object, string $format = null, array $context = []) { if (\array_key_exists(self::PAYLOAD_FIELDS, $context)) { $payloadFieldsToSerialize = $context[self::PAYLOAD_FIELDS]; @@ -109,7 +109,7 @@ public function normalize($object, string $format = null, array $context = []) /** * {@inheritdoc} */ - public function supportsNormalization($data, string $format = null) + public function supportsNormalization(mixed $data, string $format = null) { return $data instanceof ConstraintViolationListInterface; } diff --git a/Normalizer/ContextAwareDenormalizerInterface.php b/Normalizer/ContextAwareDenormalizerInterface.php index c875de1b5..b69e9c540 100644 --- a/Normalizer/ContextAwareDenormalizerInterface.php +++ b/Normalizer/ContextAwareDenormalizerInterface.php @@ -23,5 +23,5 @@ interface ContextAwareDenormalizerInterface extends DenormalizerInterface * * @param array $context options that denormalizers have access to */ - public function supportsDenormalization($data, string $type, string $format = null, array $context = []); + public function supportsDenormalization(mixed $data, string $type, string $format = null, array $context = []); } diff --git a/Normalizer/ContextAwareNormalizerInterface.php b/Normalizer/ContextAwareNormalizerInterface.php index ff0bb3a21..50a8d4127 100644 --- a/Normalizer/ContextAwareNormalizerInterface.php +++ b/Normalizer/ContextAwareNormalizerInterface.php @@ -23,5 +23,5 @@ interface ContextAwareNormalizerInterface extends NormalizerInterface * * @param array $context options that normalizers have access to */ - public function supportsNormalization($data, string $format = null, array $context = []); + public function supportsNormalization(mixed $data, string $format = null, array $context = []); } diff --git a/Normalizer/CustomNormalizer.php b/Normalizer/CustomNormalizer.php index 44ad1771b..8e3a09882 100644 --- a/Normalizer/CustomNormalizer.php +++ b/Normalizer/CustomNormalizer.php @@ -25,7 +25,7 @@ class CustomNormalizer implements NormalizerInterface, DenormalizerInterface, Se /** * {@inheritdoc} */ - public function normalize($object, string $format = null, array $context = []) + public function normalize(mixed $object, string $format = null, array $context = []) { return $object->normalize($this->serializer, $format, $context); } @@ -33,7 +33,7 @@ public function normalize($object, string $format = null, array $context = []) /** * {@inheritdoc} */ - public function denormalize($data, string $type, string $format = null, array $context = []) + public function denormalize(mixed $data, string $type, string $format = null, array $context = []) { $object = $this->extractObjectToPopulate($type, $context) ?? new $type(); $object->denormalize($this->serializer, $data, $format, $context); @@ -49,7 +49,7 @@ public function denormalize($data, string $type, string $format = null, array $c * * @return bool */ - public function supportsNormalization($data, string $format = null) + public function supportsNormalization(mixed $data, string $format = null) { return $data instanceof NormalizableInterface; } @@ -63,7 +63,7 @@ public function supportsNormalization($data, string $format = null) * * @return bool */ - public function supportsDenormalization($data, string $type, string $format = null) + public function supportsDenormalization(mixed $data, string $type, string $format = null) { return is_subclass_of($type, DenormalizableInterface::class); } diff --git a/Normalizer/DataUriNormalizer.php b/Normalizer/DataUriNormalizer.php index bb866ec9b..4d5aa1cb8 100644 --- a/Normalizer/DataUriNormalizer.php +++ b/Normalizer/DataUriNormalizer.php @@ -50,7 +50,7 @@ public function __construct(MimeTypeGuesserInterface $mimeTypeGuesser = null) * * @return string */ - public function normalize($object, string $format = null, array $context = []) + public function normalize(mixed $object, string $format = null, array $context = []) { if (!$object instanceof \SplFileInfo) { throw new InvalidArgumentException('The object must be an instance of "\SplFileInfo".'); @@ -76,7 +76,7 @@ public function normalize($object, string $format = null, array $context = []) /** * {@inheritdoc} */ - public function supportsNormalization($data, string $format = null) + public function supportsNormalization(mixed $data, string $format = null) { return $data instanceof \SplFileInfo; } @@ -93,7 +93,7 @@ public function supportsNormalization($data, string $format = null) * * @return \SplFileInfo */ - public function denormalize($data, string $type, string $format = null, array $context = []) + public function denormalize(mixed $data, string $type, string $format = null, array $context = []) { if (!preg_match('/^data:([a-z0-9][a-z0-9\!\#\$\&\-\^\_\+\.]{0,126}\/[a-z0-9][a-z0-9\!\#\$\&\-\^\_\+\.]{0,126}(;[a-z0-9\-]+\=[a-z0-9\-]+)?)?(;base64)?,[a-z0-9\!\$\&\\\'\,\(\)\*\+\,\;\=\-\.\_\~\:\@\/\?\%\s]*\s*$/i', $data)) { throw new NotNormalizableValueException('The provided "data:" URI is not valid.'); @@ -122,7 +122,7 @@ public function denormalize($data, string $type, string $format = null, array $c /** * {@inheritdoc} */ - public function supportsDenormalization($data, string $type, string $format = null) + public function supportsDenormalization(mixed $data, string $type, string $format = null) { return isset(self::SUPPORTED_TYPES[$type]); } diff --git a/Normalizer/DateIntervalNormalizer.php b/Normalizer/DateIntervalNormalizer.php index 7a02d1827..0bf42f90c 100644 --- a/Normalizer/DateIntervalNormalizer.php +++ b/Normalizer/DateIntervalNormalizer.php @@ -40,7 +40,7 @@ public function __construct(array $defaultContext = []) * * @return string */ - public function normalize($object, string $format = null, array $context = []) + public function normalize(mixed $object, string $format = null, array $context = []) { if (!$object instanceof \DateInterval) { throw new InvalidArgumentException('The object must be an instance of "\DateInterval".'); @@ -52,7 +52,7 @@ public function normalize($object, string $format = null, array $context = []) /** * {@inheritdoc} */ - public function supportsNormalization($data, string $format = null) + public function supportsNormalization(mixed $data, string $format = null) { return $data instanceof \DateInterval; } @@ -73,7 +73,7 @@ public function hasCacheableSupportsMethod(): bool * * @return \DateInterval */ - public function denormalize($data, string $type, string $format = null, array $context = []) + public function denormalize(mixed $data, string $type, string $format = null, array $context = []) { if (!\is_string($data)) { throw new InvalidArgumentException(sprintf('Data expected to be a string, "%s" given.', get_debug_type($data))); @@ -122,7 +122,7 @@ public function denormalize($data, string $type, string $format = null, array $c /** * {@inheritdoc} */ - public function supportsDenormalization($data, string $type, string $format = null) + public function supportsDenormalization(mixed $data, string $type, string $format = null) { return \DateInterval::class === $type; } diff --git a/Normalizer/DateTimeNormalizer.php b/Normalizer/DateTimeNormalizer.php index 19f9efdc0..9352e0dc1 100644 --- a/Normalizer/DateTimeNormalizer.php +++ b/Normalizer/DateTimeNormalizer.php @@ -48,7 +48,7 @@ public function __construct(array $defaultContext = []) * * @return string */ - public function normalize($object, string $format = null, array $context = []) + public function normalize(mixed $object, string $format = null, array $context = []) { if (!$object instanceof \DateTimeInterface) { throw new InvalidArgumentException('The object must implement the "\DateTimeInterface".'); @@ -68,7 +68,7 @@ public function normalize($object, string $format = null, array $context = []) /** * {@inheritdoc} */ - public function supportsNormalization($data, string $format = null) + public function supportsNormalization(mixed $data, string $format = null) { return $data instanceof \DateTimeInterface; } @@ -80,7 +80,7 @@ public function supportsNormalization($data, string $format = null) * * @return \DateTimeInterface */ - public function denormalize($data, string $type, string $format = null, array $context = []) + public function denormalize(mixed $data, string $type, string $format = null, array $context = []) { $dateTimeFormat = $context[self::FORMAT_KEY] ?? null; $timezone = $this->getTimezone($context); @@ -111,7 +111,7 @@ public function denormalize($data, string $type, string $format = null, array $c /** * {@inheritdoc} */ - public function supportsDenormalization($data, string $type, string $format = null) + public function supportsDenormalization(mixed $data, string $type, string $format = null) { return isset(self::SUPPORTED_TYPES[$type]); } diff --git a/Normalizer/DateTimeZoneNormalizer.php b/Normalizer/DateTimeZoneNormalizer.php index af262ebaa..d86fd6894 100644 --- a/Normalizer/DateTimeZoneNormalizer.php +++ b/Normalizer/DateTimeZoneNormalizer.php @@ -28,7 +28,7 @@ class DateTimeZoneNormalizer implements NormalizerInterface, DenormalizerInterfa * * @return string */ - public function normalize($object, string $format = null, array $context = []) + public function normalize(mixed $object, string $format = null, array $context = []) { if (!$object instanceof \DateTimeZone) { throw new InvalidArgumentException('The object must be an instance of "\DateTimeZone".'); @@ -40,7 +40,7 @@ public function normalize($object, string $format = null, array $context = []) /** * {@inheritdoc} */ - public function supportsNormalization($data, string $format = null) + public function supportsNormalization(mixed $data, string $format = null) { return $data instanceof \DateTimeZone; } @@ -52,7 +52,7 @@ public function supportsNormalization($data, string $format = null) * * @return \DateTimeZone */ - public function denormalize($data, string $type, string $format = null, array $context = []) + public function denormalize(mixed $data, string $type, string $format = null, array $context = []) { if ('' === $data || null === $data) { throw new NotNormalizableValueException('The data is either an empty string or null, you should pass a string that can be parsed as a DateTimeZone.'); @@ -68,7 +68,7 @@ public function denormalize($data, string $type, string $format = null, array $c /** * {@inheritdoc} */ - public function supportsDenormalization($data, string $type, string $format = null) + public function supportsDenormalization(mixed $data, string $type, string $format = null) { return \DateTimeZone::class === $type; } diff --git a/Normalizer/DenormalizableInterface.php b/Normalizer/DenormalizableInterface.php index 05c08112e..73cff7377 100644 --- a/Normalizer/DenormalizableInterface.php +++ b/Normalizer/DenormalizableInterface.php @@ -34,5 +34,5 @@ interface DenormalizableInterface * differently based on different input formats * @param array $context Options for denormalizing */ - public function denormalize(DenormalizerInterface $denormalizer, $data, string $format = null, array $context = []); + public function denormalize(DenormalizerInterface $denormalizer, array|string|int|float|bool $data, string $format = null, array $context = []); } diff --git a/Normalizer/DenormalizerInterface.php b/Normalizer/DenormalizerInterface.php index d903b3912..3f44f34c7 100644 --- a/Normalizer/DenormalizerInterface.php +++ b/Normalizer/DenormalizerInterface.php @@ -42,7 +42,7 @@ interface DenormalizerInterface * @throws RuntimeException Occurs if the class cannot be instantiated * @throws ExceptionInterface Occurs for all the other cases of errors */ - public function denormalize($data, string $type, string $format = null, array $context = []); + public function denormalize(mixed $data, string $type, string $format = null, array $context = []); /** * Checks whether the given class is supported for denormalization by this normalizer. @@ -53,5 +53,5 @@ public function denormalize($data, string $type, string $format = null, array $c * * @return bool */ - public function supportsDenormalization($data, string $type, string $format = null); + public function supportsDenormalization(mixed $data, string $type, string $format = null); } diff --git a/Normalizer/FormErrorNormalizer.php b/Normalizer/FormErrorNormalizer.php index 48399f4e6..8bd8a845f 100644 --- a/Normalizer/FormErrorNormalizer.php +++ b/Normalizer/FormErrorNormalizer.php @@ -25,7 +25,7 @@ final class FormErrorNormalizer implements NormalizerInterface, CacheableSupport /** * {@inheritdoc} */ - public function normalize($object, $format = null, array $context = []): array + public function normalize(mixed $object, string $format = null, array $context = []): array { $data = [ 'title' => $context[self::TITLE] ?? 'Validation Failed', @@ -44,7 +44,7 @@ public function normalize($object, $format = null, array $context = []): array /** * {@inheritdoc} */ - public function supportsNormalization($data, $format = null): bool + public function supportsNormalization(mixed $data, string $format = null): bool { return $data instanceof FormInterface && $data->isSubmitted() && !$data->isValid(); } diff --git a/Normalizer/GetSetMethodNormalizer.php b/Normalizer/GetSetMethodNormalizer.php index aacce5092..2d3adb861 100644 --- a/Normalizer/GetSetMethodNormalizer.php +++ b/Normalizer/GetSetMethodNormalizer.php @@ -39,7 +39,7 @@ class GetSetMethodNormalizer extends AbstractObjectNormalizer /** * {@inheritdoc} */ - public function supportsNormalization($data, string $format = null) + public function supportsNormalization(mixed $data, string $format = null) { return parent::supportsNormalization($data, $format) && $this->supports(\get_class($data)); } @@ -47,7 +47,7 @@ public function supportsNormalization($data, string $format = null) /** * {@inheritdoc} */ - public function supportsDenormalization($data, string $type, string $format = null) + public function supportsDenormalization(mixed $data, string $type, string $format = null) { return parent::supportsDenormalization($data, $type, $format) && $this->supports($type); } @@ -146,7 +146,7 @@ protected function getAttributeValue(object $object, string $attribute, string $ /** * {@inheritdoc} */ - protected function setAttributeValue(object $object, string $attribute, $value, string $format = null, array $context = []) + protected function setAttributeValue(object $object, string $attribute, mixed $value, string $format = null, array $context = []) { $setter = 'set'.ucfirst($attribute); $key = \get_class($object).':'.$setter; diff --git a/Normalizer/JsonSerializableNormalizer.php b/Normalizer/JsonSerializableNormalizer.php index f38956c3e..4533ff738 100644 --- a/Normalizer/JsonSerializableNormalizer.php +++ b/Normalizer/JsonSerializableNormalizer.php @@ -24,7 +24,7 @@ class JsonSerializableNormalizer extends AbstractNormalizer /** * {@inheritdoc} */ - public function normalize($object, string $format = null, array $context = []) + public function normalize(mixed $object, string $format = null, array $context = []) { if ($this->isCircularReference($object, $context)) { return $this->handleCircularReference($object); @@ -44,7 +44,7 @@ public function normalize($object, string $format = null, array $context = []) /** * {@inheritdoc} */ - public function supportsNormalization($data, string $format = null) + public function supportsNormalization(mixed $data, string $format = null) { return $data instanceof \JsonSerializable; } @@ -52,7 +52,7 @@ public function supportsNormalization($data, string $format = null) /** * {@inheritdoc} */ - public function supportsDenormalization($data, string $type, string $format = null) + public function supportsDenormalization(mixed $data, string $type, string $format = null) { return false; } @@ -60,7 +60,7 @@ public function supportsDenormalization($data, string $type, string $format = nu /** * {@inheritdoc} */ - public function denormalize($data, string $type, string $format = null, array $context = []) + public function denormalize(mixed $data, string $type, string $format = null, array $context = []) { throw new LogicException(sprintf('Cannot denormalize with "%s".', \JsonSerializable::class)); } diff --git a/Normalizer/MimeMessageNormalizer.php b/Normalizer/MimeMessageNormalizer.php index 0708847bd..14c6b842f 100644 --- a/Normalizer/MimeMessageNormalizer.php +++ b/Normalizer/MimeMessageNormalizer.php @@ -52,7 +52,7 @@ public function setSerializer(SerializerInterface $serializer) /** * {@inheritdoc} */ - public function normalize($object, string $format = null, array $context = []) + public function normalize(mixed $object, string $format = null, array $context = []) { if ($object instanceof Headers) { $ret = []; @@ -76,7 +76,7 @@ public function normalize($object, string $format = null, array $context = []) /** * {@inheritdoc} */ - public function denormalize($data, string $type, string $format = null, array $context = []) + public function denormalize(mixed $data, string $type, string $format = null, array $context = []) { if (Headers::class === $type) { $ret = []; @@ -100,7 +100,7 @@ public function denormalize($data, string $type, string $format = null, array $c /** * {@inheritdoc} */ - public function supportsNormalization($data, string $format = null) + public function supportsNormalization(mixed $data, string $format = null) { return $data instanceof Message || $data instanceof Headers || $data instanceof HeaderInterface || $data instanceof Address || $data instanceof AbstractPart; } @@ -108,7 +108,7 @@ public function supportsNormalization($data, string $format = null) /** * {@inheritdoc} */ - public function supportsDenormalization($data, string $type, string $format = null) + public function supportsDenormalization(mixed $data, string $type, string $format = null) { return is_a($type, Message::class, true) || Headers::class === $type || AbstractPart::class === $type; } diff --git a/Normalizer/NormalizerInterface.php b/Normalizer/NormalizerInterface.php index 653f94954..30eeafb47 100644 --- a/Normalizer/NormalizerInterface.php +++ b/Normalizer/NormalizerInterface.php @@ -36,7 +36,7 @@ interface NormalizerInterface * @throws LogicException Occurs when the normalizer is not called in an expected context * @throws ExceptionInterface Occurs for all the other cases of errors */ - public function normalize($object, string $format = null, array $context = []); + public function normalize(mixed $object, string $format = null, array $context = []); /** * Checks whether the given class is supported for normalization by this normalizer. @@ -46,5 +46,5 @@ public function normalize($object, string $format = null, array $context = []); * * @return bool */ - public function supportsNormalization($data, string $format = null); + public function supportsNormalization(mixed $data, string $format = null); } diff --git a/Normalizer/ObjectNormalizer.php b/Normalizer/ObjectNormalizer.php index 458b38f9d..aaa3867b4 100644 --- a/Normalizer/ObjectNormalizer.php +++ b/Normalizer/ObjectNormalizer.php @@ -153,7 +153,7 @@ protected function getAttributeValue(object $object, string $attribute, string $ /** * {@inheritdoc} */ - protected function setAttributeValue(object $object, string $attribute, $value, string $format = null, array $context = []) + protected function setAttributeValue(object $object, string $attribute, mixed $value, string $format = null, array $context = []) { try { $this->propertyAccessor->setValue($object, $attribute, $value); @@ -165,7 +165,7 @@ protected function setAttributeValue(object $object, string $attribute, $value, /** * {@inheritdoc} */ - protected function getAllowedAttributes($classOrObject, array $context, bool $attributesAsString = false) + protected function getAllowedAttributes(string|object $classOrObject, array $context, bool $attributesAsString = false) { if (false === $allowedAttributes = parent::getAllowedAttributes($classOrObject, $context, $attributesAsString)) { return false; diff --git a/Normalizer/ProblemNormalizer.php b/Normalizer/ProblemNormalizer.php index 6fdd2773a..1c7bb755b 100644 --- a/Normalizer/ProblemNormalizer.php +++ b/Normalizer/ProblemNormalizer.php @@ -41,7 +41,7 @@ public function __construct(bool $debug = false, array $defaultContext = []) * * @return array */ - public function normalize($object, string $format = null, array $context = []) + public function normalize(mixed $object, string $format = null, array $context = []) { if (!$object instanceof FlattenException) { throw new InvalidArgumentException(sprintf('The object must implement "%s".', FlattenException::class)); @@ -67,7 +67,7 @@ public function normalize($object, string $format = null, array $context = []) /** * {@inheritdoc} */ - public function supportsNormalization($data, string $format = null): bool + public function supportsNormalization(mixed $data, string $format = null): bool { return $data instanceof FlattenException; } diff --git a/Normalizer/PropertyNormalizer.php b/Normalizer/PropertyNormalizer.php index 8a0a3efee..ce210f075 100644 --- a/Normalizer/PropertyNormalizer.php +++ b/Normalizer/PropertyNormalizer.php @@ -33,7 +33,7 @@ class PropertyNormalizer extends AbstractObjectNormalizer /** * {@inheritdoc} */ - public function supportsNormalization($data, string $format = null) + public function supportsNormalization(mixed $data, string $format = null) { return parent::supportsNormalization($data, $format) && $this->supports(\get_class($data)); } @@ -41,7 +41,7 @@ public function supportsNormalization($data, string $format = null) /** * {@inheritdoc} */ - public function supportsDenormalization($data, string $type, string $format = null) + public function supportsDenormalization(mixed $data, string $type, string $format = null) { return parent::supportsDenormalization($data, $type, $format) && $this->supports($type); } @@ -76,7 +76,7 @@ private function supports(string $class): bool /** * {@inheritdoc} */ - protected function isAllowedAttribute($classOrObject, string $attribute, string $format = null, array $context = []) + protected function isAllowedAttribute(object|string $classOrObject, string $attribute, string $format = null, array $context = []) { if (!parent::isAllowedAttribute($classOrObject, $attribute, $format, $context)) { return false; @@ -145,7 +145,7 @@ protected function getAttributeValue(object $object, string $attribute, string $ /** * {@inheritdoc} */ - protected function setAttributeValue(object $object, string $attribute, $value, string $format = null, array $context = []) + protected function setAttributeValue(object $object, string $attribute, mixed $value, string $format = null, array $context = []) { try { $reflectionProperty = $this->getReflectionProperty($object, $attribute); @@ -166,11 +166,9 @@ protected function setAttributeValue(object $object, string $attribute, $value, } /** - * @param string|object $classOrObject - * * @throws \ReflectionException */ - private function getReflectionProperty($classOrObject, string $attribute): \ReflectionProperty + private function getReflectionProperty(string|object $classOrObject, string $attribute): \ReflectionProperty { $reflectionClass = new \ReflectionClass($classOrObject); while (true) { diff --git a/Normalizer/UidNormalizer.php b/Normalizer/UidNormalizer.php index 009d33489..835506893 100644 --- a/Normalizer/UidNormalizer.php +++ b/Normalizer/UidNormalizer.php @@ -40,7 +40,7 @@ public function __construct(array $defaultContext = []) * * @param AbstractUid $object */ - public function normalize($object, string $format = null, array $context = []) + public function normalize(mixed $object, string $format = null, array $context = []) { switch ($context[self::NORMALIZATION_FORMAT_KEY] ?? $this->defaultContext[self::NORMALIZATION_FORMAT_KEY]) { case self::NORMALIZATION_FORMAT_CANONICAL: @@ -59,7 +59,7 @@ public function normalize($object, string $format = null, array $context = []) /** * {@inheritdoc} */ - public function supportsNormalization($data, string $format = null) + public function supportsNormalization(mixed $data, string $format = null) { return $data instanceof AbstractUid; } @@ -67,7 +67,7 @@ public function supportsNormalization($data, string $format = null) /** * {@inheritdoc} */ - public function denormalize($data, string $type, string $format = null, array $context = []) + public function denormalize(mixed $data, string $type, string $format = null, array $context = []) { try { return Ulid::class === $type ? Ulid::fromString($data) : Uuid::fromString($data); @@ -79,7 +79,7 @@ public function denormalize($data, string $type, string $format = null, array $c /** * {@inheritdoc} */ - public function supportsDenormalization($data, string $type, string $format = null) + public function supportsDenormalization(mixed $data, string $type, string $format = null) { return is_a($type, AbstractUid::class, true); } diff --git a/Normalizer/UnwrappingDenormalizer.php b/Normalizer/UnwrappingDenormalizer.php index 6bc8df80b..f3531b236 100644 --- a/Normalizer/UnwrappingDenormalizer.php +++ b/Normalizer/UnwrappingDenormalizer.php @@ -35,7 +35,7 @@ public function __construct(PropertyAccessorInterface $propertyAccessor = null) /** * {@inheritdoc} */ - public function denormalize($data, $class, string $format = null, array $context = []) + public function denormalize(mixed $data, string $class, string $format = null, array $context = []) { $propertyPath = $context[self::UNWRAP_PATH]; $context['unwrapped'] = true; @@ -54,7 +54,7 @@ public function denormalize($data, $class, string $format = null, array $context /** * {@inheritdoc} */ - public function supportsDenormalization($data, $type, string $format = null, array $context = []) + public function supportsDenormalization(mixed $data, string $type, string $format = null, array $context = []) { return \array_key_exists(self::UNWRAP_PATH, $context) && !isset($context['unwrapped']); } diff --git a/Serializer.php b/Serializer.php index 686e831a7..bc535e4aa 100644 --- a/Serializer.php +++ b/Serializer.php @@ -117,7 +117,7 @@ public function __construct(array $normalizers = [], array $encoders = []) /** * {@inheritdoc} */ - final public function serialize($data, string $format, array $context = []): string + final public function serialize(mixed $data, string $format, array $context = []): string { if (!$this->supportsEncoding($format, $context)) { throw new NotEncodableValueException(sprintf('Serialization for the format "%s" is not supported.', $format)); @@ -133,7 +133,7 @@ final public function serialize($data, string $format, array $context = []): str /** * {@inheritdoc} */ - final public function deserialize($data, string $type, string $format, array $context = []) + final public function deserialize(mixed $data, string $type, string $format, array $context = []) { if (!$this->supportsDecoding($format, $context)) { throw new NotEncodableValueException(sprintf('Deserialization for the format "%s" is not supported.', $format)); @@ -147,7 +147,7 @@ final public function deserialize($data, string $type, string $format, array $co /** * {@inheritdoc} */ - public function normalize($data, string $format = null, array $context = []) + public function normalize(mixed $data, string $format = null, array $context = []) { // If a normalizer supports the given data, use it if ($normalizer = $this->getNormalizer($data, $format, $context)) { @@ -187,7 +187,7 @@ public function normalize($data, string $format = null, array $context = []) * * @throws NotNormalizableValueException */ - public function denormalize($data, string $type, string $format = null, array $context = []) + public function denormalize(mixed $data, string $type, string $format = null, array $context = []) { $normalizer = $this->getDenormalizer($data, $type, $format, $context); @@ -214,7 +214,7 @@ public function denormalize($data, string $type, string $format = null, array $c /** * {@inheritdoc} */ - public function supportsNormalization($data, string $format = null, array $context = []) + public function supportsNormalization(mixed $data, string $format = null, array $context = []) { return null !== $this->getNormalizer($data, $format, $context); } @@ -222,7 +222,7 @@ public function supportsNormalization($data, string $format = null, array $conte /** * {@inheritdoc} */ - public function supportsDenormalization($data, string $type, string $format = null, array $context = []) + public function supportsDenormalization(mixed $data, string $type, string $format = null, array $context = []) { return isset(self::SCALAR_TYPES[$type]) || null !== $this->getDenormalizer($data, $type, $format, $context); } @@ -234,7 +234,7 @@ public function supportsDenormalization($data, string $type, string $format = nu * @param string $format Format name, present to give the option to normalizers to act differently based on formats * @param array $context Options available to the normalizer */ - private function getNormalizer($data, ?string $format, array $context): ?NormalizerInterface + private function getNormalizer(mixed $data, ?string $format, array $context): ?NormalizerInterface { $type = \is_object($data) ? \get_class($data) : 'native-'.\gettype($data); @@ -273,7 +273,7 @@ private function getNormalizer($data, ?string $format, array $context): ?Normali * @param string $format Format name, present to give the option to normalizers to act differently based on formats * @param array $context Options available to the denormalizer */ - private function getDenormalizer($data, string $class, ?string $format, array $context): ?DenormalizerInterface + private function getDenormalizer(mixed $data, string $class, ?string $format, array $context): ?DenormalizerInterface { if (!isset($this->denormalizerCache[$format][$class])) { $this->denormalizerCache[$format][$class] = []; @@ -305,7 +305,7 @@ private function getDenormalizer($data, string $class, ?string $format, array $c /** * {@inheritdoc} */ - final public function encode($data, string $format, array $context = []) + final public function encode(mixed $data, string $format, array $context = []) { return $this->encoder->encode($data, $format, $context); } diff --git a/SerializerInterface.php b/SerializerInterface.php index 96e144e90..9e9a3d3b7 100644 --- a/SerializerInterface.php +++ b/SerializerInterface.php @@ -25,7 +25,7 @@ interface SerializerInterface * * @return string */ - public function serialize($data, string $format, array $context = []); + public function serialize(mixed $data, string $format, array $context = []); /** * Deserializes data into the given type. @@ -34,5 +34,5 @@ public function serialize($data, string $format, array $context = []); * * @return mixed */ - public function deserialize($data, string $type, string $format, array $context = []); + public function deserialize(mixed $data, string $type, string $format, array $context = []); } diff --git a/Tests/Annotation/DiscriminatorMapTest.php b/Tests/Annotation/DiscriminatorMapTest.php index 000011125..6a93c4e54 100644 --- a/Tests/Annotation/DiscriminatorMapTest.php +++ b/Tests/Annotation/DiscriminatorMapTest.php @@ -12,7 +12,6 @@ namespace Symfony\Component\Serializer\Tests\Annotation; use PHPUnit\Framework\TestCase; -use Symfony\Bridge\PhpUnit\ExpectDeprecationTrait; use Symfony\Component\Serializer\Annotation\DiscriminatorMap; use Symfony\Component\Serializer\Exception\InvalidArgumentException; @@ -21,8 +20,6 @@ */ class DiscriminatorMapTest extends TestCase { - use ExpectDeprecationTrait; - public function testGetTypePropertyAndMapping() { $annotation = new DiscriminatorMap(...['typeProperty' => 'type', 'mapping' => [ @@ -37,75 +34,15 @@ public function testGetTypePropertyAndMapping() ], $annotation->getMapping()); } - /** - * @group legacy - */ - public function testGetTypePropertyAndMappingLegacy() - { - $this->expectDeprecation('Since symfony/serializer 5.3: Passing an array as first argument to "Symfony\Component\Serializer\Annotation\DiscriminatorMap::__construct" is deprecated. Use named arguments instead.'); - $annotation = new DiscriminatorMap(['typeProperty' => 'type', 'mapping' => [ - 'foo' => 'FooClass', - 'bar' => 'BarClass', - ]]); - - $this->assertEquals('type', $annotation->getTypeProperty()); - $this->assertEquals([ - 'foo' => 'FooClass', - 'bar' => 'BarClass', - ], $annotation->getMapping()); - } - - /** - * @group legacy - */ - public function testExceptionWithoutTypeProperty() - { - $this->expectException(InvalidArgumentException::class); - new DiscriminatorMap(['mapping' => ['foo' => 'FooClass']]); - } - public function testExceptionWithEmptyTypeProperty() { $this->expectException(InvalidArgumentException::class); new DiscriminatorMap(...['typeProperty' => '', 'mapping' => ['foo' => 'FooClass']]); } - /** - * @group legacy - */ - public function testExceptionWithEmptyTypePropertyLegacy() - { - $this->expectException(InvalidArgumentException::class); - new DiscriminatorMap(['typeProperty' => '', 'mapping' => ['foo' => 'FooClass']]); - } - - public function testExceptionWithoutMappingProperty() - { - $this->expectException(InvalidArgumentException::class); - new DiscriminatorMap(...['typeProperty' => 'type']); - } - - /** - * @group legacy - */ - public function testExceptionWithoutMappingPropertyLegacy() - { - $this->expectException(InvalidArgumentException::class); - new DiscriminatorMap(['typeProperty' => 'type']); - } - public function testExceptionWitEmptyMappingProperty() { $this->expectException(InvalidArgumentException::class); new DiscriminatorMap(...['typeProperty' => 'type', 'mapping' => []]); } - - /** - * @group legacy - */ - public function testExceptionWitEmptyMappingPropertyLegacy() - { - $this->expectException(InvalidArgumentException::class); - new DiscriminatorMap(['typeProperty' => 'type', 'mapping' => []]); - } } diff --git a/Tests/Annotation/GroupsTest.php b/Tests/Annotation/GroupsTest.php index 890ab86c4..2e3823ba6 100644 --- a/Tests/Annotation/GroupsTest.php +++ b/Tests/Annotation/GroupsTest.php @@ -26,24 +26,12 @@ public function testEmptyGroupsParameter() new Groups([]); } - /** - * @group legacy - */ public function testEmptyGroupsParameterLegacy() { $this->expectException(InvalidArgumentException::class); new Groups(['value' => []]); } - /** - * @group legacy - */ - public function testNotAnArrayGroupsParameter() - { - $this->expectException(InvalidArgumentException::class); - new Groups(['value' => 12]); - } - public function testInvalidGroupsParameter() { $this->expectException(InvalidArgumentException::class); @@ -58,29 +46,9 @@ public function testGroupsParameters() $this->assertEquals($validData, $groups->getGroups()); } - /** - * @group legacy - */ - public function testGroupsParametersLegacy() - { - $validData = ['a', 'b']; - - $groups = new Groups(['value' => $validData]); - $this->assertEquals($validData, $groups->getGroups()); - } - public function testSingleGroup() { $groups = new Groups('a'); $this->assertEquals(['a'], $groups->getGroups()); } - - /** - * @group legacy - */ - public function testSingleGroupLegacy() - { - $groups = new Groups(['value' => 'a']); - $this->assertEquals(['a'], $groups->getGroups()); - } } diff --git a/Tests/Annotation/MaxDepthTest.php b/Tests/Annotation/MaxDepthTest.php index 3d3355e16..887e1dac3 100644 --- a/Tests/Annotation/MaxDepthTest.php +++ b/Tests/Annotation/MaxDepthTest.php @@ -12,7 +12,6 @@ namespace Symfony\Component\Serializer\Tests\Annotation; use PHPUnit\Framework\TestCase; -use Symfony\Bridge\PhpUnit\ExpectDeprecationTrait; use Symfony\Component\Serializer\Annotation\MaxDepth; use Symfony\Component\Serializer\Exception\InvalidArgumentException; @@ -21,36 +20,11 @@ */ class MaxDepthTest extends TestCase { - use ExpectDeprecationTrait; - - /** - * @group legacy - */ - public function testNotSetMaxDepthParameter() - { - $this->expectException(InvalidArgumentException::class); - $this->expectExceptionMessage('Parameter of annotation "Symfony\Component\Serializer\Annotation\MaxDepth" should be set.'); - new MaxDepth([]); - } - - public function provideInvalidValues() - { - return [ - [''], - ['foo'], - ['1'], - [0], - ]; - } - - /** - * @dataProvider provideInvalidValues - */ - public function testNotAnIntMaxDepthParameter($value) + public function testNotAnIntMaxDepthParameter() { $this->expectException(InvalidArgumentException::class); $this->expectExceptionMessage('Parameter of annotation "Symfony\Component\Serializer\Annotation\MaxDepth" must be a positive integer.'); - new MaxDepth($value); + new MaxDepth(0); } public function testMaxDepthParameters() @@ -58,15 +32,4 @@ public function testMaxDepthParameters() $maxDepth = new MaxDepth(3); $this->assertEquals(3, $maxDepth->getMaxDepth()); } - - /** - * @group legacy - */ - public function testMaxDepthParametersLegacy() - { - $this->expectDeprecation('Since symfony/serializer 5.3: Passing an array as first argument to "Symfony\Component\Serializer\Annotation\MaxDepth::__construct" is deprecated. Use named arguments instead.'); - - $maxDepth = new MaxDepth(['value' => 3]); - $this->assertEquals(3, $maxDepth->getMaxDepth()); - } } diff --git a/Tests/Annotation/SerializedNameTest.php b/Tests/Annotation/SerializedNameTest.php index 8ea70a674..54a1fce3e 100644 --- a/Tests/Annotation/SerializedNameTest.php +++ b/Tests/Annotation/SerializedNameTest.php @@ -23,16 +23,6 @@ class SerializedNameTest extends TestCase { use ExpectDeprecationTrait; - /** - * @group legacy - */ - public function testNotSetSerializedNameParameter() - { - $this->expectException(InvalidArgumentException::class); - $this->expectExceptionMessage('Parameter of annotation "Symfony\Component\Serializer\Annotation\SerializedName" should be set.'); - new SerializedName([]); - } - public function provideInvalidValues(): array { return [ @@ -57,15 +47,4 @@ public function testSerializedNameParameters() $maxDepth = new SerializedName('foo'); $this->assertEquals('foo', $maxDepth->getSerializedName()); } - - /** - * @group legacy - */ - public function testSerializedNameParametersLegacy() - { - $this->expectDeprecation('Since symfony/serializer 5.3: Passing an array as first argument to "Symfony\Component\Serializer\Annotation\SerializedName::__construct" is deprecated. Use named arguments instead.'); - - $maxDepth = new SerializedName(['value' => 'foo']); - $this->assertEquals('foo', $maxDepth->getSerializedName()); - } } diff --git a/Tests/Fixtures/AbstractNormalizerDummy.php b/Tests/Fixtures/AbstractNormalizerDummy.php index ae3b411b3..724dff9a7 100644 --- a/Tests/Fixtures/AbstractNormalizerDummy.php +++ b/Tests/Fixtures/AbstractNormalizerDummy.php @@ -23,7 +23,7 @@ class AbstractNormalizerDummy extends AbstractNormalizer /** * {@inheritdoc} */ - public function getAllowedAttributes($classOrObject, array $context, bool $attributesAsString = false) + public function getAllowedAttributes(string|object $classOrObject, array $context, bool $attributesAsString = false) { return parent::getAllowedAttributes($classOrObject, $context, $attributesAsString); } @@ -31,14 +31,14 @@ public function getAllowedAttributes($classOrObject, array $context, bool $attri /** * {@inheritdoc} */ - public function normalize($object, string $format = null, array $context = []) + public function normalize(mixed $object, string $format = null, array $context = []) { } /** * {@inheritdoc} */ - public function supportsNormalization($data, string $format = null): bool + public function supportsNormalization(mixed $data, string $format = null): bool { return true; } @@ -46,14 +46,14 @@ public function supportsNormalization($data, string $format = null): bool /** * {@inheritdoc} */ - public function denormalize($data, string $type, string $format = null, array $context = []) + public function denormalize(mixed $data, string $type, string $format = null, array $context = []) { } /** * {@inheritdoc} */ - public function supportsDenormalization($data, string $type, string $format = null): bool + public function supportsDenormalization(mixed $data, string $type, string $format = null): bool { return true; } diff --git a/Tests/Fixtures/DenormalizableDummy.php b/Tests/Fixtures/DenormalizableDummy.php index e7c03e3d8..bbf54bd5f 100644 --- a/Tests/Fixtures/DenormalizableDummy.php +++ b/Tests/Fixtures/DenormalizableDummy.php @@ -16,7 +16,7 @@ class DenormalizableDummy implements DenormalizableInterface { - public function denormalize(DenormalizerInterface $denormalizer, $data, string $format = null, array $context = []) + public function denormalize(DenormalizerInterface $denormalizer, array|string|int|float|bool $data, string $format = null, array $context = []) { } } diff --git a/Tests/Fixtures/Dummy.php b/Tests/Fixtures/Dummy.php index da0400593..afd8ed75c 100644 --- a/Tests/Fixtures/Dummy.php +++ b/Tests/Fixtures/Dummy.php @@ -33,7 +33,7 @@ public function normalize(NormalizerInterface $normalizer, string $format = null ]; } - public function denormalize(DenormalizerInterface $denormalizer, $data, string $format = null, array $context = []) + public function denormalize(DenormalizerInterface $denormalizer, array|string|int|float|bool $data, string $format = null, array $context = []) { $this->foo = $data['foo']; $this->bar = $data['bar']; diff --git a/Tests/Fixtures/NormalizableTraversableDummy.php b/Tests/Fixtures/NormalizableTraversableDummy.php index 55b4402b0..aed3a20c0 100644 --- a/Tests/Fixtures/NormalizableTraversableDummy.php +++ b/Tests/Fixtures/NormalizableTraversableDummy.php @@ -26,7 +26,7 @@ public function normalize(NormalizerInterface $normalizer, string $format = null ]; } - public function denormalize(DenormalizerInterface $denormalizer, $data, string $format = null, array $context = []) + public function denormalize(DenormalizerInterface $denormalizer, array|string|int|float|bool $data, string $format = null, array $context = []) { return [ 'foo' => 'denormalizedFoo', diff --git a/Tests/Fixtures/ScalarDummy.php b/Tests/Fixtures/ScalarDummy.php index ffe4ee658..1afbbbd80 100644 --- a/Tests/Fixtures/ScalarDummy.php +++ b/Tests/Fixtures/ScalarDummy.php @@ -26,7 +26,7 @@ public function normalize(NormalizerInterface $normalizer, string $format = null return 'xml' === $format ? $this->xmlFoo : $this->foo; } - public function denormalize(DenormalizerInterface $denormalizer, $data, string $format = null, array $context = []) + public function denormalize(DenormalizerInterface $denormalizer, array|string|int|float|bool $data, string $format = null, array $context = []) { if ('xml' === $format) { $this->xmlFoo = $data; diff --git a/Tests/Fixtures/StaticConstructorNormalizer.php b/Tests/Fixtures/StaticConstructorNormalizer.php index 10398f4fc..209a07d2c 100644 --- a/Tests/Fixtures/StaticConstructorNormalizer.php +++ b/Tests/Fixtures/StaticConstructorNormalizer.php @@ -21,7 +21,7 @@ class StaticConstructorNormalizer extends ObjectNormalizer /** * {@inheritdoc} */ - protected function getConstructor(array &$data, string $class, array &$context, \ReflectionClass $reflectionClass, $allowedAttributes): ?\ReflectionMethod + protected function getConstructor(array &$data, string $class, array &$context, \ReflectionClass $reflectionClass, array|bool $allowedAttributes): ?\ReflectionMethod { if (is_a($class, StaticConstructorDummy::class, true)) { return new \ReflectionMethod($class, 'create'); diff --git a/Tests/Normalizer/UnwrappinDenormalizerTest.php b/Tests/Normalizer/UnwrappinDenormalizerTest.php index 8d2826565..25063b862 100644 --- a/Tests/Normalizer/UnwrappinDenormalizerTest.php +++ b/Tests/Normalizer/UnwrappinDenormalizerTest.php @@ -34,9 +34,9 @@ protected function setUp(): void public function testSupportsNormalization() { - $this->assertTrue($this->denormalizer->supportsDenormalization([], new \stdClass(), 'any', [UnwrappingDenormalizer::UNWRAP_PATH => '[baz][inner]'])); - $this->assertFalse($this->denormalizer->supportsDenormalization([], new \stdClass(), 'any', [UnwrappingDenormalizer::UNWRAP_PATH => '[baz][inner]', 'unwrapped' => true])); - $this->assertFalse($this->denormalizer->supportsDenormalization([], new \stdClass(), 'any', [])); + $this->assertTrue($this->denormalizer->supportsDenormalization([], 'stdClass', 'any', [UnwrappingDenormalizer::UNWRAP_PATH => '[baz][inner]'])); + $this->assertFalse($this->denormalizer->supportsDenormalization([], 'stdClass', 'any', [UnwrappingDenormalizer::UNWRAP_PATH => '[baz][inner]', 'unwrapped' => true])); + $this->assertFalse($this->denormalizer->supportsDenormalization([], 'stdClass', 'any', [])); } public function testDenormalize() From 09d24cabe6eba8587e27346b74d9758fa2116c5b Mon Sep 17 00:00:00 2001 From: "Alexander M. Turek" Date: Thu, 10 Jun 2021 19:50:38 +0200 Subject: [PATCH 007/297] [Serializer] Remove deprecation layer Signed-off-by: Alexander M. Turek --- Annotation/Context.php | 43 ++---- Annotation/DiscriminatorMap.php | 12 +- Annotation/Groups.php | 10 +- Annotation/MaxDepth.php | 7 +- Annotation/SerializedName.php | 6 +- CHANGELOG.md | 6 + DependencyInjection/SerializerPass.php | 27 +--- Normalizer/ArrayDenormalizer.php | 23 +-- Tests/Annotation/ContextTest.php | 171 ++------------------- Tests/Annotation/DiscriminatorMapTest.php | 8 +- Tests/Annotation/GroupsTest.php | 6 - Tests/Annotation/MaxDepthTest.php | 8 +- Tests/Annotation/SerializedNameTest.php | 14 +- Tests/Normalizer/ArrayDenormalizerTest.php | 44 ------ composer.json | 1 - 15 files changed, 56 insertions(+), 330 deletions(-) diff --git a/Annotation/Context.php b/Annotation/Context.php index d4b2ffcac..5c78d7b07 100644 --- a/Annotation/Context.php +++ b/Annotation/Context.php @@ -25,55 +25,30 @@ #[\Attribute(\Attribute::TARGET_PROPERTY | \Attribute::TARGET_METHOD | \Attribute::IS_REPEATABLE)] final class Context { - private $context; - private $normalizationContext; - private $denormalizationContext; - private $groups; + private array $groups; /** * @param string|string[] $groups * * @throws InvalidArgumentException */ - public function __construct(array $options = [], array $context = [], array $normalizationContext = [], array $denormalizationContext = [], string|array $groups = []) - { - if (!$context) { - if (!array_intersect((array_keys($options)), ['normalizationContext', 'groups', 'context', 'value', 'denormalizationContext'])) { - // gracefully supports context as first, unnamed attribute argument if it cannot be confused with Doctrine-style options - $context = $options; - } else { - trigger_deprecation('symfony/serializer', '5.3', 'Passing an array of properties as first argument to "%s" is deprecated. Use named arguments instead.', __METHOD__); - - // If at least one of the options match, it's likely to be Doctrine-style options. Search for the context inside: - $context = $options['value'] ?? $options['context'] ?? []; - } - } - - $normalizationContext = $options['normalizationContext'] ?? $normalizationContext; - $denormalizationContext = $options['denormalizationContext'] ?? $denormalizationContext; - - foreach (compact(['context', 'normalizationContext', 'denormalizationContext']) as $key => $value) { - if (!\is_array($value)) { - throw new InvalidArgumentException(sprintf('Option "%s" of annotation "%s" must be an array.', $key, static::class)); - } - } - + public function __construct( + private array $context = [], + private array $normalizationContext = [], + private array $denormalizationContext = [], + string|array $groups = [], + ) { if (!$context && !$normalizationContext && !$denormalizationContext) { throw new InvalidArgumentException(sprintf('At least one of the "context", "normalizationContext", or "denormalizationContext" options of annotation "%s" must be provided as a non-empty array.', static::class)); } - $groups = (array) ($options['groups'] ?? $groups); + $this->groups = (array) $groups; - foreach ($groups as $group) { + foreach ($this->groups as $group) { if (!\is_string($group)) { throw new InvalidArgumentException(sprintf('Parameter "groups" of annotation "%s" must be a string or an array of strings. Got "%s".', static::class, get_debug_type($group))); } } - - $this->context = $context; - $this->normalizationContext = $normalizationContext; - $this->denormalizationContext = $denormalizationContext; - $this->groups = $groups; } public function getContext(): array diff --git a/Annotation/DiscriminatorMap.php b/Annotation/DiscriminatorMap.php index 79c27c7bc..2606a1e4f 100644 --- a/Annotation/DiscriminatorMap.php +++ b/Annotation/DiscriminatorMap.php @@ -25,11 +25,10 @@ #[\Attribute(\Attribute::TARGET_CLASS)] class DiscriminatorMap { - private $typeProperty; - private $mapping; - - public function __construct(string $typeProperty, array $mapping) - { + public function __construct( + private string $typeProperty, + private array $mapping, + ) { if (empty($typeProperty)) { throw new InvalidArgumentException(sprintf('Parameter "typeProperty" of annotation "%s" cannot be empty.', static::class)); } @@ -37,9 +36,6 @@ public function __construct(string $typeProperty, array $mapping) if (empty($mapping)) { throw new InvalidArgumentException(sprintf('Parameter "mapping" of annotation "%s" cannot be empty.', static::class)); } - - $this->typeProperty = $typeProperty; - $this->mapping = $mapping; } public function getTypeProperty(): string diff --git a/Annotation/Groups.php b/Annotation/Groups.php index 198a79eb3..80ea5ce5a 100644 --- a/Annotation/Groups.php +++ b/Annotation/Groups.php @@ -28,26 +28,24 @@ class Groups /** * @var string[] */ - private $groups; + private array $groups; /** * @param string|string[] $groups */ public function __construct(string|array $groups) { - $groups = (array) $groups; + $this->groups = (array) $groups; - if (empty($groups)) { + if (empty($this->groups)) { throw new InvalidArgumentException(sprintf('Parameter of annotation "%s" cannot be empty.', static::class)); } - foreach ($groups as $group) { + foreach ($this->groups as $group) { if (!\is_string($group) || '' === $group) { throw new InvalidArgumentException(sprintf('Parameter of annotation "%s" must be a string or an array of non-empty strings.', static::class)); } } - - $this->groups = $groups; } /** diff --git a/Annotation/MaxDepth.php b/Annotation/MaxDepth.php index 73c8a9e17..7c8b1ab60 100644 --- a/Annotation/MaxDepth.php +++ b/Annotation/MaxDepth.php @@ -25,15 +25,10 @@ #[\Attribute(\Attribute::TARGET_METHOD | \Attribute::TARGET_PROPERTY)] class MaxDepth { - private $maxDepth; - - public function __construct(int $maxDepth) - { + public function __construct(private int $maxDepth) { if ($maxDepth <= 0) { throw new InvalidArgumentException(sprintf('Parameter of annotation "%s" must be a positive integer.', static::class)); } - - $this->maxDepth = $maxDepth; } public function getMaxDepth() diff --git a/Annotation/SerializedName.php b/Annotation/SerializedName.php index 64e888906..93438cdfb 100644 --- a/Annotation/SerializedName.php +++ b/Annotation/SerializedName.php @@ -25,15 +25,11 @@ #[\Attribute(\Attribute::TARGET_METHOD | \Attribute::TARGET_PROPERTY)] final class SerializedName { - private $serializedName; - - public function __construct(string $serializedName) + public function __construct(private string $serializedName) { if (empty($serializedName)) { throw new InvalidArgumentException(sprintf('Parameter of annotation "%s" must be a non-empty string.', static::class)); } - - $this->serializedName = $serializedName; } public function getSerializedName(): string diff --git a/CHANGELOG.md b/CHANGELOG.md index b0bd0e711..f0b8840a9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,12 @@ CHANGELOG ========= +6.0 +--- + + * Remove `ArrayDenormalizer::setSerializer()`, call `setDenormalizer()` instead + * Remove the ability to create instances of the annotation classes by passing an array of parameters, use named arguments instead + 5.3 --- diff --git a/DependencyInjection/SerializerPass.php b/DependencyInjection/SerializerPass.php index 3bc73b792..6f6257099 100644 --- a/DependencyInjection/SerializerPass.php +++ b/DependencyInjection/SerializerPass.php @@ -27,36 +27,21 @@ class SerializerPass implements CompilerPassInterface { use PriorityTaggedServiceTrait; - private $serializerService; - private $normalizerTag; - private $encoderTag; - - public function __construct(string $serializerService = 'serializer', string $normalizerTag = 'serializer.normalizer', string $encoderTag = 'serializer.encoder') - { - if (0 < \func_num_args()) { - trigger_deprecation('symfony/serializer', '5.3', 'Configuring "%s" is deprecated.', __CLASS__); - } - - $this->serializerService = $serializerService; - $this->normalizerTag = $normalizerTag; - $this->encoderTag = $encoderTag; - } - public function process(ContainerBuilder $container) { - if (!$container->hasDefinition($this->serializerService)) { + if (!$container->hasDefinition('serializer')) { return; } - if (!$normalizers = $this->findAndSortTaggedServices($this->normalizerTag, $container)) { - throw new RuntimeException(sprintf('You must tag at least one service as "%s" to use the "%s" service.', $this->normalizerTag, $this->serializerService)); + if (!$normalizers = $this->findAndSortTaggedServices('serializer.normalizer', $container)) { + throw new RuntimeException('You must tag at least one service as "serializer.normalizer" to use the "serializer" service.'); } - $serializerDefinition = $container->getDefinition($this->serializerService); + $serializerDefinition = $container->getDefinition('serializer'); $serializerDefinition->replaceArgument(0, $normalizers); - if (!$encoders = $this->findAndSortTaggedServices($this->encoderTag, $container)) { - throw new RuntimeException(sprintf('You must tag at least one service as "%s" to use the "%s" service.', $this->encoderTag, $this->serializerService)); + if (!$encoders = $this->findAndSortTaggedServices('serializer.encoder', $container)) { + throw new RuntimeException('You must tag at least one service as "serializer.encoder" to use the "serializer" service.'); } $serializerDefinition->replaceArgument(1, $encoders); diff --git a/Normalizer/ArrayDenormalizer.php b/Normalizer/ArrayDenormalizer.php index 332f4e5a6..7be4f394b 100644 --- a/Normalizer/ArrayDenormalizer.php +++ b/Normalizer/ArrayDenormalizer.php @@ -14,9 +14,6 @@ use Symfony\Component\Serializer\Exception\BadMethodCallException; use Symfony\Component\Serializer\Exception\InvalidArgumentException; use Symfony\Component\Serializer\Exception\NotNormalizableValueException; -use Symfony\Component\Serializer\Serializer; -use Symfony\Component\Serializer\SerializerAwareInterface; -use Symfony\Component\Serializer\SerializerInterface; /** * Denormalizes arrays of objects. @@ -25,7 +22,7 @@ * * @final */ -class ArrayDenormalizer implements ContextAwareDenormalizerInterface, DenormalizerAwareInterface, SerializerAwareInterface, CacheableSupportsMethodInterface +class ArrayDenormalizer implements ContextAwareDenormalizerInterface, DenormalizerAwareInterface, CacheableSupportsMethodInterface { use DenormalizerAwareTrait; @@ -73,24 +70,6 @@ public function supportsDenormalization(mixed $data, string $type, string $forma && $this->denormalizer->supportsDenormalization($data, substr($type, 0, -2), $format, $context); } - /** - * {@inheritdoc} - * - * @deprecated call setDenormalizer() instead - */ - public function setSerializer(SerializerInterface $serializer) - { - if (!$serializer instanceof DenormalizerInterface) { - throw new InvalidArgumentException('Expected a serializer that also implements DenormalizerInterface.'); - } - - if (Serializer::class !== debug_backtrace()[1]['class'] ?? null) { - trigger_deprecation('symfony/serializer', '5.3', 'Calling "%s" is deprecated. Please call setDenormalizer() instead.'); - } - - $this->setDenormalizer($serializer); - } - /** * {@inheritdoc} */ diff --git a/Tests/Annotation/ContextTest.php b/Tests/Annotation/ContextTest.php index 0cb064868..9d5dd0098 100644 --- a/Tests/Annotation/ContextTest.php +++ b/Tests/Annotation/ContextTest.php @@ -12,7 +12,6 @@ namespace Symfony\Component\Serializer\Tests\Annotation; use PHPUnit\Framework\TestCase; -use Symfony\Bridge\PhpUnit\ExpectDeprecationTrait; use Symfony\Component\Serializer\Annotation\Context; use Symfony\Component\Serializer\Exception\InvalidArgumentException; use Symfony\Component\VarDumper\Dumper\CliDumper; @@ -23,7 +22,6 @@ */ class ContextTest extends TestCase { - use ExpectDeprecationTrait; use VarDumperTestTrait; protected function setUp(): void @@ -39,69 +37,12 @@ public function testThrowsOnEmptyContext() new Context(); } - /** - * @group legacy - * @dataProvider provideTestThrowsOnEmptyContextLegacyData - */ - public function testThrowsOnEmptyContextLegacy(callable $factory) - { - $this->expectException(InvalidArgumentException::class); - $this->expectExceptionMessage('At least one of the "context", "normalizationContext", or "denormalizationContext" options of annotation "Symfony\Component\Serializer\Annotation\Context" must be provided as a non-empty array.'); - - $factory(); - } - - public function provideTestThrowsOnEmptyContextLegacyData(): iterable - { - yield 'doctrine-style: value option as empty array' => [function () { new Context(['value' => []]); }]; - yield 'doctrine-style: context option as empty array' => [function () { new Context(['context' => []]); }]; - yield 'doctrine-style: context option not provided' => [function () { new Context(['groups' => ['group_1']]); }]; - } - - /** - * @group legacy - * @dataProvider provideTestThrowsOnNonArrayContextData - */ - public function testThrowsOnNonArrayContext(array $options) - { - $this->expectException(InvalidArgumentException::class); - $this->expectExceptionMessage(sprintf('Option "%s" of annotation "%s" must be an array.', key($options), Context::class)); - - new Context($options); - } - - public function provideTestThrowsOnNonArrayContextData(): iterable - { - yield 'non-array context' => [['context' => 'not_an_array']]; - yield 'non-array normalization context' => [['normalizationContext' => 'not_an_array']]; - yield 'non-array denormalization context' => [['normalizationContext' => 'not_an_array']]; - } - public function testInvalidGroupOption() { $this->expectException(InvalidArgumentException::class); $this->expectExceptionMessage(sprintf('Parameter "groups" of annotation "%s" must be a string or an array of strings. Got "stdClass"', Context::class)); - new Context(...['context' => ['foo' => 'bar'], 'groups' => ['fine', new \stdClass()]]); - } - - /** - * @group legacy - */ - public function testInvalidGroupOptionLegacy() - { - $this->expectException(InvalidArgumentException::class); - $this->expectExceptionMessage(sprintf('Parameter "groups" of annotation "%s" must be a string or an array of strings. Got "stdClass"', Context::class)); - - new Context(['context' => ['foo' => 'bar'], 'groups' => ['fine', new \stdClass()]]); - } - - public function testInvalidGroupArgument() - { - $this->expectException(InvalidArgumentException::class); - $this->expectExceptionMessage(sprintf('Parameter "groups" of annotation "%s" must be a string or an array of strings. Got "stdClass"', Context::class)); - - new Context([], ['foo' => 'bar'], [], [], ['fine', new \stdClass()]); + new Context(context: ['foo' => 'bar'], groups: ['fine', new \stdClass()]); } public function testAsFirstArg() @@ -116,7 +57,7 @@ public function testAsFirstArg() public function testAsContextArg() { - $context = new Context([], ['foo' => 'bar']); + $context = new Context(context: ['foo' => 'bar']); self::assertSame(['foo' => 'bar'], $context->getContext()); self::assertEmpty($context->getNormalizationContext()); @@ -135,164 +76,76 @@ public function testValidInputs(callable $factory, string $expectedDump) public function provideValidInputs(): iterable { yield 'named arguments: with context option' => [ - function () { return new Context(...['context' => ['foo' => 'bar']]); }, + function () { return new Context(context: ['foo' => 'bar']); }, << "bar", ] -normalizationContext: [] -denormalizationContext: [] - -groups: [] } DUMP ]; yield 'named arguments: with normalization context option' => [ - function () { return new Context(...['normalizationContext' => ['foo' => 'bar']]); }, + function () { return new Context(normalizationContext: ['foo' => 'bar']); }, << "bar", ] -denormalizationContext: [] - -groups: [] } DUMP ]; yield 'named arguments: with denormalization context option' => [ - function () { return new Context(...['denormalizationContext' => ['foo' => 'bar']]); }, + function () { return new Context(denormalizationContext: ['foo' => 'bar']); }, << "bar", ] - -groups: [] } DUMP ]; yield 'named arguments: with groups option as string' => [ - function () { return new Context(...['context' => ['foo' => 'bar'], 'groups' => 'a']); }, + function () { return new Context(context: ['foo' => 'bar'], groups: 'a'); }, << "bar", - ] - -normalizationContext: [] - -denormalizationContext: [] - -groups: [ - "a", - ] -} -DUMP - ]; - - yield 'named arguemnts: with groups option as array' => [ - function () { return new Context(...['context' => ['foo' => 'bar'], 'groups' => ['a', 'b']]); }, - << "bar", - ] - -normalizationContext: [] - -denormalizationContext: [] -groups: [ "a", - "b", ] -} -DUMP - ]; - } - - /** - * @group legacy - * @dataProvider provideValidLegacyInputs - */ - public function testValidLegacyInputs(callable $factory, string $expectedDump) - { - $this->expectDeprecation('Since symfony/serializer 5.3: Passing an array of properties as first argument to "Symfony\Component\Serializer\Annotation\Context::__construct" is deprecated. Use named arguments instead.'); - $this->assertDumpEquals($expectedDump, $factory()); - } - - public function provideValidLegacyInputs(): iterable - { - yield 'doctrine-style: with context option' => [ - function () { return new Context(['context' => ['foo' => 'bar']]); }, - << "bar", ] -normalizationContext: [] -denormalizationContext: [] - -groups: [] -} -DUMP - ]; - - yield 'doctrine-style: with normalization context option' => [ - function () { return new Context(['normalizationContext' => ['foo' => 'bar']]); }, - << "bar", - ] - -denormalizationContext: [] - -groups: [] } DUMP ]; - yield 'doctrine-style: with denormalization context option' => [ - function () { return new Context(['denormalizationContext' => ['foo' => 'bar']]); }, - << "bar", - ] - -groups: [] -} -DUMP - ]; - - yield 'doctrine-style: with groups option as string' => [ - function () { return new Context(['context' => ['foo' => 'bar'], 'groups' => 'a']); }, + yield 'named arguemnts: with groups option as array' => [ + function () { return new Context(context: ['foo' => 'bar'], groups: ['a', 'b']); }, << "bar", - ] - -normalizationContext: [] - -denormalizationContext: [] -groups: [ "a", + "b", ] -} -DUMP - ]; - - yield 'doctrine-style: with groups option as array' => [ - function () { return new Context(['context' => ['foo' => 'bar'], 'groups' => ['a', 'b']]); }, - << "bar", ] -normalizationContext: [] -denormalizationContext: [] - -groups: [ - "a", - "b", - ] } DUMP ]; diff --git a/Tests/Annotation/DiscriminatorMapTest.php b/Tests/Annotation/DiscriminatorMapTest.php index 6a93c4e54..5b5af5bf4 100644 --- a/Tests/Annotation/DiscriminatorMapTest.php +++ b/Tests/Annotation/DiscriminatorMapTest.php @@ -22,10 +22,10 @@ class DiscriminatorMapTest extends TestCase { public function testGetTypePropertyAndMapping() { - $annotation = new DiscriminatorMap(...['typeProperty' => 'type', 'mapping' => [ + $annotation = new DiscriminatorMap(typeProperty: 'type', mapping: [ 'foo' => 'FooClass', 'bar' => 'BarClass', - ]]); + ]); $this->assertEquals('type', $annotation->getTypeProperty()); $this->assertEquals([ @@ -37,12 +37,12 @@ public function testGetTypePropertyAndMapping() public function testExceptionWithEmptyTypeProperty() { $this->expectException(InvalidArgumentException::class); - new DiscriminatorMap(...['typeProperty' => '', 'mapping' => ['foo' => 'FooClass']]); + new DiscriminatorMap(typeProperty: '', mapping: ['foo' => 'FooClass']); } public function testExceptionWitEmptyMappingProperty() { $this->expectException(InvalidArgumentException::class); - new DiscriminatorMap(...['typeProperty' => 'type', 'mapping' => []]); + new DiscriminatorMap(typeProperty: 'type', mapping: []); } } diff --git a/Tests/Annotation/GroupsTest.php b/Tests/Annotation/GroupsTest.php index 2e3823ba6..5caae995d 100644 --- a/Tests/Annotation/GroupsTest.php +++ b/Tests/Annotation/GroupsTest.php @@ -26,12 +26,6 @@ public function testEmptyGroupsParameter() new Groups([]); } - public function testEmptyGroupsParameterLegacy() - { - $this->expectException(InvalidArgumentException::class); - new Groups(['value' => []]); - } - public function testInvalidGroupsParameter() { $this->expectException(InvalidArgumentException::class); diff --git a/Tests/Annotation/MaxDepthTest.php b/Tests/Annotation/MaxDepthTest.php index 887e1dac3..b5adbc9d8 100644 --- a/Tests/Annotation/MaxDepthTest.php +++ b/Tests/Annotation/MaxDepthTest.php @@ -20,11 +20,15 @@ */ class MaxDepthTest extends TestCase { - public function testNotAnIntMaxDepthParameter() + /** + * @testWith [-4] + * [0] + */ + public function testNotAnIntMaxDepthParameter(int $value) { $this->expectException(InvalidArgumentException::class); $this->expectExceptionMessage('Parameter of annotation "Symfony\Component\Serializer\Annotation\MaxDepth" must be a positive integer.'); - new MaxDepth(0); + new MaxDepth($value); } public function testMaxDepthParameters() diff --git a/Tests/Annotation/SerializedNameTest.php b/Tests/Annotation/SerializedNameTest.php index 54a1fce3e..4e81ad59d 100644 --- a/Tests/Annotation/SerializedNameTest.php +++ b/Tests/Annotation/SerializedNameTest.php @@ -12,7 +12,6 @@ namespace Symfony\Component\Serializer\Tests\Annotation; use PHPUnit\Framework\TestCase; -use Symfony\Bridge\PhpUnit\ExpectDeprecationTrait; use Symfony\Component\Serializer\Annotation\SerializedName; use Symfony\Component\Serializer\Exception\InvalidArgumentException; @@ -21,18 +20,9 @@ */ class SerializedNameTest extends TestCase { - use ExpectDeprecationTrait; - - public function provideInvalidValues(): array - { - return [ - [''], - [0], - ]; - } - /** - * @dataProvider provideInvalidValues + * @testWith [""] + * [0] */ public function testNotAStringSerializedNameParameter($value) { diff --git a/Tests/Normalizer/ArrayDenormalizerTest.php b/Tests/Normalizer/ArrayDenormalizerTest.php index dfbf01806..aa2541c8b 100644 --- a/Tests/Normalizer/ArrayDenormalizerTest.php +++ b/Tests/Normalizer/ArrayDenormalizerTest.php @@ -13,15 +13,11 @@ use PHPUnit\Framework\MockObject\MockObject; use PHPUnit\Framework\TestCase; -use Symfony\Bridge\PhpUnit\ExpectDeprecationTrait; use Symfony\Component\Serializer\Normalizer\ArrayDenormalizer; use Symfony\Component\Serializer\Normalizer\ContextAwareDenormalizerInterface; -use Symfony\Component\Serializer\Serializer; class ArrayDenormalizerTest extends TestCase { - use ExpectDeprecationTrait; - /** * @var ArrayDenormalizer */ @@ -69,46 +65,6 @@ public function testDenormalize() ); } - /** - * @group legacy - */ - public function testDenormalizeLegacy() - { - $serializer = $this->createMock(Serializer::class); - - $serializer->expects($this->exactly(2)) - ->method('denormalize') - ->withConsecutive( - [['foo' => 'one', 'bar' => 'two']], - [['foo' => 'three', 'bar' => 'four']] - ) - ->willReturnOnConsecutiveCalls( - new ArrayDummy('one', 'two'), - new ArrayDummy('three', 'four') - ); - - $denormalizer = new ArrayDenormalizer(); - - $this->expectDeprecation('Since symfony/serializer 5.3: Calling "%s" is deprecated. Please call setDenormalizer() instead.'); - $denormalizer->setSerializer($serializer); - - $result = $denormalizer->denormalize( - [ - ['foo' => 'one', 'bar' => 'two'], - ['foo' => 'three', 'bar' => 'four'], - ], - __NAMESPACE__.'\ArrayDummy[]' - ); - - $this->assertEquals( - [ - new ArrayDummy('one', 'two'), - new ArrayDummy('three', 'four'), - ], - $result - ); - } - public function testSupportsValidArray() { $this->serializer->expects($this->once()) diff --git a/composer.json b/composer.json index 27775ccfa..1966264c6 100644 --- a/composer.json +++ b/composer.json @@ -17,7 +17,6 @@ ], "require": { "php": ">=8.0.2", - "symfony/deprecation-contracts": "^2.1", "symfony/polyfill-ctype": "~1.8" }, "require-dev": { From 5bdb99a9b1147aa5f79791f7e0119bed2fc23931 Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Mon, 12 Jul 2021 11:26:55 +0200 Subject: [PATCH 008/297] Add return types, round 1 --- .../CompiledClassMetadataCacheWarmer.php | 4 +-- Encoder/ChainDecoder.php | 2 +- Encoder/ChainEncoder.php | 2 +- .../Factory/CompiledClassMetadataFactory.php | 5 +-- Normalizer/BackedEnumNormalizer.php | 6 ++-- Normalizer/MimeMessageNormalizer.php | 6 ++-- Normalizer/UidNormalizer.php | 6 ++-- Normalizer/UnwrappingDenormalizer.php | 4 +-- Serializer.php | 6 ++-- Tests/Encoder/ChainEncoderTest.php | 2 +- Tests/Fixtures/AbstractNormalizerDummy.php | 2 +- .../Fixtures/Annotations/GroupDummyChild.php | 10 ++---- Tests/Fixtures/Attributes/GroupDummyChild.php | 33 ------------------- .../AbstractObjectNormalizerTest.php | 14 ++++---- Tests/Normalizer/PropertyNormalizerTest.php | 4 +-- Tests/Normalizer/TestDenormalizer.php | 2 +- 16 files changed, 35 insertions(+), 73 deletions(-) delete mode 100644 Tests/Fixtures/Attributes/GroupDummyChild.php diff --git a/CacheWarmer/CompiledClassMetadataCacheWarmer.php b/CacheWarmer/CompiledClassMetadataCacheWarmer.php index 081bcff67..39a85aeeb 100644 --- a/CacheWarmer/CompiledClassMetadataCacheWarmer.php +++ b/CacheWarmer/CompiledClassMetadataCacheWarmer.php @@ -40,7 +40,7 @@ public function __construct(array $classesToCompile, ClassMetadataFactoryInterfa /** * {@inheritdoc} */ - public function warmUp(string $cacheDir) + public function warmUp(string $cacheDir): array { $metadatas = []; @@ -58,7 +58,7 @@ public function warmUp(string $cacheDir) /** * {@inheritdoc} */ - public function isOptional() + public function isOptional(): bool { return true; } diff --git a/Encoder/ChainDecoder.php b/Encoder/ChainDecoder.php index 49c2680ce..c2b4db4b9 100644 --- a/Encoder/ChainDecoder.php +++ b/Encoder/ChainDecoder.php @@ -35,7 +35,7 @@ public function __construct(array $decoders = []) /** * {@inheritdoc} */ - final public function decode(string $data, string $format, array $context = []) + final public function decode(string $data, string $format, array $context = []): mixed { return $this->getDecoder($format, $context)->decode($data, $format, $context); } diff --git a/Encoder/ChainEncoder.php b/Encoder/ChainEncoder.php index 0c2a53753..0ca0a821a 100644 --- a/Encoder/ChainEncoder.php +++ b/Encoder/ChainEncoder.php @@ -35,7 +35,7 @@ public function __construct(array $encoders = []) /** * {@inheritdoc} */ - final public function encode(mixed $data, string $format, array $context = []) + final public function encode(mixed $data, string $format, array $context = []): string { return $this->getEncoder($format, $context)->encode($data, $format, $context); } diff --git a/Mapping/Factory/CompiledClassMetadataFactory.php b/Mapping/Factory/CompiledClassMetadataFactory.php index 1753cc584..8b0f0aeff 100644 --- a/Mapping/Factory/CompiledClassMetadataFactory.php +++ b/Mapping/Factory/CompiledClassMetadataFactory.php @@ -14,6 +14,7 @@ use Symfony\Component\Serializer\Mapping\AttributeMetadata; use Symfony\Component\Serializer\Mapping\ClassDiscriminatorMapping; use Symfony\Component\Serializer\Mapping\ClassMetadata; +use Symfony\Component\Serializer\Mapping\ClassMetadataInterface; /** * @author Fabien Bourigault @@ -44,7 +45,7 @@ public function __construct(string $compiledClassMetadataFile, ClassMetadataFact /** * {@inheritdoc} */ - public function getMetadataFor(string|object $value) + public function getMetadataFor(string|object $value): ClassMetadataInterface { $className = \is_object($value) ? \get_class($value) : $value; @@ -72,7 +73,7 @@ public function getMetadataFor(string|object $value) /** * {@inheritdoc} */ - public function hasMetadataFor(mixed $value) + public function hasMetadataFor(mixed $value): bool { $className = \is_object($value) ? \get_class($value) : $value; diff --git a/Normalizer/BackedEnumNormalizer.php b/Normalizer/BackedEnumNormalizer.php index 808e04628..4e7524cc2 100644 --- a/Normalizer/BackedEnumNormalizer.php +++ b/Normalizer/BackedEnumNormalizer.php @@ -40,7 +40,7 @@ public function normalize($object, $format = null, array $context = []) /** * {@inheritdoc} */ - public function supportsNormalization($data, $format = null) + public function supportsNormalization($data, $format = null): bool { return $data instanceof \BackedEnum; } @@ -50,7 +50,7 @@ public function supportsNormalization($data, $format = null) * * @throws NotNormalizableValueException */ - public function denormalize($data, $type, $format = null, array $context = []) + public function denormalize($data, $type, $format = null, array $context = []): mixed { if (!is_subclass_of($type, \BackedEnum::class)) { throw new InvalidArgumentException('The data must belong to a backed enumeration.'); @@ -70,7 +70,7 @@ public function denormalize($data, $type, $format = null, array $context = []) /** * {@inheritdoc} */ - public function supportsDenormalization($data, $type, $format = null) + public function supportsDenormalization($data, $type, $format = null): bool { return is_subclass_of($type, \BackedEnum::class); } diff --git a/Normalizer/MimeMessageNormalizer.php b/Normalizer/MimeMessageNormalizer.php index 14c6b842f..d0f034ba0 100644 --- a/Normalizer/MimeMessageNormalizer.php +++ b/Normalizer/MimeMessageNormalizer.php @@ -76,7 +76,7 @@ public function normalize(mixed $object, string $format = null, array $context = /** * {@inheritdoc} */ - public function denormalize(mixed $data, string $type, string $format = null, array $context = []) + public function denormalize(mixed $data, string $type, string $format = null, array $context = []): mixed { if (Headers::class === $type) { $ret = []; @@ -100,7 +100,7 @@ public function denormalize(mixed $data, string $type, string $format = null, ar /** * {@inheritdoc} */ - public function supportsNormalization(mixed $data, string $format = null) + public function supportsNormalization(mixed $data, string $format = null): bool { return $data instanceof Message || $data instanceof Headers || $data instanceof HeaderInterface || $data instanceof Address || $data instanceof AbstractPart; } @@ -108,7 +108,7 @@ public function supportsNormalization(mixed $data, string $format = null) /** * {@inheritdoc} */ - public function supportsDenormalization(mixed $data, string $type, string $format = null) + public function supportsDenormalization(mixed $data, string $type, string $format = null): bool { return is_a($type, Message::class, true) || Headers::class === $type || AbstractPart::class === $type; } diff --git a/Normalizer/UidNormalizer.php b/Normalizer/UidNormalizer.php index 835506893..5fee0c660 100644 --- a/Normalizer/UidNormalizer.php +++ b/Normalizer/UidNormalizer.php @@ -59,7 +59,7 @@ public function normalize(mixed $object, string $format = null, array $context = /** * {@inheritdoc} */ - public function supportsNormalization(mixed $data, string $format = null) + public function supportsNormalization(mixed $data, string $format = null): bool { return $data instanceof AbstractUid; } @@ -67,7 +67,7 @@ public function supportsNormalization(mixed $data, string $format = null) /** * {@inheritdoc} */ - public function denormalize(mixed $data, string $type, string $format = null, array $context = []) + public function denormalize(mixed $data, string $type, string $format = null, array $context = []): mixed { try { return Ulid::class === $type ? Ulid::fromString($data) : Uuid::fromString($data); @@ -79,7 +79,7 @@ public function denormalize(mixed $data, string $type, string $format = null, ar /** * {@inheritdoc} */ - public function supportsDenormalization(mixed $data, string $type, string $format = null) + public function supportsDenormalization(mixed $data, string $type, string $format = null): bool { return is_a($type, AbstractUid::class, true); } diff --git a/Normalizer/UnwrappingDenormalizer.php b/Normalizer/UnwrappingDenormalizer.php index f3531b236..152cfa080 100644 --- a/Normalizer/UnwrappingDenormalizer.php +++ b/Normalizer/UnwrappingDenormalizer.php @@ -35,7 +35,7 @@ public function __construct(PropertyAccessorInterface $propertyAccessor = null) /** * {@inheritdoc} */ - public function denormalize(mixed $data, string $class, string $format = null, array $context = []) + public function denormalize(mixed $data, string $class, string $format = null, array $context = []): mixed { $propertyPath = $context[self::UNWRAP_PATH]; $context['unwrapped'] = true; @@ -54,7 +54,7 @@ public function denormalize(mixed $data, string $class, string $format = null, a /** * {@inheritdoc} */ - public function supportsDenormalization(mixed $data, string $type, string $format = null, array $context = []) + public function supportsDenormalization(mixed $data, string $type, string $format = null, array $context = []): bool { return \array_key_exists(self::UNWRAP_PATH, $context) && !isset($context['unwrapped']); } diff --git a/Serializer.php b/Serializer.php index bc535e4aa..ad8108417 100644 --- a/Serializer.php +++ b/Serializer.php @@ -133,7 +133,7 @@ final public function serialize(mixed $data, string $format, array $context = [] /** * {@inheritdoc} */ - final public function deserialize(mixed $data, string $type, string $format, array $context = []) + final public function deserialize(mixed $data, string $type, string $format, array $context = []): mixed { if (!$this->supportsDecoding($format, $context)) { throw new NotEncodableValueException(sprintf('Deserialization for the format "%s" is not supported.', $format)); @@ -305,7 +305,7 @@ private function getDenormalizer(mixed $data, string $class, ?string $format, ar /** * {@inheritdoc} */ - final public function encode(mixed $data, string $format, array $context = []) + final public function encode(mixed $data, string $format, array $context = []): string { return $this->encoder->encode($data, $format, $context); } @@ -313,7 +313,7 @@ final public function encode(mixed $data, string $format, array $context = []) /** * {@inheritdoc} */ - final public function decode(string $data, string $format, array $context = []) + final public function decode(string $data, string $format, array $context = []): mixed { return $this->decoder->decode($data, $format, $context); } diff --git a/Tests/Encoder/ChainEncoderTest.php b/Tests/Encoder/ChainEncoderTest.php index d90163d25..f447c684a 100644 --- a/Tests/Encoder/ChainEncoderTest.php +++ b/Tests/Encoder/ChainEncoderTest.php @@ -95,7 +95,7 @@ public function supportsEncoding(string $format): bool return true; } - public function encode($data, string $format, array $context = []) + public function encode($data, string $format, array $context = []): string { } } diff --git a/Tests/Fixtures/AbstractNormalizerDummy.php b/Tests/Fixtures/AbstractNormalizerDummy.php index 724dff9a7..c7baa5cc3 100644 --- a/Tests/Fixtures/AbstractNormalizerDummy.php +++ b/Tests/Fixtures/AbstractNormalizerDummy.php @@ -46,7 +46,7 @@ public function supportsNormalization(mixed $data, string $format = null): bool /** * {@inheritdoc} */ - public function denormalize(mixed $data, string $type, string $format = null, array $context = []) + public function denormalize(mixed $data, string $type, string $format = null, array $context = []): mixed { } diff --git a/Tests/Fixtures/Annotations/GroupDummyChild.php b/Tests/Fixtures/Annotations/GroupDummyChild.php index c8845736a..7e4081873 100644 --- a/Tests/Fixtures/Annotations/GroupDummyChild.php +++ b/Tests/Fixtures/Annotations/GroupDummyChild.php @@ -15,18 +15,12 @@ class GroupDummyChild extends GroupDummy { private $baz; - /** - * @return mixed - */ - public function getBaz() + public function getBaz(): mixed { return $this->baz; } - /** - * @param mixed $baz - */ - public function setBaz($baz) + public function setBaz(mixed $baz) { $this->baz = $baz; } diff --git a/Tests/Fixtures/Attributes/GroupDummyChild.php b/Tests/Fixtures/Attributes/GroupDummyChild.php deleted file mode 100644 index 5a7423914..000000000 --- a/Tests/Fixtures/Attributes/GroupDummyChild.php +++ /dev/null @@ -1,33 +0,0 @@ - - * - * 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\Attributes; - -class GroupDummyChild extends GroupDummy -{ - private $baz; - - /** - * @return mixed - */ - public function getBaz() - { - return $this->baz; - } - - /** - * @param mixed $baz - */ - public function setBaz($baz) - { - $this->baz = $baz; - } -} diff --git a/Tests/Normalizer/AbstractObjectNormalizerTest.php b/Tests/Normalizer/AbstractObjectNormalizerTest.php index 0863b6def..3cf0fa47d 100644 --- a/Tests/Normalizer/AbstractObjectNormalizerTest.php +++ b/Tests/Normalizer/AbstractObjectNormalizerTest.php @@ -396,7 +396,7 @@ protected function extractAttributes(object $object, string $format = null, arra return []; } - protected function getAttributeValue(object $object, string $attribute, string $format = null, array $context = []) + protected function getAttributeValue(object $object, string $attribute, string $format = null, array $context = []): mixed { } @@ -438,7 +438,7 @@ protected function extractAttributes(object $object, string $format = null, arra { } - protected function getAttributeValue(object $object, string $attribute, string $format = null, array $context = []) + protected function getAttributeValue(object $object, string $attribute, string $format = null, array $context = []): mixed { } @@ -520,11 +520,11 @@ public function serialize($data, string $format, array $context = []): string { } - public function deserialize($data, string $type, string $format, array $context = []) + public function deserialize($data, string $type, string $format, array $context = []): mixed { } - public function denormalize($data, string $type, string $format = null, array $context = []) + public function denormalize($data, string $type, string $format = null, array $context = []): mixed { foreach ($this->normalizers as $normalizer) { if ($normalizer instanceof DenormalizerInterface && $normalizer->supportsDenormalization($data, $type, $format, $context)) { @@ -547,7 +547,7 @@ protected function extractAttributes(object $object, string $format = null, arra { } - protected function getAttributeValue(object $object, string $attribute, string $format = null, array $context = []) + protected function getAttributeValue(object $object, string $attribute, string $format = null, array $context = []): mixed { } @@ -587,7 +587,7 @@ class ArrayDenormalizerDummy implements DenormalizerInterface, SerializerAwareIn * * @throws NotNormalizableValueException */ - public function denormalize($data, string $type, string $format = null, array $context = []) + public function denormalize($data, string $type, string $format = null, array $context = []): mixed { $serializer = $this->serializer; $type = substr($type, 0, -2); @@ -619,7 +619,7 @@ public function setSerializer(SerializerInterface $serializer) class NotSerializable { - public function __sleep() + public function __sleep(): array { if (class_exists(\Error::class)) { throw new \Error('not serializable'); diff --git a/Tests/Normalizer/PropertyNormalizerTest.php b/Tests/Normalizer/PropertyNormalizerTest.php index 4fd54388e..e66df1441 100644 --- a/Tests/Normalizer/PropertyNormalizerTest.php +++ b/Tests/Normalizer/PropertyNormalizerTest.php @@ -480,7 +480,7 @@ public function getChildren(): array /** * @return Dummy[][][] */ - public function getGrandChildren() + public function getGrandChildren(): array { return $this->grandChildren; } @@ -488,7 +488,7 @@ public function getGrandChildren() /** * @return array */ - public function getIntMatrix() + public function getIntMatrix(): array { return $this->intMatrix; } diff --git a/Tests/Normalizer/TestDenormalizer.php b/Tests/Normalizer/TestDenormalizer.php index 68c8c532c..56398232a 100644 --- a/Tests/Normalizer/TestDenormalizer.php +++ b/Tests/Normalizer/TestDenormalizer.php @@ -23,7 +23,7 @@ class TestDenormalizer implements DenormalizerInterface /** * {@inheritdoc} */ - public function denormalize($data, string $type, string $format = null, array $context = []) + public function denormalize($data, string $type, string $format = null, array $context = []): mixed { } From aaffc4953970d713246966d8d1ce115d714c8b00 Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Tue, 20 Jul 2021 15:07:23 +0200 Subject: [PATCH 009/297] Add return type unions to private/internal/final/test methods --- Encoder/XmlEncoder.php | 8 ++------ Normalizer/BackedEnumNormalizer.php | 6 +----- Normalizer/MimeMessageNormalizer.php | 2 +- Normalizer/UidNormalizer.php | 2 +- Tests/Fixtures/AbstractNormalizerDummy.php | 5 +++-- Tests/Fixtures/Dummy.php | 2 +- Tests/Fixtures/NormalizableTraversableDummy.php | 2 +- Tests/Fixtures/ScalarDummy.php | 2 +- Tests/Fixtures/VariadicConstructorTypedArgsDummy.php | 2 +- Tests/Normalizer/TestNormalizer.php | 3 ++- 10 files changed, 14 insertions(+), 20 deletions(-) diff --git a/Encoder/XmlEncoder.php b/Encoder/XmlEncoder.php index 7a885e711..331fe256e 100644 --- a/Encoder/XmlEncoder.php +++ b/Encoder/XmlEncoder.php @@ -247,10 +247,8 @@ final protected function isElementNameValid(string $name): bool /** * Parse the input DOMNode into an array or a string. - * - * @return array|string */ - private function parseXml(\DOMNode $node, array $context = []) + private function parseXml(\DOMNode $node, array $context = []): array|string { $data = $this->parseXmlAttributes($node, $context); @@ -312,10 +310,8 @@ private function parseXmlAttributes(\DOMNode $node, array $context = []): array /** * Parse the input DOMNode value (content and children) into an array or a string. - * - * @return array|string */ - private function parseXmlValue(\DOMNode $node, array $context = []) + private function parseXmlValue(\DOMNode $node, array $context = []): array|string { if (!$node->hasChildNodes()) { return $node->nodeValue; diff --git a/Normalizer/BackedEnumNormalizer.php b/Normalizer/BackedEnumNormalizer.php index 4e7524cc2..a6d6e6ef1 100644 --- a/Normalizer/BackedEnumNormalizer.php +++ b/Normalizer/BackedEnumNormalizer.php @@ -23,12 +23,8 @@ final class BackedEnumNormalizer implements NormalizerInterface, DenormalizerInt { /** * {@inheritdoc} - * - * @throws InvalidArgumentException - * - * @return int|string */ - public function normalize($object, $format = null, array $context = []) + public function normalize($object, $format = null, array $context = []): int|string { if (!$object instanceof \BackedEnum) { throw new InvalidArgumentException('The data must belong to a backed enumeration.'); diff --git a/Normalizer/MimeMessageNormalizer.php b/Normalizer/MimeMessageNormalizer.php index d0f034ba0..f421d99e1 100644 --- a/Normalizer/MimeMessageNormalizer.php +++ b/Normalizer/MimeMessageNormalizer.php @@ -52,7 +52,7 @@ public function setSerializer(SerializerInterface $serializer) /** * {@inheritdoc} */ - public function normalize(mixed $object, string $format = null, array $context = []) + public function normalize(mixed $object, string $format = null, array $context = []): array|string|int|float|bool|\ArrayObject|null { if ($object instanceof Headers) { $ret = []; diff --git a/Normalizer/UidNormalizer.php b/Normalizer/UidNormalizer.php index 5fee0c660..10d0ae007 100644 --- a/Normalizer/UidNormalizer.php +++ b/Normalizer/UidNormalizer.php @@ -40,7 +40,7 @@ public function __construct(array $defaultContext = []) * * @param AbstractUid $object */ - public function normalize(mixed $object, string $format = null, array $context = []) + public function normalize(mixed $object, string $format = null, array $context = []): array|string|int|float|bool|\ArrayObject|null { switch ($context[self::NORMALIZATION_FORMAT_KEY] ?? $this->defaultContext[self::NORMALIZATION_FORMAT_KEY]) { case self::NORMALIZATION_FORMAT_CANONICAL: diff --git a/Tests/Fixtures/AbstractNormalizerDummy.php b/Tests/Fixtures/AbstractNormalizerDummy.php index c7baa5cc3..e5108ee20 100644 --- a/Tests/Fixtures/AbstractNormalizerDummy.php +++ b/Tests/Fixtures/AbstractNormalizerDummy.php @@ -11,6 +11,7 @@ namespace Symfony\Component\Serializer\Tests\Fixtures; +use Symfony\Component\Serializer\Mapping\AttributeMetadataInterface; use Symfony\Component\Serializer\Normalizer\AbstractNormalizer; /** @@ -23,7 +24,7 @@ class AbstractNormalizerDummy extends AbstractNormalizer /** * {@inheritdoc} */ - public function getAllowedAttributes(string|object $classOrObject, array $context, bool $attributesAsString = false) + public function getAllowedAttributes(string|object $classOrObject, array $context, bool $attributesAsString = false): array|bool { return parent::getAllowedAttributes($classOrObject, $context, $attributesAsString); } @@ -31,7 +32,7 @@ public function getAllowedAttributes(string|object $classOrObject, array $contex /** * {@inheritdoc} */ - public function normalize(mixed $object, string $format = null, array $context = []) + public function normalize(mixed $object, string $format = null, array $context = []): array|string|int|float|bool|\ArrayObject|null { } diff --git a/Tests/Fixtures/Dummy.php b/Tests/Fixtures/Dummy.php index afd8ed75c..76f042e0b 100644 --- a/Tests/Fixtures/Dummy.php +++ b/Tests/Fixtures/Dummy.php @@ -23,7 +23,7 @@ class Dummy implements NormalizableInterface, DenormalizableInterface public $baz; public $qux; - public function normalize(NormalizerInterface $normalizer, string $format = null, array $context = []) + public function normalize(NormalizerInterface $normalizer, string $format = null, array $context = []): array|string|int|float|bool { return [ 'foo' => $this->foo, diff --git a/Tests/Fixtures/NormalizableTraversableDummy.php b/Tests/Fixtures/NormalizableTraversableDummy.php index aed3a20c0..5ee205fd1 100644 --- a/Tests/Fixtures/NormalizableTraversableDummy.php +++ b/Tests/Fixtures/NormalizableTraversableDummy.php @@ -18,7 +18,7 @@ class NormalizableTraversableDummy extends TraversableDummy implements NormalizableInterface, DenormalizableInterface { - public function normalize(NormalizerInterface $normalizer, string $format = null, array $context = []) + public function normalize(NormalizerInterface $normalizer, string $format = null, array $context = []): array|string|int|float|bool { return [ 'foo' => 'normalizedFoo', diff --git a/Tests/Fixtures/ScalarDummy.php b/Tests/Fixtures/ScalarDummy.php index 1afbbbd80..31ca36ab7 100644 --- a/Tests/Fixtures/ScalarDummy.php +++ b/Tests/Fixtures/ScalarDummy.php @@ -21,7 +21,7 @@ class ScalarDummy implements NormalizableInterface, DenormalizableInterface public $foo; public $xmlFoo; - public function normalize(NormalizerInterface $normalizer, string $format = null, array $context = []) + public function normalize(NormalizerInterface $normalizer, string $format = null, array $context = []): array|string|int|float|bool { return 'xml' === $format ? $this->xmlFoo : $this->foo; } diff --git a/Tests/Fixtures/VariadicConstructorTypedArgsDummy.php b/Tests/Fixtures/VariadicConstructorTypedArgsDummy.php index 020fd0d22..1a39064d6 100644 --- a/Tests/Fixtures/VariadicConstructorTypedArgsDummy.php +++ b/Tests/Fixtures/VariadicConstructorTypedArgsDummy.php @@ -21,7 +21,7 @@ public function __construct(Dummy ...$foo) } /** @return Dummy[] */ - public function getFoo() + public function getFoo(): array { return $this->foo; } diff --git a/Tests/Normalizer/TestNormalizer.php b/Tests/Normalizer/TestNormalizer.php index 86835a608..bf1f8f725 100644 --- a/Tests/Normalizer/TestNormalizer.php +++ b/Tests/Normalizer/TestNormalizer.php @@ -23,8 +23,9 @@ class TestNormalizer implements NormalizerInterface /** * {@inheritdoc} */ - public function normalize($object, string $format = null, array $context = []) + public function normalize($object, string $format = null, array $context = []): array|string|int|float|bool|\ArrayObject|null { + return null; } /** From b53c11dda79130abf3d6193349bf449aded9b91b Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Mon, 16 Aug 2021 18:31:32 +0200 Subject: [PATCH 010/297] Run php-cs-fixer --- Annotation/MaxDepth.php | 3 ++- SerializerInterface.php | 2 -- Tests/Normalizer/PropertyNormalizerTest.php | 3 --- 3 files changed, 2 insertions(+), 6 deletions(-) diff --git a/Annotation/MaxDepth.php b/Annotation/MaxDepth.php index 7c8b1ab60..7ecefb8e3 100644 --- a/Annotation/MaxDepth.php +++ b/Annotation/MaxDepth.php @@ -25,7 +25,8 @@ #[\Attribute(\Attribute::TARGET_METHOD | \Attribute::TARGET_PROPERTY)] class MaxDepth { - public function __construct(private int $maxDepth) { + public function __construct(private int $maxDepth) + { if ($maxDepth <= 0) { throw new InvalidArgumentException(sprintf('Parameter of annotation "%s" must be a positive integer.', static::class)); } diff --git a/SerializerInterface.php b/SerializerInterface.php index 9e9a3d3b7..5064e8a82 100644 --- a/SerializerInterface.php +++ b/SerializerInterface.php @@ -30,8 +30,6 @@ public function serialize(mixed $data, string $format, array $context = []); /** * Deserializes data into the given type. * - * @param mixed $data - * * @return mixed */ public function deserialize(mixed $data, string $type, string $format, array $context = []); diff --git a/Tests/Normalizer/PropertyNormalizerTest.php b/Tests/Normalizer/PropertyNormalizerTest.php index e66df1441..224df7cf6 100644 --- a/Tests/Normalizer/PropertyNormalizerTest.php +++ b/Tests/Normalizer/PropertyNormalizerTest.php @@ -485,9 +485,6 @@ public function getGrandChildren(): array return $this->grandChildren; } - /** - * @return array - */ public function getIntMatrix(): array { return $this->intMatrix; From 7d2f07bffadfae9bef59471515f1165094758e8c Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Thu, 12 Aug 2021 19:03:50 +0200 Subject: [PATCH 011/297] [Serializer] add return types --- Annotation/Groups.php | 2 +- Encoder/ContextAwareDecoderInterface.php | 2 +- Encoder/ContextAwareEncoderInterface.php | 2 +- Encoder/CsvEncoder.php | 8 +++---- Encoder/DecoderInterface.php | 8 ++----- Encoder/EncoderInterface.php | 8 ++----- Encoder/JsonDecode.php | 6 ++--- Encoder/JsonEncode.php | 4 ++-- Encoder/JsonEncoder.php | 8 +++---- Encoder/XmlEncoder.php | 8 +++---- Encoder/YamlEncoder.php | 8 +++---- Exception/ExtraAttributesException.php | 4 +--- Mapping/AttributeMetadata.php | 4 ++-- Mapping/AttributeMetadataInterface.php | 4 +--- Mapping/ClassMetadata.php | 2 +- Mapping/Factory/CacheClassMetadataFactory.php | 5 +++-- Mapping/Factory/ClassMetadataFactory.php | 5 +++-- .../Factory/ClassMetadataFactoryInterface.php | 8 ++----- Mapping/Loader/AnnotationLoader.php | 2 +- Mapping/Loader/LoaderChain.php | 4 ++-- Mapping/Loader/LoaderInterface.php | 5 +---- Mapping/Loader/XmlFileLoader.php | 4 ++-- Mapping/Loader/YamlFileLoader.php | 4 ++-- .../AdvancedNameConverterInterface.php | 4 ++-- .../CamelCaseToSnakeCaseNameConverter.php | 4 ++-- NameConverter/NameConverterInterface.php | 8 ++----- Normalizer/AbstractNormalizer.php | 22 +++++-------------- Normalizer/AbstractObjectNormalizer.php | 18 +++++++-------- .../ConstraintViolationListNormalizer.php | 6 ++--- .../ContextAwareDenormalizerInterface.php | 2 +- .../ContextAwareNormalizerInterface.php | 2 +- Normalizer/CustomNormalizer.php | 12 ++++------ Normalizer/DataUriNormalizer.php | 12 ++++------ Normalizer/DateIntervalNormalizer.php | 12 ++++------ Normalizer/DateTimeNormalizer.php | 12 ++++------ Normalizer/DateTimeZoneNormalizer.php | 12 ++++------ Normalizer/DenormalizerInterface.php | 8 ++----- Normalizer/GetSetMethodNormalizer.php | 8 +++---- Normalizer/JsonSerializableNormalizer.php | 8 +++---- Normalizer/NormalizableInterface.php | 2 +- Normalizer/NormalizerInterface.php | 6 ++--- Normalizer/ObjectNormalizer.php | 6 ++--- Normalizer/ProblemNormalizer.php | 4 +--- Normalizer/PropertyNormalizer.php | 10 ++++----- Serializer.php | 12 +++++----- SerializerInterface.php | 8 ++----- Tests/Fixtures/AbstractNormalizerDummy.php | 1 - Tests/SerializerTest.php | 4 +++- 48 files changed, 125 insertions(+), 193 deletions(-) diff --git a/Annotation/Groups.php b/Annotation/Groups.php index 80ea5ce5a..7f377c8fb 100644 --- a/Annotation/Groups.php +++ b/Annotation/Groups.php @@ -51,7 +51,7 @@ public function __construct(string|array $groups) /** * @return string[] */ - public function getGroups() + public function getGroups(): array { return $this->groups; } diff --git a/Encoder/ContextAwareDecoderInterface.php b/Encoder/ContextAwareDecoderInterface.php index ce665f2eb..6ac2e38cc 100644 --- a/Encoder/ContextAwareDecoderInterface.php +++ b/Encoder/ContextAwareDecoderInterface.php @@ -23,5 +23,5 @@ interface ContextAwareDecoderInterface extends DecoderInterface * * @param array $context options that decoders have access to */ - public function supportsDecoding(string $format, array $context = []); + public function supportsDecoding(string $format, array $context = []): bool; } diff --git a/Encoder/ContextAwareEncoderInterface.php b/Encoder/ContextAwareEncoderInterface.php index 17789e88e..832b600ee 100644 --- a/Encoder/ContextAwareEncoderInterface.php +++ b/Encoder/ContextAwareEncoderInterface.php @@ -23,5 +23,5 @@ interface ContextAwareEncoderInterface extends EncoderInterface * * @param array $context options that encoders have access to */ - public function supportsEncoding(string $format, array $context = []); + public function supportsEncoding(string $format, array $context = []): bool; } diff --git a/Encoder/CsvEncoder.php b/Encoder/CsvEncoder.php index b53dfef0b..4b45a1ffc 100644 --- a/Encoder/CsvEncoder.php +++ b/Encoder/CsvEncoder.php @@ -58,7 +58,7 @@ public function __construct(array $defaultContext = []) /** * {@inheritdoc} */ - public function encode(mixed $data, string $format, array $context = []) + public function encode(mixed $data, string $format, array $context = []): string { $handle = fopen('php://temp,', 'w+'); @@ -123,7 +123,7 @@ public function encode(mixed $data, string $format, array $context = []) /** * {@inheritdoc} */ - public function supportsEncoding(string $format) + public function supportsEncoding(string $format): bool { return self::FORMAT === $format; } @@ -131,7 +131,7 @@ public function supportsEncoding(string $format) /** * {@inheritdoc} */ - public function decode(string $data, string $format, array $context = []) + public function decode(string $data, string $format, array $context = []): mixed { $handle = fopen('php://temp', 'r+'); fwrite($handle, $data); @@ -209,7 +209,7 @@ public function decode(string $data, string $format, array $context = []) /** * {@inheritdoc} */ - public function supportsDecoding(string $format) + public function supportsDecoding(string $format): bool { return self::FORMAT === $format; } diff --git a/Encoder/DecoderInterface.php b/Encoder/DecoderInterface.php index 84a84ad1f..09ec9434d 100644 --- a/Encoder/DecoderInterface.php +++ b/Encoder/DecoderInterface.php @@ -30,18 +30,14 @@ interface DecoderInterface * are encouraged to document which formats they support in a non-inherited * phpdoc comment. * - * @return mixed - * * @throws UnexpectedValueException */ - public function decode(string $data, string $format, array $context = []); + public function decode(string $data, string $format, array $context = []): mixed; /** * Checks whether the deserializer can decode from given format. * * @param string $format Format name - * - * @return bool */ - public function supportsDecoding(string $format); + public function supportsDecoding(string $format): bool; } diff --git a/Encoder/EncoderInterface.php b/Encoder/EncoderInterface.php index 5b4c5ea43..e0f303b1e 100644 --- a/Encoder/EncoderInterface.php +++ b/Encoder/EncoderInterface.php @@ -25,18 +25,14 @@ interface EncoderInterface * @param string $format Format name * @param array $context Options that normalizers/encoders have access to * - * @return string - * * @throws UnexpectedValueException */ - public function encode(mixed $data, string $format, array $context = []); + public function encode(mixed $data, string $format, array $context = []): string; /** * Checks whether the serializer can encode to given format. * * @param string $format Format name - * - * @return bool */ - public function supportsEncoding(string $format); + public function supportsEncoding(string $format): bool; } diff --git a/Encoder/JsonDecode.php b/Encoder/JsonDecode.php index 01593edbe..ad094afac 100644 --- a/Encoder/JsonDecode.php +++ b/Encoder/JsonDecode.php @@ -66,13 +66,11 @@ public function __construct(array $defaultContext = []) * json_decode_options: integer * Specifies additional options as per documentation for json_decode * - * @return mixed - * * @throws NotEncodableValueException * * @see https://php.net/json_decode */ - public function decode(string $data, string $format, array $context = []) + public function decode(string $data, string $format, array $context = []): mixed { $associative = $context[self::ASSOCIATIVE] ?? $this->defaultContext[self::ASSOCIATIVE]; $recursionDepth = $context[self::RECURSION_DEPTH] ?? $this->defaultContext[self::RECURSION_DEPTH]; @@ -98,7 +96,7 @@ public function decode(string $data, string $format, array $context = []) /** * {@inheritdoc} */ - public function supportsDecoding(string $format) + public function supportsDecoding(string $format): bool { return JsonEncoder::FORMAT === $format; } diff --git a/Encoder/JsonEncode.php b/Encoder/JsonEncode.php index 4da793ffd..23d0fdd96 100644 --- a/Encoder/JsonEncode.php +++ b/Encoder/JsonEncode.php @@ -34,7 +34,7 @@ public function __construct(array $defaultContext = []) /** * {@inheritdoc} */ - public function encode(mixed $data, string $format, array $context = []) + public function encode(mixed $data, string $format, array $context = []): string { $options = $context[self::OPTIONS] ?? $this->defaultContext[self::OPTIONS]; @@ -58,7 +58,7 @@ public function encode(mixed $data, string $format, array $context = []) /** * {@inheritdoc} */ - public function supportsEncoding(string $format) + public function supportsEncoding(string $format): bool { return JsonEncoder::FORMAT === $format; } diff --git a/Encoder/JsonEncoder.php b/Encoder/JsonEncoder.php index c10be44b1..d17ef0492 100644 --- a/Encoder/JsonEncoder.php +++ b/Encoder/JsonEncoder.php @@ -32,7 +32,7 @@ public function __construct(JsonEncode $encodingImpl = null, JsonDecode $decodin /** * {@inheritdoc} */ - public function encode(mixed $data, string $format, array $context = []) + public function encode(mixed $data, string $format, array $context = []): string { return $this->encodingImpl->encode($data, self::FORMAT, $context); } @@ -40,7 +40,7 @@ public function encode(mixed $data, string $format, array $context = []) /** * {@inheritdoc} */ - public function decode(string $data, string $format, array $context = []) + public function decode(string $data, string $format, array $context = []): mixed { return $this->decodingImpl->decode($data, self::FORMAT, $context); } @@ -48,7 +48,7 @@ public function decode(string $data, string $format, array $context = []) /** * {@inheritdoc} */ - public function supportsEncoding(string $format) + public function supportsEncoding(string $format): bool { return self::FORMAT === $format; } @@ -56,7 +56,7 @@ public function supportsEncoding(string $format) /** * {@inheritdoc} */ - public function supportsDecoding(string $format) + public function supportsDecoding(string $format): bool { return self::FORMAT === $format; } diff --git a/Encoder/XmlEncoder.php b/Encoder/XmlEncoder.php index d54124649..a0750e3ff 100644 --- a/Encoder/XmlEncoder.php +++ b/Encoder/XmlEncoder.php @@ -80,7 +80,7 @@ public function __construct(array $defaultContext = []) /** * {@inheritdoc} */ - public function encode(mixed $data, string $format, array $context = []) + public function encode(mixed $data, string $format, array $context = []): string { $encoderIgnoredNodeTypes = $context[self::ENCODER_IGNORED_NODE_TYPES] ?? $this->defaultContext[self::ENCODER_IGNORED_NODE_TYPES]; $ignorePiNode = \in_array(\XML_PI_NODE, $encoderIgnoredNodeTypes, true); @@ -108,7 +108,7 @@ public function encode(mixed $data, string $format, array $context = []) /** * {@inheritdoc} */ - public function decode(string $data, string $format, array $context = []) + public function decode(string $data, string $format, array $context = []): mixed { if ('' === trim($data)) { throw new NotEncodableValueException('Invalid XML data, it can not be empty.'); @@ -175,7 +175,7 @@ public function decode(string $data, string $format, array $context = []) /** * {@inheritdoc} */ - public function supportsEncoding(string $format) + public function supportsEncoding(string $format): bool { return self::FORMAT === $format; } @@ -183,7 +183,7 @@ public function supportsEncoding(string $format) /** * {@inheritdoc} */ - public function supportsDecoding(string $format) + public function supportsDecoding(string $format): bool { return self::FORMAT === $format; } diff --git a/Encoder/YamlEncoder.php b/Encoder/YamlEncoder.php index 651cfc70f..64fefee0e 100644 --- a/Encoder/YamlEncoder.php +++ b/Encoder/YamlEncoder.php @@ -54,7 +54,7 @@ public function __construct(Dumper $dumper = null, Parser $parser = null, array /** * {@inheritdoc} */ - public function encode(mixed $data, string $format, array $context = []) + public function encode(mixed $data, string $format, array $context = []): string { $context = array_merge($this->defaultContext, $context); @@ -68,7 +68,7 @@ public function encode(mixed $data, string $format, array $context = []) /** * {@inheritdoc} */ - public function supportsEncoding(string $format) + public function supportsEncoding(string $format): bool { return self::FORMAT === $format || self::ALTERNATIVE_FORMAT === $format; } @@ -76,7 +76,7 @@ public function supportsEncoding(string $format) /** * {@inheritdoc} */ - public function decode(string $data, string $format, array $context = []) + public function decode(string $data, string $format, array $context = []): mixed { $context = array_merge($this->defaultContext, $context); @@ -86,7 +86,7 @@ public function decode(string $data, string $format, array $context = []) /** * {@inheritdoc} */ - public function supportsDecoding(string $format) + public function supportsDecoding(string $format): bool { return self::FORMAT === $format || self::ALTERNATIVE_FORMAT === $format; } diff --git a/Exception/ExtraAttributesException.php b/Exception/ExtraAttributesException.php index 37cfb556f..22fcc87b8 100644 --- a/Exception/ExtraAttributesException.php +++ b/Exception/ExtraAttributesException.php @@ -31,10 +31,8 @@ public function __construct(array $extraAttributes, \Throwable $previous = null) /** * Get the extra attributes that are not allowed. - * - * @return array */ - public function getExtraAttributes() + public function getExtraAttributes(): array { return $this->extraAttributes; } diff --git a/Mapping/AttributeMetadata.php b/Mapping/AttributeMetadata.php index 36d1e92b6..3097b52c4 100644 --- a/Mapping/AttributeMetadata.php +++ b/Mapping/AttributeMetadata.php @@ -119,7 +119,7 @@ public function setMaxDepth(?int $maxDepth) /** * {@inheritdoc} */ - public function getMaxDepth() + public function getMaxDepth(): ?int { return $this->maxDepth; } @@ -261,7 +261,7 @@ public function merge(AttributeMetadataInterface $attributeMetadata) * * @return string[] */ - public function __sleep() + public function __sleep(): array { return ['name', 'groups', 'maxDepth', 'serializedName', 'ignore', 'normalizationContexts', 'denormalizationContexts']; } diff --git a/Mapping/AttributeMetadataInterface.php b/Mapping/AttributeMetadataInterface.php index 9e5a1ae2d..492beb1b4 100644 --- a/Mapping/AttributeMetadataInterface.php +++ b/Mapping/AttributeMetadataInterface.php @@ -46,10 +46,8 @@ public function setMaxDepth(?int $maxDepth); /** * Gets the serialization max depth for this attribute. - * - * @return int|null */ - public function getMaxDepth(); + public function getMaxDepth(): ?int; /** * Sets the serialization name for this attribute. diff --git a/Mapping/ClassMetadata.php b/Mapping/ClassMetadata.php index 65b42ceba..ace7ba329 100644 --- a/Mapping/ClassMetadata.php +++ b/Mapping/ClassMetadata.php @@ -128,7 +128,7 @@ public function setClassDiscriminatorMapping(ClassDiscriminatorMapping $mapping * * @return string[] */ - public function __sleep() + public function __sleep(): array { return [ 'name', diff --git a/Mapping/Factory/CacheClassMetadataFactory.php b/Mapping/Factory/CacheClassMetadataFactory.php index 4d1fe154c..e7edc4d37 100644 --- a/Mapping/Factory/CacheClassMetadataFactory.php +++ b/Mapping/Factory/CacheClassMetadataFactory.php @@ -12,6 +12,7 @@ namespace Symfony\Component\Serializer\Mapping\Factory; use Psr\Cache\CacheItemPoolInterface; +use Symfony\Component\Serializer\Mapping\ClassMetadataInterface; /** * Caches metadata using a PSR-6 implementation. @@ -43,7 +44,7 @@ public function __construct(ClassMetadataFactoryInterface $decorated, CacheItemP /** * {@inheritdoc} */ - public function getMetadataFor(string|object $value) + public function getMetadataFor(string|object $value): ClassMetadataInterface { $class = $this->getClass($value); @@ -67,7 +68,7 @@ public function getMetadataFor(string|object $value) /** * {@inheritdoc} */ - public function hasMetadataFor(mixed $value) + public function hasMetadataFor(mixed $value): bool { return $this->decorated->hasMetadataFor($value); } diff --git a/Mapping/Factory/ClassMetadataFactory.php b/Mapping/Factory/ClassMetadataFactory.php index 5516e7eea..59fb589f0 100644 --- a/Mapping/Factory/ClassMetadataFactory.php +++ b/Mapping/Factory/ClassMetadataFactory.php @@ -12,6 +12,7 @@ namespace Symfony\Component\Serializer\Mapping\Factory; use Symfony\Component\Serializer\Mapping\ClassMetadata; +use Symfony\Component\Serializer\Mapping\ClassMetadataInterface; use Symfony\Component\Serializer\Mapping\Loader\LoaderInterface; /** @@ -38,7 +39,7 @@ public function __construct(LoaderInterface $loader) /** * {@inheritdoc} */ - public function getMetadataFor(string|object $value) + public function getMetadataFor(string|object $value): ClassMetadataInterface { $class = $this->getClass($value); @@ -67,7 +68,7 @@ public function getMetadataFor(string|object $value) /** * {@inheritdoc} */ - public function hasMetadataFor(mixed $value) + public function hasMetadataFor(mixed $value): bool { return \is_object($value) || (\is_string($value) && (class_exists($value) || interface_exists($value, false))); } diff --git a/Mapping/Factory/ClassMetadataFactoryInterface.php b/Mapping/Factory/ClassMetadataFactoryInterface.php index 5d0cfeacd..169db634f 100644 --- a/Mapping/Factory/ClassMetadataFactoryInterface.php +++ b/Mapping/Factory/ClassMetadataFactoryInterface.php @@ -34,16 +34,12 @@ interface ClassMetadataFactoryInterface * {@link \Symfony\Component\Serializer\Mapping\Loader\LoaderInterface::loadClassMetadata()} method for further * configuration. At last, the new object is returned. * - * @return ClassMetadataInterface - * * @throws InvalidArgumentException */ - public function getMetadataFor(string|object $value); + public function getMetadataFor(string|object $value): ClassMetadataInterface; /** * Checks if class has metadata. - * - * @return bool */ - public function hasMetadataFor(mixed $value); + public function hasMetadataFor(mixed $value): bool; } diff --git a/Mapping/Loader/AnnotationLoader.php b/Mapping/Loader/AnnotationLoader.php index 48f351415..96ab32dba 100644 --- a/Mapping/Loader/AnnotationLoader.php +++ b/Mapping/Loader/AnnotationLoader.php @@ -51,7 +51,7 @@ public function __construct(Reader $reader = null) /** * {@inheritdoc} */ - public function loadClassMetadata(ClassMetadataInterface $classMetadata) + public function loadClassMetadata(ClassMetadataInterface $classMetadata): bool { $reflectionClass = $classMetadata->getReflectionClass(); $className = $reflectionClass->name; diff --git a/Mapping/Loader/LoaderChain.php b/Mapping/Loader/LoaderChain.php index aa428a3bd..c2b3423c8 100644 --- a/Mapping/Loader/LoaderChain.php +++ b/Mapping/Loader/LoaderChain.php @@ -50,7 +50,7 @@ public function __construct(array $loaders) /** * {@inheritdoc} */ - public function loadClassMetadata(ClassMetadataInterface $metadata) + public function loadClassMetadata(ClassMetadataInterface $metadata): bool { $success = false; @@ -64,7 +64,7 @@ public function loadClassMetadata(ClassMetadataInterface $metadata) /** * @return LoaderInterface[] */ - public function getLoaders() + public function getLoaders(): array { return $this->loaders; } diff --git a/Mapping/Loader/LoaderInterface.php b/Mapping/Loader/LoaderInterface.php index 1310a7169..4801e4764 100644 --- a/Mapping/Loader/LoaderInterface.php +++ b/Mapping/Loader/LoaderInterface.php @@ -20,8 +20,5 @@ */ interface LoaderInterface { - /** - * @return bool - */ - public function loadClassMetadata(ClassMetadataInterface $classMetadata); + public function loadClassMetadata(ClassMetadataInterface $classMetadata): bool; } diff --git a/Mapping/Loader/XmlFileLoader.php b/Mapping/Loader/XmlFileLoader.php index d023402f7..26aa1f51e 100644 --- a/Mapping/Loader/XmlFileLoader.php +++ b/Mapping/Loader/XmlFileLoader.php @@ -34,7 +34,7 @@ class XmlFileLoader extends FileLoader /** * {@inheritdoc} */ - public function loadClassMetadata(ClassMetadataInterface $classMetadata) + public function loadClassMetadata(ClassMetadataInterface $classMetadata): bool { if (null === $this->classes) { $this->classes = $this->getClassesFromXml(); @@ -119,7 +119,7 @@ public function loadClassMetadata(ClassMetadataInterface $classMetadata) * * @return string[] */ - public function getMappedClasses() + public function getMappedClasses(): array { if (null === $this->classes) { $this->classes = $this->getClassesFromXml(); diff --git a/Mapping/Loader/YamlFileLoader.php b/Mapping/Loader/YamlFileLoader.php index 8c3792db6..6e77a989d 100644 --- a/Mapping/Loader/YamlFileLoader.php +++ b/Mapping/Loader/YamlFileLoader.php @@ -37,7 +37,7 @@ class YamlFileLoader extends FileLoader /** * {@inheritdoc} */ - public function loadClassMetadata(ClassMetadataInterface $classMetadata) + public function loadClassMetadata(ClassMetadataInterface $classMetadata): bool { if (null === $this->classes) { $this->classes = $this->getClassesFromYaml(); @@ -144,7 +144,7 @@ public function loadClassMetadata(ClassMetadataInterface $classMetadata) * * @return string[] */ - public function getMappedClasses() + public function getMappedClasses(): array { if (null === $this->classes) { $this->classes = $this->getClassesFromYaml(); diff --git a/NameConverter/AdvancedNameConverterInterface.php b/NameConverter/AdvancedNameConverterInterface.php index 0b277d40e..208d649f7 100644 --- a/NameConverter/AdvancedNameConverterInterface.php +++ b/NameConverter/AdvancedNameConverterInterface.php @@ -21,10 +21,10 @@ interface AdvancedNameConverterInterface extends NameConverterInterface /** * {@inheritdoc} */ - public function normalize(string $propertyName, string $class = null, string $format = null, array $context = []); + public function normalize(string $propertyName, string $class = null, string $format = null, array $context = []): string; /** * {@inheritdoc} */ - public function denormalize(string $propertyName, string $class = null, string $format = null, array $context = []); + public function denormalize(string $propertyName, string $class = null, string $format = null, array $context = []): string; } diff --git a/NameConverter/CamelCaseToSnakeCaseNameConverter.php b/NameConverter/CamelCaseToSnakeCaseNameConverter.php index 4060e5ac3..84f3f5c8c 100644 --- a/NameConverter/CamelCaseToSnakeCaseNameConverter.php +++ b/NameConverter/CamelCaseToSnakeCaseNameConverter.php @@ -34,7 +34,7 @@ public function __construct(array $attributes = null, bool $lowerCamelCase = tru /** * {@inheritdoc} */ - public function normalize(string $propertyName) + public function normalize(string $propertyName): string { if (null === $this->attributes || \in_array($propertyName, $this->attributes)) { return strtolower(preg_replace('/[A-Z]/', '_\\0', lcfirst($propertyName))); @@ -46,7 +46,7 @@ public function normalize(string $propertyName) /** * {@inheritdoc} */ - public function denormalize(string $propertyName) + public function denormalize(string $propertyName): string { $camelCasedName = preg_replace_callback('/(^|_|\.)+(.)/', function ($match) { return ('.' === $match[1] ? '_' : '').strtoupper($match[2]); diff --git a/NameConverter/NameConverterInterface.php b/NameConverter/NameConverterInterface.php index ae2f4c59c..aba69a49e 100644 --- a/NameConverter/NameConverterInterface.php +++ b/NameConverter/NameConverterInterface.php @@ -20,15 +20,11 @@ interface NameConverterInterface { /** * Converts a property name to its normalized value. - * - * @return string */ - public function normalize(string $propertyName); + public function normalize(string $propertyName): string; /** * Converts a property name to its denormalized value. - * - * @return string */ - public function denormalize(string $propertyName); + public function denormalize(string $propertyName): string; } diff --git a/Normalizer/AbstractNormalizer.php b/Normalizer/AbstractNormalizer.php index 0aec8c6f9..4c9f8320c 100644 --- a/Normalizer/AbstractNormalizer.php +++ b/Normalizer/AbstractNormalizer.php @@ -170,11 +170,9 @@ public function hasCacheableSupportsMethod(): bool /** * Detects if the configured circular reference limit is reached. * - * @return bool - * * @throws CircularReferenceException */ - protected function isCircularReference(object $object, array &$context) + protected function isCircularReference(object $object, array &$context): bool { $objectHash = spl_object_hash($object); @@ -223,7 +221,7 @@ protected function handleCircularReference(object $object, string $format = null * * @return string[]|AttributeMetadataInterface[]|bool */ - protected function getAllowedAttributes(string|object $classOrObject, array $context, bool $attributesAsString = false) + protected function getAllowedAttributes(string|object $classOrObject, array $context, bool $attributesAsString = false): array|bool { $allowExtraAttributes = $context[self::ALLOW_EXTRA_ATTRIBUTES] ?? $this->defaultContext[self::ALLOW_EXTRA_ATTRIBUTES]; if (!$this->classMetadataFactory) { @@ -270,10 +268,8 @@ protected function getGroups(array $context): array /** * Is this attribute allowed? - * - * @return bool */ - protected function isAllowedAttribute(object|string $classOrObject, string $attribute, string $format = null, array $context = []) + protected function isAllowedAttribute(object|string $classOrObject, string $attribute, string $format = null, array $context = []): bool { $ignoredAttributes = $context[self::IGNORED_ATTRIBUTES] ?? $this->defaultContext[self::IGNORED_ATTRIBUTES]; if (\in_array($attribute, $ignoredAttributes)) { @@ -296,10 +292,8 @@ protected function isAllowedAttribute(object|string $classOrObject, string $attr /** * Normalizes the given data to an array. It's particularly useful during * the denormalization process. - * - * @return array */ - protected function prepareForDenormalization(object|array|null $data) + protected function prepareForDenormalization(object|array|null $data): array { return (array) $data; } @@ -307,10 +301,8 @@ protected function prepareForDenormalization(object|array|null $data) /** * Returns the method to use to construct an object. This method must be either * the object constructor or static. - * - * @return \ReflectionMethod|null */ - protected function getConstructor(array &$data, string $class, array &$context, \ReflectionClass $reflectionClass, array|bool $allowedAttributes) + protected function getConstructor(array &$data, string $class, array &$context, \ReflectionClass $reflectionClass, array|bool $allowedAttributes): ?\ReflectionMethod { return $reflectionClass->getConstructor(); } @@ -323,12 +315,10 @@ protected function getConstructor(array &$data, string $class, array &$context, * is removed from the context before being returned to avoid side effects * when recursively normalizing an object graph. * - * @return object - * * @throws RuntimeException * @throws MissingConstructorArgumentsException */ - protected function instantiateObject(array &$data, string $class, array &$context, \ReflectionClass $reflectionClass, array|bool $allowedAttributes, string $format = null) + protected function instantiateObject(array &$data, string $class, array &$context, \ReflectionClass $reflectionClass, array|bool $allowedAttributes, string $format = null): object { if (null !== $object = $this->extractObjectToPopulate($class, $context, self::OBJECT_TO_POPULATE)) { unset($context[self::OBJECT_TO_POPULATE]); diff --git a/Normalizer/AbstractObjectNormalizer.php b/Normalizer/AbstractObjectNormalizer.php index a36064c4a..0b96f4ec3 100644 --- a/Normalizer/AbstractObjectNormalizer.php +++ b/Normalizer/AbstractObjectNormalizer.php @@ -129,7 +129,7 @@ public function __construct(ClassMetadataFactoryInterface $classMetadataFactory /** * {@inheritdoc} */ - public function supportsNormalization(mixed $data, string $format = null) + public function supportsNormalization(mixed $data, string $format = null): bool { return \is_object($data) && !$data instanceof \Traversable; } @@ -137,7 +137,7 @@ public function supportsNormalization(mixed $data, string $format = null) /** * {@inheritdoc} */ - public function normalize(mixed $object, string $format = null, array $context = []) + public function normalize(mixed $object, string $format = null, array $context = []): array|string|int|float|bool|\ArrayObject|null { if (!isset($context['cache_key'])) { $context['cache_key'] = $this->getCacheKey($format, $context); @@ -254,7 +254,7 @@ private function getAttributeMetadata(object|string $objectOrClass, string $attr /** * {@inheritdoc} */ - protected function instantiateObject(array &$data, string $class, array &$context, \ReflectionClass $reflectionClass, array|bool $allowedAttributes, string $format = null) + protected function instantiateObject(array &$data, string $class, array &$context, \ReflectionClass $reflectionClass, array|bool $allowedAttributes, string $format = null): object { if ($this->classDiscriminatorResolver && $mapping = $this->classDiscriminatorResolver->getMappingForClass($class)) { if (!isset($data[$mapping->getTypeProperty()])) { @@ -279,7 +279,7 @@ protected function instantiateObject(array &$data, string $class, array &$contex * * @return string[] */ - protected function getAttributes(object $object, ?string $format, array $context) + protected function getAttributes(object $object, ?string $format, array $context): array { $class = $this->objectClassResolver ? ($this->objectClassResolver)($object) : \get_class($object); $key = $class.'-'.$context['cache_key']; @@ -316,19 +316,17 @@ protected function getAttributes(object $object, ?string $format, array $context * * @return string[] */ - abstract protected function extractAttributes(object $object, string $format = null, array $context = []); + abstract protected function extractAttributes(object $object, string $format = null, array $context = []): array; /** * Gets the attribute value. - * - * @return mixed */ - abstract protected function getAttributeValue(object $object, string $attribute, string $format = null, array $context = []); + abstract protected function getAttributeValue(object $object, string $attribute, string $format = null, array $context = []): mixed; /** * {@inheritdoc} */ - public function supportsDenormalization(mixed $data, string $type, string $format = null) + public function supportsDenormalization(mixed $data, string $type, string $format = null): bool { return class_exists($type) || (interface_exists($type, false) && $this->classDiscriminatorResolver && null !== $this->classDiscriminatorResolver->getMappingForClass($type)); } @@ -336,7 +334,7 @@ public function supportsDenormalization(mixed $data, string $type, string $forma /** * {@inheritdoc} */ - public function denormalize(mixed $data, string $type, string $format = null, array $context = []) + public function denormalize(mixed $data, string $type, string $format = null, array $context = []): mixed { if (!isset($context['cache_key'])) { $context['cache_key'] = $this->getCacheKey($format, $context); diff --git a/Normalizer/ConstraintViolationListNormalizer.php b/Normalizer/ConstraintViolationListNormalizer.php index 3ca313466..fcedfa531 100644 --- a/Normalizer/ConstraintViolationListNormalizer.php +++ b/Normalizer/ConstraintViolationListNormalizer.php @@ -41,10 +41,8 @@ public function __construct(array $defaultContext = [], NameConverterInterface $ /** * {@inheritdoc} - * - * @return array */ - public function normalize(mixed $object, string $format = null, array $context = []) + public function normalize(mixed $object, string $format = null, array $context = []): array { if (\array_key_exists(self::PAYLOAD_FIELDS, $context)) { $payloadFieldsToSerialize = $context[self::PAYLOAD_FIELDS]; @@ -109,7 +107,7 @@ public function normalize(mixed $object, string $format = null, array $context = /** * {@inheritdoc} */ - public function supportsNormalization(mixed $data, string $format = null) + public function supportsNormalization(mixed $data, string $format = null): bool { return $data instanceof ConstraintViolationListInterface; } diff --git a/Normalizer/ContextAwareDenormalizerInterface.php b/Normalizer/ContextAwareDenormalizerInterface.php index b69e9c540..991db4470 100644 --- a/Normalizer/ContextAwareDenormalizerInterface.php +++ b/Normalizer/ContextAwareDenormalizerInterface.php @@ -23,5 +23,5 @@ interface ContextAwareDenormalizerInterface extends DenormalizerInterface * * @param array $context options that denormalizers have access to */ - public function supportsDenormalization(mixed $data, string $type, string $format = null, array $context = []); + public function supportsDenormalization(mixed $data, string $type, string $format = null, array $context = []): bool; } diff --git a/Normalizer/ContextAwareNormalizerInterface.php b/Normalizer/ContextAwareNormalizerInterface.php index 50a8d4127..eb28a7048 100644 --- a/Normalizer/ContextAwareNormalizerInterface.php +++ b/Normalizer/ContextAwareNormalizerInterface.php @@ -23,5 +23,5 @@ interface ContextAwareNormalizerInterface extends NormalizerInterface * * @param array $context options that normalizers have access to */ - public function supportsNormalization(mixed $data, string $format = null, array $context = []); + public function supportsNormalization(mixed $data, string $format = null, array $context = []): bool; } diff --git a/Normalizer/CustomNormalizer.php b/Normalizer/CustomNormalizer.php index 8e3a09882..e9e6f3d1a 100644 --- a/Normalizer/CustomNormalizer.php +++ b/Normalizer/CustomNormalizer.php @@ -25,7 +25,7 @@ class CustomNormalizer implements NormalizerInterface, DenormalizerInterface, Se /** * {@inheritdoc} */ - public function normalize(mixed $object, string $format = null, array $context = []) + public function normalize(mixed $object, string $format = null, array $context = []): array|string|int|float|bool|\ArrayObject|null { return $object->normalize($this->serializer, $format, $context); } @@ -33,7 +33,7 @@ public function normalize(mixed $object, string $format = null, array $context = /** * {@inheritdoc} */ - public function denormalize(mixed $data, string $type, string $format = null, array $context = []) + public function denormalize(mixed $data, string $type, string $format = null, array $context = []): mixed { $object = $this->extractObjectToPopulate($type, $context) ?? new $type(); $object->denormalize($this->serializer, $data, $format, $context); @@ -46,10 +46,8 @@ public function denormalize(mixed $data, string $type, string $format = null, ar * * @param mixed $data Data to normalize * @param string $format The format being (de-)serialized from or into - * - * @return bool */ - public function supportsNormalization(mixed $data, string $format = null) + public function supportsNormalization(mixed $data, string $format = null): bool { return $data instanceof NormalizableInterface; } @@ -60,10 +58,8 @@ public function supportsNormalization(mixed $data, string $format = null) * @param mixed $data Data to denormalize from * @param string $type The class to which the data should be denormalized * @param string $format The format being deserialized from - * - * @return bool */ - public function supportsDenormalization(mixed $data, string $type, string $format = null) + public function supportsDenormalization(mixed $data, string $type, string $format = null): bool { return is_subclass_of($type, DenormalizableInterface::class); } diff --git a/Normalizer/DataUriNormalizer.php b/Normalizer/DataUriNormalizer.php index 4d5aa1cb8..739ee6dcf 100644 --- a/Normalizer/DataUriNormalizer.php +++ b/Normalizer/DataUriNormalizer.php @@ -47,10 +47,8 @@ public function __construct(MimeTypeGuesserInterface $mimeTypeGuesser = null) /** * {@inheritdoc} - * - * @return string */ - public function normalize(mixed $object, string $format = null, array $context = []) + public function normalize(mixed $object, string $format = null, array $context = []): string { if (!$object instanceof \SplFileInfo) { throw new InvalidArgumentException('The object must be an instance of "\SplFileInfo".'); @@ -76,7 +74,7 @@ public function normalize(mixed $object, string $format = null, array $context = /** * {@inheritdoc} */ - public function supportsNormalization(mixed $data, string $format = null) + public function supportsNormalization(mixed $data, string $format = null): bool { return $data instanceof \SplFileInfo; } @@ -90,10 +88,8 @@ public function supportsNormalization(mixed $data, string $format = null) * * @throws InvalidArgumentException * @throws NotNormalizableValueException - * - * @return \SplFileInfo */ - public function denormalize(mixed $data, string $type, string $format = null, array $context = []) + public function denormalize(mixed $data, string $type, string $format = null, array $context = []): \SplFileInfo { if (!preg_match('/^data:([a-z0-9][a-z0-9\!\#\$\&\-\^\_\+\.]{0,126}\/[a-z0-9][a-z0-9\!\#\$\&\-\^\_\+\.]{0,126}(;[a-z0-9\-]+\=[a-z0-9\-]+)?)?(;base64)?,[a-z0-9\!\$\&\\\'\,\(\)\*\+\,\;\=\-\.\_\~\:\@\/\?\%\s]*\s*$/i', $data)) { throw new NotNormalizableValueException('The provided "data:" URI is not valid.'); @@ -122,7 +118,7 @@ public function denormalize(mixed $data, string $type, string $format = null, ar /** * {@inheritdoc} */ - public function supportsDenormalization(mixed $data, string $type, string $format = null) + public function supportsDenormalization(mixed $data, string $type, string $format = null): bool { return isset(self::SUPPORTED_TYPES[$type]); } diff --git a/Normalizer/DateIntervalNormalizer.php b/Normalizer/DateIntervalNormalizer.php index 0bf42f90c..db1117500 100644 --- a/Normalizer/DateIntervalNormalizer.php +++ b/Normalizer/DateIntervalNormalizer.php @@ -37,10 +37,8 @@ public function __construct(array $defaultContext = []) * {@inheritdoc} * * @throws InvalidArgumentException - * - * @return string */ - public function normalize(mixed $object, string $format = null, array $context = []) + public function normalize(mixed $object, string $format = null, array $context = []): string { if (!$object instanceof \DateInterval) { throw new InvalidArgumentException('The object must be an instance of "\DateInterval".'); @@ -52,7 +50,7 @@ public function normalize(mixed $object, string $format = null, array $context = /** * {@inheritdoc} */ - public function supportsNormalization(mixed $data, string $format = null) + public function supportsNormalization(mixed $data, string $format = null): bool { return $data instanceof \DateInterval; } @@ -70,10 +68,8 @@ public function hasCacheableSupportsMethod(): bool * * @throws InvalidArgumentException * @throws UnexpectedValueException - * - * @return \DateInterval */ - public function denormalize(mixed $data, string $type, string $format = null, array $context = []) + public function denormalize(mixed $data, string $type, string $format = null, array $context = []): \DateInterval { if (!\is_string($data)) { throw new InvalidArgumentException(sprintf('Data expected to be a string, "%s" given.', get_debug_type($data))); @@ -122,7 +118,7 @@ public function denormalize(mixed $data, string $type, string $format = null, ar /** * {@inheritdoc} */ - public function supportsDenormalization(mixed $data, string $type, string $format = null) + public function supportsDenormalization(mixed $data, string $type, string $format = null): bool { return \DateInterval::class === $type; } diff --git a/Normalizer/DateTimeNormalizer.php b/Normalizer/DateTimeNormalizer.php index 9352e0dc1..187adf099 100644 --- a/Normalizer/DateTimeNormalizer.php +++ b/Normalizer/DateTimeNormalizer.php @@ -45,10 +45,8 @@ public function __construct(array $defaultContext = []) * {@inheritdoc} * * @throws InvalidArgumentException - * - * @return string */ - public function normalize(mixed $object, string $format = null, array $context = []) + public function normalize(mixed $object, string $format = null, array $context = []): string { if (!$object instanceof \DateTimeInterface) { throw new InvalidArgumentException('The object must implement the "\DateTimeInterface".'); @@ -68,7 +66,7 @@ public function normalize(mixed $object, string $format = null, array $context = /** * {@inheritdoc} */ - public function supportsNormalization(mixed $data, string $format = null) + public function supportsNormalization(mixed $data, string $format = null): bool { return $data instanceof \DateTimeInterface; } @@ -77,10 +75,8 @@ public function supportsNormalization(mixed $data, string $format = null) * {@inheritdoc} * * @throws NotNormalizableValueException - * - * @return \DateTimeInterface */ - public function denormalize(mixed $data, string $type, string $format = null, array $context = []) + public function denormalize(mixed $data, string $type, string $format = null, array $context = []): \DateTimeInterface { $dateTimeFormat = $context[self::FORMAT_KEY] ?? null; $timezone = $this->getTimezone($context); @@ -111,7 +107,7 @@ public function denormalize(mixed $data, string $type, string $format = null, ar /** * {@inheritdoc} */ - public function supportsDenormalization(mixed $data, string $type, string $format = null) + public function supportsDenormalization(mixed $data, string $type, string $format = null): bool { return isset(self::SUPPORTED_TYPES[$type]); } diff --git a/Normalizer/DateTimeZoneNormalizer.php b/Normalizer/DateTimeZoneNormalizer.php index d86fd6894..a7d23eceb 100644 --- a/Normalizer/DateTimeZoneNormalizer.php +++ b/Normalizer/DateTimeZoneNormalizer.php @@ -25,10 +25,8 @@ class DateTimeZoneNormalizer implements NormalizerInterface, DenormalizerInterfa * {@inheritdoc} * * @throws InvalidArgumentException - * - * @return string */ - public function normalize(mixed $object, string $format = null, array $context = []) + public function normalize(mixed $object, string $format = null, array $context = []): string { if (!$object instanceof \DateTimeZone) { throw new InvalidArgumentException('The object must be an instance of "\DateTimeZone".'); @@ -40,7 +38,7 @@ public function normalize(mixed $object, string $format = null, array $context = /** * {@inheritdoc} */ - public function supportsNormalization(mixed $data, string $format = null) + public function supportsNormalization(mixed $data, string $format = null): bool { return $data instanceof \DateTimeZone; } @@ -49,10 +47,8 @@ public function supportsNormalization(mixed $data, string $format = null) * {@inheritdoc} * * @throws NotNormalizableValueException - * - * @return \DateTimeZone */ - public function denormalize(mixed $data, string $type, string $format = null, array $context = []) + public function denormalize(mixed $data, string $type, string $format = null, array $context = []): \DateTimeZone { if ('' === $data || null === $data) { throw new NotNormalizableValueException('The data is either an empty string or null, you should pass a string that can be parsed as a DateTimeZone.'); @@ -68,7 +64,7 @@ public function denormalize(mixed $data, string $type, string $format = null, ar /** * {@inheritdoc} */ - public function supportsDenormalization(mixed $data, string $type, string $format = null) + public function supportsDenormalization(mixed $data, string $type, string $format = null): bool { return \DateTimeZone::class === $type; } diff --git a/Normalizer/DenormalizerInterface.php b/Normalizer/DenormalizerInterface.php index 3f44f34c7..f3bc06ba7 100644 --- a/Normalizer/DenormalizerInterface.php +++ b/Normalizer/DenormalizerInterface.php @@ -32,8 +32,6 @@ interface DenormalizerInterface * @param string $format Format the given data was extracted from * @param array $context Options available to the denormalizer * - * @return mixed - * * @throws BadMethodCallException Occurs when the normalizer is not called in an expected context * @throws InvalidArgumentException Occurs when the arguments are not coherent or not supported * @throws UnexpectedValueException Occurs when the item cannot be hydrated with the given data @@ -42,7 +40,7 @@ interface DenormalizerInterface * @throws RuntimeException Occurs if the class cannot be instantiated * @throws ExceptionInterface Occurs for all the other cases of errors */ - public function denormalize(mixed $data, string $type, string $format = null, array $context = []); + public function denormalize(mixed $data, string $type, string $format = null, array $context = []): mixed; /** * Checks whether the given class is supported for denormalization by this normalizer. @@ -50,8 +48,6 @@ public function denormalize(mixed $data, string $type, string $format = null, ar * @param mixed $data Data to denormalize from * @param string $type The class to which the data should be denormalized * @param string $format The format being deserialized from - * - * @return bool */ - public function supportsDenormalization(mixed $data, string $type, string $format = null); + public function supportsDenormalization(mixed $data, string $type, string $format = null): bool; } diff --git a/Normalizer/GetSetMethodNormalizer.php b/Normalizer/GetSetMethodNormalizer.php index 16160e4a5..bb8b79c2e 100644 --- a/Normalizer/GetSetMethodNormalizer.php +++ b/Normalizer/GetSetMethodNormalizer.php @@ -39,7 +39,7 @@ class GetSetMethodNormalizer extends AbstractObjectNormalizer /** * {@inheritdoc} */ - public function supportsNormalization(mixed $data, string $format = null) + public function supportsNormalization(mixed $data, string $format = null): bool { return parent::supportsNormalization($data, $format) && $this->supports(\get_class($data)); } @@ -47,7 +47,7 @@ public function supportsNormalization(mixed $data, string $format = null) /** * {@inheritdoc} */ - public function supportsDenormalization(mixed $data, string $type, string $format = null) + public function supportsDenormalization(mixed $data, string $type, string $format = null): bool { return parent::supportsDenormalization($data, $type, $format) && $this->supports($type); } @@ -97,7 +97,7 @@ private function isGetMethod(\ReflectionMethod $method): bool /** * {@inheritdoc} */ - protected function extractAttributes(object $object, string $format = null, array $context = []) + protected function extractAttributes(object $object, string $format = null, array $context = []): array { $reflectionObject = new \ReflectionObject($object); $reflectionMethods = $reflectionObject->getMethods(\ReflectionMethod::IS_PUBLIC); @@ -121,7 +121,7 @@ protected function extractAttributes(object $object, string $format = null, arra /** * {@inheritdoc} */ - protected function getAttributeValue(object $object, string $attribute, string $format = null, array $context = []) + protected function getAttributeValue(object $object, string $attribute, string $format = null, array $context = []): mixed { $ucfirsted = ucfirst($attribute); diff --git a/Normalizer/JsonSerializableNormalizer.php b/Normalizer/JsonSerializableNormalizer.php index 4533ff738..2bf431911 100644 --- a/Normalizer/JsonSerializableNormalizer.php +++ b/Normalizer/JsonSerializableNormalizer.php @@ -24,7 +24,7 @@ class JsonSerializableNormalizer extends AbstractNormalizer /** * {@inheritdoc} */ - public function normalize(mixed $object, string $format = null, array $context = []) + public function normalize(mixed $object, string $format = null, array $context = []): array|string|int|float|bool|\ArrayObject|null { if ($this->isCircularReference($object, $context)) { return $this->handleCircularReference($object); @@ -44,7 +44,7 @@ public function normalize(mixed $object, string $format = null, array $context = /** * {@inheritdoc} */ - public function supportsNormalization(mixed $data, string $format = null) + public function supportsNormalization(mixed $data, string $format = null): bool { return $data instanceof \JsonSerializable; } @@ -52,7 +52,7 @@ public function supportsNormalization(mixed $data, string $format = null) /** * {@inheritdoc} */ - public function supportsDenormalization(mixed $data, string $type, string $format = null) + public function supportsDenormalization(mixed $data, string $type, string $format = null): bool { return false; } @@ -60,7 +60,7 @@ public function supportsDenormalization(mixed $data, string $type, string $forma /** * {@inheritdoc} */ - public function denormalize(mixed $data, string $type, string $format = null, array $context = []) + public function denormalize(mixed $data, string $type, string $format = null, array $context = []): mixed { throw new LogicException(sprintf('Cannot denormalize with "%s".', \JsonSerializable::class)); } diff --git a/Normalizer/NormalizableInterface.php b/Normalizer/NormalizableInterface.php index ce4af8be5..e518608eb 100644 --- a/Normalizer/NormalizableInterface.php +++ b/Normalizer/NormalizableInterface.php @@ -35,5 +35,5 @@ interface NormalizableInterface * * @return array|string|int|float|bool */ - public function normalize(NormalizerInterface $normalizer, string $format = null, array $context = []); + public function normalize(NormalizerInterface $normalizer, string $format = null, array $context = []): array|string|int|float|bool; } diff --git a/Normalizer/NormalizerInterface.php b/Normalizer/NormalizerInterface.php index 30eeafb47..419c58624 100644 --- a/Normalizer/NormalizerInterface.php +++ b/Normalizer/NormalizerInterface.php @@ -36,15 +36,13 @@ interface NormalizerInterface * @throws LogicException Occurs when the normalizer is not called in an expected context * @throws ExceptionInterface Occurs for all the other cases of errors */ - public function normalize(mixed $object, string $format = null, array $context = []); + public function normalize(mixed $object, string $format = null, array $context = []): array|string|int|float|bool|\ArrayObject|null; /** * Checks whether the given class is supported for normalization by this normalizer. * * @param mixed $data Data to normalize * @param string $format The format being (de-)serialized from or into - * - * @return bool */ - public function supportsNormalization(mixed $data, string $format = null); + public function supportsNormalization(mixed $data, string $format = null): bool; } diff --git a/Normalizer/ObjectNormalizer.php b/Normalizer/ObjectNormalizer.php index dd65b4c19..c032714f3 100644 --- a/Normalizer/ObjectNormalizer.php +++ b/Normalizer/ObjectNormalizer.php @@ -60,7 +60,7 @@ public function hasCacheableSupportsMethod(): bool /** * {@inheritdoc} */ - protected function extractAttributes(object $object, string $format = null, array $context = []) + protected function extractAttributes(object $object, string $format = null, array $context = []): array { if (\stdClass::class === \get_class($object)) { return array_keys((array) $object); @@ -136,7 +136,7 @@ protected function extractAttributes(object $object, string $format = null, arra /** * {@inheritdoc} */ - protected function getAttributeValue(object $object, string $attribute, string $format = null, array $context = []) + protected function getAttributeValue(object $object, string $attribute, string $format = null, array $context = []): mixed { $cacheKey = \get_class($object); if (!\array_key_exists($cacheKey, $this->discriminatorCache)) { @@ -165,7 +165,7 @@ protected function setAttributeValue(object $object, string $attribute, mixed $v /** * {@inheritdoc} */ - protected function getAllowedAttributes(string|object $classOrObject, array $context, bool $attributesAsString = false) + protected function getAllowedAttributes(string|object $classOrObject, array $context, bool $attributesAsString = false): array|bool { if (false === $allowedAttributes = parent::getAllowedAttributes($classOrObject, $context, $attributesAsString)) { return false; diff --git a/Normalizer/ProblemNormalizer.php b/Normalizer/ProblemNormalizer.php index 1c7bb755b..533b24e65 100644 --- a/Normalizer/ProblemNormalizer.php +++ b/Normalizer/ProblemNormalizer.php @@ -38,10 +38,8 @@ public function __construct(bool $debug = false, array $defaultContext = []) /** * {@inheritdoc} - * - * @return array */ - public function normalize(mixed $object, string $format = null, array $context = []) + public function normalize(mixed $object, string $format = null, array $context = []): array { if (!$object instanceof FlattenException) { throw new InvalidArgumentException(sprintf('The object must implement "%s".', FlattenException::class)); diff --git a/Normalizer/PropertyNormalizer.php b/Normalizer/PropertyNormalizer.php index ce210f075..dcfb6b3a1 100644 --- a/Normalizer/PropertyNormalizer.php +++ b/Normalizer/PropertyNormalizer.php @@ -33,7 +33,7 @@ class PropertyNormalizer extends AbstractObjectNormalizer /** * {@inheritdoc} */ - public function supportsNormalization(mixed $data, string $format = null) + public function supportsNormalization(mixed $data, string $format = null): bool { return parent::supportsNormalization($data, $format) && $this->supports(\get_class($data)); } @@ -41,7 +41,7 @@ public function supportsNormalization(mixed $data, string $format = null) /** * {@inheritdoc} */ - public function supportsDenormalization(mixed $data, string $type, string $format = null) + public function supportsDenormalization(mixed $data, string $type, string $format = null): bool { return parent::supportsDenormalization($data, $type, $format) && $this->supports($type); } @@ -76,7 +76,7 @@ private function supports(string $class): bool /** * {@inheritdoc} */ - protected function isAllowedAttribute(object|string $classOrObject, string $attribute, string $format = null, array $context = []) + protected function isAllowedAttribute(object|string $classOrObject, string $attribute, string $format = null, array $context = []): bool { if (!parent::isAllowedAttribute($classOrObject, $attribute, $format, $context)) { return false; @@ -97,7 +97,7 @@ protected function isAllowedAttribute(object|string $classOrObject, string $attr /** * {@inheritdoc} */ - protected function extractAttributes(object $object, string $format = null, array $context = []) + protected function extractAttributes(object $object, string $format = null, array $context = []): array { $reflectionObject = new \ReflectionObject($object); $attributes = []; @@ -126,7 +126,7 @@ protected function extractAttributes(object $object, string $format = null, arra /** * {@inheritdoc} */ - protected function getAttributeValue(object $object, string $attribute, string $format = null, array $context = []) + protected function getAttributeValue(object $object, string $attribute, string $format = null, array $context = []): mixed { try { $reflectionProperty = $this->getReflectionProperty($object, $attribute); diff --git a/Serializer.php b/Serializer.php index 97ebf6107..1e4529f8d 100644 --- a/Serializer.php +++ b/Serializer.php @@ -153,7 +153,7 @@ final public function deserialize(mixed $data, string $type, string $format, arr /** * {@inheritdoc} */ - public function normalize(mixed $data, string $format = null, array $context = []) + public function normalize(mixed $data, string $format = null, array $context = []): array|string|int|float|bool|\ArrayObject|null { // If a normalizer supports the given data, use it if ($normalizer = $this->getNormalizer($data, $format, $context)) { @@ -198,7 +198,7 @@ public function normalize(mixed $data, string $format = null, array $context = [ * * @throws NotNormalizableValueException */ - public function denormalize(mixed $data, string $type, string $format = null, array $context = []) + public function denormalize(mixed $data, string $type, string $format = null, array $context = []): mixed { $normalizer = $this->getDenormalizer($data, $type, $format, $context); @@ -225,7 +225,7 @@ public function denormalize(mixed $data, string $type, string $format = null, ar /** * {@inheritdoc} */ - public function supportsNormalization(mixed $data, string $format = null, array $context = []) + public function supportsNormalization(mixed $data, string $format = null, array $context = []): bool { return null !== $this->getNormalizer($data, $format, $context); } @@ -233,7 +233,7 @@ public function supportsNormalization(mixed $data, string $format = null, array /** * {@inheritdoc} */ - public function supportsDenormalization(mixed $data, string $type, string $format = null, array $context = []) + public function supportsDenormalization(mixed $data, string $type, string $format = null, array $context = []): bool { return isset(self::SCALAR_TYPES[$type]) || null !== $this->getDenormalizer($data, $type, $format, $context); } @@ -332,7 +332,7 @@ final public function decode(string $data, string $format, array $context = []): /** * {@inheritdoc} */ - public function supportsEncoding(string $format, array $context = []) + public function supportsEncoding(string $format, array $context = []): bool { return $this->encoder->supportsEncoding($format, $context); } @@ -340,7 +340,7 @@ public function supportsEncoding(string $format, array $context = []) /** * {@inheritdoc} */ - public function supportsDecoding(string $format, array $context = []) + public function supportsDecoding(string $format, array $context = []): bool { return $this->decoder->supportsDecoding($format, $context); } diff --git a/SerializerInterface.php b/SerializerInterface.php index 5064e8a82..d78e83d75 100644 --- a/SerializerInterface.php +++ b/SerializerInterface.php @@ -22,15 +22,11 @@ interface SerializerInterface * @param mixed $data Any data * @param string $format Format name * @param array $context Options normalizers/encoders have access to - * - * @return string */ - public function serialize(mixed $data, string $format, array $context = []); + public function serialize(mixed $data, string $format, array $context = []): string; /** * Deserializes data into the given type. - * - * @return mixed */ - public function deserialize(mixed $data, string $type, string $format, array $context = []); + public function deserialize(mixed $data, string $type, string $format, array $context = []): mixed; } diff --git a/Tests/Fixtures/AbstractNormalizerDummy.php b/Tests/Fixtures/AbstractNormalizerDummy.php index e5108ee20..5fb5ba3d3 100644 --- a/Tests/Fixtures/AbstractNormalizerDummy.php +++ b/Tests/Fixtures/AbstractNormalizerDummy.php @@ -11,7 +11,6 @@ namespace Symfony\Component\Serializer\Tests\Fixtures; -use Symfony\Component\Serializer\Mapping\AttributeMetadataInterface; use Symfony\Component\Serializer\Normalizer\AbstractNormalizer; /** diff --git a/Tests/SerializerTest.php b/Tests/SerializerTest.php index 78a038ec8..ab74b06ab 100644 --- a/Tests/SerializerTest.php +++ b/Tests/SerializerTest.php @@ -791,13 +791,15 @@ public function __construct(array $list) } } -class DummyList implements \Countable, \IteratorAggregate +class DummyList extends \ArrayObject { public $list; public function __construct(array $list) { $this->list = $list; + + $this->setFlags(\ArrayObject::STD_PROP_LIST); } public function count(): int From eefdb3d98778075f8cdc2690e2f7baddbc2d98d0 Mon Sep 17 00:00:00 2001 From: "Alexander M. Turek" Date: Tue, 24 Aug 2021 08:28:39 +0200 Subject: [PATCH 012/297] Fix merge Signed-off-by: Alexander M. Turek --- Tests/SerializerTest.php | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/Tests/SerializerTest.php b/Tests/SerializerTest.php index 8802349b3..c44cbf2ac 100644 --- a/Tests/SerializerTest.php +++ b/Tests/SerializerTest.php @@ -880,6 +880,26 @@ public function __construct(array $list) } } +class DummyListLegacy implements \Countable, \IteratorAggregate +{ + public $list; + + public function __construct(array $list) + { + $this->list = $list; + } + + public function count(): int + { + return \count($this->list); + } + + public function getIterator(): \Traversable + { + return new \ArrayIterator($this->list); + } +} + interface NormalizerAwareNormalizer extends NormalizerInterface, NormalizerAwareInterface { } From a40f8a711de1803de91036f9b7068ad9a27e73a4 Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Tue, 5 Oct 2021 17:32:15 +0200 Subject: [PATCH 013/297] Add type to final/internal public/protected properties --- Encoder/ChainDecoder.php | 4 ++-- Encoder/ChainEncoder.php | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Encoder/ChainDecoder.php b/Encoder/ChainDecoder.php index c2b4db4b9..3348e3e19 100644 --- a/Encoder/ChainDecoder.php +++ b/Encoder/ChainDecoder.php @@ -24,8 +24,8 @@ */ class ChainDecoder implements ContextAwareDecoderInterface { - protected $decoders = []; - protected $decoderByFormat = []; + private array $decoders = []; + private array $decoderByFormat = []; public function __construct(array $decoders = []) { diff --git a/Encoder/ChainEncoder.php b/Encoder/ChainEncoder.php index 0ca0a821a..514e30909 100644 --- a/Encoder/ChainEncoder.php +++ b/Encoder/ChainEncoder.php @@ -24,8 +24,8 @@ */ class ChainEncoder implements ContextAwareEncoderInterface { - protected $encoders = []; - protected $encoderByFormat = []; + private array $encoders = []; + private array $encoderByFormat = []; public function __construct(array $encoders = []) { From 13884b0d5d1914c884b40be68255db4794b2202c Mon Sep 17 00:00:00 2001 From: Christian Flothmann Date: Wed, 27 Oct 2021 16:37:50 +0200 Subject: [PATCH 014/297] use hard-coded values as tag names are not configurable anymore --- DependencyInjection/SerializerPass.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/DependencyInjection/SerializerPass.php b/DependencyInjection/SerializerPass.php index 0964488dc..58ade72fe 100644 --- a/DependencyInjection/SerializerPass.php +++ b/DependencyInjection/SerializerPass.php @@ -52,7 +52,7 @@ public function process(ContainerBuilder $container) } $defaultContext = $container->getParameter('serializer.default_context'); - foreach (array_keys(array_merge($container->findTaggedServiceIds($this->normalizerTag), $container->findTaggedServiceIds($this->encoderTag))) as $service) { + foreach (array_keys(array_merge($container->findTaggedServiceIds('serializer.normalizer'), $container->findTaggedServiceIds('serializer.encoder'))) as $service) { $definition = $container->getDefinition($service); $definition->setBindings(['array $defaultContext' => new BoundArgument($defaultContext, false)] + $definition->getBindings()); } From 88fcd18f2205ddb6583d2b0c6da0b1dc9dd076ba Mon Sep 17 00:00:00 2001 From: Christian Flothmann Date: Wed, 27 Oct 2021 12:20:15 +0200 Subject: [PATCH 015/297] remove no longer needed PHP version requirements from tests --- Tests/SerializerTest.php | 1 - 1 file changed, 1 deletion(-) diff --git a/Tests/SerializerTest.php b/Tests/SerializerTest.php index 52cb6e035..cdfa3c978 100644 --- a/Tests/SerializerTest.php +++ b/Tests/SerializerTest.php @@ -997,7 +997,6 @@ public function testCollectDenormalizationErrors2() $this->assertSame($expected, $exceptionsAsArray); } - /** @requires PHP 8.0 */ public function testCollectDenormalizationErrorsWithConstructor() { $json = '{"bool": "bool"}'; From 657ea71628d3aabf5f120d30318d5fa63eb45094 Mon Sep 17 00:00:00 2001 From: PierreRebeilleau Date: Tue, 23 Nov 2021 17:32:25 +0100 Subject: [PATCH 016/297] [Serializer] Remove some type hints for API Platform compatibility --- Normalizer/AbstractNormalizer.php | 10 +++++++--- Normalizer/AbstractObjectNormalizer.php | 16 +++++++++------- Normalizer/DenormalizerInterface.php | 8 ++++++-- Normalizer/NormalizerInterface.php | 6 ++++-- 4 files changed, 26 insertions(+), 14 deletions(-) diff --git a/Normalizer/AbstractNormalizer.php b/Normalizer/AbstractNormalizer.php index a118639df..b0a1f3218 100644 --- a/Normalizer/AbstractNormalizer.php +++ b/Normalizer/AbstractNormalizer.php @@ -222,7 +222,7 @@ protected function handleCircularReference(object $object, string $format = null * * @return string[]|AttributeMetadataInterface[]|bool */ - protected function getAllowedAttributes(string|object $classOrObject, array $context, bool $attributesAsString = false): array|bool + protected function getAllowedAttributes(string|object $classOrObject, array $context, bool $attributesAsString = false) { $allowExtraAttributes = $context[self::ALLOW_EXTRA_ATTRIBUTES] ?? $this->defaultContext[self::ALLOW_EXTRA_ATTRIBUTES]; if (!$this->classMetadataFactory) { @@ -269,8 +269,10 @@ protected function getGroups(array $context): array /** * Is this attribute allowed? + * + * @return bool */ - protected function isAllowedAttribute(object|string $classOrObject, string $attribute, string $format = null, array $context = []): bool + protected function isAllowedAttribute(object|string $classOrObject, string $attribute, string $format = null, array $context = []) { $ignoredAttributes = $context[self::IGNORED_ATTRIBUTES] ?? $this->defaultContext[self::IGNORED_ATTRIBUTES]; if (\in_array($attribute, $ignoredAttributes)) { @@ -316,10 +318,12 @@ protected function getConstructor(array &$data, string $class, array &$context, * is removed from the context before being returned to avoid side effects * when recursively normalizing an object graph. * + * @return object + * * @throws RuntimeException * @throws MissingConstructorArgumentsException */ - protected function instantiateObject(array &$data, string $class, array &$context, \ReflectionClass $reflectionClass, array|bool $allowedAttributes, string $format = null): object + protected function instantiateObject(array &$data, string $class, array &$context, \ReflectionClass $reflectionClass, array|bool $allowedAttributes, string $format = null) { if (null !== $object = $this->extractObjectToPopulate($class, $context, self::OBJECT_TO_POPULATE)) { unset($context[self::OBJECT_TO_POPULATE]); diff --git a/Normalizer/AbstractObjectNormalizer.php b/Normalizer/AbstractObjectNormalizer.php index 13a34ef45..a24121513 100644 --- a/Normalizer/AbstractObjectNormalizer.php +++ b/Normalizer/AbstractObjectNormalizer.php @@ -135,7 +135,7 @@ public function __construct(ClassMetadataFactoryInterface $classMetadataFactory /** * {@inheritdoc} */ - public function supportsNormalization(mixed $data, string $format = null): bool + public function supportsNormalization(mixed $data, string $format = null) { return \is_object($data) && !$data instanceof \Traversable; } @@ -143,7 +143,7 @@ public function supportsNormalization(mixed $data, string $format = null): bool /** * {@inheritdoc} */ - public function normalize(mixed $object, string $format = null, array $context = []): array|string|int|float|bool|\ArrayObject|null + public function normalize(mixed $object, string $format = null, array $context = []) { if (!isset($context['cache_key'])) { $context['cache_key'] = $this->getCacheKey($format, $context); @@ -276,7 +276,7 @@ private function getAttributeMetadata(object|string $objectOrClass, string $attr /** * {@inheritdoc} */ - protected function instantiateObject(array &$data, string $class, array &$context, \ReflectionClass $reflectionClass, array|bool $allowedAttributes, string $format = null): object + protected function instantiateObject(array &$data, string $class, array &$context, \ReflectionClass $reflectionClass, array|bool $allowedAttributes, string $format = null) { if ($this->classDiscriminatorResolver && $mapping = $this->classDiscriminatorResolver->getMappingForClass($class)) { if (!isset($data[$mapping->getTypeProperty()])) { @@ -338,17 +338,19 @@ protected function getAttributes(object $object, ?string $format, array $context * * @return string[] */ - abstract protected function extractAttributes(object $object, string $format = null, array $context = []): array; + abstract protected function extractAttributes(object $object, string $format = null, array $context = []); /** * Gets the attribute value. + * + * @return mixed */ - abstract protected function getAttributeValue(object $object, string $attribute, string $format = null, array $context = []): mixed; + abstract protected function getAttributeValue(object $object, string $attribute, string $format = null, array $context = []); /** * {@inheritdoc} */ - public function supportsDenormalization(mixed $data, string $type, string $format = null): bool + public function supportsDenormalization(mixed $data, string $type, string $format = null) { return class_exists($type) || (interface_exists($type, false) && $this->classDiscriminatorResolver && null !== $this->classDiscriminatorResolver->getMappingForClass($type)); } @@ -356,7 +358,7 @@ public function supportsDenormalization(mixed $data, string $type, string $forma /** * {@inheritdoc} */ - public function denormalize(mixed $data, string $type, string $format = null, array $context = []): mixed + public function denormalize(mixed $data, string $type, string $format = null, array $context = []) { if (!isset($context['cache_key'])) { $context['cache_key'] = $this->getCacheKey($format, $context); diff --git a/Normalizer/DenormalizerInterface.php b/Normalizer/DenormalizerInterface.php index 95131b84d..5e94400b8 100644 --- a/Normalizer/DenormalizerInterface.php +++ b/Normalizer/DenormalizerInterface.php @@ -34,6 +34,8 @@ interface DenormalizerInterface * @param string $format Format the given data was extracted from * @param array $context Options available to the denormalizer * + * @return mixed + * * @throws BadMethodCallException Occurs when the normalizer is not called in an expected context * @throws InvalidArgumentException Occurs when the arguments are not coherent or not supported * @throws UnexpectedValueException Occurs when the item cannot be hydrated with the given data @@ -42,7 +44,7 @@ interface DenormalizerInterface * @throws RuntimeException Occurs if the class cannot be instantiated * @throws ExceptionInterface Occurs for all the other cases of errors */ - public function denormalize(mixed $data, string $type, string $format = null, array $context = []): mixed; + public function denormalize(mixed $data, string $type, string $format = null, array $context = []); /** * Checks whether the given class is supported for denormalization by this normalizer. @@ -50,6 +52,8 @@ public function denormalize(mixed $data, string $type, string $format = null, ar * @param mixed $data Data to denormalize from * @param string $type The class to which the data should be denormalized * @param string $format The format being deserialized from + * + * @return bool */ - public function supportsDenormalization(mixed $data, string $type, string $format = null): bool; + public function supportsDenormalization(mixed $data, string $type, string $format = null); } diff --git a/Normalizer/NormalizerInterface.php b/Normalizer/NormalizerInterface.php index 419c58624..30eeafb47 100644 --- a/Normalizer/NormalizerInterface.php +++ b/Normalizer/NormalizerInterface.php @@ -36,13 +36,15 @@ interface NormalizerInterface * @throws LogicException Occurs when the normalizer is not called in an expected context * @throws ExceptionInterface Occurs for all the other cases of errors */ - public function normalize(mixed $object, string $format = null, array $context = []): array|string|int|float|bool|\ArrayObject|null; + public function normalize(mixed $object, string $format = null, array $context = []); /** * Checks whether the given class is supported for normalization by this normalizer. * * @param mixed $data Data to normalize * @param string $format The format being (de-)serialized from or into + * + * @return bool */ - public function supportsNormalization(mixed $data, string $format = null): bool; + public function supportsNormalization(mixed $data, string $format = null); } From 60637437ca5bfa519e4085e9ea28ead456f9d85e Mon Sep 17 00:00:00 2001 From: PierreRebeilleau Date: Wed, 1 Dec 2021 17:18:41 +0100 Subject: [PATCH 017/297] [Serializer] Remove DecoderInterface type hint for API Platform compatibility --- Encoder/DecoderInterface.php | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/Encoder/DecoderInterface.php b/Encoder/DecoderInterface.php index 09ec9434d..84a84ad1f 100644 --- a/Encoder/DecoderInterface.php +++ b/Encoder/DecoderInterface.php @@ -30,14 +30,18 @@ interface DecoderInterface * are encouraged to document which formats they support in a non-inherited * phpdoc comment. * + * @return mixed + * * @throws UnexpectedValueException */ - public function decode(string $data, string $format, array $context = []): mixed; + public function decode(string $data, string $format, array $context = []); /** * Checks whether the deserializer can decode from given format. * * @param string $format Format name + * + * @return bool */ - public function supportsDecoding(string $format): bool; + public function supportsDecoding(string $format); } From 7cb92883011018236c736dce8b284ee12bdda0b9 Mon Sep 17 00:00:00 2001 From: "Alexander M. Turek" Date: Mon, 13 Dec 2021 16:44:47 +0100 Subject: [PATCH 018/297] Make use of the nullsafe operator --- Normalizer/AbstractObjectNormalizer.php | 2 +- Normalizer/ObjectNormalizer.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Normalizer/AbstractObjectNormalizer.php b/Normalizer/AbstractObjectNormalizer.php index a24121513..3e1e7edc8 100644 --- a/Normalizer/AbstractObjectNormalizer.php +++ b/Normalizer/AbstractObjectNormalizer.php @@ -169,7 +169,7 @@ public function normalize(mixed $object, string $format = null, array $context = $stack = []; $attributes = $this->getAttributes($object, $format, $context); $class = $this->objectClassResolver ? ($this->objectClassResolver)($object) : \get_class($object); - $attributesMetadata = $this->classMetadataFactory ? $this->classMetadataFactory->getMetadataFor($class)->getAttributesMetadata() : null; + $attributesMetadata = $this->classMetadataFactory?->getMetadataFor($class)->getAttributesMetadata(); if (isset($context[self::MAX_DEPTH_HANDLER])) { $maxDepthHandler = $context[self::MAX_DEPTH_HANDLER]; if (!\is_callable($maxDepthHandler)) { diff --git a/Normalizer/ObjectNormalizer.php b/Normalizer/ObjectNormalizer.php index 527d26931..78a456887 100644 --- a/Normalizer/ObjectNormalizer.php +++ b/Normalizer/ObjectNormalizer.php @@ -133,7 +133,7 @@ protected function getAttributeValue(object $object, string $attribute, string $ $this->discriminatorCache[$cacheKey] = null; if (null !== $this->classDiscriminatorResolver) { $mapping = $this->classDiscriminatorResolver->getMappingForMappedObject($object); - $this->discriminatorCache[$cacheKey] = null === $mapping ? null : $mapping->getTypeProperty(); + $this->discriminatorCache[$cacheKey] = $mapping?->getTypeProperty(); } } From 2aa7e940e1d76204f77c3173a3ff108e5f8b38b9 Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Thu, 16 Dec 2021 23:13:01 +0100 Subject: [PATCH 019/297] [6.0] cs fixes --- Normalizer/NormalizableInterface.php | 2 -- 1 file changed, 2 deletions(-) diff --git a/Normalizer/NormalizableInterface.php b/Normalizer/NormalizableInterface.php index e518608eb..d449a4306 100644 --- a/Normalizer/NormalizableInterface.php +++ b/Normalizer/NormalizableInterface.php @@ -32,8 +32,6 @@ interface NormalizableInterface * @param string|null $format The format is optionally given to be able to normalize differently * based on different output formats * @param array $context Options for normalizing this object - * - * @return array|string|int|float|bool */ public function normalize(NormalizerInterface $normalizer, string $format = null, array $context = []): array|string|int|float|bool; } From b2f3d8a8227a131423fd0cd049fb94127b730886 Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Thu, 16 Dec 2021 11:11:51 +0100 Subject: [PATCH 020/297] Add more nullsafe operators --- Normalizer/ObjectToPopulateTrait.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Normalizer/ObjectToPopulateTrait.php b/Normalizer/ObjectToPopulateTrait.php index 6a0d324ce..23be4c6af 100644 --- a/Normalizer/ObjectToPopulateTrait.php +++ b/Normalizer/ObjectToPopulateTrait.php @@ -23,7 +23,7 @@ trait ObjectToPopulateTrait */ protected function extractObjectToPopulate(string $class, array $context, string $key = null): ?object { - $key = $key ?? AbstractNormalizer::OBJECT_TO_POPULATE; + $key ??= AbstractNormalizer::OBJECT_TO_POPULATE; if (isset($context[$key]) && \is_object($context[$key]) && $context[$key] instanceof $class) { return $context[$key]; From 7314810e3cf0635786d3e9e1e5068e436a1e6be4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gr=C3=A9goire=20Pineau?= Date: Fri, 24 Dec 2021 14:22:20 +0100 Subject: [PATCH 021/297] [Serializer] Give more hints when an attribute is not correctly used --- Mapping/Loader/AnnotationLoader.php | 16 ++++++++++++- .../Fixtures/Attributes/BadAttributeDummy.php | 23 +++++++++++++++++++ Tests/Mapping/Loader/AnnotationLoaderTest.php | 5 +--- .../AnnotationLoaderWithAttributesTest.php | 12 ++++++++++ 4 files changed, 51 insertions(+), 5 deletions(-) create mode 100644 Tests/Fixtures/Attributes/BadAttributeDummy.php diff --git a/Mapping/Loader/AnnotationLoader.php b/Mapping/Loader/AnnotationLoader.php index da0bd4800..c20f1d6fc 100644 --- a/Mapping/Loader/AnnotationLoader.php +++ b/Mapping/Loader/AnnotationLoader.php @@ -157,7 +157,21 @@ public function loadAnnotations(object $reflector): iterable { foreach ($reflector->getAttributes() as $attribute) { if ($this->isKnownAttribute($attribute->getName())) { - yield $attribute->newInstance(); + try { + yield $attribute->newInstance(); + } catch (\Error $e) { + if ($e::class !== \Error::class) { + throw $e; + } + $on = match (true) { + $reflector instanceof \ReflectionClass => ' on class '.$reflector->name, + $reflector instanceof \ReflectionMethod => sprintf(' on "%s::%s()"', $reflector->getDeclaringClass()->name, $reflector->name), + $reflector instanceof \ReflectionProperty => sprintf(' on "%s::$%s"', $reflector->getDeclaringClass()->name, $reflector->name), + default => '', + }; + + throw new MappingException(sprintf('Could not instantiate attribute "%s"%s.', $attribute->getName(), $on), 0, $e); + } } } diff --git a/Tests/Fixtures/Attributes/BadAttributeDummy.php b/Tests/Fixtures/Attributes/BadAttributeDummy.php new file mode 100644 index 000000000..a6bd82915 --- /dev/null +++ b/Tests/Fixtures/Attributes/BadAttributeDummy.php @@ -0,0 +1,23 @@ + + * + * 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\Attributes; + +use Symfony\Component\Serializer\Annotation\Groups; + +class BadAttributeDummy extends ContextDummyParent +{ + #[Groups(['bar'])] + #[Groups(['foo'])] + public function myMethod() + { + } +} diff --git a/Tests/Mapping/Loader/AnnotationLoaderTest.php b/Tests/Mapping/Loader/AnnotationLoaderTest.php index a135bfdaa..9245e1dcd 100644 --- a/Tests/Mapping/Loader/AnnotationLoaderTest.php +++ b/Tests/Mapping/Loader/AnnotationLoaderTest.php @@ -28,10 +28,7 @@ abstract class AnnotationLoaderTest extends TestCase { use ContextMappingTestTrait; - /** - * @var AnnotationLoader - */ - private $loader; + protected AnnotationLoader $loader; protected function setUp(): void { diff --git a/Tests/Mapping/Loader/AnnotationLoaderWithAttributesTest.php b/Tests/Mapping/Loader/AnnotationLoaderWithAttributesTest.php index b0db50a9a..0983620b8 100644 --- a/Tests/Mapping/Loader/AnnotationLoaderWithAttributesTest.php +++ b/Tests/Mapping/Loader/AnnotationLoaderWithAttributesTest.php @@ -11,6 +11,8 @@ namespace Symfony\Component\Serializer\Tests\Mapping\Loader; +use Symfony\Component\Serializer\Exception\MappingException; +use Symfony\Component\Serializer\Mapping\ClassMetadata; use Symfony\Component\Serializer\Mapping\Loader\AnnotationLoader; class AnnotationLoaderWithAttributesTest extends AnnotationLoaderTest @@ -24,4 +26,14 @@ protected function getNamespace(): string { return 'Symfony\Component\Serializer\Tests\Fixtures\Attributes'; } + + public function testLoadWithInvalidAttribute() + { + $this->expectException(MappingException::class); + $this->expectExceptionMessage('Could not instantiate attribute "Symfony\Component\Serializer\Annotation\Groups" on "Symfony\Component\Serializer\Tests\Fixtures\Attributes\BadAttributeDummy::myMethod()".'); + + $classMetadata = new ClassMetadata($this->getNamespace().'\BadAttributeDummy'); + + $this->loader->loadClassMetadata($classMetadata); + } } From 91dac2d8f3df4b85d41850edad536f6df8f750a5 Mon Sep 17 00:00:00 2001 From: Mathias Arlaud Date: Fri, 7 Jan 2022 12:40:44 +0100 Subject: [PATCH 022/297] Merge context aware interfaces into regular ones --- CHANGELOG.md | 8 ++++++++ Encoder/ContextAwareDecoderInterface.php | 2 ++ Encoder/ContextAwareEncoderInterface.php | 2 ++ Encoder/CsvEncoder.php | 8 ++++++-- Encoder/DecoderInterface.php | 3 ++- Encoder/EncoderInterface.php | 5 +++-- Encoder/JsonDecode.php | 4 +++- Encoder/JsonEncode.php | 4 +++- Encoder/JsonEncoder.php | 8 ++++++-- Encoder/XmlEncoder.php | 8 ++++++-- Encoder/YamlEncoder.php | 8 ++++++-- Normalizer/AbstractObjectNormalizer.php | 8 ++++++-- Normalizer/BackedEnumNormalizer.php | 4 ++-- Normalizer/ConstraintViolationListNormalizer.php | 4 +++- Normalizer/ContextAwareDenormalizerInterface.php | 2 ++ Normalizer/ContextAwareNormalizerInterface.php | 2 ++ Normalizer/CustomNormalizer.php | 16 +++++++++------- Normalizer/DataUriNormalizer.php | 8 ++++++-- Normalizer/DateIntervalNormalizer.php | 8 ++++++-- Normalizer/DateTimeNormalizer.php | 8 ++++++-- Normalizer/DateTimeZoneNormalizer.php | 8 ++++++-- Normalizer/DenormalizerInterface.php | 9 +++++---- Normalizer/FormErrorNormalizer.php | 2 +- Normalizer/GetSetMethodNormalizer.php | 8 ++++++-- Normalizer/JsonSerializableNormalizer.php | 8 ++++++-- Normalizer/MimeMessageNormalizer.php | 4 ++-- Normalizer/NormalizerInterface.php | 7 ++++--- Normalizer/ProblemNormalizer.php | 4 +++- Normalizer/PropertyNormalizer.php | 8 ++++++-- Normalizer/UidNormalizer.php | 4 ++-- Tests/Encoder/ChainEncoderTest.php | 2 +- Tests/Fixtures/AbstractNormalizerDummy.php | 4 ++-- .../Normalizer/AbstractObjectNormalizerTest.php | 2 +- Tests/Normalizer/TestDenormalizer.php | 2 +- Tests/Normalizer/TestNormalizer.php | 2 +- 35 files changed, 136 insertions(+), 58 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c2bbd4b6c..363c3c88c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,14 @@ CHANGELOG ========= +6.1 +--- + + * Deprecate `ContextAwareNormalizerInterface`, use `NormalizerInterface` instead + * Deprecate `ContextAwareDenormalizerInterface`, use `DenormalizerInterface` instead + * Deprecate `ContextAwareEncoderInterface`, use `EncoderInterface` instead + * Deprecate `ContextAwareDecoderInterface`, use `DecoderInterface` instead + 6.0 --- diff --git a/Encoder/ContextAwareDecoderInterface.php b/Encoder/ContextAwareDecoderInterface.php index 6ac2e38cc..910b26bac 100644 --- a/Encoder/ContextAwareDecoderInterface.php +++ b/Encoder/ContextAwareDecoderInterface.php @@ -15,6 +15,8 @@ * Adds the support of an extra $context parameter for the supportsDecoding method. * * @author Kévin Dunglas + * + * @deprecated since symfony/serializer 6.1, use DecoderInterface instead */ interface ContextAwareDecoderInterface extends DecoderInterface { diff --git a/Encoder/ContextAwareEncoderInterface.php b/Encoder/ContextAwareEncoderInterface.php index 832b600ee..f828f87a4 100644 --- a/Encoder/ContextAwareEncoderInterface.php +++ b/Encoder/ContextAwareEncoderInterface.php @@ -15,6 +15,8 @@ * Adds the support of an extra $context parameter for the supportsEncoding method. * * @author Kévin Dunglas + * + * @deprecated since symfony/serializer 6.1, use EncoderInterface instead */ interface ContextAwareEncoderInterface extends EncoderInterface { diff --git a/Encoder/CsvEncoder.php b/Encoder/CsvEncoder.php index cee61fa03..8a8e47fb8 100644 --- a/Encoder/CsvEncoder.php +++ b/Encoder/CsvEncoder.php @@ -123,8 +123,10 @@ public function encode(mixed $data, string $format, array $context = []): string /** * {@inheritdoc} + * + * @param array $context */ - public function supportsEncoding(string $format): bool + public function supportsEncoding(string $format /*, array $context = [] */): bool { return self::FORMAT === $format; } @@ -209,8 +211,10 @@ public function decode(string $data, string $format, array $context = []): mixed /** * {@inheritdoc} + * + * @param array $context */ - public function supportsDecoding(string $format): bool + public function supportsDecoding(string $format /*, array $context = [] */): bool { return self::FORMAT === $format; } diff --git a/Encoder/DecoderInterface.php b/Encoder/DecoderInterface.php index 84a84ad1f..f38069e47 100644 --- a/Encoder/DecoderInterface.php +++ b/Encoder/DecoderInterface.php @@ -40,8 +40,9 @@ public function decode(string $data, string $format, array $context = []); * Checks whether the deserializer can decode from given format. * * @param string $format Format name + * @param array $context Options that decoders have access to * * @return bool */ - public function supportsDecoding(string $format); + public function supportsDecoding(string $format /*, array $context = [] */); } diff --git a/Encoder/EncoderInterface.php b/Encoder/EncoderInterface.php index e0f303b1e..22da956d2 100644 --- a/Encoder/EncoderInterface.php +++ b/Encoder/EncoderInterface.php @@ -32,7 +32,8 @@ public function encode(mixed $data, string $format, array $context = []): string /** * Checks whether the serializer can encode to given format. * - * @param string $format Format name + * @param string $format Format name + * @param array $context Options that normalizers/encoders have access to */ - public function supportsEncoding(string $format): bool; + public function supportsEncoding(string $format /*, array $context = [] */): bool; } diff --git a/Encoder/JsonDecode.php b/Encoder/JsonDecode.php index ad094afac..f0f94f6d7 100644 --- a/Encoder/JsonDecode.php +++ b/Encoder/JsonDecode.php @@ -95,8 +95,10 @@ public function decode(string $data, string $format, array $context = []): mixed /** * {@inheritdoc} + * + * @param array $context */ - public function supportsDecoding(string $format): bool + public function supportsDecoding(string $format /*, array $context = [] */): bool { return JsonEncoder::FORMAT === $format; } diff --git a/Encoder/JsonEncode.php b/Encoder/JsonEncode.php index 23d0fdd96..9a0a9393b 100644 --- a/Encoder/JsonEncode.php +++ b/Encoder/JsonEncode.php @@ -57,8 +57,10 @@ public function encode(mixed $data, string $format, array $context = []): string /** * {@inheritdoc} + * + * @param array $context */ - public function supportsEncoding(string $format): bool + public function supportsEncoding(string $format /*, array $context = [] */): bool { return JsonEncoder::FORMAT === $format; } diff --git a/Encoder/JsonEncoder.php b/Encoder/JsonEncoder.php index d17ef0492..2ce119bcb 100644 --- a/Encoder/JsonEncoder.php +++ b/Encoder/JsonEncoder.php @@ -47,16 +47,20 @@ public function decode(string $data, string $format, array $context = []): mixed /** * {@inheritdoc} + * + * @param array $context */ - public function supportsEncoding(string $format): bool + public function supportsEncoding(string $format /*, array $context = [] */): bool { return self::FORMAT === $format; } /** * {@inheritdoc} + * + * @param array $context */ - public function supportsDecoding(string $format): bool + public function supportsDecoding(string $format /*, array $context = [] */): bool { return self::FORMAT === $format; } diff --git a/Encoder/XmlEncoder.php b/Encoder/XmlEncoder.php index 44bfe8630..e91a2cb30 100644 --- a/Encoder/XmlEncoder.php +++ b/Encoder/XmlEncoder.php @@ -174,16 +174,20 @@ public function decode(string $data, string $format, array $context = []): mixed /** * {@inheritdoc} + * + * @param array $context */ - public function supportsEncoding(string $format): bool + public function supportsEncoding(string $format /*, array $context = [] */): bool { return self::FORMAT === $format; } /** * {@inheritdoc} + * + * @param array $context */ - public function supportsDecoding(string $format): bool + public function supportsDecoding(string $format /*, array $context = [] */): bool { return self::FORMAT === $format; } diff --git a/Encoder/YamlEncoder.php b/Encoder/YamlEncoder.php index 64fefee0e..990d0039c 100644 --- a/Encoder/YamlEncoder.php +++ b/Encoder/YamlEncoder.php @@ -67,8 +67,10 @@ public function encode(mixed $data, string $format, array $context = []): string /** * {@inheritdoc} + * + * @param array $context */ - public function supportsEncoding(string $format): bool + public function supportsEncoding(string $format /*, array $context = [] */): bool { return self::FORMAT === $format || self::ALTERNATIVE_FORMAT === $format; } @@ -85,8 +87,10 @@ public function decode(string $data, string $format, array $context = []): mixed /** * {@inheritdoc} + * + * @param array $context */ - public function supportsDecoding(string $format): bool + public function supportsDecoding(string $format /*, array $context = [] */): bool { return self::FORMAT === $format || self::ALTERNATIVE_FORMAT === $format; } diff --git a/Normalizer/AbstractObjectNormalizer.php b/Normalizer/AbstractObjectNormalizer.php index 3e1e7edc8..a8943113c 100644 --- a/Normalizer/AbstractObjectNormalizer.php +++ b/Normalizer/AbstractObjectNormalizer.php @@ -134,8 +134,10 @@ public function __construct(ClassMetadataFactoryInterface $classMetadataFactory /** * {@inheritdoc} + * + * @param array $context */ - public function supportsNormalization(mixed $data, string $format = null) + public function supportsNormalization(mixed $data, string $format = null /*, array $context = [] */) { return \is_object($data) && !$data instanceof \Traversable; } @@ -349,8 +351,10 @@ abstract protected function getAttributeValue(object $object, string $attribute, /** * {@inheritdoc} + * + * @param array $context */ - public function supportsDenormalization(mixed $data, string $type, string $format = null) + public function supportsDenormalization(mixed $data, string $type, string $format = null /*, array $context = [] */) { return class_exists($type) || (interface_exists($type, false) && $this->classDiscriminatorResolver && null !== $this->classDiscriminatorResolver->getMappingForClass($type)); } diff --git a/Normalizer/BackedEnumNormalizer.php b/Normalizer/BackedEnumNormalizer.php index ba17cae4a..859a09362 100644 --- a/Normalizer/BackedEnumNormalizer.php +++ b/Normalizer/BackedEnumNormalizer.php @@ -37,7 +37,7 @@ public function normalize($object, $format = null, array $context = []): int|str /** * {@inheritdoc} */ - public function supportsNormalization($data, $format = null): bool + public function supportsNormalization($data, $format = null, array $context = []): bool { return $data instanceof \BackedEnum; } @@ -67,7 +67,7 @@ public function denormalize($data, $type, $format = null, array $context = []): /** * {@inheritdoc} */ - public function supportsDenormalization($data, $type, $format = null): bool + public function supportsDenormalization($data, $type, $format = null, array $context = []): bool { return is_subclass_of($type, \BackedEnum::class); } diff --git a/Normalizer/ConstraintViolationListNormalizer.php b/Normalizer/ConstraintViolationListNormalizer.php index fcedfa531..2ac3c3681 100644 --- a/Normalizer/ConstraintViolationListNormalizer.php +++ b/Normalizer/ConstraintViolationListNormalizer.php @@ -106,8 +106,10 @@ public function normalize(mixed $object, string $format = null, array $context = /** * {@inheritdoc} + * + * @param array $context */ - public function supportsNormalization(mixed $data, string $format = null): bool + public function supportsNormalization(mixed $data, string $format = null /*, array $context = [] */): bool { return $data instanceof ConstraintViolationListInterface; } diff --git a/Normalizer/ContextAwareDenormalizerInterface.php b/Normalizer/ContextAwareDenormalizerInterface.php index 991db4470..38c07a268 100644 --- a/Normalizer/ContextAwareDenormalizerInterface.php +++ b/Normalizer/ContextAwareDenormalizerInterface.php @@ -15,6 +15,8 @@ * Adds the support of an extra $context parameter for the supportsDenormalization method. * * @author Kévin Dunglas + * + * @deprecated since symfony/serializer 6.1, use DenormalizerInterface instead */ interface ContextAwareDenormalizerInterface extends DenormalizerInterface { diff --git a/Normalizer/ContextAwareNormalizerInterface.php b/Normalizer/ContextAwareNormalizerInterface.php index eb28a7048..6f85225bd 100644 --- a/Normalizer/ContextAwareNormalizerInterface.php +++ b/Normalizer/ContextAwareNormalizerInterface.php @@ -15,6 +15,8 @@ * Adds the support of an extra $context parameter for the supportsNormalization method. * * @author Kévin Dunglas + * + * @deprecated since symfony/serializer 6.1, use NormalizerInterface instead */ interface ContextAwareNormalizerInterface extends NormalizerInterface { diff --git a/Normalizer/CustomNormalizer.php b/Normalizer/CustomNormalizer.php index e9e6f3d1a..d12361d50 100644 --- a/Normalizer/CustomNormalizer.php +++ b/Normalizer/CustomNormalizer.php @@ -44,10 +44,11 @@ public function denormalize(mixed $data, string $type, string $format = null, ar /** * Checks if the given class implements the NormalizableInterface. * - * @param mixed $data Data to normalize - * @param string $format The format being (de-)serialized from or into + * @param mixed $data Data to normalize + * @param string $format The format being (de-)serialized from or into + * @param array $context */ - public function supportsNormalization(mixed $data, string $format = null): bool + public function supportsNormalization(mixed $data, string $format = null /*, array $context = [] */): bool { return $data instanceof NormalizableInterface; } @@ -55,11 +56,12 @@ public function supportsNormalization(mixed $data, string $format = null): bool /** * Checks if the given class implements the DenormalizableInterface. * - * @param mixed $data Data to denormalize from - * @param string $type The class to which the data should be denormalized - * @param string $format The format being deserialized from + * @param mixed $data Data to denormalize from + * @param string $type The class to which the data should be denormalized + * @param string $format The format being deserialized from + * @param array $context */ - public function supportsDenormalization(mixed $data, string $type, string $format = null): bool + public function supportsDenormalization(mixed $data, string $type, string $format = null /*, array $context = [] */): bool { return is_subclass_of($type, DenormalizableInterface::class); } diff --git a/Normalizer/DataUriNormalizer.php b/Normalizer/DataUriNormalizer.php index 93ad3d905..675b9a13f 100644 --- a/Normalizer/DataUriNormalizer.php +++ b/Normalizer/DataUriNormalizer.php @@ -73,8 +73,10 @@ public function normalize(mixed $object, string $format = null, array $context = /** * {@inheritdoc} + * + * @param array $context */ - public function supportsNormalization(mixed $data, string $format = null): bool + public function supportsNormalization(mixed $data, string $format = null /*, array $context = [] */): bool { return $data instanceof \SplFileInfo; } @@ -117,8 +119,10 @@ public function denormalize(mixed $data, string $type, string $format = null, ar /** * {@inheritdoc} + * + * @param array $context */ - public function supportsDenormalization(mixed $data, string $type, string $format = null): bool + public function supportsDenormalization(mixed $data, string $type, string $format = null /*, array $context = [] */): bool { return isset(self::SUPPORTED_TYPES[$type]); } diff --git a/Normalizer/DateIntervalNormalizer.php b/Normalizer/DateIntervalNormalizer.php index db1117500..9a7aa0496 100644 --- a/Normalizer/DateIntervalNormalizer.php +++ b/Normalizer/DateIntervalNormalizer.php @@ -49,8 +49,10 @@ public function normalize(mixed $object, string $format = null, array $context = /** * {@inheritdoc} + * + * @param array $context */ - public function supportsNormalization(mixed $data, string $format = null): bool + public function supportsNormalization(mixed $data, string $format = null /*, array $context = [] */): bool { return $data instanceof \DateInterval; } @@ -117,8 +119,10 @@ public function denormalize(mixed $data, string $type, string $format = null, ar /** * {@inheritdoc} + * + * @param array $context */ - public function supportsDenormalization(mixed $data, string $type, string $format = null): bool + public function supportsDenormalization(mixed $data, string $type, string $format = null /*, array $context = [] */): bool { return \DateInterval::class === $type; } diff --git a/Normalizer/DateTimeNormalizer.php b/Normalizer/DateTimeNormalizer.php index da4f503eb..ea7e30f9e 100644 --- a/Normalizer/DateTimeNormalizer.php +++ b/Normalizer/DateTimeNormalizer.php @@ -71,8 +71,10 @@ public function normalize(mixed $object, string $format = null, array $context = /** * {@inheritdoc} + * + * @param array $context */ - public function supportsNormalization(mixed $data, string $format = null): bool + public function supportsNormalization(mixed $data, string $format = null /*, array $context = [] */): bool { return $data instanceof \DateTimeInterface; } @@ -112,8 +114,10 @@ public function denormalize(mixed $data, string $type, string $format = null, ar /** * {@inheritdoc} + * + * @param array $context */ - public function supportsDenormalization(mixed $data, string $type, string $format = null): bool + public function supportsDenormalization(mixed $data, string $type, string $format = null /*, array $context = [] */): bool { return isset(self::SUPPORTED_TYPES[$type]); } diff --git a/Normalizer/DateTimeZoneNormalizer.php b/Normalizer/DateTimeZoneNormalizer.php index 67ff3d92b..89adcb56f 100644 --- a/Normalizer/DateTimeZoneNormalizer.php +++ b/Normalizer/DateTimeZoneNormalizer.php @@ -38,8 +38,10 @@ public function normalize(mixed $object, string $format = null, array $context = /** * {@inheritdoc} + * + * @param array $context */ - public function supportsNormalization(mixed $data, string $format = null): bool + public function supportsNormalization(mixed $data, string $format = null /*, array $context = [] */): bool { return $data instanceof \DateTimeZone; } @@ -64,8 +66,10 @@ public function denormalize(mixed $data, string $type, string $format = null, ar /** * {@inheritdoc} + * + * @param array $context */ - public function supportsDenormalization(mixed $data, string $type, string $format = null): bool + public function supportsDenormalization(mixed $data, string $type, string $format = null /*, array $context = [] */): bool { return \DateTimeZone::class === $type; } diff --git a/Normalizer/DenormalizerInterface.php b/Normalizer/DenormalizerInterface.php index 5e94400b8..1c708738a 100644 --- a/Normalizer/DenormalizerInterface.php +++ b/Normalizer/DenormalizerInterface.php @@ -49,11 +49,12 @@ public function denormalize(mixed $data, string $type, string $format = null, ar /** * Checks whether the given class is supported for denormalization by this normalizer. * - * @param mixed $data Data to denormalize from - * @param string $type The class to which the data should be denormalized - * @param string $format The format being deserialized from + * @param mixed $data Data to denormalize from + * @param string $type The class to which the data should be denormalized + * @param string $format The format being deserialized from + * @param array $context Options available to the denormalizer * * @return bool */ - public function supportsDenormalization(mixed $data, string $type, string $format = null); + public function supportsDenormalization(mixed $data, string $type, string $format = null /*, array $context = [] */); } diff --git a/Normalizer/FormErrorNormalizer.php b/Normalizer/FormErrorNormalizer.php index 8bd8a845f..0ffa9f072 100644 --- a/Normalizer/FormErrorNormalizer.php +++ b/Normalizer/FormErrorNormalizer.php @@ -44,7 +44,7 @@ public function normalize(mixed $object, string $format = null, array $context = /** * {@inheritdoc} */ - public function supportsNormalization(mixed $data, string $format = null): bool + public function supportsNormalization(mixed $data, string $format = null, array $context = []): bool { return $data instanceof FormInterface && $data->isSubmitted() && !$data->isValid(); } diff --git a/Normalizer/GetSetMethodNormalizer.php b/Normalizer/GetSetMethodNormalizer.php index bb8b79c2e..7e42144f6 100644 --- a/Normalizer/GetSetMethodNormalizer.php +++ b/Normalizer/GetSetMethodNormalizer.php @@ -38,16 +38,20 @@ class GetSetMethodNormalizer extends AbstractObjectNormalizer /** * {@inheritdoc} + * + * @param array $context */ - public function supportsNormalization(mixed $data, string $format = null): bool + public function supportsNormalization(mixed $data, string $format = null /*, array $context = [] */): bool { return parent::supportsNormalization($data, $format) && $this->supports(\get_class($data)); } /** * {@inheritdoc} + * + * @param array $context */ - public function supportsDenormalization(mixed $data, string $type, string $format = null): bool + public function supportsDenormalization(mixed $data, string $type, string $format = null /*, array $context = [] */): bool { return parent::supportsDenormalization($data, $type, $format) && $this->supports($type); } diff --git a/Normalizer/JsonSerializableNormalizer.php b/Normalizer/JsonSerializableNormalizer.php index 2bf431911..5560ea916 100644 --- a/Normalizer/JsonSerializableNormalizer.php +++ b/Normalizer/JsonSerializableNormalizer.php @@ -43,16 +43,20 @@ public function normalize(mixed $object, string $format = null, array $context = /** * {@inheritdoc} + * + * @param array $context */ - public function supportsNormalization(mixed $data, string $format = null): bool + public function supportsNormalization(mixed $data, string $format = null /*, array $context = [] */): bool { return $data instanceof \JsonSerializable; } /** * {@inheritdoc} + * + * @param array $context */ - public function supportsDenormalization(mixed $data, string $type, string $format = null): bool + public function supportsDenormalization(mixed $data, string $type, string $format = null /*, array $context = [] */): bool { return false; } diff --git a/Normalizer/MimeMessageNormalizer.php b/Normalizer/MimeMessageNormalizer.php index f421d99e1..7c195bf30 100644 --- a/Normalizer/MimeMessageNormalizer.php +++ b/Normalizer/MimeMessageNormalizer.php @@ -100,7 +100,7 @@ public function denormalize(mixed $data, string $type, string $format = null, ar /** * {@inheritdoc} */ - public function supportsNormalization(mixed $data, string $format = null): bool + public function supportsNormalization(mixed $data, string $format = null, array $context = []): bool { return $data instanceof Message || $data instanceof Headers || $data instanceof HeaderInterface || $data instanceof Address || $data instanceof AbstractPart; } @@ -108,7 +108,7 @@ public function supportsNormalization(mixed $data, string $format = null): bool /** * {@inheritdoc} */ - public function supportsDenormalization(mixed $data, string $type, string $format = null): bool + public function supportsDenormalization(mixed $data, string $type, string $format = null, array $context = []): bool { return is_a($type, Message::class, true) || Headers::class === $type || AbstractPart::class === $type; } diff --git a/Normalizer/NormalizerInterface.php b/Normalizer/NormalizerInterface.php index 30eeafb47..741f19e50 100644 --- a/Normalizer/NormalizerInterface.php +++ b/Normalizer/NormalizerInterface.php @@ -41,10 +41,11 @@ public function normalize(mixed $object, string $format = null, array $context = /** * Checks whether the given class is supported for normalization by this normalizer. * - * @param mixed $data Data to normalize - * @param string $format The format being (de-)serialized from or into + * @param mixed $data Data to normalize + * @param string $format The format being (de-)serialized from or into + * @param array $context Context options for the normalizer * * @return bool */ - public function supportsNormalization(mixed $data, string $format = null); + public function supportsNormalization(mixed $data, string $format = null /*, array $context = [] */); } diff --git a/Normalizer/ProblemNormalizer.php b/Normalizer/ProblemNormalizer.php index 533b24e65..f7609945f 100644 --- a/Normalizer/ProblemNormalizer.php +++ b/Normalizer/ProblemNormalizer.php @@ -64,8 +64,10 @@ public function normalize(mixed $object, string $format = null, array $context = /** * {@inheritdoc} + * + * @param array $context */ - public function supportsNormalization(mixed $data, string $format = null): bool + public function supportsNormalization(mixed $data, string $format = null /*, array $context = [] */): bool { return $data instanceof FlattenException; } diff --git a/Normalizer/PropertyNormalizer.php b/Normalizer/PropertyNormalizer.php index c85cfd36d..dda4246b0 100644 --- a/Normalizer/PropertyNormalizer.php +++ b/Normalizer/PropertyNormalizer.php @@ -34,16 +34,20 @@ class PropertyNormalizer extends AbstractObjectNormalizer { /** * {@inheritdoc} + * + * @param array $context */ - public function supportsNormalization(mixed $data, string $format = null): bool + public function supportsNormalization(mixed $data, string $format = null /*, array $context = [] */): bool { return parent::supportsNormalization($data, $format) && $this->supports(\get_class($data)); } /** * {@inheritdoc} + * + * @param array $context */ - public function supportsDenormalization(mixed $data, string $type, string $format = null): bool + public function supportsDenormalization(mixed $data, string $type, string $format = null /*, array $context = [] */): bool { return parent::supportsDenormalization($data, $type, $format) && $this->supports($type); } diff --git a/Normalizer/UidNormalizer.php b/Normalizer/UidNormalizer.php index 42a86d9e9..889999031 100644 --- a/Normalizer/UidNormalizer.php +++ b/Normalizer/UidNormalizer.php @@ -59,7 +59,7 @@ public function normalize(mixed $object, string $format = null, array $context = /** * {@inheritdoc} */ - public function supportsNormalization(mixed $data, string $format = null): bool + public function supportsNormalization(mixed $data, string $format = null, array $context = []): bool { return $data instanceof AbstractUid; } @@ -85,7 +85,7 @@ public function denormalize(mixed $data, string $type, string $format = null, ar /** * {@inheritdoc} */ - public function supportsDenormalization(mixed $data, string $type, string $format = null): bool + public function supportsDenormalization(mixed $data, string $type, string $format = null, array $context = []): bool { return is_a($type, AbstractUid::class, true); } diff --git a/Tests/Encoder/ChainEncoderTest.php b/Tests/Encoder/ChainEncoderTest.php index f447c684a..6f999f612 100644 --- a/Tests/Encoder/ChainEncoderTest.php +++ b/Tests/Encoder/ChainEncoderTest.php @@ -90,7 +90,7 @@ public function testNeedsNormalizationNormalizationAware() class NormalizationAwareEncoder implements EncoderInterface, NormalizationAwareInterface { - public function supportsEncoding(string $format): bool + public function supportsEncoding(string $format, array $context = []): bool { return true; } diff --git a/Tests/Fixtures/AbstractNormalizerDummy.php b/Tests/Fixtures/AbstractNormalizerDummy.php index 5fb5ba3d3..afdfdec16 100644 --- a/Tests/Fixtures/AbstractNormalizerDummy.php +++ b/Tests/Fixtures/AbstractNormalizerDummy.php @@ -38,7 +38,7 @@ public function normalize(mixed $object, string $format = null, array $context = /** * {@inheritdoc} */ - public function supportsNormalization(mixed $data, string $format = null): bool + public function supportsNormalization(mixed $data, string $format = null, array $context = []): bool { return true; } @@ -53,7 +53,7 @@ public function denormalize(mixed $data, string $type, string $format = null, ar /** * {@inheritdoc} */ - public function supportsDenormalization(mixed $data, string $type, string $format = null): bool + public function supportsDenormalization(mixed $data, string $type, string $format = null, array $context = []): bool { return true; } diff --git a/Tests/Normalizer/AbstractObjectNormalizerTest.php b/Tests/Normalizer/AbstractObjectNormalizerTest.php index b93671aaf..b2f4a08ed 100644 --- a/Tests/Normalizer/AbstractObjectNormalizerTest.php +++ b/Tests/Normalizer/AbstractObjectNormalizerTest.php @@ -535,7 +535,7 @@ public function denormalize($data, string $type, string $format = null, array $c return null; } - public function supportsDenormalization($data, string $type, string $format = null): bool + public function supportsDenormalization($data, string $type, string $format = null, array $context = []): bool { return true; } diff --git a/Tests/Normalizer/TestDenormalizer.php b/Tests/Normalizer/TestDenormalizer.php index 56398232a..cef09715d 100644 --- a/Tests/Normalizer/TestDenormalizer.php +++ b/Tests/Normalizer/TestDenormalizer.php @@ -30,7 +30,7 @@ public function denormalize($data, string $type, string $format = null, array $c /** * {@inheritdoc} */ - public function supportsDenormalization($data, string $type, string $format = null): bool + public function supportsDenormalization($data, string $type, string $format = null, array $context = []): bool { return true; } diff --git a/Tests/Normalizer/TestNormalizer.php b/Tests/Normalizer/TestNormalizer.php index bf1f8f725..f3b604bfe 100644 --- a/Tests/Normalizer/TestNormalizer.php +++ b/Tests/Normalizer/TestNormalizer.php @@ -31,7 +31,7 @@ public function normalize($object, string $format = null, array $context = []): /** * {@inheritdoc} */ - public function supportsNormalization($data, string $format = null): bool + public function supportsNormalization($data, string $format = null, array $context = []): bool { return true; } From 46b8cd33e6660a3fcae2d9e442132e61754a8dae Mon Sep 17 00:00:00 2001 From: JustDylan23 Date: Tue, 4 Jan 2022 22:23:09 +0100 Subject: [PATCH 023/297] [Serializer] Fix AbstractObjectNormalizer TypeError on denormalization --- Normalizer/AbstractNormalizer.php | 2 +- Tests/Normalizer/AbstractObjectNormalizerTest.php | 12 ++++++++++++ 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/Normalizer/AbstractNormalizer.php b/Normalizer/AbstractNormalizer.php index b0a1f3218..7f86ed8d7 100644 --- a/Normalizer/AbstractNormalizer.php +++ b/Normalizer/AbstractNormalizer.php @@ -296,7 +296,7 @@ protected function isAllowedAttribute(object|string $classOrObject, string $attr * Normalizes the given data to an array. It's particularly useful during * the denormalization process. */ - protected function prepareForDenormalization(object|array|null $data): array + protected function prepareForDenormalization(mixed $data): array { return (array) $data; } diff --git a/Tests/Normalizer/AbstractObjectNormalizerTest.php b/Tests/Normalizer/AbstractObjectNormalizerTest.php index b93671aaf..03e5a889f 100644 --- a/Tests/Normalizer/AbstractObjectNormalizerTest.php +++ b/Tests/Normalizer/AbstractObjectNormalizerTest.php @@ -14,6 +14,7 @@ use Doctrine\Common\Annotations\AnnotationReader; use PHPUnit\Framework\TestCase; use Symfony\Component\PropertyInfo\Extractor\PhpDocExtractor; +use Symfony\Component\PropertyInfo\Extractor\ReflectionExtractor; use Symfony\Component\PropertyInfo\Type; use Symfony\Component\Serializer\Exception\ExtraAttributesException; use Symfony\Component\Serializer\Exception\InvalidArgumentException; @@ -387,6 +388,17 @@ public function testNormalizeEmptyObject() $normalizedData = $normalizer->normalize(new EmptyDummy(), 'any', ['preserve_empty_objects' => true]); $this->assertEquals(new \ArrayObject(), $normalizedData); } + + public function testDenormalizeRecursiveWithObjectAttributeWithStringValue() + { + $extractor = new ReflectionExtractor(); + $normalizer = new ObjectNormalizer(null, null, null, $extractor); + $serializer = new Serializer([$normalizer]); + + $obj = $serializer->denormalize(['inner' => 'foo'], ObjectOuter::class); + + $this->assertInstanceOf(ObjectInner::class, $obj->getInner()); + } } class AbstractObjectNormalizerDummy extends AbstractObjectNormalizer From 194354390eef1be5295eecd386a7ff7ec6768ad3 Mon Sep 17 00:00:00 2001 From: benjaminmal Date: Mon, 24 Jan 2022 14:58:16 +0100 Subject: [PATCH 024/297] [Serializer] Set context annotation as not final --- Annotation/Context.php | 2 +- CHANGELOG.md | 1 + .../Features/ContextMetadataTestTrait.php | 49 ++++++++++++++++--- .../Normalizer/Features/DummyContextChild.php | 17 +++++++ 4 files changed, 60 insertions(+), 9 deletions(-) create mode 100644 Tests/Normalizer/Features/DummyContextChild.php diff --git a/Annotation/Context.php b/Annotation/Context.php index 5c78d7b07..908825047 100644 --- a/Annotation/Context.php +++ b/Annotation/Context.php @@ -23,7 +23,7 @@ * @author Maxime Steinhausser */ #[\Attribute(\Attribute::TARGET_PROPERTY | \Attribute::TARGET_METHOD | \Attribute::IS_REPEATABLE)] -final class Context +class Context { private array $groups; diff --git a/CHANGELOG.md b/CHANGELOG.md index 363c3c88c..b78832802 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,7 @@ CHANGELOG 6.1 --- + * Set `Context` annotation as not final * Deprecate `ContextAwareNormalizerInterface`, use `NormalizerInterface` instead * Deprecate `ContextAwareDenormalizerInterface`, use `DenormalizerInterface` instead * Deprecate `ContextAwareEncoderInterface`, use `EncoderInterface` instead diff --git a/Tests/Normalizer/Features/ContextMetadataTestTrait.php b/Tests/Normalizer/Features/ContextMetadataTestTrait.php index 374cacaf7..28b1bf7f1 100644 --- a/Tests/Normalizer/Features/ContextMetadataTestTrait.php +++ b/Tests/Normalizer/Features/ContextMetadataTestTrait.php @@ -28,13 +28,16 @@ */ trait ContextMetadataTestTrait { - public function testContextMetadataNormalize() + /** + * @dataProvider contextMetadataDummyProvider + */ + public function testContextMetadataNormalize(string $contextMetadataDummyClass) { $classMetadataFactory = new ClassMetadataFactory(new AnnotationLoader(new AnnotationReader())); $normalizer = new ObjectNormalizer($classMetadataFactory, null, null, new PhpDocExtractor()); new Serializer([new DateTimeNormalizer(), $normalizer]); - $dummy = new ContextMetadataDummy(); + $dummy = new $contextMetadataDummyClass(); $dummy->date = new \DateTime('2011-07-28T08:44:00.123+00:00'); self::assertEquals(['date' => '2011-07-28T08:44:00+00:00'], $normalizer->normalize($dummy)); @@ -48,28 +51,39 @@ public function testContextMetadataNormalize() ]), 'base denormalization context is unchanged for this group'); } - public function testContextMetadataContextDenormalize() + /** + * @dataProvider contextMetadataDummyProvider + */ + public function testContextMetadataContextDenormalize(string $contextMetadataDummyClass) { $classMetadataFactory = new ClassMetadataFactory(new AnnotationLoader(new AnnotationReader())); $normalizer = new ObjectNormalizer($classMetadataFactory, null, null, new PhpDocExtractor()); new Serializer([new DateTimeNormalizer(), $normalizer]); - /** @var ContextMetadataDummy $dummy */ - $dummy = $normalizer->denormalize(['date' => '2011-07-28T08:44:00+00:00'], ContextMetadataDummy::class); + /** @var ContextMetadataDummy|ContextChildMetadataDummy $dummy */ + $dummy = $normalizer->denormalize(['date' => '2011-07-28T08:44:00+00:00'], $contextMetadataDummyClass); self::assertEquals(new \DateTime('2011-07-28T08:44:00+00:00'), $dummy->date); - /** @var ContextMetadataDummy $dummy */ + /** @var ContextMetadataDummy|ContextChildMetadataDummy $dummy */ $dummy = $normalizer->denormalize(['date' => '2011-07-28T08:44:00+00:00'], ContextMetadataDummy::class, null, [ ObjectNormalizer::GROUPS => 'extended', ]); self::assertEquals(new \DateTime('2011-07-28T08:44:00+00:00'), $dummy->date, 'base denormalization context is unchanged for this group'); - /** @var ContextMetadataDummy $dummy */ - $dummy = $normalizer->denormalize(['date' => '28/07/2011'], ContextMetadataDummy::class, null, [ + /** @var ContextMetadataDummy|ContextChildMetadataDummy $dummy */ + $dummy = $normalizer->denormalize(['date' => '28/07/2011'], $contextMetadataDummyClass, null, [ ObjectNormalizer::GROUPS => 'simple', ]); self::assertEquals('2011-07-28', $dummy->date->format('Y-m-d'), 'a specific denormalization context is used for this group'); } + + public function contextMetadataDummyProvider() + { + return [ + [ContextMetadataDummy::class], + [ContextChildMetadataDummy::class], + ]; + } } class ContextMetadataDummy @@ -90,3 +104,22 @@ class ContextMetadataDummy */ public $date; } + +class ContextChildMetadataDummy +{ + /** + * @var \DateTime + * + * @Groups({ "extended", "simple" }) + * @DummyContextChild({ DateTimeNormalizer::FORMAT_KEY = \DateTime::RFC3339 }) + * @DummyContextChild( + * normalizationContext = { DateTimeNormalizer::FORMAT_KEY = \DateTime::RFC3339_EXTENDED }, + * groups = {"extended"} + * ) + * @DummyContextChild( + * denormalizationContext = { DateTimeNormalizer::FORMAT_KEY = "d/m/Y" }, + * groups = {"simple"} + * ) + */ + public $date; +} diff --git a/Tests/Normalizer/Features/DummyContextChild.php b/Tests/Normalizer/Features/DummyContextChild.php new file mode 100644 index 000000000..0e4a24015 --- /dev/null +++ b/Tests/Normalizer/Features/DummyContextChild.php @@ -0,0 +1,17 @@ + Date: Sat, 30 Oct 2021 18:04:48 +0200 Subject: [PATCH 025/297] Add context builers --- CHANGELOG.md | 1 + Context/ContextBuilderTrait.php | 50 ++++++ Context/Encoder/CsvEncoderContextBuilder.php | 138 +++++++++++++++ Context/Encoder/JsonEncoderContextBuilder.php | 71 ++++++++ Context/Encoder/XmlEncoderContextBuilder.php | 134 ++++++++++++++ Context/Encoder/YamlEncoderContextBuilder.php | 63 +++++++ .../AbstractNormalizerContextBuilder.php | 166 ++++++++++++++++++ ...AbstractObjectNormalizerContextBuilder.php | 131 ++++++++++++++ ...tViolationListNormalizerContextBuilder.php | 70 ++++++++ .../DateIntervalNormalizerContextBuilder.php | 35 ++++ .../DateTimeNormalizerContextBuilder.php | 63 +++++++ .../FormErrorNormalizerContextBuilder.php | 49 ++++++ .../GetSetMethodNormalizerContextBuilder.php | 21 +++ ...onSerializableNormalizerContextBuilder.php | 21 +++ .../ObjectNormalizerContextBuilder.php | 21 +++ .../ProblemNormalizerContextBuilder.php | 49 ++++++ .../PropertyNormalizerContextBuilder.php | 21 +++ .../UidNormalizerContextBuilder.php | 40 +++++ .../UnwrappingDenormalizerContextBuilder.php | 52 ++++++ Context/SerializerContextBuilder.php | 39 ++++ Encoder/YamlEncoder.php | 2 +- Normalizer/AbstractObjectNormalizer.php | 3 +- Normalizer/ProblemNormalizer.php | 14 +- Normalizer/UidNormalizer.php | 6 + Tests/Context/ContextBuilderTraitTest.php | 48 +++++ .../Encoder/CsvEncoderContextBuilderTest.php | 132 ++++++++++++++ .../Encoder/JsonEncoderContextBuilderTest.php | 67 +++++++ .../Encoder/XmlEncoderContextBuilderTest.php | 87 +++++++++ .../Encoder/YamlEncoderContextBuilderTest.php | 66 +++++++ .../AbstractNormalizerContextBuilderTest.php | 93 ++++++++++ ...ractObjectNormalizerContextBuilderTest.php | 111 ++++++++++++ ...lationListNormalizerContextBuilderTest.php | 70 ++++++++ ...teIntervalNormalizerContextBuilderTest.php | 57 ++++++ .../DateTimeNormalizerContextBuilderTest.php | 72 ++++++++ .../FormErrorNormalizerContextBuilderTest.php | 63 +++++++ .../ProblemNormalizerContextBuilderTest.php | 63 +++++++ .../UidNormalizerContextBuilderTest.php | 64 +++++++ ...wrappingDenormalizerContextBuilderTest.php | 64 +++++++ .../Context/SerializerContextBuilderTest.php | 61 +++++++ 39 files changed, 2371 insertions(+), 7 deletions(-) create mode 100644 Context/ContextBuilderTrait.php create mode 100644 Context/Encoder/CsvEncoderContextBuilder.php create mode 100644 Context/Encoder/JsonEncoderContextBuilder.php create mode 100644 Context/Encoder/XmlEncoderContextBuilder.php create mode 100644 Context/Encoder/YamlEncoderContextBuilder.php create mode 100644 Context/Normalizer/AbstractNormalizerContextBuilder.php create mode 100644 Context/Normalizer/AbstractObjectNormalizerContextBuilder.php create mode 100644 Context/Normalizer/ConstraintViolationListNormalizerContextBuilder.php create mode 100644 Context/Normalizer/DateIntervalNormalizerContextBuilder.php create mode 100644 Context/Normalizer/DateTimeNormalizerContextBuilder.php create mode 100644 Context/Normalizer/FormErrorNormalizerContextBuilder.php create mode 100644 Context/Normalizer/GetSetMethodNormalizerContextBuilder.php create mode 100644 Context/Normalizer/JsonSerializableNormalizerContextBuilder.php create mode 100644 Context/Normalizer/ObjectNormalizerContextBuilder.php create mode 100644 Context/Normalizer/ProblemNormalizerContextBuilder.php create mode 100644 Context/Normalizer/PropertyNormalizerContextBuilder.php create mode 100644 Context/Normalizer/UidNormalizerContextBuilder.php create mode 100644 Context/Normalizer/UnwrappingDenormalizerContextBuilder.php create mode 100644 Context/SerializerContextBuilder.php create mode 100644 Tests/Context/ContextBuilderTraitTest.php create mode 100644 Tests/Context/Encoder/CsvEncoderContextBuilderTest.php create mode 100644 Tests/Context/Encoder/JsonEncoderContextBuilderTest.php create mode 100644 Tests/Context/Encoder/XmlEncoderContextBuilderTest.php create mode 100644 Tests/Context/Encoder/YamlEncoderContextBuilderTest.php create mode 100644 Tests/Context/Normalizer/AbstractNormalizerContextBuilderTest.php create mode 100644 Tests/Context/Normalizer/AbstractObjectNormalizerContextBuilderTest.php create mode 100644 Tests/Context/Normalizer/ConstraintViolationListNormalizerContextBuilderTest.php create mode 100644 Tests/Context/Normalizer/DateIntervalNormalizerContextBuilderTest.php create mode 100644 Tests/Context/Normalizer/DateTimeNormalizerContextBuilderTest.php create mode 100644 Tests/Context/Normalizer/FormErrorNormalizerContextBuilderTest.php create mode 100644 Tests/Context/Normalizer/ProblemNormalizerContextBuilderTest.php create mode 100644 Tests/Context/Normalizer/UidNormalizerContextBuilderTest.php create mode 100644 Tests/Context/Normalizer/UnwrappingDenormalizerContextBuilderTest.php create mode 100644 Tests/Context/SerializerContextBuilderTest.php diff --git a/CHANGELOG.md b/CHANGELOG.md index b78832802..3f16b2ba4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,7 @@ CHANGELOG 6.1 --- + * Add the ability to create contexts using context builders * Set `Context` annotation as not final * Deprecate `ContextAwareNormalizerInterface`, use `NormalizerInterface` instead * Deprecate `ContextAwareDenormalizerInterface`, use `DenormalizerInterface` instead diff --git a/Context/ContextBuilderTrait.php b/Context/ContextBuilderTrait.php new file mode 100644 index 000000000..73eb66e1f --- /dev/null +++ b/Context/ContextBuilderTrait.php @@ -0,0 +1,50 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Serializer\Context; + +/** + * @author Mathias Arlaud + */ +trait ContextBuilderTrait +{ + /** + * @var array + */ + protected array $context = []; + + protected function with(string $key, mixed $value): static + { + $instance = new static(); + $instance->context = array_merge($this->context, [$key => $value]); + + return $instance; + } + + /** + * @param array $context + */ + public function withContext(array $context): static + { + $instance = new static(); + $instance->context = array_merge($this->context, $context); + + return $instance; + } + + /** + * @return array + */ + public function toArray(): array + { + return $this->context; + } +} diff --git a/Context/Encoder/CsvEncoderContextBuilder.php b/Context/Encoder/CsvEncoderContextBuilder.php new file mode 100644 index 000000000..8a7bedc04 --- /dev/null +++ b/Context/Encoder/CsvEncoderContextBuilder.php @@ -0,0 +1,138 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Serializer\Context\Encoder; + +use Symfony\Component\Serializer\Context\ContextBuilderTrait; +use Symfony\Component\Serializer\Encoder\CsvEncoder; +use Symfony\Component\Serializer\Exception\InvalidArgumentException; + +/** + * A helper providing autocompletion for available CsvEncoder options. + * + * @author Mathias Arlaud + */ +final class CsvEncoderContextBuilder +{ + use ContextBuilderTrait; + + /** + * Configures the column delimiter character. + * + * Must be a single character. + * + * @param non-empty-string|null $delimiter + * + * @throws InvalidArgumentException + */ + public function withDelimiter(?string $delimiter): static + { + if (null !== $delimiter && 1 !== \strlen($delimiter)) { + throw new InvalidArgumentException(sprintf('The "%s" delimiter must be a single character.', $delimiter)); + } + + return $this->with(CsvEncoder::DELIMITER_KEY, $delimiter); + } + + /** + * Configures the field enclosure character. + * + * Must be a single character. + * + * @param non-empty-string|null $enclosure + * + * @throws InvalidArgumentException + */ + public function withEnclosure(?string $enclosure): static + { + if (null !== $enclosure && 1 !== \strlen($enclosure)) { + throw new InvalidArgumentException(sprintf('The "%s" enclosure must be a single character.', $enclosure)); + } + + return $this->with(CsvEncoder::ENCLOSURE_KEY, $enclosure); + } + + /** + * Configures the escape character. + * + * Must be empty or a single character. + * + * @throws InvalidArgumentException + */ + public function withEscapeChar(?string $escapeChar): static + { + if (null !== $escapeChar && \strlen($escapeChar) > 1) { + throw new InvalidArgumentException(sprintf('The "%s" escape character must be empty or a single character.', $escapeChar)); + } + + return $this->with(CsvEncoder::ESCAPE_CHAR_KEY, $escapeChar); + } + + /** + * Configures the key separator when (un)flattening arrays. + */ + public function withKeySeparator(?string $keySeparator): static + { + return $this->with(CsvEncoder::KEY_SEPARATOR_KEY, $keySeparator); + } + + /** + * Configures the headers. + * + * @param list|null $headers + */ + public function withHeaders(?array $headers): static + { + return $this->with(CsvEncoder::HEADERS_KEY, $headers); + } + + /** + * Configures whether formulas should be escaped. + */ + public function withEscapedFormulas(?bool $escapedFormulas): static + { + return $this->with(CsvEncoder::ESCAPE_FORMULAS_KEY, $escapedFormulas); + } + + /** + * Configures whether the decoded result should be considered as a collection + * or as a single element. + */ + public function withAsCollection(?bool $asCollection): static + { + return $this->with(CsvEncoder::AS_COLLECTION_KEY, $asCollection); + } + + /** + * Configures whether the input (or output) is containing (or will contain) headers. + */ + public function withNoHeaders(?bool $noHeaders): static + { + return $this->with(CsvEncoder::NO_HEADERS_KEY, $noHeaders); + } + + /** + * Configures the end of line characters. + */ + public function withEndOfLine(?string $endOfLine): static + { + return $this->with(CsvEncoder::END_OF_LINE, $endOfLine); + } + + /** + * Configures whether to add the UTF-8 Byte Order Mark (BOM) + * at the beginning of the encoded result or not. + */ + public function withOutputUtf8Bom(?bool $outputUtf8Bom): static + { + return $this->with(CsvEncoder::OUTPUT_UTF8_BOM_KEY, $outputUtf8Bom); + } +} diff --git a/Context/Encoder/JsonEncoderContextBuilder.php b/Context/Encoder/JsonEncoderContextBuilder.php new file mode 100644 index 000000000..dfb43ac09 --- /dev/null +++ b/Context/Encoder/JsonEncoderContextBuilder.php @@ -0,0 +1,71 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Serializer\Context\Encoder; + +use Symfony\Component\Serializer\Context\ContextBuilderTrait; +use Symfony\Component\Serializer\Encoder\JsonDecode; +use Symfony\Component\Serializer\Encoder\JsonEncode; + +/** + * A helper providing autocompletion for available JsonEncoder options. + * + * @author Mathias Arlaud + */ +final class JsonEncoderContextBuilder +{ + use ContextBuilderTrait; + + /** + * Configures the json_encode flags bitmask. + * + * @see https://www.php.net/manual/en/json.constants.php + * + * @param positive-int|null $options + */ + public function withEncodeOptions(?int $options): static + { + return $this->with(JsonEncode::OPTIONS, $options); + } + + /** + * Configures the json_decode flags bitmask. + * + * @see https://www.php.net/manual/en/json.constants.php + * + * @param positive-int|null $options + */ + public function withDecodeOptions(?int $options): static + { + return $this->with(JsonDecode::OPTIONS, $options); + } + + /** + * Configures whether decoded objects will be given as + * associative arrays or as nested stdClass. + */ + public function withAssociative(?bool $associative): static + { + return $this->with(JsonDecode::ASSOCIATIVE, $associative); + } + + /** + * Configures the maximum recursion depth. + * + * Must be strictly positive. + * + * @param positive-int|null $recursionDepth + */ + public function withRecursionDepth(?int $recursionDepth): static + { + return $this->with(JsonDecode::RECURSION_DEPTH, $recursionDepth); + } +} diff --git a/Context/Encoder/XmlEncoderContextBuilder.php b/Context/Encoder/XmlEncoderContextBuilder.php new file mode 100644 index 000000000..d8273a722 --- /dev/null +++ b/Context/Encoder/XmlEncoderContextBuilder.php @@ -0,0 +1,134 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Serializer\Context\Encoder; + +use Symfony\Component\Serializer\Context\ContextBuilderTrait; +use Symfony\Component\Serializer\Encoder\XmlEncoder; + +/** + * A helper providing autocompletion for available XmlEncoder options. + * + * @author Mathias Arlaud + */ +final class XmlEncoderContextBuilder +{ + use ContextBuilderTrait; + + /** + * Configures whether the decoded result should be considered as a collection + * or as a single element. + */ + public function withAsCollection(?bool $asCollection): static + { + return $this->with(XmlEncoder::AS_COLLECTION, $asCollection); + } + + /** + * Configures node types to ignore while decoding. + * + * @see https://www.php.net/manual/en/dom.constants.php + * + * @param list|null $decoderIgnoredNodeTypes + */ + public function withDecoderIgnoredNodeTypes(?array $decoderIgnoredNodeTypes): static + { + return $this->with(XmlEncoder::DECODER_IGNORED_NODE_TYPES, $decoderIgnoredNodeTypes); + } + + /** + * Configures node types to ignore while encoding. + * + * @see https://www.php.net/manual/en/dom.constants.php + * + * @param list|null $encoderIgnoredNodeTypes + */ + public function withEncoderIgnoredNodeTypes(?array $encoderIgnoredNodeTypes): static + { + return $this->with(XmlEncoder::ENCODER_IGNORED_NODE_TYPES, $encoderIgnoredNodeTypes); + } + + /** + * Configures the DOMDocument encoding. + * + * @see https://www.php.net/manual/en/class.domdocument.php#domdocument.props.encoding + */ + public function withEncoding(?string $encoding): static + { + return $this->with(XmlEncoder::ENCODING, $encoding); + } + + /** + * Configures whether to encode with indentation and extra space. + * + * @see https://php.net/manual/en/class.domdocument.php#domdocument.props.formatoutput + */ + public function withFormatOutput(?bool $formatOutput): static + { + return $this->with(XmlEncoder::FORMAT_OUTPUT, $formatOutput); + } + + /** + * Configures the DOMDocument::loadXml options bitmask. + * + * @see https://www.php.net/manual/en/libxml.constants.php + * + * @param positive-int|null $loadOptions + */ + public function withLoadOptions(?int $loadOptions): static + { + return $this->with(XmlEncoder::LOAD_OPTIONS, $loadOptions); + } + + /** + * Configures whether to keep empty nodes. + */ + public function withRemoveEmptyTags(?bool $removeEmptyTags): static + { + return $this->with(XmlEncoder::REMOVE_EMPTY_TAGS, $removeEmptyTags); + } + + /** + * Configures name of the root node. + */ + public function withRootNodeName(?string $rootNodeName): static + { + return $this->with(XmlEncoder::ROOT_NODE_NAME, $rootNodeName); + } + + /** + * Configures whether the document will be standalone. + * + * @see https://php.net/manual/en/class.domdocument.php#domdocument.props.xmlstandalone + */ + public function withStandalone(?bool $standalone): static + { + return $this->with(XmlEncoder::STANDALONE, $standalone); + } + + /** + * Configures whether casting numeric string attributes to integers or floats. + */ + public function withTypeCastAttributes(?bool $typeCastAttributes): static + { + return $this->with(XmlEncoder::TYPE_CAST_ATTRIBUTES, $typeCastAttributes); + } + + /** + * Configures the version number of the document. + * + * @see https://php.net/manual/en/class.domdocument.php#domdocument.props.xmlversion + */ + public function withVersion(?string $version): static + { + return $this->with(XmlEncoder::VERSION, $version); + } +} diff --git a/Context/Encoder/YamlEncoderContextBuilder.php b/Context/Encoder/YamlEncoderContextBuilder.php new file mode 100644 index 000000000..aa8e554b7 --- /dev/null +++ b/Context/Encoder/YamlEncoderContextBuilder.php @@ -0,0 +1,63 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Serializer\Context\Encoder; + +use Symfony\Component\Serializer\Context\ContextBuilderTrait; +use Symfony\Component\Serializer\Encoder\YamlEncoder; + +/** + * A helper providing autocompletion for available YamlEncoder options. + * + * @author Mathias Arlaud + */ +final class YamlEncoderContextBuilder +{ + use ContextBuilderTrait; + + /** + * Configures the threshold to switch to inline YAML. + */ + public function withInlineThreshold(?int $inlineThreshold): static + { + return $this->with(YamlEncoder::YAML_INLINE, $inlineThreshold); + } + + /** + * Configures the indentation level. + * + * Must be positive. + * + * @param int<0, max>|null $indentLevel + */ + public function withIndentLevel(?int $indentLevel): static + { + return $this->with(YamlEncoder::YAML_INDENT, $indentLevel); + } + + /** + * Configures \Symfony\Component\Yaml\Dumper::dump flags bitmask. + * + * @see \Symfony\Component\Yaml\Yaml + */ + public function withFlags(?int $flags): static + { + return $this->with(YamlEncoder::YAML_FLAGS, $flags); + } + + /** + * Configures whether to perserve empty objects "{}" or to convert them to null. + */ + public function withPreservedEmptyObjects(?bool $preserveEmptyObjects): static + { + return $this->with(YamlEncoder::PRESERVE_EMPTY_OBJECTS, $preserveEmptyObjects); + } +} diff --git a/Context/Normalizer/AbstractNormalizerContextBuilder.php b/Context/Normalizer/AbstractNormalizerContextBuilder.php new file mode 100644 index 000000000..4d65948a8 --- /dev/null +++ b/Context/Normalizer/AbstractNormalizerContextBuilder.php @@ -0,0 +1,166 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Serializer\Context\Normalizer; + +use Symfony\Component\Serializer\Context\ContextBuilderTrait; +use Symfony\Component\Serializer\Exception\InvalidArgumentException; +use Symfony\Component\Serializer\Normalizer\AbstractNormalizer; + +/** + * A helper providing autocompletion for available AbstractNormalizer options. + * + * @author Mathias Arlaud + */ +abstract class AbstractNormalizerContextBuilder +{ + use ContextBuilderTrait; + + /** + * Configures how many loops of circular reference to allow while normalizing. + * + * The value 1 means that when we encounter the same object a + * second time, we consider that a circular reference. + * + * You can raise this value for special cases, e.g. in combination with the + * max depth setting of the object normalizer. + * + * Must be strictly positive. + * + * @param positive-int|null $circularReferenceLimit + */ + public function withCircularReferenceLimit(?int $circularReferenceLimit): static + { + return $this->with(AbstractNormalizer::CIRCULAR_REFERENCE_LIMIT, $circularReferenceLimit); + } + + /** + * Configures an object to be updated instead of creating a new instance. + * + * If you have a nested structure, child objects will be overwritten with + * new instances unless you set AbstractObjectNormalizer::DEEP_OBJECT_TO_POPULATE to true. + */ + public function withObjectToPopulate(?object $objectToPopulate): static + { + return $this->with(AbstractNormalizer::OBJECT_TO_POPULATE, $objectToPopulate); + } + + /** + * Configures groups containing attributes to (de)normalize. + * + * Eg: ['group1', 'group2'] + * + * @param list|string|null $groups + */ + public function withGroups(array|string|null $groups): static + { + if (null === $groups) { + return $this->with(AbstractNormalizer::GROUPS, null); + } + + return $this->with(AbstractNormalizer::GROUPS, (array) $groups); + } + + /** + * Configures attributes to (de)normalize. + * + * For nested structures, this list needs to reflect the object tree. + * + * Eg: ['foo', 'bar', 'object' => ['baz']] + * + * @param array|null $attributes + * + * @throws InvalidArgumentException + */ + public function withAttributes(?array $attributes): static + { + $it = new \RecursiveIteratorIterator(new \RecursiveArrayIterator($attributes ?? []), \RecursiveIteratorIterator::LEAVES_ONLY); + + foreach ($it as $attribute) { + if (!\is_string($attribute)) { + throw new InvalidArgumentException(sprintf('Each attribute must be a string, "%s" given.', get_debug_type($attribute))); + } + } + + return $this->with(AbstractNormalizer::ATTRIBUTES, $attributes); + } + + /** + * If AbstractNormalizer::ATTRIBUTES are specified, and the source has fields that are not part of that list, + * configures whether to ignore those attributes or throw an ExtraAttributesException. + */ + public function withAllowExtraAttributes(?bool $allowExtraAttributes): static + { + return $this->with(AbstractNormalizer::ALLOW_EXTRA_ATTRIBUTES, $allowExtraAttributes); + } + + /** + * Configures an hashmap of classes containing hashmaps of constructor argument => default value. + * + * The names need to match the parameter names in the constructor arguments. + * + * Eg: [Foo::class => ['foo' => true, 'bar' => 0]] + * + * @param array>|null $defaultContructorArguments + */ + public function withDefaultContructorArguments(?array $defaultContructorArguments): static + { + return $this->with(AbstractNormalizer::DEFAULT_CONSTRUCTOR_ARGUMENTS, $defaultContructorArguments); + } + + /** + * Configures an hashmap of field name => callable to normalize this field. + * + * The callable is called if the field is encountered with the arguments: + * + * - mixed $attributeValue value of this field + * - object $object the whole object being normalized + * - string $attributeName name of the attribute being normalized + * - string $format the requested format + * - array $context the serialization context + * + * @param array|null $callbacks + */ + public function withCallbacks(?array $callbacks): static + { + return $this->with(AbstractNormalizer::CALLBACKS, $callbacks); + } + + /** + * Configures an handler to call when a circular reference has been detected. + * + * If no handler is specified, a CircularReferenceException is thrown. + * + * The method will be called with ($object, $format, $context) and its + * return value is returned as the result of the normalize call. + */ + public function withCircularReferenceHandler(?callable $circularReferenceHandler): static + { + return $this->with(AbstractNormalizer::CIRCULAR_REFERENCE_HANDLER, $circularReferenceHandler); + } + + /** + * Configures attributes to be skipped when normalizing an object tree. + * + * This list is applied to each element of nested structures. + * + * Eg: ['foo', 'bar'] + * + * Note: The behaviour for nested structures is different from ATTRIBUTES + * for historical reason. Aligning the behaviour would be a BC break. + * + * @param list|null $attributes + */ + public function withIgnoredAttributes(?array $ignoredAttributes): static + { + return $this->with(AbstractNormalizer::IGNORED_ATTRIBUTES, $ignoredAttributes); + } +} diff --git a/Context/Normalizer/AbstractObjectNormalizerContextBuilder.php b/Context/Normalizer/AbstractObjectNormalizerContextBuilder.php new file mode 100644 index 000000000..a27f00c5b --- /dev/null +++ b/Context/Normalizer/AbstractObjectNormalizerContextBuilder.php @@ -0,0 +1,131 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Serializer\Context\Normalizer; + +use Symfony\Component\Serializer\Exception\InvalidArgumentException; +use Symfony\Component\Serializer\Normalizer\AbstractObjectNormalizer; + +/** + * A helper providing autocompletion for available AbstractObjectNormalizer options. + * + * @author Mathias Arlaud + */ +abstract class AbstractObjectNormalizerContextBuilder extends AbstractNormalizerContextBuilder +{ + /** + * Configures whether to respect the max depth metadata on fields. + */ + public function withEnableMaxDepth(?bool $enableMaxDepth): static + { + return $this->with(AbstractObjectNormalizer::ENABLE_MAX_DEPTH, $enableMaxDepth); + } + + /** + * Configures a pattern to keep track of the current depth. + * + * Must contain exactly two string placeholders. + * + * @throws InvalidArgumentException + */ + public function withDepthKeyPattern(?string $depthKeyPattern): static + { + if (null === $depthKeyPattern) { + return $this->with(AbstractObjectNormalizer::DEPTH_KEY_PATTERN, null); + } + + // This will match every occurrences of sprintf specifiers + $matches = []; + preg_match_all('/(?[a-z])/', $depthKeyPattern, $matches); + + if (2 !== \count($matches['specifier']) || 's' !== $matches['specifier'][0] || 's' !== $matches['specifier'][1]) { + throw new InvalidArgumentException(sprintf('The depth key pattern "%s" is not valid. You must set exactly two string placeholders.', $depthKeyPattern)); + } + + return $this->with(AbstractObjectNormalizer::DEPTH_KEY_PATTERN, $depthKeyPattern); + } + + /** + * Configures whether verifying types match during denormalization. + */ + public function withDisableTypeEnforcement(?bool $disableTypeEnforcement): static + { + return $this->with(AbstractObjectNormalizer::DISABLE_TYPE_ENFORCEMENT, $disableTypeEnforcement); + } + + /** + * Configures whether fields with the value `null` should be output during normalization. + */ + public function withSkipNullValues(?bool $skipNullValues): static + { + return $this->with(AbstractObjectNormalizer::SKIP_NULL_VALUES, $skipNullValues); + } + + /** + * Configures whether uninitialized typed class properties should be excluded during normalization. + */ + public function withSkipUninitializedValues(?bool $skipUninitializedValues): static + { + return $this->with(AbstractObjectNormalizer::SKIP_UNINITIALIZED_VALUES, $skipUninitializedValues); + } + + /** + * Configures a callback to allow to set a value for an attribute when the max depth has + * been reached. + * + * If no callback is given, the attribute is skipped. If a callable is + * given, its return value is used (even if null). + * + * The arguments are: + * + * - mixed $attributeValue value of this field + * - object $object the whole object being normalized + * - string $attributeName name of the attribute being normalized + * - string $format the requested format + * - array $context the serialization context + */ + public function withMaxDepthHandler(?callable $maxDepthHandler): static + { + return $this->with(AbstractObjectNormalizer::MAX_DEPTH_HANDLER, $maxDepthHandler); + } + + /** + * Configures which context key are not relevant to determine which attributes + * of an object to (de)normalize. + * + * @param list|null $excludeFromCacheKeys + */ + public function withExcludeFromCacheKeys(?array $excludeFromCacheKeys): static + { + return $this->with(AbstractObjectNormalizer::EXCLUDE_FROM_CACHE_KEY, $excludeFromCacheKeys); + } + + /** + * Configures whether to tell the denormalizer to also populate existing objects on + * attributes of the main object. + * + * Setting this to true is only useful if you also specify the root object + * in AbstractNormalizer::OBJECT_TO_POPULATE. + */ + public function withDeepObjectToPopulate(?bool $deepObjectToPopulate): static + { + return $this->with(AbstractObjectNormalizer::DEEP_OBJECT_TO_POPULATE, $deepObjectToPopulate); + } + + /** + * Configures whether an empty object should be kept as an object (in + * JSON: {}) or converted to a list (in JSON: []). + */ + public function withPreserveEmptyObjects(?bool $preserveEmptyObjects): static + { + return $this->with(AbstractObjectNormalizer::PRESERVE_EMPTY_OBJECTS, $preserveEmptyObjects); + } +} diff --git a/Context/Normalizer/ConstraintViolationListNormalizerContextBuilder.php b/Context/Normalizer/ConstraintViolationListNormalizerContextBuilder.php new file mode 100644 index 000000000..41dec70d4 --- /dev/null +++ b/Context/Normalizer/ConstraintViolationListNormalizerContextBuilder.php @@ -0,0 +1,70 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Serializer\Context\Normalizer; + +use Symfony\Component\Serializer\Context\ContextBuilderTrait; +use Symfony\Component\Serializer\Normalizer\ConstraintViolationListNormalizer; + +/** + * A helper providing autocompletion for available ConstraintViolationList options. + * + * @author Mathias Arlaud + */ +final class ConstraintViolationListNormalizerContextBuilder +{ + use ContextBuilderTrait; + + /** + * Configure the instance field of normalized data. + */ + public function withInstance(mixed $instance): static + { + return $this->with(ConstraintViolationListNormalizer::INSTANCE, $instance); + } + + /** + * Configure the status field of normalized data. + */ + public function withStatus(?int $status): static + { + return $this->with(ConstraintViolationListNormalizer::STATUS, $status); + } + + /** + * Configure the title field of normalized data. + */ + public function withTitle(?string $title): static + { + return $this->with(ConstraintViolationListNormalizer::TITLE, $title); + } + + /** + * Configure the type field of normalized data. + */ + public function withType(?string $type): static + { + return $this->with(ConstraintViolationListNormalizer::TYPE, $type); + } + + /** + * Configures the payload fields which will act as an allowlist + * for the payload field of normalized data. + * + * Eg: ['foo', 'bar'] + * + * @param list|null $payloadFields + */ + public function withPayloadFields(?array $payloadFields): static + { + return $this->with(ConstraintViolationListNormalizer::PAYLOAD_FIELDS, $payloadFields); + } +} diff --git a/Context/Normalizer/DateIntervalNormalizerContextBuilder.php b/Context/Normalizer/DateIntervalNormalizerContextBuilder.php new file mode 100644 index 000000000..4f5558be5 --- /dev/null +++ b/Context/Normalizer/DateIntervalNormalizerContextBuilder.php @@ -0,0 +1,35 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Serializer\Context\Normalizer; + +use Symfony\Component\Serializer\Context\ContextBuilderTrait; +use Symfony\Component\Serializer\Normalizer\DateIntervalNormalizer; + +/** + * A helper providing autocompletion for available DateIntervalNormalizer options. + * + * @author Mathias Arlaud + */ +final class DateIntervalNormalizerContextBuilder +{ + use ContextBuilderTrait; + + /** + * Configures the format of the interval. + * + * @see https://php.net/manual/en/dateinterval.format.php + */ + public function withFormat(?string $format): static + { + return $this->with(DateIntervalNormalizer::FORMAT_KEY, $format); + } +} diff --git a/Context/Normalizer/DateTimeNormalizerContextBuilder.php b/Context/Normalizer/DateTimeNormalizerContextBuilder.php new file mode 100644 index 000000000..8e51bf419 --- /dev/null +++ b/Context/Normalizer/DateTimeNormalizerContextBuilder.php @@ -0,0 +1,63 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Serializer\Context\Normalizer; + +use Symfony\Component\Serializer\Context\ContextBuilderTrait; +use Symfony\Component\Serializer\Exception\InvalidArgumentException; +use Symfony\Component\Serializer\Normalizer\DateTimeNormalizer; + +/** + * A helper providing autocompletion for available DateTimeNormalizer options. + * + * @author Mathias Arlaud + */ +final class DateTimeNormalizerContextBuilder +{ + use ContextBuilderTrait; + + /** + * Configures the format of the date. + * + * @see https://secure.php.net/manual/en/datetime.format.php + */ + public function withFormat(?string $format): static + { + return $this->with(DateTimeNormalizer::FORMAT_KEY, $format); + } + + /** + * Configures the timezone of the date. + * + * It could be either a \DateTimeZone or a string + * that will be used to construct the \DateTimeZone + * + * @see https://secure.php.net/manual/en/class.datetimezone.php + * + * @throws InvalidArgumentException + */ + public function withTimezone(\DateTimeZone|string|null $timezone): static + { + if (null === $timezone) { + return $this->with(DateTimeNormalizer::TIMEZONE_KEY, null); + } + + if (\is_string($timezone)) { + try { + $timezone = new \DateTimeZone($timezone); + } catch (\Exception $e) { + throw new InvalidArgumentException(sprintf('The "%s" timezone is invalid.', $timezone), previous: $e); + } + } + + return $this->with(DateTimeNormalizer::TIMEZONE_KEY, $timezone); + } +} diff --git a/Context/Normalizer/FormErrorNormalizerContextBuilder.php b/Context/Normalizer/FormErrorNormalizerContextBuilder.php new file mode 100644 index 000000000..deaf5c2a8 --- /dev/null +++ b/Context/Normalizer/FormErrorNormalizerContextBuilder.php @@ -0,0 +1,49 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Serializer\Context\Normalizer; + +use Symfony\Component\Serializer\Context\ContextBuilderTrait; +use Symfony\Component\Serializer\Normalizer\FormErrorNormalizer; + +/** + * A helper providing autocompletion for available FormErrorNormalizer options. + * + * @author Mathias Arlaud + */ +final class FormErrorNormalizerContextBuilder +{ + use ContextBuilderTrait; + + /** + * Configures the title of the normalized data. + */ + public function withTitle(?string $title): static + { + return $this->with(FormErrorNormalizer::TITLE, $title); + } + + /** + * Configures the type of the normalized data. + */ + public function withType(?string $type): static + { + return $this->with(FormErrorNormalizer::TYPE, $type); + } + + /** + * Configures the code of the normalized data. + */ + public function withStatusCode(?int $statusCode): static + { + return $this->with(FormErrorNormalizer::CODE, $statusCode); + } +} diff --git a/Context/Normalizer/GetSetMethodNormalizerContextBuilder.php b/Context/Normalizer/GetSetMethodNormalizerContextBuilder.php new file mode 100644 index 000000000..a7d2f8244 --- /dev/null +++ b/Context/Normalizer/GetSetMethodNormalizerContextBuilder.php @@ -0,0 +1,21 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Serializer\Context\Normalizer; + +/** + * A helper providing autocompletion for available GetSetMethodNormalizer options. + * + * @author Mathias Arlaud + */ +final class GetSetMethodNormalizerContextBuilder extends AbstractObjectNormalizerContextBuilder +{ +} diff --git a/Context/Normalizer/JsonSerializableNormalizerContextBuilder.php b/Context/Normalizer/JsonSerializableNormalizerContextBuilder.php new file mode 100644 index 000000000..249e74ff0 --- /dev/null +++ b/Context/Normalizer/JsonSerializableNormalizerContextBuilder.php @@ -0,0 +1,21 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Serializer\Context\Normalizer; + +/** + * A helper providing autocompletion for available JsonSerializableNormalizer options. + * + * @author Mathias Arlaud + */ +final class JsonSerializableNormalizerContextBuilder extends AbstractNormalizerContextBuilder +{ +} diff --git a/Context/Normalizer/ObjectNormalizerContextBuilder.php b/Context/Normalizer/ObjectNormalizerContextBuilder.php new file mode 100644 index 000000000..eb8d15710 --- /dev/null +++ b/Context/Normalizer/ObjectNormalizerContextBuilder.php @@ -0,0 +1,21 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Serializer\Context\Normalizer; + +/** + * A helper providing autocompletion for available ObjectNormalizer options. + * + * @author Mathias Arlaud + */ +final class ObjectNormalizerContextBuilder extends AbstractObjectNormalizerContextBuilder +{ +} diff --git a/Context/Normalizer/ProblemNormalizerContextBuilder.php b/Context/Normalizer/ProblemNormalizerContextBuilder.php new file mode 100644 index 000000000..ab448f361 --- /dev/null +++ b/Context/Normalizer/ProblemNormalizerContextBuilder.php @@ -0,0 +1,49 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Serializer\Context\Normalizer; + +use Symfony\Component\Serializer\Context\ContextBuilderTrait; +use Symfony\Component\Serializer\Normalizer\ProblemNormalizer; + +/** + * A helper providing autocompletion for available ProblemNormalizer options. + * + * @author Mathias Arlaud + */ +final class ProblemNormalizerContextBuilder +{ + use ContextBuilderTrait; + + /** + * Configure the title field of normalized data. + */ + public function withTitle(?string $title): static + { + return $this->with(ProblemNormalizer::TITLE, $title); + } + + /** + * Configure the type field of normalized data. + */ + public function withType(?string $type): static + { + return $this->with(ProblemNormalizer::TYPE, $type); + } + + /** + * Configure the status field of normalized data. + */ + public function withStatusCode(int|string|null $statusCode): static + { + return $this->with(ProblemNormalizer::STATUS, $statusCode); + } +} diff --git a/Context/Normalizer/PropertyNormalizerContextBuilder.php b/Context/Normalizer/PropertyNormalizerContextBuilder.php new file mode 100644 index 000000000..e0a8f2f91 --- /dev/null +++ b/Context/Normalizer/PropertyNormalizerContextBuilder.php @@ -0,0 +1,21 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Serializer\Context\Normalizer; + +/** + * A helper providing autocompletion for available PropertyNormalizer options. + * + * @author Mathias Arlaud + */ +final class PropertyNormalizerContextBuilder extends AbstractObjectNormalizerContextBuilder +{ +} diff --git a/Context/Normalizer/UidNormalizerContextBuilder.php b/Context/Normalizer/UidNormalizerContextBuilder.php new file mode 100644 index 000000000..c7386d047 --- /dev/null +++ b/Context/Normalizer/UidNormalizerContextBuilder.php @@ -0,0 +1,40 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Serializer\Context\Normalizer; + +use Symfony\Component\Serializer\Context\ContextBuilderTrait; +use Symfony\Component\Serializer\Exception\InvalidArgumentException; +use Symfony\Component\Serializer\Normalizer\UidNormalizer; + +/** + * A helper providing autocompletion for available UidNormalizer options. + * + * @author Mathias Arlaud + */ +final class UidNormalizerContextBuilder +{ + use ContextBuilderTrait; + + /** + * Configures the uuid format for normalization. + * + * @throws InvalidArgumentException + */ + public function withNormalizationFormat(?string $normalizationFormat): static + { + if (null !== $normalizationFormat && !\in_array($normalizationFormat, UidNormalizer::NORMALIZATION_FORMATS, true)) { + throw new InvalidArgumentException(sprintf('The "%s" normalization format is not valid.', $normalizationFormat)); + } + + return $this->with(UidNormalizer::NORMALIZATION_FORMAT_KEY, $normalizationFormat); + } +} diff --git a/Context/Normalizer/UnwrappingDenormalizerContextBuilder.php b/Context/Normalizer/UnwrappingDenormalizerContextBuilder.php new file mode 100644 index 000000000..c09e6df04 --- /dev/null +++ b/Context/Normalizer/UnwrappingDenormalizerContextBuilder.php @@ -0,0 +1,52 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Serializer\Context\Normalizer; + +use Symfony\Component\PropertyAccess\Exception\InvalidPropertyPathException; +use Symfony\Component\PropertyAccess\PropertyPath; +use Symfony\Component\Serializer\Context\ContextBuilderTrait; +use Symfony\Component\Serializer\Exception\InvalidArgumentException; +use Symfony\Component\Serializer\Normalizer\UnwrappingDenormalizer; + +/** + * A helper providing autocompletion for available UnwrappingDenormalizer options. + * + * @author Mathias Arlaud + */ +final class UnwrappingDenormalizerContextBuilder +{ + use ContextBuilderTrait; + + /** + * Configures the path of wrapped data during denormalization. + * + * Eg: [foo].bar[bar] + * + * @see https://symfony.com/doc/current/components/property_access.html + * + * @throws InvalidArgumentException + */ + public function withUnwrapPath(?string $unwrapPath): static + { + if (null === $unwrapPath) { + return $this->with(UnwrappingDenormalizer::UNWRAP_PATH, null); + } + + try { + new PropertyPath($unwrapPath); + } catch (InvalidPropertyPathException $e) { + throw new InvalidArgumentException('The "%s" property path is not valid.', previous: $e); + } + + return $this->with(UnwrappingDenormalizer::UNWRAP_PATH, $unwrapPath); + } +} diff --git a/Context/SerializerContextBuilder.php b/Context/SerializerContextBuilder.php new file mode 100644 index 000000000..48ea999ec --- /dev/null +++ b/Context/SerializerContextBuilder.php @@ -0,0 +1,39 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Serializer\Context; + +use Symfony\Component\Serializer\Normalizer\DenormalizerInterface; +use Symfony\Component\Serializer\Serializer; + +/** + * A helper providing autocompletion for available Serializer options. + * + * @author Mathias Arlaud + */ +final class SerializerContextBuilder +{ + use ContextBuilderTrait; + + /** + * Configures whether an empty array should be transformed to an + * object (in JSON: {}) or to a list (in JSON: []). + */ + public function withEmptyArrayAsObject(?bool $emptyArrayAsObject): static + { + return $this->with(Serializer::EMPTY_ARRAY_AS_OBJECT, $emptyArrayAsObject); + } + + public function withCollectDenormalizationErrors(?bool $collectDenormalizationErrors): static + { + return $this->with(DenormalizerInterface::COLLECT_DENORMALIZATION_ERRORS, $collectDenormalizationErrors); + } +} diff --git a/Encoder/YamlEncoder.php b/Encoder/YamlEncoder.php index 990d0039c..8bd19cb86 100644 --- a/Encoder/YamlEncoder.php +++ b/Encoder/YamlEncoder.php @@ -58,7 +58,7 @@ public function encode(mixed $data, string $format, array $context = []): string { $context = array_merge($this->defaultContext, $context); - if (isset($context[self::PRESERVE_EMPTY_OBJECTS])) { + if ($context[self::PRESERVE_EMPTY_OBJECTS] ?? false) { $context[self::YAML_FLAGS] |= Yaml::DUMP_OBJECT_AS_MAP; } diff --git a/Normalizer/AbstractObjectNormalizer.php b/Normalizer/AbstractObjectNormalizer.php index a8943113c..766501745 100644 --- a/Normalizer/AbstractObjectNormalizer.php +++ b/Normalizer/AbstractObjectNormalizer.php @@ -233,7 +233,8 @@ public function normalize(mixed $object, string $format = null, array $context = $data = $this->updateData($data, $attribute, $this->serializer->normalize($attributeValue, $format, $childContext), $class, $format, $attributeContext); } - if (isset($context[self::PRESERVE_EMPTY_OBJECTS]) && !\count($data)) { + $preserveEmptyObjects = $context[self::PRESERVE_EMPTY_OBJECTS] ?? $this->defaultContext[self::PRESERVE_EMPTY_OBJECTS] ?? false; + if ($preserveEmptyObjects && !\count($data)) { return new \ArrayObject(); } diff --git a/Normalizer/ProblemNormalizer.php b/Normalizer/ProblemNormalizer.php index f7609945f..be543ec9b 100644 --- a/Normalizer/ProblemNormalizer.php +++ b/Normalizer/ProblemNormalizer.php @@ -24,10 +24,14 @@ */ class ProblemNormalizer implements NormalizerInterface, CacheableSupportsMethodInterface { + public const TITLE = 'title'; + public const TYPE = 'type'; + public const STATUS = 'status'; + private $debug; private $defaultContext = [ - 'type' => 'https://tools.ietf.org/html/rfc2616#section-10', - 'title' => 'An error occurred', + self::TYPE => 'https://tools.ietf.org/html/rfc2616#section-10', + self::TITLE => 'An error occurred', ]; public function __construct(bool $debug = false, array $defaultContext = []) @@ -49,9 +53,9 @@ public function normalize(mixed $object, string $format = null, array $context = $debug = $this->debug && ($context['debug'] ?? true); $data = [ - 'type' => $context['type'], - 'title' => $context['title'], - 'status' => $context['status'] ?? $object->getStatusCode(), + self::TYPE => $context['type'], + self::TITLE => $context['title'], + self::STATUS => $context['status'] ?? $object->getStatusCode(), 'detail' => $debug ? $object->getMessage() : $object->getStatusText(), ]; if ($debug) { diff --git a/Normalizer/UidNormalizer.php b/Normalizer/UidNormalizer.php index 889999031..02d47e744 100644 --- a/Normalizer/UidNormalizer.php +++ b/Normalizer/UidNormalizer.php @@ -25,6 +25,12 @@ final class UidNormalizer implements NormalizerInterface, DenormalizerInterface, public const NORMALIZATION_FORMAT_BASE58 = 'base58'; public const NORMALIZATION_FORMAT_BASE32 = 'base32'; public const NORMALIZATION_FORMAT_RFC4122 = 'rfc4122'; + public const NORMALIZATION_FORMATS = [ + self::NORMALIZATION_FORMAT_CANONICAL, + self::NORMALIZATION_FORMAT_BASE58, + self::NORMALIZATION_FORMAT_BASE32, + self::NORMALIZATION_FORMAT_RFC4122, + ]; private $defaultContext = [ self::NORMALIZATION_FORMAT_KEY => self::NORMALIZATION_FORMAT_CANONICAL, diff --git a/Tests/Context/ContextBuilderTraitTest.php b/Tests/Context/ContextBuilderTraitTest.php new file mode 100644 index 000000000..4c25e17c0 --- /dev/null +++ b/Tests/Context/ContextBuilderTraitTest.php @@ -0,0 +1,48 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Serializer\Tests\Context; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\Serializer\Context\ContextBuilderTrait; + +/** + * @author Mathias Arlaud + */ +class ContextBuilderTraitTest extends TestCase +{ + public function testWithContext() + { + $contextBuilder = new class() { + use ContextBuilderTrait; + }; + + $context = $contextBuilder->withContext(['foo' => 'bar'])->toArray(); + + $this->assertSame(['foo' => 'bar'], $context); + } + + public function testWith() + { + $contextBuilder = new class() { + use ContextBuilderTrait; + + public function withFoo(string $value): static + { + return $this->with('foo', $value); + } + }; + + $context = $contextBuilder->withFoo('bar')->toArray(); + + $this->assertSame(['foo' => 'bar'], $context); + } +} diff --git a/Tests/Context/Encoder/CsvEncoderContextBuilderTest.php b/Tests/Context/Encoder/CsvEncoderContextBuilderTest.php new file mode 100644 index 000000000..47403888c --- /dev/null +++ b/Tests/Context/Encoder/CsvEncoderContextBuilderTest.php @@ -0,0 +1,132 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Serializer\Tests\Context\Encoder; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\Serializer\Context\Encoder\CsvEncoderContextBuilder; +use Symfony\Component\Serializer\Encoder\CsvEncoder; +use Symfony\Component\Serializer\Exception\InvalidArgumentException; + +/** + * @author Mathias Arlaud + */ +class CsvEncoderContextBuilderTest extends TestCase +{ + private CsvEncoderContextBuilder $contextBuilder; + + protected function setUp(): void + { + $this->contextBuilder = new CsvEncoderContextBuilder(); + } + + /** + * @dataProvider withersDataProvider + * + * @param array $values + */ + public function testWithers(array $values) + { + $context = $this->contextBuilder + ->withDelimiter($values[CsvEncoder::DELIMITER_KEY]) + ->withEnclosure($values[CsvEncoder::ENCLOSURE_KEY]) + ->withEscapeChar($values[CsvEncoder::ESCAPE_CHAR_KEY]) + ->withKeySeparator($values[CsvEncoder::KEY_SEPARATOR_KEY]) + ->withHeaders($values[CsvEncoder::HEADERS_KEY]) + ->withEscapedFormulas($values[CsvEncoder::ESCAPE_FORMULAS_KEY]) + ->withAsCollection($values[CsvEncoder::AS_COLLECTION_KEY]) + ->withNoHeaders($values[CsvEncoder::NO_HEADERS_KEY]) + ->withEndOfLine($values[CsvEncoder::END_OF_LINE]) + ->withOutputUtf8Bom($values[CsvEncoder::OUTPUT_UTF8_BOM_KEY]) + ->toArray(); + + $this->assertSame($values, $context); + } + + /** + * @return iterable|}> + */ + public function withersDataProvider(): iterable + { + yield 'With values' => [[ + CsvEncoder::DELIMITER_KEY => ';', + CsvEncoder::ENCLOSURE_KEY => '"', + CsvEncoder::ESCAPE_CHAR_KEY => '\\', + CsvEncoder::KEY_SEPARATOR_KEY => '_', + CsvEncoder::HEADERS_KEY => ['h1', 'h2'], + CsvEncoder::ESCAPE_FORMULAS_KEY => true, + CsvEncoder::AS_COLLECTION_KEY => true, + CsvEncoder::NO_HEADERS_KEY => false, + CsvEncoder::END_OF_LINE => 'EOL', + CsvEncoder::OUTPUT_UTF8_BOM_KEY => false, + ]]; + + yield 'With null values' => [[ + CsvEncoder::DELIMITER_KEY => null, + CsvEncoder::ENCLOSURE_KEY => null, + CsvEncoder::ESCAPE_CHAR_KEY => null, + CsvEncoder::KEY_SEPARATOR_KEY => null, + CsvEncoder::HEADERS_KEY => null, + CsvEncoder::ESCAPE_FORMULAS_KEY => null, + CsvEncoder::AS_COLLECTION_KEY => null, + CsvEncoder::NO_HEADERS_KEY => null, + CsvEncoder::END_OF_LINE => null, + CsvEncoder::OUTPUT_UTF8_BOM_KEY => null, + ]]; + } + + public function testWithersWithoutValue() + { + $context = $this->contextBuilder + ->withDelimiter(null) + ->withEnclosure(null) + ->withEscapeChar(null) + ->withKeySeparator(null) + ->withHeaders(null) + ->withEscapedFormulas(null) + ->withAsCollection(null) + ->withNoHeaders(null) + ->withEndOfLine(null) + ->withOutputUtf8Bom(null) + ->toArray(); + + $this->assertSame([ + CsvEncoder::DELIMITER_KEY => null, + CsvEncoder::ENCLOSURE_KEY => null, + CsvEncoder::ESCAPE_CHAR_KEY => null, + CsvEncoder::KEY_SEPARATOR_KEY => null, + CsvEncoder::HEADERS_KEY => null, + CsvEncoder::ESCAPE_FORMULAS_KEY => null, + CsvEncoder::AS_COLLECTION_KEY => null, + CsvEncoder::NO_HEADERS_KEY => null, + CsvEncoder::END_OF_LINE => null, + CsvEncoder::OUTPUT_UTF8_BOM_KEY => null, + ], $context); + } + + public function testCannotSetMultipleBytesAsDelimiter() + { + $this->expectException(InvalidArgumentException::class); + $this->contextBuilder->withDelimiter('ọ'); + } + + public function testCannotSetMultipleBytesAsEnclosure() + { + $this->expectException(InvalidArgumentException::class); + $this->contextBuilder->withEnclosure('ọ'); + } + + public function testCannotSetMultipleBytesAsEscapeChar() + { + $this->expectException(InvalidArgumentException::class); + $this->contextBuilder->withEscapeChar('ọ'); + } +} diff --git a/Tests/Context/Encoder/JsonEncoderContextBuilderTest.php b/Tests/Context/Encoder/JsonEncoderContextBuilderTest.php new file mode 100644 index 000000000..6f06525cd --- /dev/null +++ b/Tests/Context/Encoder/JsonEncoderContextBuilderTest.php @@ -0,0 +1,67 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Serializer\Tests\Context\Encoder; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\Serializer\Context\Encoder\JsonEncoderContextBuilder; +use Symfony\Component\Serializer\Encoder\JsonDecode; +use Symfony\Component\Serializer\Encoder\JsonEncode; + +/** + * @author Mathias Arlaud + */ +class JsonEncoderContextBuilderTest extends TestCase +{ + private JsonEncoderContextBuilder $contextBuilder; + + protected function setUp(): void + { + $this->contextBuilder = new JsonEncoderContextBuilder(); + } + + /** + * @dataProvider withersDataProvider + * + * @param array $values + */ + public function testWithers(array $values) + { + $context = $this->contextBuilder + ->withEncodeOptions($values[JsonEncode::OPTIONS]) + ->withDecodeOptions($values[JsonDecode::OPTIONS]) + ->withAssociative($values[JsonDecode::ASSOCIATIVE]) + ->withRecursionDepth($values[JsonDecode::RECURSION_DEPTH]) + ->toArray(); + + $this->assertSame($values, $context); + } + + /** + * @return iterable|}> + */ + public function withersDataProvider(): iterable + { + yield 'With values' => [[ + JsonEncode::OPTIONS => \JSON_PRETTY_PRINT, + JsonDecode::OPTIONS => \JSON_BIGINT_AS_STRING, + JsonDecode::ASSOCIATIVE => true, + JsonDecode::RECURSION_DEPTH => 1024, + ]]; + + yield 'With null values' => [[ + JsonEncode::OPTIONS => null, + JsonDecode::OPTIONS => null, + JsonDecode::ASSOCIATIVE => null, + JsonDecode::RECURSION_DEPTH => null, + ]]; + } +} diff --git a/Tests/Context/Encoder/XmlEncoderContextBuilderTest.php b/Tests/Context/Encoder/XmlEncoderContextBuilderTest.php new file mode 100644 index 000000000..f4b836c73 --- /dev/null +++ b/Tests/Context/Encoder/XmlEncoderContextBuilderTest.php @@ -0,0 +1,87 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Serializer\Tests\Context\Encoder; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\Serializer\Context\Encoder\XmlEncoderContextBuilder; +use Symfony\Component\Serializer\Encoder\XmlEncoder; + +/** + * @author Mathias Arlaud + */ +class XmlEncoderContextBuilderTest extends TestCase +{ + private XmlEncoderContextBuilder $contextBuilder; + + protected function setUp(): void + { + $this->contextBuilder = new XmlEncoderContextBuilder(); + } + + /** + * @dataProvider withersDataProvider + * + * @param array $values + */ + public function testWithers(array $values) + { + $context = $this->contextBuilder + ->withAsCollection($values[XmlEncoder::AS_COLLECTION]) + ->withDecoderIgnoredNodeTypes($values[XmlEncoder::DECODER_IGNORED_NODE_TYPES]) + ->withEncoderIgnoredNodeTypes($values[XmlEncoder::ENCODER_IGNORED_NODE_TYPES]) + ->withEncoding($values[XmlEncoder::ENCODING]) + ->withFormatOutput($values[XmlEncoder::FORMAT_OUTPUT]) + ->withLoadOptions($values[XmlEncoder::LOAD_OPTIONS]) + ->withRemoveEmptyTags($values[XmlEncoder::REMOVE_EMPTY_TAGS]) + ->withRootNodeName($values[XmlEncoder::ROOT_NODE_NAME]) + ->withStandalone($values[XmlEncoder::STANDALONE]) + ->withTypeCastAttributes($values[XmlEncoder::TYPE_CAST_ATTRIBUTES]) + ->withVersion($values[XmlEncoder::VERSION]) + ->toArray(); + + $this->assertSame($values, $context); + } + + /** + * @return iterable|}> + */ + public function withersDataProvider(): iterable + { + yield 'With values' => [[ + XmlEncoder::AS_COLLECTION => true, + XmlEncoder::DECODER_IGNORED_NODE_TYPES => [\XML_PI_NODE, \XML_COMMENT_NODE], + XmlEncoder::ENCODER_IGNORED_NODE_TYPES => [\XML_TEXT_NODE], + XmlEncoder::ENCODING => 'UTF-8', + XmlEncoder::FORMAT_OUTPUT => false, + XmlEncoder::LOAD_OPTIONS => \LIBXML_COMPACT, + XmlEncoder::REMOVE_EMPTY_TAGS => true, + XmlEncoder::ROOT_NODE_NAME => 'root', + XmlEncoder::STANDALONE => false, + XmlEncoder::TYPE_CAST_ATTRIBUTES => true, + XmlEncoder::VERSION => '1.0', + ]]; + + yield 'With null values' => [[ + XmlEncoder::AS_COLLECTION => null, + XmlEncoder::DECODER_IGNORED_NODE_TYPES => null, + XmlEncoder::ENCODER_IGNORED_NODE_TYPES => null, + XmlEncoder::ENCODING => null, + XmlEncoder::FORMAT_OUTPUT => null, + XmlEncoder::LOAD_OPTIONS => null, + XmlEncoder::REMOVE_EMPTY_TAGS => null, + XmlEncoder::ROOT_NODE_NAME => null, + XmlEncoder::STANDALONE => null, + XmlEncoder::TYPE_CAST_ATTRIBUTES => null, + XmlEncoder::VERSION => null, + ]]; + } +} diff --git a/Tests/Context/Encoder/YamlEncoderContextBuilderTest.php b/Tests/Context/Encoder/YamlEncoderContextBuilderTest.php new file mode 100644 index 000000000..72e650a83 --- /dev/null +++ b/Tests/Context/Encoder/YamlEncoderContextBuilderTest.php @@ -0,0 +1,66 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Serializer\Tests\Context\Encoder; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\Serializer\Context\Encoder\YamlEncoderContextBuilder; +use Symfony\Component\Serializer\Encoder\YamlEncoder; + +/** + * @author Mathias Arlaud + */ +class YamlEncoderContextBuilderTest extends TestCase +{ + private YamlEncoderContextBuilder $contextBuilder; + + protected function setUp(): void + { + $this->contextBuilder = new YamlEncoderContextBuilder(); + } + + /** + * @dataProvider withersDataProvider + * + * @param array $values + */ + public function testWithers(array $values) + { + $context = $this->contextBuilder + ->withIndentLevel($values[YamlEncoder::YAML_INDENT]) + ->withInlineThreshold($values[YamlEncoder::YAML_INLINE]) + ->withFlags($values[YamlEncoder::YAML_FLAGS]) + ->withPreservedEmptyObjects($values[YamlEncoder::PRESERVE_EMPTY_OBJECTS]) + ->toArray(); + + $this->assertSame($values, $context); + } + + /** + * @return iterable|}> + */ + public function withersDataProvider(): iterable + { + yield 'With values' => [[ + YamlEncoder::YAML_INDENT => 4, + YamlEncoder::YAML_INLINE => 16, + YamlEncoder::YAML_FLAGS => 128, + YamlEncoder::PRESERVE_EMPTY_OBJECTS => false, + ]]; + + yield 'With null values' => [[ + YamlEncoder::YAML_INDENT => null, + YamlEncoder::YAML_INLINE => null, + YamlEncoder::YAML_FLAGS => null, + YamlEncoder::PRESERVE_EMPTY_OBJECTS => null, + ]]; + } +} diff --git a/Tests/Context/Normalizer/AbstractNormalizerContextBuilderTest.php b/Tests/Context/Normalizer/AbstractNormalizerContextBuilderTest.php new file mode 100644 index 000000000..ffc9969b5 --- /dev/null +++ b/Tests/Context/Normalizer/AbstractNormalizerContextBuilderTest.php @@ -0,0 +1,93 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Serializer\Tests\Context\Normalizer; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\Serializer\Context\Normalizer\AbstractNormalizerContextBuilder; +use Symfony\Component\Serializer\Exception\InvalidArgumentException; +use Symfony\Component\Serializer\Normalizer\AbstractNormalizer; + +/** + * @author Mathias Arlaud + */ +class AbstractNormalizerContextBuilderTest extends TestCase +{ + private AbstractNormalizerContextBuilder $contextBuilder; + + protected function setUp(): void + { + $this->contextBuilder = new class() extends AbstractNormalizerContextBuilder {}; + } + + /** + * @dataProvider withersDataProvider + * + * @param array $values + */ + public function testWithers(array $values) + { + $context = $this->contextBuilder + ->withCircularReferenceLimit($values[AbstractNormalizer::CIRCULAR_REFERENCE_LIMIT]) + ->withObjectToPopulate($values[AbstractNormalizer::OBJECT_TO_POPULATE]) + ->withGroups($values[AbstractNormalizer::GROUPS]) + ->withAttributes($values[AbstractNormalizer::ATTRIBUTES]) + ->withAllowExtraAttributes($values[AbstractNormalizer::ALLOW_EXTRA_ATTRIBUTES]) + ->withDefaultContructorArguments($values[AbstractNormalizer::DEFAULT_CONSTRUCTOR_ARGUMENTS]) + ->withCallbacks($values[AbstractNormalizer::CALLBACKS]) + ->withCircularReferenceHandler($values[AbstractNormalizer::CIRCULAR_REFERENCE_HANDLER]) + ->withIgnoredAttributes($values[AbstractNormalizer::IGNORED_ATTRIBUTES]) + ->toArray(); + + $this->assertEquals($values, $context); + } + + /** + * @return iterable|}> + */ + public function withersDataProvider(): iterable + { + yield 'With values' => [[ + AbstractNormalizer::CIRCULAR_REFERENCE_LIMIT => 12, + AbstractNormalizer::OBJECT_TO_POPULATE => new \stdClass(), + AbstractNormalizer::GROUPS => ['group'], + AbstractNormalizer::ATTRIBUTES => ['attribute1', 'attribute2'], + AbstractNormalizer::ALLOW_EXTRA_ATTRIBUTES => true, + AbstractNormalizer::DEFAULT_CONSTRUCTOR_ARGUMENTS => [self::class => ['foo' => 'bar']], + AbstractNormalizer::CALLBACKS => [static function (): void {}], + AbstractNormalizer::CIRCULAR_REFERENCE_HANDLER => static function (): void {}, + AbstractNormalizer::IGNORED_ATTRIBUTES => ['attribute3'], + ]]; + + yield 'With null values' => [[ + AbstractNormalizer::CIRCULAR_REFERENCE_LIMIT => null, + AbstractNormalizer::OBJECT_TO_POPULATE => null, + AbstractNormalizer::GROUPS => null, + AbstractNormalizer::ATTRIBUTES => null, + AbstractNormalizer::ALLOW_EXTRA_ATTRIBUTES => null, + AbstractNormalizer::DEFAULT_CONSTRUCTOR_ARGUMENTS => null, + AbstractNormalizer::CALLBACKS => null, + AbstractNormalizer::CIRCULAR_REFERENCE_HANDLER => null, + AbstractNormalizer::IGNORED_ATTRIBUTES => null, + ]]; + } + + public function testCastSingleGroupToArray() + { + $this->assertSame([AbstractNormalizer::GROUPS => ['group']], $this->contextBuilder->withGroups('group')->toArray()); + } + + public function testCannotSetNonStringAttributes() + { + $this->expectException(InvalidArgumentException::class); + $this->contextBuilder->withAttributes(['attribute', ['nested attribute', 1]]); + } +} diff --git a/Tests/Context/Normalizer/AbstractObjectNormalizerContextBuilderTest.php b/Tests/Context/Normalizer/AbstractObjectNormalizerContextBuilderTest.php new file mode 100644 index 000000000..b9e0cf0f6 --- /dev/null +++ b/Tests/Context/Normalizer/AbstractObjectNormalizerContextBuilderTest.php @@ -0,0 +1,111 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Serializer\Tests\Context\Normalizer; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\Serializer\Context\Normalizer\AbstractObjectNormalizerContextBuilder; +use Symfony\Component\Serializer\Exception\InvalidArgumentException; +use Symfony\Component\Serializer\Normalizer\AbstractObjectNormalizer; + +/** + * @author Mathias Arlaud + */ +class AbstractObjectNormalizerContextBuilderTest extends TestCase +{ + private AbstractObjectNormalizerContextBuilder $contextBuilder; + + protected function setUp(): void + { + $this->contextBuilder = new class() extends AbstractObjectNormalizerContextBuilder {}; + } + + /** + * @dataProvider withersDataProvider + * + * @param array $values + */ + public function testWithers(array $values) + { + $context = $this->contextBuilder + ->withEnableMaxDepth($values[AbstractObjectNormalizer::ENABLE_MAX_DEPTH]) + ->withDepthKeyPattern($values[AbstractObjectNormalizer::DEPTH_KEY_PATTERN]) + ->withDisableTypeEnforcement($values[AbstractObjectNormalizer::DISABLE_TYPE_ENFORCEMENT]) + ->withSkipNullValues($values[AbstractObjectNormalizer::SKIP_NULL_VALUES]) + ->withSkipUninitializedValues($values[AbstractObjectNormalizer::SKIP_UNINITIALIZED_VALUES]) + ->withMaxDepthHandler($values[AbstractObjectNormalizer::MAX_DEPTH_HANDLER]) + ->withExcludeFromCacheKeys($values[AbstractObjectNormalizer::EXCLUDE_FROM_CACHE_KEY]) + ->withDeepObjectToPopulate($values[AbstractObjectNormalizer::DEEP_OBJECT_TO_POPULATE]) + ->withPreserveEmptyObjects($values[AbstractObjectNormalizer::PRESERVE_EMPTY_OBJECTS]) + ->toArray(); + + $this->assertSame($values, $context); + } + + /** + * @return iterable|}> + */ + public function withersDataProvider(): iterable + { + yield 'With values' => [[ + AbstractObjectNormalizer::ENABLE_MAX_DEPTH => true, + AbstractObjectNormalizer::DEPTH_KEY_PATTERN => '%s_%s', + AbstractObjectNormalizer::DISABLE_TYPE_ENFORCEMENT => false, + AbstractObjectNormalizer::SKIP_NULL_VALUES => true, + AbstractObjectNormalizer::SKIP_UNINITIALIZED_VALUES => false, + AbstractObjectNormalizer::MAX_DEPTH_HANDLER => static function (): void {}, + AbstractObjectNormalizer::EXCLUDE_FROM_CACHE_KEY => ['key'], + AbstractObjectNormalizer::DEEP_OBJECT_TO_POPULATE => true, + AbstractObjectNormalizer::PRESERVE_EMPTY_OBJECTS => false, + ]]; + + yield 'With null values' => [[ + AbstractObjectNormalizer::ENABLE_MAX_DEPTH => null, + AbstractObjectNormalizer::DEPTH_KEY_PATTERN => null, + AbstractObjectNormalizer::DISABLE_TYPE_ENFORCEMENT => null, + AbstractObjectNormalizer::SKIP_NULL_VALUES => null, + AbstractObjectNormalizer::SKIP_UNINITIALIZED_VALUES => null, + AbstractObjectNormalizer::MAX_DEPTH_HANDLER => null, + AbstractObjectNormalizer::EXCLUDE_FROM_CACHE_KEY => null, + AbstractObjectNormalizer::DEEP_OBJECT_TO_POPULATE => null, + AbstractObjectNormalizer::PRESERVE_EMPTY_OBJECTS => null, + ]]; + } + + /** + * @dataProvider validateDepthKeyPatternDataProvider + */ + public function testValidateDepthKeyPattern(string $pattern, bool $expectException) + { + $exception = null; + + try { + $this->contextBuilder->withDepthKeyPattern($pattern); + } catch (InvalidArgumentException $e) { + $exception = $e; + } + + $this->assertSame($expectException, null !== $exception); + } + + /** + * @return iterable + */ + public function validateDepthKeyPatternDataProvider(): iterable + { + yield ['depth_%s::%s', false]; + yield ['%%%s %%s %%%%%s', false]; + yield ['%s%%%s', false]; + yield ['', true]; + yield ['depth_%d::%s', true]; + yield ['%s_%s::%s', true]; + } +} diff --git a/Tests/Context/Normalizer/ConstraintViolationListNormalizerContextBuilderTest.php b/Tests/Context/Normalizer/ConstraintViolationListNormalizerContextBuilderTest.php new file mode 100644 index 000000000..b2b22755a --- /dev/null +++ b/Tests/Context/Normalizer/ConstraintViolationListNormalizerContextBuilderTest.php @@ -0,0 +1,70 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Serializer\Tests\Context\Normalizer; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\Serializer\Exception\InvalidArgumentException; +use Symfony\Component\Serializer\Context\Normalizer\ConstraintViolationListNormalizerContextBuilder; +use Symfony\Component\Serializer\Normalizer\ConstraintViolationListNormalizer; + +/** + * @author Mathias Arlaud + */ +class ConstraintViolationListNormalizerContextBuilderTest extends TestCase +{ + private ConstraintViolationListNormalizerContextBuilder $contextBuilder; + + protected function setUp(): void + { + $this->contextBuilder = new ConstraintViolationListNormalizerContextBuilder(); + } + + /** + * @dataProvider withersDataProvider + * + * @param array $values + */ + public function testWithers(array $values) + { + $context = $this->contextBuilder + ->withInstance($values[ConstraintViolationListNormalizer::INSTANCE]) + ->withStatus($values[ConstraintViolationListNormalizer::STATUS]) + ->withTitle($values[ConstraintViolationListNormalizer::TITLE]) + ->withType($values[ConstraintViolationListNormalizer::TYPE]) + ->withPayloadFields($values[ConstraintViolationListNormalizer::PAYLOAD_FIELDS]) + ->toArray(); + + $this->assertSame($values, $context); + } + + /** + * @return iterable}> + */ + public function withersDataProvider(): iterable + { + yield 'With values' => [[ + ConstraintViolationListNormalizer::INSTANCE => new \stdClass(), + ConstraintViolationListNormalizer::STATUS => 418, + ConstraintViolationListNormalizer::TITLE => 'title', + ConstraintViolationListNormalizer::TYPE => 'type', + ConstraintViolationListNormalizer::PAYLOAD_FIELDS => ['field'], + ]]; + + yield 'With null values' => [[ + ConstraintViolationListNormalizer::INSTANCE => null, + ConstraintViolationListNormalizer::STATUS => null, + ConstraintViolationListNormalizer::TITLE => null, + ConstraintViolationListNormalizer::TYPE => null, + ConstraintViolationListNormalizer::PAYLOAD_FIELDS => null, + ]]; + } +} diff --git a/Tests/Context/Normalizer/DateIntervalNormalizerContextBuilderTest.php b/Tests/Context/Normalizer/DateIntervalNormalizerContextBuilderTest.php new file mode 100644 index 000000000..b76da9d50 --- /dev/null +++ b/Tests/Context/Normalizer/DateIntervalNormalizerContextBuilderTest.php @@ -0,0 +1,57 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Serializer\Tests\Context\Normalizer; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\Serializer\Context\Normalizer\DateIntervalNormalizerContextBuilder; +use Symfony\Component\Serializer\Normalizer\DateIntervalNormalizer; + +/** + * @author Mathias Arlaud + */ +class DateIntervalNormalizerContextBuilderTest extends TestCase +{ + private DateIntervalNormalizerContextBuilder $contextBuilder; + + protected function setUp(): void + { + $this->contextBuilder = new DateIntervalNormalizerContextBuilder(); + } + + /** + * @dataProvider withersDataProvider + * + * @param array $values + */ + public function testWithers(array $values) + { + $context = $this->contextBuilder + ->withFormat($values[DateIntervalNormalizer::FORMAT_KEY]) + ->toArray(); + + $this->assertSame($values, $context); + } + + /** + * @return iterable}> + */ + public function withersDataProvider(): iterable + { + yield 'With values' => [[ + DateIntervalNormalizer::FORMAT_KEY => 'format', + ]]; + + yield 'With null values' => [[ + DateIntervalNormalizer::FORMAT_KEY => null, + ]]; + } +} diff --git a/Tests/Context/Normalizer/DateTimeNormalizerContextBuilderTest.php b/Tests/Context/Normalizer/DateTimeNormalizerContextBuilderTest.php new file mode 100644 index 000000000..aa8070541 --- /dev/null +++ b/Tests/Context/Normalizer/DateTimeNormalizerContextBuilderTest.php @@ -0,0 +1,72 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Serializer\Tests\Context\Normalizer; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\Serializer\Context\Normalizer\DateTimeNormalizerContextBuilder; +use Symfony\Component\Serializer\Exception\InvalidArgumentException; +use Symfony\Component\Serializer\Normalizer\DateTimeNormalizer; + +/** + * @author Mathias Arlaud + */ +class DateTimeNormalizerContextBuilderTest extends TestCase +{ + private DateTimeNormalizerContextBuilder $contextBuilder; + + protected function setUp(): void + { + $this->contextBuilder = new DateTimeNormalizerContextBuilder(); + } + + /** + * @dataProvider withersDataProvider + * + * @param array $values + */ + public function testWithers(array $values) + { + $context = $this->contextBuilder + ->withFormat($values[DateTimeNormalizer::FORMAT_KEY]) + ->withTimezone($values[DateTimeNormalizer::TIMEZONE_KEY]) + ->toArray(); + + $this->assertEquals($values, $context); + } + + /** + * @return iterable}> + */ + public function withersDataProvider(): iterable + { + yield 'With values' => [[ + DateTimeNormalizer::FORMAT_KEY => 'format', + DateTimeNormalizer::TIMEZONE_KEY => new \DateTimeZone('GMT'), + ]]; + + yield 'With null values' => [[ + DateTimeNormalizer::FORMAT_KEY => null, + DateTimeNormalizer::TIMEZONE_KEY => null, + ]]; + } + + public function testCastTimezoneStringToTimezone() + { + $this->assertEquals([DateTimeNormalizer::TIMEZONE_KEY => new \DateTimeZone('GMT')], $this->contextBuilder->withTimezone('GMT')->toArray()); + } + + public function testCannotSetInvalidTimezone() + { + $this->expectException(InvalidArgumentException::class); + $this->contextBuilder->withTimezone('not a timezone'); + } +} diff --git a/Tests/Context/Normalizer/FormErrorNormalizerContextBuilderTest.php b/Tests/Context/Normalizer/FormErrorNormalizerContextBuilderTest.php new file mode 100644 index 000000000..42915b55c --- /dev/null +++ b/Tests/Context/Normalizer/FormErrorNormalizerContextBuilderTest.php @@ -0,0 +1,63 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Serializer\Tests\Context\Normalizer; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\Serializer\Context\Normalizer\FormErrorNormalizerContextBuilder; +use Symfony\Component\Serializer\Normalizer\FormErrorNormalizer; + +/** + * @author Mathias Arlaud + */ +class FormErrorNormalizerContextBuilderTest extends TestCase +{ + private FormErrorNormalizerContextBuilder $contextBuilder; + + protected function setUp(): void + { + $this->contextBuilder = new FormErrorNormalizerContextBuilder(); + } + + /** + * @dataProvider withersDataProvider + * + * @param array $values + */ + public function testWithers(array $values) + { + $context = $this->contextBuilder + ->withTitle($values[FormErrorNormalizer::TITLE]) + ->withType($values[FormErrorNormalizer::TYPE]) + ->withStatusCode($values[FormErrorNormalizer::CODE]) + ->toArray(); + + $this->assertSame($values, $context); + } + + /** + * @return iterable}> + */ + public function withersDataProvider(): iterable + { + yield 'With values' => [[ + FormErrorNormalizer::TITLE => 'title', + FormErrorNormalizer::TYPE => 'type', + FormErrorNormalizer::CODE => 418, + ]]; + + yield 'With null values' => [[ + FormErrorNormalizer::TITLE => null, + FormErrorNormalizer::TYPE => null, + FormErrorNormalizer::CODE => null, + ]]; + } +} diff --git a/Tests/Context/Normalizer/ProblemNormalizerContextBuilderTest.php b/Tests/Context/Normalizer/ProblemNormalizerContextBuilderTest.php new file mode 100644 index 000000000..68f49dac9 --- /dev/null +++ b/Tests/Context/Normalizer/ProblemNormalizerContextBuilderTest.php @@ -0,0 +1,63 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Serializer\Tests\Context\Normalizer; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\Serializer\Context\Normalizer\ProblemNormalizerContextBuilder; +use Symfony\Component\Serializer\Normalizer\ProblemNormalizer; + +/** + * @author Mathias Arlaud + */ +class ProblemNormalizerContextBuilderTest extends TestCase +{ + private ProblemNormalizerContextBuilder $contextBuilder; + + protected function setUp(): void + { + $this->contextBuilder = new ProblemNormalizerContextBuilder(); + } + + /** + * @dataProvider withersDataProvider + * + * @param array $values + */ + public function testWithers(array $values) + { + $context = $this->contextBuilder + ->withTitle($values[ProblemNormalizer::TITLE]) + ->withType($values[ProblemNormalizer::TYPE]) + ->withStatusCode($values[ProblemNormalizer::STATUS]) + ->toArray(); + + $this->assertSame($values, $context); + } + + /** + * @return iterable}> + */ + public function withersDataProvider(): iterable + { + yield 'With values' => [[ + ProblemNormalizer::TITLE => 'title', + ProblemNormalizer::TYPE => 'type', + ProblemNormalizer::STATUS => 418, + ]]; + + yield 'With null values' => [[ + ProblemNormalizer::TITLE => null, + ProblemNormalizer::TYPE => null, + ProblemNormalizer::STATUS => null, + ]]; + } +} diff --git a/Tests/Context/Normalizer/UidNormalizerContextBuilderTest.php b/Tests/Context/Normalizer/UidNormalizerContextBuilderTest.php new file mode 100644 index 000000000..2bfe04091 --- /dev/null +++ b/Tests/Context/Normalizer/UidNormalizerContextBuilderTest.php @@ -0,0 +1,64 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Serializer\Tests\Context\Normalizer; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\Serializer\Exception\InvalidArgumentException; +use Symfony\Component\Serializer\Context\Normalizer\UidNormalizerContextBuilder; +use Symfony\Component\Serializer\Normalizer\UidNormalizer; + +/** + * @author Mathias Arlaud + */ +class UidNormalizerContextBuilderTest extends TestCase +{ + private UidNormalizerContextBuilder $contextBuilder; + + protected function setUp(): void + { + $this->contextBuilder = new UidNormalizerContextBuilder(); + } + + /** + * @dataProvider withersDataProvider + * + * @param array $values + */ + public function testWithers(array $values) + { + $context = $this->contextBuilder + ->withNormalizationFormat($values[UidNormalizer::NORMALIZATION_FORMAT_KEY]) + ->toArray(); + + $this->assertSame($values, $context); + } + + /** + * @return iterable}> + */ + public function withersDataProvider(): iterable + { + yield 'With values' => [[ + UidNormalizer::NORMALIZATION_FORMAT_KEY => UidNormalizer::NORMALIZATION_FORMAT_BASE32, + ]]; + + yield 'With null values' => [[ + UidNormalizer::NORMALIZATION_FORMAT_KEY => null, + ]]; + } + + public function testCannotSetInvalidUidNormalizationFormat() + { + $this->expectException(InvalidArgumentException::class); + $this->contextBuilder->withNormalizationFormat('invalid format'); + } +} diff --git a/Tests/Context/Normalizer/UnwrappingDenormalizerContextBuilderTest.php b/Tests/Context/Normalizer/UnwrappingDenormalizerContextBuilderTest.php new file mode 100644 index 000000000..5d257aee6 --- /dev/null +++ b/Tests/Context/Normalizer/UnwrappingDenormalizerContextBuilderTest.php @@ -0,0 +1,64 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Serializer\Tests\Context\Normalizer; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\Serializer\Exception\InvalidArgumentException; +use Symfony\Component\Serializer\Context\Normalizer\UnwrappingDenormalizerContextBuilder; +use Symfony\Component\Serializer\Normalizer\UnwrappingDenormalizer; + +/** + * @author Mathias Arlaud + */ +class UnwrappingDenormalizerContextBuilderTest extends TestCase +{ + private UnwrappingDenormalizerContextBuilder $contextBuilder; + + protected function setUp(): void + { + $this->contextBuilder = new UnwrappingDenormalizerContextBuilder(); + } + + /** + * @dataProvider withersDataProvider + * + * @param array $values + */ + public function testWithers(array $values) + { + $context = $this->contextBuilder + ->withUnwrapPath($values[UnwrappingDenormalizer::UNWRAP_PATH]) + ->toArray(); + + $this->assertSame($values, $context); + } + + /** + * @return iterable}> + */ + public function withersDataProvider(): iterable + { + yield 'With values' => [[ + UnwrappingDenormalizer::UNWRAP_PATH => 'foo' + ]]; + + yield 'With null values' => [[ + UnwrappingDenormalizer::UNWRAP_PATH => null, + ]]; + } + + public function testCannotSetInvalidPropertyPath() + { + $this->expectException(InvalidArgumentException::class); + $this->contextBuilder->withUnwrapPath('invalid path...'); + } +} diff --git a/Tests/Context/SerializerContextBuilderTest.php b/Tests/Context/SerializerContextBuilderTest.php new file mode 100644 index 000000000..ca13c6530 --- /dev/null +++ b/Tests/Context/SerializerContextBuilderTest.php @@ -0,0 +1,61 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Serializer\Tests\Context; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\Serializer\Context\SerializerContextBuilder; +use Symfony\Component\Serializer\Normalizer\DenormalizerInterface; +use Symfony\Component\Serializer\Serializer; + +/** + * @author Mathias Arlaud + */ +class SerializerContextBuilderTest extends TestCase +{ + private SerializerContextBuilder $contextBuilder; + + protected function setUp(): void + { + $this->contextBuilder = new SerializerContextBuilder(); + } + + /** + * @dataProvider withersDataProvider + * + * @param array $values + */ + public function testWithers(array $values) + { + $context = $this->contextBuilder + ->withEmptyArrayAsObject($values[Serializer::EMPTY_ARRAY_AS_OBJECT]) + ->withCollectDenormalizationErrors($values[DenormalizerInterface::COLLECT_DENORMALIZATION_ERRORS]) + ->toArray(); + + $this->assertSame($values, $context); + } + + /** + * @return iterable}> + */ + public function withersDataProvider(): iterable + { + yield 'With values' => [[ + Serializer::EMPTY_ARRAY_AS_OBJECT => true, + DenormalizerInterface::COLLECT_DENORMALIZATION_ERRORS => false, + ]]; + + yield 'With null values' => [[ + Serializer::EMPTY_ARRAY_AS_OBJECT => null, + DenormalizerInterface::COLLECT_DENORMALIZATION_ERRORS => null, + ]]; + } +} From 61a1ab9fb0b1b3098d6770cfb84957e3654e1dd7 Mon Sep 17 00:00:00 2001 From: Robin Chalas Date: Tue, 8 Feb 2022 00:05:51 +0100 Subject: [PATCH 026/297] [Serializer] Make `ContextBuilderTrait::$context` private --- Context/ContextBuilderTrait.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Context/ContextBuilderTrait.php b/Context/ContextBuilderTrait.php index 73eb66e1f..97907d7b5 100644 --- a/Context/ContextBuilderTrait.php +++ b/Context/ContextBuilderTrait.php @@ -19,7 +19,7 @@ trait ContextBuilderTrait /** * @var array */ - protected array $context = []; + private array $context = []; protected function with(string $key, mixed $value): static { From 5594fee73607ceab61c30de7dbd5fed090a10a17 Mon Sep 17 00:00:00 2001 From: Thomas Calvet Date: Tue, 15 Feb 2022 16:42:18 +0100 Subject: [PATCH 027/297] Leverage the match expression --- Normalizer/AbstractObjectNormalizer.php | 18 ++++++---------- .../AbstractObjectNormalizerTest.php | 21 ++++++++----------- 2 files changed, 15 insertions(+), 24 deletions(-) diff --git a/Normalizer/AbstractObjectNormalizer.php b/Normalizer/AbstractObjectNormalizer.php index 09481036d..acecec58d 100644 --- a/Normalizer/AbstractObjectNormalizer.php +++ b/Normalizer/AbstractObjectNormalizer.php @@ -494,18 +494,12 @@ private function validateAndDenormalize(array $types, string $currentClass, stri return (float) $data; } - switch ($data) { - case 'NaN': - return \NAN; - case 'INF': - return \INF; - case '-INF': - return -\INF; - default: - throw NotNormalizableValueException::createForUnexpectedDataType(sprintf('The type of the "%s" attribute for class "%s" must be float ("%s" given).', $attribute, $currentClass, $data), $data, [Type::BUILTIN_TYPE_FLOAT], $context['deserialization_path'] ?? null); - } - - break; + return match ($data) { + 'NaN' => \NAN, + 'INF' => \INF, + '-INF' => -\INF, + default => throw NotNormalizableValueException::createForUnexpectedDataType(sprintf('The type of the "%s" attribute for class "%s" must be float ("%s" given).', $attribute, $currentClass, $data), $data, [Type::BUILTIN_TYPE_FLOAT], $context['deserialization_path'] ?? null), + }; } } diff --git a/Tests/Normalizer/AbstractObjectNormalizerTest.php b/Tests/Normalizer/AbstractObjectNormalizerTest.php index 7609686b8..fe006c187 100644 --- a/Tests/Normalizer/AbstractObjectNormalizerTest.php +++ b/Tests/Normalizer/AbstractObjectNormalizerTest.php @@ -258,18 +258,15 @@ public function testDenormalizeWithNestedDiscriminatorMap() $classDiscriminatorResolver = new class() implements ClassDiscriminatorResolverInterface { public function getMappingForClass(string $class): ?ClassDiscriminatorMapping { - switch ($class) { - case AbstractDummy::class: - return new ClassDiscriminatorMapping('type', [ - 'foo' => AbstractDummyFirstChild::class, - ]); - case AbstractDummyFirstChild::class: - return new ClassDiscriminatorMapping('nested_type', [ - 'bar' => AbstractDummySecondChild::class, - ]); - default: - return null; - } + return match ($class) { + AbstractDummy::class => new ClassDiscriminatorMapping('type', [ + 'foo' => AbstractDummyFirstChild::class, + ]), + AbstractDummyFirstChild::class => new ClassDiscriminatorMapping('nested_type', [ + 'bar' => AbstractDummySecondChild::class, + ]), + default => null, + }; } public function getMappingForMappedObject($object): ?ClassDiscriminatorMapping From e82b0400db063a208336626f0e5d344ba5d70370 Mon Sep 17 00:00:00 2001 From: Thomas Calvet Date: Mon, 20 Dec 2021 10:15:21 +0100 Subject: [PATCH 028/297] [Serializer] Deprecate support for abstract uid denormalization in UidNormalizer --- CHANGELOG.md | 2 ++ Normalizer/UidNormalizer.php | 18 +++++++++++++++--- Tests/Normalizer/UidNormalizerTest.php | 18 ++++++++++++++++++ 3 files changed, 35 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3f16b2ba4..28b194edb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,8 @@ CHANGELOG * Deprecate `ContextAwareDenormalizerInterface`, use `DenormalizerInterface` instead * Deprecate `ContextAwareEncoderInterface`, use `EncoderInterface` instead * Deprecate `ContextAwareDecoderInterface`, use `DecoderInterface` instead + * Deprecate supporting denormalization for `AbstractUid` in `UidNormalizer`, use one of `AbstractUid` child class instead + * Deprecate denormalizing to an abstract class in `UidNormalizer` 6.0 --- diff --git a/Normalizer/UidNormalizer.php b/Normalizer/UidNormalizer.php index 02d47e744..f52fcc72f 100644 --- a/Normalizer/UidNormalizer.php +++ b/Normalizer/UidNormalizer.php @@ -76,10 +76,16 @@ public function supportsNormalization(mixed $data, string $format = null, array public function denormalize(mixed $data, string $type, string $format = null, array $context = []): mixed { try { - return AbstractUid::class !== $type ? $type::fromString($data) : Uuid::fromString($data); + if (AbstractUid::class === $type) { + trigger_deprecation('symfony/serializer', '6.1', 'Denormalizing to an abstract class in "%s" is deprecated.', __CLASS__); + + return Uuid::fromString($data); + } + + return $type::fromString($data); } catch (\InvalidArgumentException|\TypeError $exception) { throw NotNormalizableValueException::createForUnexpectedDataType(sprintf('The data is not a valid "%s" string representation.', $type), $data, [Type::BUILTIN_TYPE_STRING], $context['deserialization_path'] ?? null, true); - } catch (\Error $e) { + } catch (\Error $e) { // @deprecated remove this catch block in 7.0 if (str_starts_with($e->getMessage(), 'Cannot instantiate abstract class')) { return $this->denormalize($data, AbstractUid::class, $format, $context); } @@ -93,7 +99,13 @@ public function denormalize(mixed $data, string $type, string $format = null, ar */ public function supportsDenormalization(mixed $data, string $type, string $format = null, array $context = []): bool { - return is_a($type, AbstractUid::class, true); + if (AbstractUid::class === $type) { + trigger_deprecation('symfony/serializer', '6.1', 'Supporting denormalization for the "%s" type in "%s" is deprecated, use one of "%s" child class instead.', AbstractUid::class, __CLASS__, AbstractUid::class); + + return true; + } + + return is_subclass_of($type, AbstractUid::class, true); } /** diff --git a/Tests/Normalizer/UidNormalizerTest.php b/Tests/Normalizer/UidNormalizerTest.php index 14fa10866..88fc7d4b5 100644 --- a/Tests/Normalizer/UidNormalizerTest.php +++ b/Tests/Normalizer/UidNormalizerTest.php @@ -3,6 +3,7 @@ namespace Symfony\Component\Serializer\Tests\Normalizer; use PHPUnit\Framework\TestCase; +use Symfony\Bridge\PhpUnit\ExpectDeprecationTrait; use Symfony\Component\Serializer\Exception\LogicException; use Symfony\Component\Serializer\Normalizer\UidNormalizer; use Symfony\Component\Uid\AbstractUid; @@ -16,6 +17,8 @@ class UidNormalizerTest extends TestCase { + use ExpectDeprecationTrait; + /** * @var UidNormalizer */ @@ -134,8 +137,13 @@ public function testSupportsDenormalizationForNonUid() $this->assertFalse($this->normalizer->supportsDenormalization('foo', \stdClass::class)); } + /** + * @group legacy + */ public function testSupportOurAbstractUid() { + $this->expectDeprecation('Since symfony/serializer 6.1: Supporting denormalization for the "Symfony\Component\Uid\AbstractUid" type in "Symfony\Component\Serializer\Normalizer\UidNormalizer" is deprecated, use one of "Symfony\Component\Uid\AbstractUid" child class instead.'); + $this->assertTrue($this->normalizer->supportsDenormalization('1ea6ecef-eb9a-66fe-b62b-957b45f17e43', AbstractUid::class)); } @@ -152,13 +160,23 @@ public function testDenormalize($uuidString, $class) $this->assertEquals($class::fromString($uuidString), $this->normalizer->denormalize($uuidString, $class)); } + /** + * @group legacy + */ public function testDenormalizeOurAbstractUid() { + $this->expectDeprecation('Since symfony/serializer 6.1: Denormalizing to an abstract class in "Symfony\Component\Serializer\Normalizer\UidNormalizer" is deprecated.'); + $this->assertEquals(Uuid::fromString($uuidString = '1ea6ecef-eb9a-66fe-b62b-957b45f17e43'), $this->normalizer->denormalize($uuidString, AbstractUid::class)); } + /** + * @group legacy + */ public function testDenormalizeCustomAbstractUid() { + $this->expectDeprecation('Since symfony/serializer 6.1: Denormalizing to an abstract class in "Symfony\Component\Serializer\Normalizer\UidNormalizer" is deprecated.'); + $this->assertEquals(Uuid::fromString($uuidString = '1ea6ecef-eb9a-66fe-b62b-957b45f17e43'), $this->normalizer->denormalize($uuidString, TestAbstractCustomUid::class)); } From 02ad7dbcba1aeaf1f27e18d4e2ed34d43404869d Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Wed, 9 Feb 2022 15:00:38 +0100 Subject: [PATCH 029/297] Bump minimum version of PHP to 8.1 --- Normalizer/MimeMessageNormalizer.php | 1 - Normalizer/PropertyNormalizer.php | 10 ------- Tests/Encoder/XmlEncoderTest.php | 5 +--- Tests/Normalizer/BackedEnumNormalizerTest.php | 27 ------------------- composer.json | 2 +- 5 files changed, 2 insertions(+), 43 deletions(-) diff --git a/Normalizer/MimeMessageNormalizer.php b/Normalizer/MimeMessageNormalizer.php index 7c195bf30..fea86e863 100644 --- a/Normalizer/MimeMessageNormalizer.php +++ b/Normalizer/MimeMessageNormalizer.php @@ -40,7 +40,6 @@ public function __construct(PropertyNormalizer $normalizer) $this->normalizer = $normalizer; $this->headerClassMap = (new \ReflectionClassConstant(Headers::class, 'HEADER_CLASS_MAP'))->getValue(); $this->headersProperty = new \ReflectionProperty(Headers::class, 'headers'); - $this->headersProperty->setAccessible(true); } public function setSerializer(SerializerInterface $serializer) diff --git a/Normalizer/PropertyNormalizer.php b/Normalizer/PropertyNormalizer.php index dda4246b0..5856b28e4 100644 --- a/Normalizer/PropertyNormalizer.php +++ b/Normalizer/PropertyNormalizer.php @@ -132,11 +132,6 @@ protected function getAttributeValue(object $object, string $attribute, string $ return null; } - // Override visibility - if (!$reflectionProperty->isPublic()) { - $reflectionProperty->setAccessible(true); - } - if ($reflectionProperty->hasType()) { return $reflectionProperty->getValue($object); } @@ -170,11 +165,6 @@ protected function setAttributeValue(object $object, string $attribute, mixed $v return; } - // Override visibility - if (!$reflectionProperty->isPublic()) { - $reflectionProperty->setAccessible(true); - } - $reflectionProperty->setValue($object, $value); } diff --git a/Tests/Encoder/XmlEncoderTest.php b/Tests/Encoder/XmlEncoderTest.php index adf4a1c9f..f99b3b333 100644 --- a/Tests/Encoder/XmlEncoderTest.php +++ b/Tests/Encoder/XmlEncoderTest.php @@ -922,10 +922,7 @@ private function createXmlEncoderWithDateTimeNormalizer(): XmlEncoder return $encoder; } - /** - * @return MockObject&NormalizerInterface - */ - private function createMockDateTimeNormalizer(): NormalizerInterface + private function createMockDateTimeNormalizer(): MockObject&NormalizerInterface { $mock = $this->createMock(CustomNormalizer::class); diff --git a/Tests/Normalizer/BackedEnumNormalizerTest.php b/Tests/Normalizer/BackedEnumNormalizerTest.php index f36120465..8ea2ac2b0 100644 --- a/Tests/Normalizer/BackedEnumNormalizerTest.php +++ b/Tests/Normalizer/BackedEnumNormalizerTest.php @@ -34,9 +34,6 @@ protected function setUp(): void $this->normalizer = new BackedEnumNormalizer(); } - /** - * @requires PHP 8.1 - */ public function testSupportsNormalization() { $this->assertTrue($this->normalizer->supportsNormalization(StringBackedEnumDummy::GET)); @@ -45,27 +42,18 @@ public function testSupportsNormalization() $this->assertFalse($this->normalizer->supportsNormalization(new \stdClass())); } - /** - * @requires PHP 8.1 - */ public function testNormalize() { $this->assertSame('GET', $this->normalizer->normalize(StringBackedEnumDummy::GET)); $this->assertSame(200, $this->normalizer->normalize(IntegerBackedEnumDummy::SUCCESS)); } - /** - * @requires PHP 8.1 - */ public function testNormalizeBadObjectTypeThrowsException() { $this->expectException(InvalidArgumentException::class); $this->normalizer->normalize(new \stdClass()); } - /** - * @requires PHP 8.1 - */ public function testSupportsDenormalization() { $this->assertTrue($this->normalizer->supportsDenormalization(null, StringBackedEnumDummy::class)); @@ -74,45 +62,30 @@ public function testSupportsDenormalization() $this->assertFalse($this->normalizer->supportsDenormalization(null, \stdClass::class)); } - /** - * @requires PHP 8.1 - */ public function testDenormalize() { $this->assertSame(StringBackedEnumDummy::GET, $this->normalizer->denormalize('GET', StringBackedEnumDummy::class)); $this->assertSame(IntegerBackedEnumDummy::SUCCESS, $this->normalizer->denormalize(200, IntegerBackedEnumDummy::class)); } - /** - * @requires PHP 8.1 - */ public function testDenormalizeNullValueThrowsException() { $this->expectException(NotNormalizableValueException::class); $this->normalizer->denormalize(null, StringBackedEnumDummy::class); } - /** - * @requires PHP 8.1 - */ public function testDenormalizeBooleanValueThrowsException() { $this->expectException(NotNormalizableValueException::class); $this->normalizer->denormalize(true, StringBackedEnumDummy::class); } - /** - * @requires PHP 8.1 - */ public function testDenormalizeObjectThrowsException() { $this->expectException(NotNormalizableValueException::class); $this->normalizer->denormalize(new \stdClass(), StringBackedEnumDummy::class); } - /** - * @requires PHP 8.1 - */ public function testDenormalizeBadBackingValueThrowsException() { $this->expectException(NotNormalizableValueException::class); diff --git a/composer.json b/composer.json index 159c5fe0f..5888e662d 100644 --- a/composer.json +++ b/composer.json @@ -16,7 +16,7 @@ } ], "require": { - "php": ">=8.0.2", + "php": ">=8.1", "symfony/polyfill-ctype": "~1.8" }, "require-dev": { From 384208d97ba09fdeeb49096bf3b3db4c2c48dcef Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Thu, 24 Mar 2022 19:04:39 +0100 Subject: [PATCH 030/297] Fix merge --- Tests/SerializerTest.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Tests/SerializerTest.php b/Tests/SerializerTest.php index 715449b1d..67e36ef1f 100644 --- a/Tests/SerializerTest.php +++ b/Tests/SerializerTest.php @@ -1193,7 +1193,7 @@ class DummyUnionType * * @return $this */ - public function setChanged($changed): self + public function setChanged($changed): static { $this->changed = $changed; From f29973873ab70aeb4b5ea498c4b2f02e2eeb64a9 Mon Sep 17 00:00:00 2001 From: Thomas Calvet Date: Thu, 31 Mar 2022 18:23:12 +0200 Subject: [PATCH 031/297] Leverage non-capturing catches --- Encoder/ChainDecoder.php | 2 +- Encoder/ChainEncoder.php | 2 +- Normalizer/AbstractObjectNormalizer.php | 4 ++-- Normalizer/ObjectNormalizer.php | 2 +- Normalizer/PropertyNormalizer.php | 6 +++--- Normalizer/UidNormalizer.php | 2 +- 6 files changed, 9 insertions(+), 9 deletions(-) diff --git a/Encoder/ChainDecoder.php b/Encoder/ChainDecoder.php index 3348e3e19..0f7f94fad 100644 --- a/Encoder/ChainDecoder.php +++ b/Encoder/ChainDecoder.php @@ -47,7 +47,7 @@ public function supportsDecoding(string $format, array $context = []): bool { try { $this->getDecoder($format, $context); - } catch (RuntimeException $e) { + } catch (RuntimeException) { return false; } diff --git a/Encoder/ChainEncoder.php b/Encoder/ChainEncoder.php index 514e30909..dec57e494 100644 --- a/Encoder/ChainEncoder.php +++ b/Encoder/ChainEncoder.php @@ -47,7 +47,7 @@ public function supportsEncoding(string $format, array $context = []): bool { try { $this->getEncoder($format, $context); - } catch (RuntimeException $e) { + } catch (RuntimeException) { return false; } diff --git a/Normalizer/AbstractObjectNormalizer.php b/Normalizer/AbstractObjectNormalizer.php index 516989871..6409ab213 100644 --- a/Normalizer/AbstractObjectNormalizer.php +++ b/Normalizer/AbstractObjectNormalizer.php @@ -381,7 +381,7 @@ 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) { try { $attributeContext[self::OBJECT_TO_POPULATE] = $this->getAttributeValue($object, $attribute, $format, $attributeContext); - } catch (NoSuchPropertyException $e) { + } catch (NoSuchPropertyException) { } } @@ -722,7 +722,7 @@ private function getCacheKey(?string $format, array $context): bool|string 'context' => $context, 'ignored' => $context[self::IGNORED_ATTRIBUTES] ?? $this->defaultContext[self::IGNORED_ATTRIBUTES], ])); - } catch (\Exception $e) { + } catch (\Exception) { // The context cannot be serialized, skip the cache return false; } diff --git a/Normalizer/ObjectNormalizer.php b/Normalizer/ObjectNormalizer.php index 78a456887..0dbc8bc3e 100644 --- a/Normalizer/ObjectNormalizer.php +++ b/Normalizer/ObjectNormalizer.php @@ -147,7 +147,7 @@ protected function setAttributeValue(object $object, string $attribute, mixed $v { try { $this->propertyAccessor->setValue($object, $attribute, $value); - } catch (NoSuchPropertyException $exception) { + } catch (NoSuchPropertyException) { // Properties not found are ignored } } diff --git a/Normalizer/PropertyNormalizer.php b/Normalizer/PropertyNormalizer.php index 5856b28e4..1c68896aa 100644 --- a/Normalizer/PropertyNormalizer.php +++ b/Normalizer/PropertyNormalizer.php @@ -93,7 +93,7 @@ protected function isAllowedAttribute(object|string $classOrObject, string $attr if ($reflectionProperty->isStatic()) { return false; } - } catch (\ReflectionException $reflectionException) { + } catch (\ReflectionException) { return false; } @@ -128,7 +128,7 @@ protected function getAttributeValue(object $object, string $attribute, string $ { try { $reflectionProperty = $this->getReflectionProperty($object, $attribute); - } catch (\ReflectionException $reflectionException) { + } catch (\ReflectionException) { return null; } @@ -157,7 +157,7 @@ protected function setAttributeValue(object $object, string $attribute, mixed $v { try { $reflectionProperty = $this->getReflectionProperty($object, $attribute); - } catch (\ReflectionException $reflectionException) { + } catch (\ReflectionException) { return; } diff --git a/Normalizer/UidNormalizer.php b/Normalizer/UidNormalizer.php index f52fcc72f..264ddd801 100644 --- a/Normalizer/UidNormalizer.php +++ b/Normalizer/UidNormalizer.php @@ -83,7 +83,7 @@ public function denormalize(mixed $data, string $type, string $format = null, ar } return $type::fromString($data); - } catch (\InvalidArgumentException|\TypeError $exception) { + } catch (\InvalidArgumentException|\TypeError) { throw NotNormalizableValueException::createForUnexpectedDataType(sprintf('The data is not a valid "%s" string representation.', $type), $data, [Type::BUILTIN_TYPE_STRING], $context['deserialization_path'] ?? null, true); } catch (\Error $e) { // @deprecated remove this catch block in 7.0 if (str_starts_with($e->getMessage(), 'Cannot instantiate abstract class')) { From f52aefcfe3e546cc4cfbfe8d1ad87da0dd960c7b Mon Sep 17 00:00:00 2001 From: Mathias Arlaud Date: Fri, 4 Feb 2022 12:48:48 +0100 Subject: [PATCH 032/297] [Serializer] Add serializer profiler --- CHANGELOG.md | 1 + DataCollector/SerializerDataCollector.php | 239 ++++++++++++++ Debug/TraceableEncoder.php | 126 ++++++++ Debug/TraceableNormalizer.php | 155 +++++++++ Debug/TraceableSerializer.php | 182 +++++++++++ DependencyInjection/SerializerPass.php | 27 +- Encoder/ChainEncoder.php | 5 + .../SerializerDataCollectorTest.php | 293 ++++++++++++++++++ Tests/Debug/TraceableEncoderTest.php | 106 +++++++ Tests/Debug/TraceableNormalizerTest.php | 106 +++++++ Tests/Debug/TraceableSerializerTest.php | 194 ++++++++++++ .../SerializerPassTest.php | 34 ++ Tests/Encoder/ChainEncoderTest.php | 16 + 13 files changed, 1480 insertions(+), 4 deletions(-) create mode 100644 DataCollector/SerializerDataCollector.php create mode 100644 Debug/TraceableEncoder.php create mode 100644 Debug/TraceableNormalizer.php create mode 100644 Debug/TraceableSerializer.php create mode 100644 Tests/DataCollector/SerializerDataCollectorTest.php create mode 100644 Tests/Debug/TraceableEncoderTest.php create mode 100644 Tests/Debug/TraceableNormalizerTest.php create mode 100644 Tests/Debug/TraceableSerializerTest.php diff --git a/CHANGELOG.md b/CHANGELOG.md index 28b194edb..9bee69dbd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,7 @@ CHANGELOG 6.1 --- + * Add `TraceableSerializer`, `TraceableNormalizer`, `TraceableEncoder` and `SerializerDataCollector` to integrate with the web profiler * Add the ability to create contexts using context builders * Set `Context` annotation as not final * Deprecate `ContextAwareNormalizerInterface`, use `NormalizerInterface` instead diff --git a/DataCollector/SerializerDataCollector.php b/DataCollector/SerializerDataCollector.php new file mode 100644 index 000000000..57a8aecab --- /dev/null +++ b/DataCollector/SerializerDataCollector.php @@ -0,0 +1,239 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Serializer\DataCollector; + +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\HttpKernel\DataCollector\DataCollector; +use Symfony\Component\HttpKernel\DataCollector\LateDataCollectorInterface; +use Symfony\Component\Serializer\Debug\TraceableSerializer; +use Symfony\Component\VarDumper\Cloner\Data; + +/** + * @author Mathias Arlaud + * + * @final + * + * @internal + */ +class SerializerDataCollector extends DataCollector implements LateDataCollectorInterface +{ + private array $collected = []; + + public function reset(): void + { + $this->data = []; + $this->collected = []; + } + + /** + * {@inheritDoc} + */ + public function collect(Request $request, Response $response, \Throwable $exception = null): void + { + // Everything is collected during the request, and formatted on kernel terminate. + } + + /** + * {@inheritDoc} + */ + public function getName(): string + { + return 'serializer'; + } + + public function getData(): Data|array + { + return $this->data; + } + + public function getHandledCount(): int + { + return array_sum(array_map('count', $this->data)); + } + + public function getTotalTime(): float + { + $totalTime = 0; + + foreach ($this->data as $handled) { + $totalTime += array_sum(array_map(fn (array $el): float => $el['time'], $handled)); + } + + return $totalTime; + } + + public function collectSerialize(string $traceId, mixed $data, string $format, array $context, float $time): void + { + unset($context[TraceableSerializer::DEBUG_TRACE_ID]); + + $this->collected[$traceId] = array_merge( + $this->collected[$traceId] ?? [], + compact('data', 'format', 'context', 'time'), + ['method' => 'serialize'], + ); + } + + public function collectDeserialize(string $traceId, mixed $data, string $type, string $format, array $context, float $time): void + { + unset($context[TraceableSerializer::DEBUG_TRACE_ID]); + + $this->collected[$traceId] = array_merge( + $this->collected[$traceId] ?? [], + compact('data', 'format', 'type', 'context', 'time'), + ['method' => 'deserialize'], + ); + } + + public function collectNormalize(string $traceId, mixed $data, ?string $format, array $context, float $time): void + { + unset($context[TraceableSerializer::DEBUG_TRACE_ID]); + + $this->collected[$traceId] = array_merge( + $this->collected[$traceId] ?? [], + compact('data', 'format', 'context', 'time'), + ['method' => 'normalize'], + ); + } + + public function collectDenormalize(string $traceId, mixed $data, string $type, ?string $format, array $context, float $time): void + { + unset($context[TraceableSerializer::DEBUG_TRACE_ID]); + + $this->collected[$traceId] = array_merge( + $this->collected[$traceId] ?? [], + compact('data', 'format', 'type', 'context', 'time'), + ['method' => 'denormalize'], + ); + } + + public function collectEncode(string $traceId, mixed $data, ?string $format, array $context, float $time): void + { + unset($context[TraceableSerializer::DEBUG_TRACE_ID]); + + $this->collected[$traceId] = array_merge( + $this->collected[$traceId] ?? [], + compact('data', 'format', 'context', 'time'), + ['method' => 'encode'], + ); + } + + public function collectDecode(string $traceId, mixed $data, ?string $format, array $context, float $time): void + { + unset($context[TraceableSerializer::DEBUG_TRACE_ID]); + + $this->collected[$traceId] = array_merge( + $this->collected[$traceId] ?? [], + compact('data', 'format', 'context', 'time'), + ['method' => 'decode'], + ); + } + + public function collectNormalization(string $traceId, string $normalizer, float $time): void + { + $method = 'normalize'; + + $this->collected[$traceId]['normalization'][] = compact('normalizer', 'method', 'time'); + } + + public function collectDenormalization(string $traceId, string $normalizer, float $time): void + { + $method = 'denormalize'; + + $this->collected[$traceId]['normalization'][] = compact('normalizer', 'method', 'time'); + } + + public function collectEncoding(string $traceId, string $encoder, float $time): void + { + $method = 'encode'; + + $this->collected[$traceId]['encoding'][] = compact('encoder', 'method', 'time'); + } + + public function collectDecoding(string $traceId, string $encoder, float $time): void + { + $method = 'decode'; + + $this->collected[$traceId]['encoding'][] = compact('encoder', 'method', 'time'); + } + + /** + * {@inheritDoc} + */ + public function lateCollect(): void + { + $this->data = [ + 'serialize' => [], + 'deserialize' => [], + 'normalize' => [], + 'denormalize' => [], + 'encode' => [], + 'decode' => [], + ]; + + foreach ($this->collected as $collected) { + $data = [ + 'data' => $this->cloneVar($collected['data']), + 'dataType' => get_debug_type($collected['data']), + 'type' => $collected['type'] ?? null, + 'format' => $collected['format'], + 'time' => $collected['time'], + 'context' => $this->cloneVar($collected['context']), + 'normalization' => [], + 'encoding' => [], + ]; + + if (isset($collected['normalization'])) { + $mainNormalization = array_pop($collected['normalization']); + + $data['normalizer'] = ['time' => $mainNormalization['time']] + $this->getMethodLocation($mainNormalization['normalizer'], $mainNormalization['method']); + + foreach ($collected['normalization'] as $normalization) { + if (!isset($data['normalization'][$normalization['normalizer']])) { + $data['normalization'][$normalization['normalizer']] = ['time' => 0, 'calls' => 0] + $this->getMethodLocation($normalization['normalizer'], $normalization['method']); + } + + ++$data['normalization'][$normalization['normalizer']]['calls']; + $data['normalization'][$normalization['normalizer']]['time'] += $normalization['time']; + } + } + + if (isset($collected['encoding'])) { + $mainEncoding = array_pop($collected['encoding']); + + $data['encoder'] = ['time' => $mainEncoding['time']] + $this->getMethodLocation($mainEncoding['encoder'], $mainEncoding['method']); + + foreach ($collected['encoding'] as $encoding) { + if (!isset($data['encoding'][$encoding['encoder']])) { + $data['encoding'][$encoding['encoder']] = ['time' => 0, 'calls' => 0] + $this->getMethodLocation($encoding['encoder'], $encoding['method']); + } + + ++$data['encoding'][$encoding['encoder']]['calls']; + $data['encoding'][$encoding['encoder']]['time'] += $encoding['time']; + } + } + + $this->data[$collected['method']][] = $data; + } + } + + private function getMethodLocation(string $class, string $method): array + { + $reflection = new \ReflectionClass($class); + + return [ + 'class' => $reflection->getShortName(), + 'file' => $reflection->getFileName(), + 'line' => $reflection->getMethod($method)->getStartLine(), + ]; + } +} diff --git a/Debug/TraceableEncoder.php b/Debug/TraceableEncoder.php new file mode 100644 index 000000000..cd4a351c6 --- /dev/null +++ b/Debug/TraceableEncoder.php @@ -0,0 +1,126 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Serializer\Debug; + +use Symfony\Component\Serializer\DataCollector\SerializerDataCollector; +use Symfony\Component\Serializer\Encoder\DecoderInterface; +use Symfony\Component\Serializer\Encoder\EncoderInterface; +use Symfony\Component\Serializer\SerializerAwareInterface; +use Symfony\Component\Serializer\SerializerInterface; +use Symfony\Component\Serializer\Tests\Encoder\NormalizationAwareEncoder; + +/** + * Collects some data about encoding. + * + * @author Mathias Arlaud + * + * @final + * + * @internal + */ +class TraceableEncoder implements EncoderInterface, DecoderInterface, SerializerAwareInterface +{ + public function __construct( + private EncoderInterface|DecoderInterface $encoder, + private SerializerDataCollector $dataCollector, + ) { + } + + /** + * {@inheritDoc} + */ + public function encode(mixed $data, string $format, array $context = []): string + { + if (!$this->encoder instanceof EncoderInterface) { + throw new \BadMethodCallException(sprintf('The "%s()" method cannot be called as nested encoder doesn\'t implements "%s".', __METHOD__, EncoderInterface::class)); + } + + $startTime = microtime(true); + $encoded = $this->encoder->encode($data, $format, $context); + $time = microtime(true) - $startTime; + + if ($traceId = ($context[TraceableSerializer::DEBUG_TRACE_ID] ?? null)) { + $this->dataCollector->collectEncoding($traceId, \get_class($this->encoder), $time); + } + + return $encoded; + } + + /** + * {@inheritDoc} + * + * @param array $context + */ + public function supportsEncoding(string $format /*, array $context = [] */): bool + { + if (!$this->encoder instanceof EncoderInterface) { + return false; + } + + $context = \func_num_args() > 1 ? func_get_arg(1) : []; + + return $this->encoder->supportsEncoding($format, $context); + } + + /** + * {@inheritDoc} + */ + public function decode(string $data, string $format, array $context = []): mixed + { + if (!$this->encoder instanceof DecoderInterface) { + throw new \BadMethodCallException(sprintf('The "%s()" method cannot be called as nested encoder doesn\'t implements "%s".', __METHOD__, DecoderInterface::class)); + } + + $startTime = microtime(true); + $encoded = $this->encoder->decode($data, $format, $context); + $time = microtime(true) - $startTime; + + if ($traceId = ($context[TraceableSerializer::DEBUG_TRACE_ID] ?? null)) { + $this->dataCollector->collectDecoding($traceId, \get_class($this->encoder), $time); + } + + return $encoded; + } + + /** + * {@inheritDoc} + * + * @param array $context + */ + public function supportsDecoding(string $format /*, array $context = [] */): bool + { + if (!$this->encoder instanceof DecoderInterface) { + return false; + } + + $context = \func_num_args() > 1 ? func_get_arg(1) : []; + + return $this->encoder->supportsDecoding($format, $context); + } + + /** + * {@inheritDoc} + */ + public function setSerializer(SerializerInterface $serializer) + { + if (!$this->encoder instanceof SerializerAwareInterface) { + return; + } + + $this->encoder->setSerializer($serializer); + } + + public function needsNormalization(): bool + { + return !$this->encoder instanceof NormalizationAwareEncoder; + } +} diff --git a/Debug/TraceableNormalizer.php b/Debug/TraceableNormalizer.php new file mode 100644 index 000000000..e32475e3c --- /dev/null +++ b/Debug/TraceableNormalizer.php @@ -0,0 +1,155 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Serializer\Debug; + +use Symfony\Component\Serializer\DataCollector\SerializerDataCollector; +use Symfony\Component\Serializer\Normalizer\CacheableSupportsMethodInterface; +use Symfony\Component\Serializer\Normalizer\DenormalizerAwareInterface; +use Symfony\Component\Serializer\Normalizer\DenormalizerInterface; +use Symfony\Component\Serializer\Normalizer\NormalizerAwareInterface; +use Symfony\Component\Serializer\Normalizer\NormalizerInterface; +use Symfony\Component\Serializer\SerializerAwareInterface; +use Symfony\Component\Serializer\SerializerInterface; + +/** + * Collects some data about normalization. + * + * @author Mathias Arlaud + * + * @final + * + * @internal + */ +class TraceableNormalizer implements NormalizerInterface, DenormalizerInterface, SerializerAwareInterface, NormalizerAwareInterface, DenormalizerAwareInterface, CacheableSupportsMethodInterface +{ + public function __construct( + private NormalizerInterface|DenormalizerInterface $normalizer, + private SerializerDataCollector $dataCollector, + ) { + } + + /** + * {@inheritDoc} + */ + public function normalize(mixed $object, string $format = null, array $context = []): array|string|int|float|bool|\ArrayObject|null + { + if (!$this->normalizer instanceof NormalizerInterface) { + throw new \BadMethodCallException(sprintf('The "%s()" method cannot be called as nested normalizer doesn\'t implements "%s".', __METHOD__, NormalizerInterface::class)); + } + + $startTime = microtime(true); + $normalized = $this->normalizer->normalize($object, $format, $context); + $time = microtime(true) - $startTime; + + if ($traceId = ($context[TraceableSerializer::DEBUG_TRACE_ID] ?? null)) { + $this->dataCollector->collectNormalization($traceId, \get_class($this->normalizer), $time); + } + + return $normalized; + } + + /** + * {@inheritDoc} + * + * @param array $context + */ + public function supportsNormalization(mixed $data, string $format = null /*, array $context = [] */): bool + { + if (!$this->normalizer instanceof NormalizerInterface) { + return false; + } + + $context = \func_num_args() > 2 ? func_get_arg(2) : []; + + return $this->normalizer->supportsNormalization($data, $format, $context); + } + + /** + * {@inheritDoc} + */ + public function denormalize(mixed $data, string $type, string $format = null, array $context = []): mixed + { + if (!$this->normalizer instanceof DenormalizerInterface) { + throw new \BadMethodCallException(sprintf('The "%s()" method cannot be called as nested normalizer doesn\'t implements "%s".', __METHOD__, DenormalizerInterface::class)); + } + + $startTime = microtime(true); + $denormalized = $this->normalizer->denormalize($data, $type, $format, $context); + $time = microtime(true) - $startTime; + + if ($traceId = ($context[TraceableSerializer::DEBUG_TRACE_ID] ?? null)) { + $this->dataCollector->collectDenormalization($traceId, \get_class($this->normalizer), $time); + } + + return $denormalized; + } + + /** + * {@inheritDoc} + * + * @param array $context + */ + public function supportsDenormalization(mixed $data, string $type, string $format = null /*, array $context = [] */): bool + { + if (!$this->normalizer instanceof DenormalizerInterface) { + return false; + } + + $context = \func_num_args() > 3 ? func_get_arg(3) : []; + + return $this->normalizer->supportsDenormalization($data, $type, $format, $context); + } + + /** + * {@inheritDoc} + */ + public function setSerializer(SerializerInterface $serializer) + { + if (!$this->normalizer instanceof SerializerAwareInterface) { + return; + } + + $this->normalizer->setSerializer($serializer); + } + + /** + * {@inheritDoc} + */ + public function setNormalizer(NormalizerInterface $normalizer) + { + if (!$this->normalizer instanceof NormalizerAwareInterface) { + return; + } + + $this->normalizer->setNormalizer($normalizer); + } + + /** + * {@inheritDoc} + */ + public function setDenormalizer(DenormalizerInterface $denormalizer) + { + if (!$this->normalizer instanceof DenormalizerAwareInterface) { + return; + } + + $this->normalizer->setDenormalizer($denormalizer); + } + + /** + * {@inheritDoc} + */ + public function hasCacheableSupportsMethod(): bool + { + return $this->normalizer instanceof CacheableSupportsMethodInterface && $this->normalizer->hasCacheableSupportsMethod(); + } +} diff --git a/Debug/TraceableSerializer.php b/Debug/TraceableSerializer.php new file mode 100644 index 000000000..d98d0017b --- /dev/null +++ b/Debug/TraceableSerializer.php @@ -0,0 +1,182 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Serializer\Debug; + +use Symfony\Component\Serializer\DataCollector\SerializerDataCollector; +use Symfony\Component\Serializer\Encoder\DecoderInterface; +use Symfony\Component\Serializer\Encoder\EncoderInterface; +use Symfony\Component\Serializer\Normalizer\DenormalizerInterface; +use Symfony\Component\Serializer\Normalizer\NormalizerInterface; +use Symfony\Component\Serializer\SerializerInterface; + +/** + * Collects some data about serialization. + * + * @author Mathias Arlaud + * + * @final + * @internal + */ +class TraceableSerializer implements SerializerInterface, NormalizerInterface, DenormalizerInterface, EncoderInterface, DecoderInterface +{ + public const DEBUG_TRACE_ID = 'debug_trace_id'; + + public function __construct( + private SerializerInterface&NormalizerInterface&DenormalizerInterface&EncoderInterface&DecoderInterface $serializer, + private SerializerDataCollector $dataCollector, + ) { + } + + /** + * {@inheritdoc} + */ + final public function serialize(mixed $data, string $format, array $context = []): string + { + $context[self::DEBUG_TRACE_ID] = $traceId = uniqid(); + + $startTime = microtime(true); + $result = $this->serializer->serialize($data, $format, $context); + $time = microtime(true) - $startTime; + + $this->dataCollector->collectSerialize($traceId, $data, $format, $context, $time); + + return $result; + } + + /** + * {@inheritdoc} + */ + final public function deserialize(mixed $data, string $type, string $format, array $context = []): mixed + { + $context[self::DEBUG_TRACE_ID] = $traceId = uniqid(); + + $startTime = microtime(true); + $result = $this->serializer->deserialize($data, $type, $format, $context); + $time = microtime(true) - $startTime; + + $this->dataCollector->collectDeserialize($traceId, $data, $type, $format, $context, $time); + + return $result; + } + + /** + * {@inheritdoc} + */ + final public function normalize(mixed $object, string $format = null, array $context = []): array|string|int|float|bool|\ArrayObject|null + { + $context[self::DEBUG_TRACE_ID] = $traceId = uniqid(); + + $startTime = microtime(true); + $result = $this->serializer->normalize($object, $format, $context); + $time = microtime(true) - $startTime; + + $this->dataCollector->collectNormalize($traceId, $object, $format, $context, $time); + + return $result; + } + + /** + * {@inheritdoc} + */ + final public function denormalize(mixed $data, string $type, string $format = null, array $context = []): mixed + { + $context[self::DEBUG_TRACE_ID] = $traceId = uniqid(); + + $startTime = microtime(true); + $result = $this->serializer->denormalize($data, $type, $format, $context); + $time = microtime(true) - $startTime; + + $this->dataCollector->collectDenormalize($traceId, $data, $type, $format, $context, $time); + + return $result; + } + + /** + * {@inheritdoc} + */ + final public function encode(mixed $data, string $format, array $context = []): string + { + $context[self::DEBUG_TRACE_ID] = $traceId = uniqid(); + + $startTime = microtime(true); + $result = $this->serializer->encode($data, $format, $context); + $time = microtime(true) - $startTime; + + $this->dataCollector->collectEncode($traceId, $data, $format, $context, $time); + + return $result; + } + + /** + * {@inheritdoc} + */ + final public function decode(string $data, string $format, array $context = []): mixed + { + $context[self::DEBUG_TRACE_ID] = $traceId = uniqid(); + + $startTime = microtime(true); + $result = $this->serializer->decode($data, $format, $context); + $time = microtime(true) - $startTime; + + $this->dataCollector->collectDecode($traceId, $data, $format, $context, $time); + + return $result; + } + + /** + * {@inheritdoc} + * + * @param array $context + */ + final public function supportsNormalization(mixed $data, string $format = null /*, array $context = [] */): bool + { + $context = \func_num_args() > 2 ? \func_get_arg(2) : []; + + return $this->serializer->supportsNormalization($data, $format, $context); + } + + /** + * {@inheritdoc} + * + * @param array $context + */ + final public function supportsDenormalization(mixed $data, string $type, string $format = null /*, array $context = [] */): bool + { + $context = \func_num_args() > 3 ? \func_get_arg(3) : []; + + return $this->serializer->supportsDenormalization($data, $type, $format, $context); + } + + /** + * {@inheritdoc} + * + * @param array $context + */ + final public function supportsEncoding(string $format /*, array $context = [] */): bool + { + $context = \func_num_args() > 1 ? \func_get_arg(1) : []; + + return $this->serializer->supportsEncoding($format, $context); + } + + /** + * {@inheritdoc} + * + * @param array $context + */ + final public function supportsDecoding(string $format /*, array $context = [] */): bool + { + $context = \func_num_args() > 1 ? \func_get_arg(1) : []; + + return $this->serializer->supportsDecoding($format, $context); + } +} diff --git a/DependencyInjection/SerializerPass.php b/DependencyInjection/SerializerPass.php index 58ade72fe..00bd0eea0 100644 --- a/DependencyInjection/SerializerPass.php +++ b/DependencyInjection/SerializerPass.php @@ -16,6 +16,9 @@ use Symfony\Component\DependencyInjection\Compiler\PriorityTaggedServiceTrait; use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\DependencyInjection\Exception\RuntimeException; +use Symfony\Component\DependencyInjection\Reference; +use Symfony\Component\Serializer\Debug\TraceableEncoder; +use Symfony\Component\Serializer\Debug\TraceableNormalizer; /** * Adds all services with the tags "serializer.encoder" and "serializer.normalizer" as @@ -34,18 +37,34 @@ public function process(ContainerBuilder $container) return; } - if (!$normalizers = $this->findAndSortTaggedServices('serializer.normalizer', $container)) { + if (!$normalizers = $container->findTaggedServiceIds('serializer.normalizer')) { throw new RuntimeException('You must tag at least one service as "serializer.normalizer" to use the "serializer" service.'); } + if ($container->getParameter('kernel.debug') && $container->hasDefinition('serializer.data_collector')) { + foreach (array_keys($normalizers) as $normalizer) { + $container->register('debug.'.$normalizer, TraceableNormalizer::class) + ->setDecoratedService($normalizer, null, 255) + ->setArguments([new Reference('debug.'.$normalizer.'.inner'), new Reference('serializer.data_collector')]); + } + } + $serializerDefinition = $container->getDefinition('serializer'); - $serializerDefinition->replaceArgument(0, $normalizers); + $serializerDefinition->replaceArgument(0, $this->findAndSortTaggedServices('serializer.normalizer', $container)); - if (!$encoders = $this->findAndSortTaggedServices('serializer.encoder', $container)) { + if (!$encoders = $container->findTaggedServiceIds('serializer.encoder')) { throw new RuntimeException('You must tag at least one service as "serializer.encoder" to use the "serializer" service.'); } - $serializerDefinition->replaceArgument(1, $encoders); + if ($container->getParameter('kernel.debug') && $container->hasDefinition('serializer.data_collector')) { + foreach (array_keys($encoders) as $encoder) { + $container->register('debug.'.$encoder, TraceableEncoder::class) + ->setDecoratedService($encoder, null, 255) + ->setArguments([new Reference('debug.'.$encoder.'.inner'), new Reference('serializer.data_collector')]); + } + } + + $serializerDefinition->replaceArgument(1, $this->findAndSortTaggedServices('serializer.encoder', $container)); if (!$container->hasParameter('serializer.default_context')) { return; diff --git a/Encoder/ChainEncoder.php b/Encoder/ChainEncoder.php index dec57e494..70d7c9fd1 100644 --- a/Encoder/ChainEncoder.php +++ b/Encoder/ChainEncoder.php @@ -11,6 +11,7 @@ namespace Symfony\Component\Serializer\Encoder; +use Symfony\Component\Serializer\Debug\TraceableEncoder; use Symfony\Component\Serializer\Exception\RuntimeException; /** @@ -61,6 +62,10 @@ public function needsNormalization(string $format, array $context = []): bool { $encoder = $this->getEncoder($format, $context); + if ($encoder instanceof TraceableEncoder) { + return $encoder->needsNormalization(); + } + if (!$encoder instanceof NormalizationAwareInterface) { return true; } diff --git a/Tests/DataCollector/SerializerDataCollectorTest.php b/Tests/DataCollector/SerializerDataCollectorTest.php new file mode 100644 index 000000000..7b22b6064 --- /dev/null +++ b/Tests/DataCollector/SerializerDataCollectorTest.php @@ -0,0 +1,293 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Serializer\Tests\DataCollector; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\Serializer\DataCollector\SerializerDataCollector; +use Symfony\Component\Serializer\Encoder\CsvEncoder; +use Symfony\Component\Serializer\Encoder\JsonEncoder; +use Symfony\Component\Serializer\Normalizer\DateTimeNormalizer; +use Symfony\Component\Serializer\Normalizer\ObjectNormalizer; + +class SerializerDataCollectorTest extends TestCase +{ + public function testCollectSerialize() + { + $dataCollector = new SerializerDataCollector(); + + $dataCollector->collectSerialize('traceIdOne', 'data', 'format', ['foo' => 'bar'], 1.0); + $dataCollector->collectDeserialize('traceIdTwo', 'data', 'type', 'format', ['foo' => 'bar'], 1.0); + + $dataCollector->lateCollect(); + $collectedData = $this->castCollectedData($dataCollector->getData()); + + $this->assertSame([[ + 'data' => 'data', + 'dataType' => 'string', + 'type' => null, + 'format' => 'format', + 'time' => 1.0, + 'context' => ['foo' => 'bar'], + 'normalization' => [], + 'encoding' => [], + ]], $collectedData['serialize']); + + $this->assertSame([[ + 'data' => 'data', + 'dataType' => 'string', + 'type' => 'type', + 'format' => 'format', + 'time' => 1.0, + 'context' => ['foo' => 'bar'], + 'normalization' => [], + 'encoding' => [], + ]], $collectedData['deserialize']); + } + + public function testCollectNormalize() + { + $dataCollector = new SerializerDataCollector(); + + $dataCollector->collectNormalize('traceIdOne', 'data', 'format', ['foo' => 'bar'], 1.0); + $dataCollector->collectDenormalize('traceIdTwo', 'data', 'type', 'format', ['foo' => 'bar'], 1.0); + + $dataCollector->lateCollect(); + $collectedData = $this->castCollectedData($dataCollector->getData()); + + $this->assertSame([[ + 'data' => 'data', + 'dataType' => 'string', + 'type' => null, + 'format' => 'format', + 'time' => 1.0, + 'context' => ['foo' => 'bar'], + 'normalization' => [], + 'encoding' => [], + ]], $collectedData['normalize']); + + $this->assertSame([[ + 'data' => 'data', + 'dataType' => 'string', + 'type' => 'type', + 'format' => 'format', + 'time' => 1.0, + 'context' => ['foo' => 'bar'], + 'normalization' => [], + 'encoding' => [], + ]], $collectedData['denormalize']); + } + + public function testCollectEncode() + { + $dataCollector = new SerializerDataCollector(); + + $dataCollector->collectEncode('traceIdOne', 'data', 'format', ['foo' => 'bar'], 1.0); + $dataCollector->collectDecode('traceIdTwo', 'data', 'format', ['foo' => 'bar'], 1.0); + + $dataCollector->lateCollect(); + $collectedData = $this->castCollectedData($dataCollector->getData()); + + $this->assertSame([[ + 'data' => 'data', + 'dataType' => 'string', + 'type' => null, + 'format' => 'format', + 'time' => 1.0, + 'context' => ['foo' => 'bar'], + 'normalization' => [], + 'encoding' => [], + ]], $collectedData['encode']); + + $this->assertSame([[ + 'data' => 'data', + 'dataType' => 'string', + 'type' => null, + 'format' => 'format', + 'time' => 1.0, + 'context' => ['foo' => 'bar'], + 'normalization' => [], + 'encoding' => [], + ]], $collectedData['decode']); + } + + public function testCollectNormalization() + { + $dataCollector = new SerializerDataCollector(); + + $dataCollector->collectNormalize('traceIdOne', 'data', 'format', ['foo' => 'bar'], 20.0); + $dataCollector->collectDenormalize('traceIdTwo', 'data', 'type', 'format', ['foo' => 'bar'], 20.0); + + $dataCollector->collectNormalization('traceIdOne', DateTimeNormalizer::class, 1.0); + $dataCollector->collectNormalization('traceIdOne', DateTimeNormalizer::class, 2.0); + $dataCollector->collectNormalization('traceIdOne', ObjectNormalizer::class, 5.0); + $dataCollector->collectNormalization('traceIdOne', ObjectNormalizer::class, 10.0); + + $dataCollector->collectNormalization('traceIdTwo', DateTimeNormalizer::class, 1.0); + $dataCollector->collectNormalization('traceIdTwo', DateTimeNormalizer::class, 2.0); + $dataCollector->collectNormalization('traceIdTwo', ObjectNormalizer::class, 5.0); + $dataCollector->collectNormalization('traceIdTwo', ObjectNormalizer::class, 10.0); + + $dataCollector->lateCollect(); + $collectedData = $dataCollector->getData(); + + $this->assertSame(10.0, $collectedData['normalize'][0]['normalizer']['time']); + $this->assertSame('ObjectNormalizer', $collectedData['normalize'][0]['normalizer']['class']); + $this->assertArrayHasKey('file', $collectedData['normalize'][0]['normalizer']); + $this->assertArrayHasKey('line', $collectedData['normalize'][0]['normalizer']); + + $this->assertSame(3.0, $collectedData['normalize'][0]['normalization'][DateTimeNormalizer::class]['time']); + $this->assertSame(2, $collectedData['normalize'][0]['normalization'][DateTimeNormalizer::class]['calls']); + $this->assertSame('DateTimeNormalizer', $collectedData['normalize'][0]['normalization'][DateTimeNormalizer::class]['class']); + $this->assertArrayHasKey('file', $collectedData['normalize'][0]['normalization'][DateTimeNormalizer::class]); + $this->assertArrayHasKey('line', $collectedData['normalize'][0]['normalization'][DateTimeNormalizer::class]); + + $this->assertSame(5.0, $collectedData['normalize'][0]['normalization'][ObjectNormalizer::class]['time']); + $this->assertSame(1, $collectedData['normalize'][0]['normalization'][ObjectNormalizer::class]['calls']); + $this->assertSame('ObjectNormalizer', $collectedData['normalize'][0]['normalization'][ObjectNormalizer::class]['class']); + $this->assertArrayHasKey('file', $collectedData['normalize'][0]['normalization'][ObjectNormalizer::class]); + $this->assertArrayHasKey('line', $collectedData['normalize'][0]['normalization'][ObjectNormalizer::class]); + + $this->assertSame(10.0, $collectedData['denormalize'][0]['normalizer']['time']); + $this->assertSame('ObjectNormalizer', $collectedData['denormalize'][0]['normalizer']['class']); + $this->assertArrayHasKey('file', $collectedData['denormalize'][0]['normalizer']); + $this->assertArrayHasKey('line', $collectedData['denormalize'][0]['normalizer']); + + $this->assertSame(3.0, $collectedData['denormalize'][0]['normalization'][DateTimeNormalizer::class]['time']); + $this->assertSame(2, $collectedData['denormalize'][0]['normalization'][DateTimeNormalizer::class]['calls']); + $this->assertSame('DateTimeNormalizer', $collectedData['denormalize'][0]['normalization'][DateTimeNormalizer::class]['class']); + $this->assertArrayHasKey('file', $collectedData['denormalize'][0]['normalization'][DateTimeNormalizer::class]); + $this->assertArrayHasKey('line', $collectedData['denormalize'][0]['normalization'][DateTimeNormalizer::class]); + + $this->assertSame(5.0, $collectedData['denormalize'][0]['normalization'][ObjectNormalizer::class]['time']); + $this->assertSame(1, $collectedData['denormalize'][0]['normalization'][ObjectNormalizer::class]['calls']); + $this->assertSame('ObjectNormalizer', $collectedData['denormalize'][0]['normalization'][ObjectNormalizer::class]['class']); + $this->assertArrayHasKey('file', $collectedData['denormalize'][0]['normalization'][ObjectNormalizer::class]); + $this->assertArrayHasKey('line', $collectedData['denormalize'][0]['normalization'][ObjectNormalizer::class]); + } + + public function testCollectEncoding() + { + $dataCollector = new SerializerDataCollector(); + + $dataCollector->collectEncode('traceIdOne', 'data', 'format', ['foo' => 'bar'], 20.0); + $dataCollector->collectDecode('traceIdTwo', 'data', 'format', ['foo' => 'bar'], 20.0); + + $dataCollector->collectEncoding('traceIdOne', JsonEncoder::class, 1.0); + $dataCollector->collectEncoding('traceIdOne', JsonEncoder::class, 2.0); + $dataCollector->collectEncoding('traceIdOne', CsvEncoder::class, 5.0); + $dataCollector->collectEncoding('traceIdOne', CsvEncoder::class, 10.0); + + $dataCollector->collectDecoding('traceIdTwo', JsonEncoder::class, 1.0); + $dataCollector->collectDecoding('traceIdTwo', JsonEncoder::class, 2.0); + $dataCollector->collectDecoding('traceIdTwo', CsvEncoder::class, 5.0); + $dataCollector->collectDecoding('traceIdTwo', CsvEncoder::class, 10.0); + + $dataCollector->lateCollect(); + $collectedData = $dataCollector->getData(); + + $this->assertSame(10.0, $collectedData['encode'][0]['encoder']['time']); + $this->assertSame('CsvEncoder', $collectedData['encode'][0]['encoder']['class']); + $this->assertArrayHasKey('file', $collectedData['encode'][0]['encoder']); + $this->assertArrayHasKey('line', $collectedData['encode'][0]['encoder']); + + $this->assertSame(3.0, $collectedData['encode'][0]['encoding'][JsonEncoder::class]['time']); + $this->assertSame(2, $collectedData['encode'][0]['encoding'][JsonEncoder::class]['calls']); + $this->assertSame('JsonEncoder', $collectedData['encode'][0]['encoding'][JsonEncoder::class]['class']); + $this->assertArrayHasKey('file', $collectedData['encode'][0]['encoding'][JsonEncoder::class]); + $this->assertArrayHasKey('line', $collectedData['encode'][0]['encoding'][JsonEncoder::class]); + + $this->assertSame(5.0, $collectedData['encode'][0]['encoding'][CsvEncoder::class]['time']); + $this->assertSame(1, $collectedData['encode'][0]['encoding'][CsvEncoder::class]['calls']); + $this->assertSame('CsvEncoder', $collectedData['encode'][0]['encoding'][CsvEncoder::class]['class']); + $this->assertArrayHasKey('file', $collectedData['encode'][0]['encoding'][CsvEncoder::class]); + $this->assertArrayHasKey('line', $collectedData['encode'][0]['encoding'][CsvEncoder::class]); + + $this->assertSame(10.0, $collectedData['decode'][0]['encoder']['time']); + $this->assertSame('CsvEncoder', $collectedData['decode'][0]['encoder']['class']); + $this->assertArrayHasKey('file', $collectedData['decode'][0]['encoder']); + $this->assertArrayHasKey('line', $collectedData['decode'][0]['encoder']); + + $this->assertSame(3.0, $collectedData['decode'][0]['encoding'][JsonEncoder::class]['time']); + $this->assertSame(2, $collectedData['decode'][0]['encoding'][JsonEncoder::class]['calls']); + $this->assertSame('JsonEncoder', $collectedData['decode'][0]['encoding'][JsonEncoder::class]['class']); + $this->assertArrayHasKey('file', $collectedData['decode'][0]['encoding'][JsonEncoder::class]); + $this->assertArrayHasKey('line', $collectedData['decode'][0]['encoding'][JsonEncoder::class]); + + $this->assertSame(5.0, $collectedData['decode'][0]['encoding'][CsvEncoder::class]['time']); + $this->assertSame(1, $collectedData['decode'][0]['encoding'][CsvEncoder::class]['calls']); + $this->assertSame('CsvEncoder', $collectedData['decode'][0]['encoding'][CsvEncoder::class]['class']); + $this->assertArrayHasKey('file', $collectedData['decode'][0]['encoding'][CsvEncoder::class]); + $this->assertArrayHasKey('line', $collectedData['decode'][0]['encoding'][CsvEncoder::class]); + } + + public function testCountHandled() + { + $dataCollector = new SerializerDataCollector(); + + $dataCollector->collectSerialize('traceIdOne', 'data', 'format', ['foo' => 'bar'], 1.0); + $dataCollector->collectDeserialize('traceIdTwo', 'data', 'type', 'format', ['foo' => 'bar'], 1.0); + $dataCollector->collectNormalize('traceIdThree', 'data', 'format', ['foo' => 'bar'], 20.0); + $dataCollector->collectDenormalize('traceIdFour', 'data', 'type', 'format', ['foo' => 'bar'], 20.0); + $dataCollector->collectEncode('traceIdFive', 'data', 'format', ['foo' => 'bar'], 20.0); + $dataCollector->collectDecode('traceIdSix', 'data', 'format', ['foo' => 'bar'], 20.0); + $dataCollector->collectSerialize('traceIdSeven', 'data', 'format', ['foo' => 'bar'], 1.0); + + $dataCollector->lateCollect(); + + $this->assertSame(7, $dataCollector->getHandledCount()); + } + + public function testGetTotalTime() + { + $dataCollector = new SerializerDataCollector(); + + $dataCollector->collectSerialize('traceIdOne', 'data', 'format', ['foo' => 'bar'], 1.0); + $dataCollector->collectDeserialize('traceIdTwo', 'data', 'type', 'format', ['foo' => 'bar'], 2.0); + $dataCollector->collectNormalize('traceIdThree', 'data', 'format', ['foo' => 'bar'], 3.0); + $dataCollector->collectDenormalize('traceIdFour', 'data', 'type', 'format', ['foo' => 'bar'], 4.0); + $dataCollector->collectEncode('traceIdFive', 'data', 'format', ['foo' => 'bar'], 5.0); + $dataCollector->collectDecode('traceIdSix', 'data', 'format', ['foo' => 'bar'], 6.0); + $dataCollector->collectSerialize('traceIdSeven', 'data', 'format', ['foo' => 'bar'], 7.0); + + $dataCollector->lateCollect(); + + $this->assertSame(28.0, $dataCollector->getTotalTime()); + } + + public function testReset() + { + $dataCollector = new SerializerDataCollector(); + + $dataCollector->collectSerialize('traceIdOne', 'data', 'format', ['foo' => 'bar'], 1.0); + $dataCollector->lateCollect(); + + $this->assertNotSame([], $dataCollector->getData()); + + $dataCollector->reset(); + $this->assertSame([], $dataCollector->getData()); + } + + /** + * Cast cloned vars to be able to test nested values. + */ + private function castCollectedData(array $collectedData): array + { + foreach ($collectedData as $method => $collectedMethodData) { + foreach ($collectedMethodData as $i => $collected) { + $collectedData[$method][$i]['data'] = $collected['data']->getValue(); + $collectedData[$method][$i]['context'] = $collected['context']->getValue(true); + } + } + + return $collectedData; + } +} diff --git a/Tests/Debug/TraceableEncoderTest.php b/Tests/Debug/TraceableEncoderTest.php new file mode 100644 index 000000000..aa2393bbe --- /dev/null +++ b/Tests/Debug/TraceableEncoderTest.php @@ -0,0 +1,106 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Serializer\Tests\Debug; + +use BadMethodCallException; +use PHPUnit\Framework\TestCase; +use Symfony\Component\Serializer\DataCollector\SerializerDataCollector; +use Symfony\Component\Serializer\Debug\TraceableEncoder; +use Symfony\Component\Serializer\Debug\TraceableSerializer; +use Symfony\Component\Serializer\Encoder\DecoderInterface; +use Symfony\Component\Serializer\Encoder\EncoderInterface; + +class TraceableEncoderTest extends TestCase +{ + public function testForwardsToEncoder() + { + $encoder = $this->createMock(EncoderInterface::class); + $encoder + ->expects($this->once()) + ->method('encode') + ->with('data', 'format', $this->isType('array')) + ->willReturn('encoded'); + + $decoder = $this->createMock(DecoderInterface::class); + $decoder + ->expects($this->once()) + ->method('decode') + ->with('data', 'format', $this->isType('array')) + ->willReturn('decoded'); + + $this->assertSame('encoded', (new TraceableEncoder($encoder, new SerializerDataCollector()))->encode('data', 'format')); + $this->assertSame('decoded', (new TraceableEncoder($decoder, new SerializerDataCollector()))->decode('data', 'format')); + } + + public function testCollectEncodingData() + { + $encoder = $this->createMock(EncoderInterface::class); + $decoder = $this->createMock(DecoderInterface::class); + + $dataCollector = $this->createMock(SerializerDataCollector::class); + $dataCollector + ->expects($this->once()) + ->method('collectEncoding') + ->with($this->isType('string'), \get_class($encoder), $this->isType('float')); + $dataCollector + ->expects($this->once()) + ->method('collectDecoding') + ->with($this->isType('string'), \get_class($decoder), $this->isType('float')); + + (new TraceableEncoder($encoder, $dataCollector))->encode('data', 'format', [TraceableSerializer::DEBUG_TRACE_ID => 'debug']); + (new TraceableEncoder($decoder, $dataCollector))->decode('data', 'format', [TraceableSerializer::DEBUG_TRACE_ID => 'debug']); + } + + public function testNotCollectEncodingDataIfNoDebugTraceId() + { + $encoder = $this->createMock(EncoderInterface::class); + $decoder = $this->createMock(DecoderInterface::class); + + $dataCollector = $this->createMock(SerializerDataCollector::class); + $dataCollector->expects($this->never())->method('collectEncoding'); + $dataCollector->expects($this->never())->method('collectDecoding'); + + (new TraceableEncoder($encoder, $dataCollector))->encode('data', 'format'); + (new TraceableEncoder($decoder, $dataCollector))->decode('data', 'format'); + } + + public function testCannotEncodeIfNotEncoder() + { + $this->expectException(BadMethodCallException::class); + + (new TraceableEncoder($this->createMock(DecoderInterface::class), new SerializerDataCollector()))->encode('data', 'format'); + } + + public function testCannotDecodeIfNotDecoder() + { + $this->expectException(BadMethodCallException::class); + + (new TraceableEncoder($this->createMock(EncoderInterface::class), new SerializerDataCollector()))->decode('data', 'format'); + } + + public function testSupports() + { + $encoder = $this->createMock(EncoderInterface::class); + $encoder->method('supportsEncoding')->willReturn(true); + + $decoder = $this->createMock(DecoderInterface::class); + $decoder->method('supportsDecoding')->willReturn(true); + + $traceableEncoder = new TraceableEncoder($encoder, new SerializerDataCollector()); + $traceableDecoder = new TraceableEncoder($decoder, new SerializerDataCollector()); + + $this->assertTrue($traceableEncoder->supportsEncoding('data')); + $this->assertTrue($traceableDecoder->supportsDecoding('data')); + $this->assertFalse($traceableEncoder->supportsDecoding('data')); + $this->assertFalse($traceableDecoder->supportsEncoding('data')); + } +} diff --git a/Tests/Debug/TraceableNormalizerTest.php b/Tests/Debug/TraceableNormalizerTest.php new file mode 100644 index 000000000..dc99a03c0 --- /dev/null +++ b/Tests/Debug/TraceableNormalizerTest.php @@ -0,0 +1,106 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Serializer\Tests\Debug; + +use BadMethodCallException; +use PHPUnit\Framework\TestCase; +use Symfony\Component\Serializer\DataCollector\SerializerDataCollector; +use Symfony\Component\Serializer\Debug\TraceableNormalizer; +use Symfony\Component\Serializer\Debug\TraceableSerializer; +use Symfony\Component\Serializer\Normalizer\DenormalizerInterface; +use Symfony\Component\Serializer\Normalizer\NormalizerInterface; + +class TraceableNormalizerTest extends TestCase +{ + public function testForwardsToNormalizer() + { + $normalizer = $this->createMock(NormalizerInterface::class); + $normalizer + ->expects($this->once()) + ->method('normalize') + ->with('data', 'format', $this->isType('array')) + ->willReturn('normalized'); + + $denormalizer = $this->createMock(DenormalizerInterface::class); + $denormalizer + ->expects($this->once()) + ->method('denormalize') + ->with('data', 'type', 'format', $this->isType('array')) + ->willReturn('denormalized'); + + $this->assertSame('normalized', (new TraceableNormalizer($normalizer, new SerializerDataCollector()))->normalize('data', 'format')); + $this->assertSame('denormalized', (new TraceableNormalizer($denormalizer, new SerializerDataCollector()))->denormalize('data', 'type', 'format')); + } + + public function testCollectNormalizationData() + { + $normalizer = $this->createMock(NormalizerInterface::class); + $denormalizer = $this->createMock(DenormalizerInterface::class); + + $dataCollector = $this->createMock(SerializerDataCollector::class); + $dataCollector + ->expects($this->once()) + ->method('collectNormalization') + ->with($this->isType('string'), \get_class($normalizer), $this->isType('float')); + $dataCollector + ->expects($this->once()) + ->method('collectDenormalization') + ->with($this->isType('string'), \get_class($denormalizer), $this->isType('float')); + + (new TraceableNormalizer($normalizer, $dataCollector))->normalize('data', 'format', [TraceableSerializer::DEBUG_TRACE_ID => 'debug']); + (new TraceableNormalizer($denormalizer, $dataCollector))->denormalize('data', 'type', 'format', [TraceableSerializer::DEBUG_TRACE_ID => 'debug']); + } + + public function testNotCollectNormalizationDataIfNoDebugTraceId() + { + $normalizer = $this->createMock(NormalizerInterface::class); + $denormalizer = $this->createMock(DenormalizerInterface::class); + + $dataCollector = $this->createMock(SerializerDataCollector::class); + $dataCollector->expects($this->never())->method('collectNormalization'); + $dataCollector->expects($this->never())->method('collectDenormalization'); + + (new TraceableNormalizer($normalizer, $dataCollector))->normalize('data', 'format'); + (new TraceableNormalizer($denormalizer, $dataCollector))->denormalize('data', 'type', 'format'); + } + + public function testCannotNormalizeIfNotNormalizer() + { + $this->expectException(BadMethodCallException::class); + + (new TraceableNormalizer($this->createMock(DenormalizerInterface::class), new SerializerDataCollector()))->normalize('data'); + } + + public function testCannotDenormalizeIfNotDenormalizer() + { + $this->expectException(BadMethodCallException::class); + + (new TraceableNormalizer($this->createMock(NormalizerInterface::class), new SerializerDataCollector()))->denormalize('data', 'type'); + } + + public function testSupports() + { + $normalizer = $this->createMock(NormalizerInterface::class); + $normalizer->method('supportsNormalization')->willReturn(true); + + $denormalizer = $this->createMock(DenormalizerInterface::class); + $denormalizer->method('supportsDenormalization')->willReturn(true); + + $traceableNormalizer = new TraceableNormalizer($normalizer, new SerializerDataCollector()); + $traceableDenormalizer = new TraceableNormalizer($denormalizer, new SerializerDataCollector()); + + $this->assertTrue($traceableNormalizer->supportsNormalization('data')); + $this->assertTrue($traceableDenormalizer->supportsDenormalization('data', 'type')); + $this->assertFalse($traceableNormalizer->supportsDenormalization('data', 'type')); + $this->assertFalse($traceableDenormalizer->supportsNormalization('data')); + } +} diff --git a/Tests/Debug/TraceableSerializerTest.php b/Tests/Debug/TraceableSerializerTest.php new file mode 100644 index 000000000..ae8a01623 --- /dev/null +++ b/Tests/Debug/TraceableSerializerTest.php @@ -0,0 +1,194 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Serializer\Tests\Debug; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\Serializer\DataCollector\SerializerDataCollector; +use Symfony\Component\Serializer\Debug\TraceableSerializer; +use Symfony\Component\Serializer\Encoder\DecoderInterface; +use Symfony\Component\Serializer\Encoder\EncoderInterface; +use Symfony\Component\Serializer\Normalizer\DenormalizerInterface; +use Symfony\Component\Serializer\Normalizer\NormalizerInterface; +use Symfony\Component\Serializer\SerializerInterface; + +class TraceableSerializerTest extends TestCase +{ + public function testForwardsToSerializer() + { + $serializer = $this->createMock(Serializer::class); + $serializer + ->expects($this->once()) + ->method('serialize') + ->with('data', 'format', $this->isType('array')) + ->willReturn('serialized'); + $serializer + ->expects($this->once()) + ->method('deserialize') + ->with('data', 'type', 'format', $this->isType('array')) + ->willReturn('deserialized'); + $serializer + ->expects($this->once()) + ->method('normalize') + ->with('data', 'format', $this->isType('array')) + ->willReturn('normalized'); + $serializer + ->expects($this->once()) + ->method('denormalize') + ->with('data', 'type', 'format', $this->isType('array')) + ->willReturn('denormalized'); + $serializer + ->expects($this->once()) + ->method('encode') + ->with('data', 'format', $this->isType('array')) + ->willReturn('encoded'); + $serializer + ->expects($this->once()) + ->method('decode') + ->with('data', 'format', $this->isType('array')) + ->willReturn('decoded'); + + $traceableSerializer = new TraceableSerializer($serializer, new SerializerDataCollector()); + + $this->assertSame('serialized', $traceableSerializer->serialize('data', 'format')); + $this->assertSame('deserialized', $traceableSerializer->deserialize('data', 'type', 'format')); + $this->assertSame('normalized', $traceableSerializer->normalize('data', 'format')); + $this->assertSame('denormalized', $traceableSerializer->denormalize('data', 'type', 'format')); + $this->assertSame('encoded', $traceableSerializer->encode('data', 'format')); + $this->assertSame('decoded', $traceableSerializer->decode('data', 'format')); + } + + public function testCollectData() + { + $dataCollector = $this->createMock(SerializerDataCollector::class); + $dataCollector + ->expects($this->once()) + ->method('collectSerialize') + ->with($this->isType('string'), 'data', 'format', $this->isType('array'), $this->isType('float')); + $dataCollector + ->expects($this->once()) + ->method('collectDeserialize') + ->with($this->isType('string'), 'data', 'type', 'format', $this->isType('array'), $this->isType('float')); + $dataCollector + ->expects($this->once()) + ->method('collectNormalize') + ->with($this->isType('string'), 'data', 'format', $this->isType('array'), $this->isType('float')); + $dataCollector + ->expects($this->once()) + ->method('collectDenormalize') + ->with($this->isType('string'), 'data', 'type', 'format', $this->isType('array'), $this->isType('float')); + $dataCollector + ->expects($this->once()) + ->method('collectEncode') + ->with($this->isType('string'), 'data', 'format', $this->isType('array'), $this->isType('float')); + $dataCollector + ->expects($this->once()) + ->method('collectDecode') + ->with($this->isType('string'), 'data', 'format', $this->isType('array'), $this->isType('float')); + + $traceableSerializer = new TraceableSerializer(new Serializer(), $dataCollector); + + $traceableSerializer->serialize('data', 'format'); + $traceableSerializer->deserialize('data', 'type', 'format'); + $traceableSerializer->normalize('data', 'format'); + $traceableSerializer->denormalize('data', 'type', 'format'); + $traceableSerializer->encode('data', 'format'); + $traceableSerializer->decode('data', 'format'); + } + + public function testAddDebugTraceIdInContext() + { + $serializer = $this->createMock(Serializer::class); + + foreach (['serialize', 'deserialize', 'normalize', 'denormalize', 'encode', 'decode'] as $method) { + $serializer->method($method)->willReturnCallback(function (): string { + $context = func_get_arg(\func_num_args() - 1); + $this->assertIsString($context[TraceableSerializer::DEBUG_TRACE_ID]); + + return ''; + }); + } + + $traceableSerializer = new TraceableSerializer($serializer, new SerializerDataCollector()); + + $traceableSerializer->serialize('data', 'format'); + $traceableSerializer->deserialize('data', 'format', 'type'); + $traceableSerializer->normalize('data', 'format'); + $traceableSerializer->denormalize('data', 'format'); + $traceableSerializer->encode('data', 'format'); + $traceableSerializer->decode('data', 'format'); + } +} + +class Serializer implements SerializerInterface, NormalizerInterface, DenormalizerInterface, EncoderInterface, DecoderInterface +{ + public function serialize(mixed $data, string $format, array $context = []): string + { + return 'serialized'; + } + + public function deserialize(mixed $data, string $type, string $format, array $context = []): mixed + { + return 'deserialized'; + } + + public function normalize(mixed $object, string $format = null, array $context = []): array|string|int|float|bool|\ArrayObject|null + { + return 'normalized'; + } + + /** + * @param array $context + */ + public function supportsNormalization(mixed $data, string $format = null /*, array $context = [] */): bool + { + return true; + } + + public function denormalize(mixed $data, string $type, string $format = null, array $context = []): mixed + { + return 'denormalized'; + } + + /** + * @param array $context + */ + public function supportsDenormalization(mixed $data, string $type, string $format = null /*, array $context = [] */): bool + { + return true; + } + + public function encode(mixed $data, string $format, array $context = []): string + { + return 'encoded'; + } + + /** + * @param array $context + */ + public function supportsEncoding(string $format /*, array $context = [] */): bool + { + return true; + } + + public function decode(string $data, string $format, array $context = []): mixed + { + return 'decoded'; + } + + /** + * @param array $context + */ + public function supportsDecoding(string $format /*, array $context = [] */): bool + { + return true; + } +} diff --git a/Tests/DependencyInjection/SerializerPassTest.php b/Tests/DependencyInjection/SerializerPassTest.php index e9b1efd22..92c258a82 100644 --- a/Tests/DependencyInjection/SerializerPassTest.php +++ b/Tests/DependencyInjection/SerializerPassTest.php @@ -15,6 +15,8 @@ use Symfony\Component\DependencyInjection\Argument\BoundArgument; use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\DependencyInjection\Reference; +use Symfony\Component\Serializer\Debug\TraceableEncoder; +use Symfony\Component\Serializer\Debug\TraceableNormalizer; use Symfony\Component\Serializer\DependencyInjection\SerializerPass; /** @@ -29,6 +31,7 @@ public function testThrowExceptionWhenNoNormalizers() $this->expectException(\RuntimeException::class); $this->expectExceptionMessage('You must tag at least one service as "serializer.normalizer" to use the "serializer" service'); $container = new ContainerBuilder(); + $container->setParameter('kernel.debug', false); $container->register('serializer'); $serializerPass = new SerializerPass(); @@ -40,6 +43,7 @@ public function testThrowExceptionWhenNoEncoders() $this->expectException(\RuntimeException::class); $this->expectExceptionMessage('You must tag at least one service as "serializer.encoder" to use the "serializer" service'); $container = new ContainerBuilder(); + $container->setParameter('kernel.debug', false); $container->register('serializer') ->addArgument([]) ->addArgument([]); @@ -52,6 +56,7 @@ public function testThrowExceptionWhenNoEncoders() public function testServicesAreOrderedAccordingToPriority() { $container = new ContainerBuilder(); + $container->setParameter('kernel.debug', false); $definition = $container->register('serializer')->setArguments([null, null]); $container->register('n2')->addTag('serializer.normalizer', ['priority' => 100])->addTag('serializer.encoder', ['priority' => 100]); @@ -73,6 +78,7 @@ public function testServicesAreOrderedAccordingToPriority() public function testBindSerializerDefaultContext() { $container = new ContainerBuilder(); + $container->setParameter('kernel.debug', false); $container->register('serializer')->setArguments([null, null]); $container->setParameter('serializer.default_context', ['enable_max_depth' => true]); $definition = $container->register('n1')->addTag('serializer.normalizer')->addTag('serializer.encoder'); @@ -83,4 +89,32 @@ public function testBindSerializerDefaultContext() $bindings = $definition->getBindings(); $this->assertEquals($bindings['array $defaultContext'], new BoundArgument(['enable_max_depth' => true], false)); } + + public function testNormalizersAndEncodersAreDecoredAndOrderedWhenCollectingData() + { + $container = new ContainerBuilder(); + + $container->setParameter('kernel.debug', true); + $container->register('serializer.data_collector'); + + $container->register('serializer')->setArguments([null, null]); + $container->register('n')->addTag('serializer.normalizer'); + $container->register('e')->addTag('serializer.encoder'); + + $serializerPass = new SerializerPass(); + $serializerPass->process($container); + + $traceableNormalizerDefinition = $container->getDefinition('debug.n'); + $traceableEncoderDefinition = $container->getDefinition('debug.e'); + + $this->assertEquals(TraceableNormalizer::class, $traceableNormalizerDefinition->getClass()); + $this->assertEquals(['n', null, 255], $traceableNormalizerDefinition->getDecoratedService()); + $this->assertEquals(new Reference('debug.n.inner'), $traceableNormalizerDefinition->getArgument(0)); + $this->assertEquals(new Reference('serializer.data_collector'), $traceableNormalizerDefinition->getArgument(1)); + + $this->assertEquals(TraceableEncoder::class, $traceableEncoderDefinition->getClass()); + $this->assertEquals(['e', null, 255], $traceableEncoderDefinition->getDecoratedService()); + $this->assertEquals(new Reference('debug.e.inner'), $traceableEncoderDefinition->getArgument(0)); + $this->assertEquals(new Reference('serializer.data_collector'), $traceableEncoderDefinition->getArgument(1)); + } } diff --git a/Tests/Encoder/ChainEncoderTest.php b/Tests/Encoder/ChainEncoderTest.php index 6f999f612..848087145 100644 --- a/Tests/Encoder/ChainEncoderTest.php +++ b/Tests/Encoder/ChainEncoderTest.php @@ -12,6 +12,7 @@ namespace Symfony\Component\Serializer\Tests\Encoder; use PHPUnit\Framework\TestCase; +use Symfony\Component\Serializer\Debug\TraceableEncoder; use Symfony\Component\Serializer\Encoder\ChainEncoder; use Symfony\Component\Serializer\Encoder\EncoderInterface; use Symfony\Component\Serializer\Encoder\NormalizationAwareInterface; @@ -86,6 +87,21 @@ public function testNeedsNormalizationNormalizationAware() $this->assertFalse($sut->needsNormalization(self::FORMAT_1)); } + + public function testNeedsNormalizationTraceableEncoder() + { + $traceableEncoder = $this->createMock(TraceableEncoder::class); + $traceableEncoder->method('needsNormalization')->willReturn(true); + $traceableEncoder->method('supportsEncoding')->willReturn(true); + + $this->assertTrue((new ChainEncoder([$traceableEncoder]))->needsNormalization('format')); + + $traceableEncoder = $this->createMock(TraceableEncoder::class); + $traceableEncoder->method('needsNormalization')->willReturn(false); + $traceableEncoder->method('supportsEncoding')->willReturn(true); + + $this->assertFalse((new ChainEncoder([$traceableEncoder]))->needsNormalization('format')); + } } class NormalizationAwareEncoder implements EncoderInterface, NormalizationAwareInterface From 0607a37a436615046b2f4b9f2a363e28850ea8b3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gr=C3=A9goire=20Pineau?= Date: Tue, 12 Apr 2022 18:34:08 +0200 Subject: [PATCH 033/297] Add missing license header --- Tests/Normalizer/Features/DummyContextChild.php | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/Tests/Normalizer/Features/DummyContextChild.php b/Tests/Normalizer/Features/DummyContextChild.php index 0e4a24015..29c684b3e 100644 --- a/Tests/Normalizer/Features/DummyContextChild.php +++ b/Tests/Normalizer/Features/DummyContextChild.php @@ -1,5 +1,14 @@ + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + namespace Symfony\Component\Serializer\Tests\Normalizer\Features; use Symfony\Component\Serializer\Annotation\Context; From ac87259e63bdc6f29330879495ee435bb596a0c6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rokas=20Mikalk=C4=97nas?= Date: Wed, 2 Feb 2022 12:17:50 +0200 Subject: [PATCH 034/297] [Serializer] Support canners in object normalizer --- CHANGELOG.md | 1 + Normalizer/ObjectNormalizer.php | 4 ++-- Tests/Normalizer/Features/ObjectDummy.php | 11 +++++++++++ Tests/Normalizer/ObjectNormalizerTest.php | 6 ++++++ 4 files changed, 20 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9bee69dbd..c2b3ebfb3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,7 @@ CHANGELOG * Deprecate `ContextAwareDecoderInterface`, use `DecoderInterface` instead * Deprecate supporting denormalization for `AbstractUid` in `UidNormalizer`, use one of `AbstractUid` child class instead * Deprecate denormalizing to an abstract class in `UidNormalizer` + * Add support for `can*()` methods to `ObjectNormalizer` 6.0 --- diff --git a/Normalizer/ObjectNormalizer.php b/Normalizer/ObjectNormalizer.php index 0dbc8bc3e..263959c1d 100644 --- a/Normalizer/ObjectNormalizer.php +++ b/Normalizer/ObjectNormalizer.php @@ -86,8 +86,8 @@ protected function extractAttributes(object $object, string $format = null, arra $name = $reflMethod->name; $attributeName = null; - if (str_starts_with($name, 'get') || str_starts_with($name, 'has')) { - // getters and hassers + if (str_starts_with($name, 'get') || str_starts_with($name, 'has') || str_starts_with($name, 'can')) { + // getters, hassers and canners $attributeName = substr($name, 3); if (!$reflClass->hasProperty($attributeName)) { diff --git a/Tests/Normalizer/Features/ObjectDummy.php b/Tests/Normalizer/Features/ObjectDummy.php index 69661fd16..39f3c354a 100644 --- a/Tests/Normalizer/Features/ObjectDummy.php +++ b/Tests/Normalizer/Features/ObjectDummy.php @@ -22,6 +22,7 @@ class ObjectDummy private $baz; protected $camelCase; protected $object; + private $go; public function getFoo() { @@ -72,4 +73,14 @@ public function getObject() { return $this->object; } + + public function setGo($go) + { + $this->go = $go; + } + + public function canGo() + { + return $this->go; + } } diff --git a/Tests/Normalizer/ObjectNormalizerTest.php b/Tests/Normalizer/ObjectNormalizerTest.php index 244bf9498..f7b5dc88d 100644 --- a/Tests/Normalizer/ObjectNormalizerTest.php +++ b/Tests/Normalizer/ObjectNormalizerTest.php @@ -107,6 +107,7 @@ public function testNormalize() $obj->setBaz(true); $obj->setCamelCase('camelcase'); $obj->setObject($object); + $obj->setGo(true); $this->serializer ->expects($this->once()) @@ -123,6 +124,7 @@ public function testNormalize() 'fooBar' => 'foobar', 'camelCase' => 'camelcase', 'object' => 'string_object', + 'go' => true, ], $this->normalizer->normalize($obj, 'any') ); @@ -669,6 +671,7 @@ public function testNormalizeNotSerializableContext() 'camelCase' => null, 'object' => null, 'bar' => null, + 'go' => null, ]; $this->assertEquals($expected, $this->normalizer->normalize($objectDummy, null, ['not_serializable' => function () { @@ -805,6 +808,7 @@ public function testDefaultObjectClassResolver() $obj->setBaz(true); $obj->setCamelCase('camelcase'); $obj->unwantedProperty = 'notwanted'; + $obj->setGo(false); $this->assertEquals( [ @@ -814,6 +818,7 @@ public function testDefaultObjectClassResolver() 'fooBar' => 'foobar', 'camelCase' => 'camelcase', 'object' => null, + 'go' => false, ], $normalizer->normalize($obj, 'any') ); @@ -842,6 +847,7 @@ public function testObjectClassResolver() 'fooBar' => 'foobar', 'camelCase' => 'camelcase', 'object' => null, + 'go' => null, ], $normalizer->normalize($obj, 'any') ); From 842b94506980bd17310465092388dbebe7130c35 Mon Sep 17 00:00:00 2001 From: Robin Chalas Date: Tue, 26 Apr 2022 17:26:22 +0200 Subject: [PATCH 035/297] [Serializer] Fine-tune `ContextBuilder::withContext()` --- Context/ContextBuilderInterface.php | 31 +++++++++++++++++++ Context/ContextBuilderTrait.php | 8 +++-- Context/Encoder/CsvEncoderContextBuilder.php | 3 +- Context/Encoder/JsonEncoderContextBuilder.php | 3 +- Context/Encoder/XmlEncoderContextBuilder.php | 3 +- Context/Encoder/YamlEncoderContextBuilder.php | 3 +- .../AbstractNormalizerContextBuilder.php | 3 +- ...tViolationListNormalizerContextBuilder.php | 3 +- .../DateIntervalNormalizerContextBuilder.php | 3 +- .../DateTimeNormalizerContextBuilder.php | 3 +- .../FormErrorNormalizerContextBuilder.php | 3 +- .../ProblemNormalizerContextBuilder.php | 3 +- .../UidNormalizerContextBuilder.php | 3 +- .../UnwrappingDenormalizerContextBuilder.php | 3 +- Context/SerializerContextBuilder.php | 2 +- Tests/Context/ContextBuilderTraitTest.php | 7 ++++- 16 files changed, 68 insertions(+), 16 deletions(-) create mode 100644 Context/ContextBuilderInterface.php diff --git a/Context/ContextBuilderInterface.php b/Context/ContextBuilderInterface.php new file mode 100644 index 000000000..4366d07bc --- /dev/null +++ b/Context/ContextBuilderInterface.php @@ -0,0 +1,31 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Serializer\Context; + +/** + * Common interface for context builders. + * + * @author Mathias Arlaud + * @author Robin Chalas + */ +interface ContextBuilderInterface +{ + /** + * @param self|array $context + */ + public function withContext(self|array $context): static; + + /** + * @return array + */ + public function toArray(): array; +} diff --git a/Context/ContextBuilderTrait.php b/Context/ContextBuilderTrait.php index 97907d7b5..d7ce5becf 100644 --- a/Context/ContextBuilderTrait.php +++ b/Context/ContextBuilderTrait.php @@ -30,10 +30,14 @@ protected function with(string $key, mixed $value): static } /** - * @param array $context + * @param ContextBuilderInterface|array $context */ - public function withContext(array $context): static + public function withContext(ContextBuilderInterface|array $context): static { + if ($context instanceof ContextBuilderInterface) { + $context = $context->toArray(); + } + $instance = new static(); $instance->context = array_merge($this->context, $context); diff --git a/Context/Encoder/CsvEncoderContextBuilder.php b/Context/Encoder/CsvEncoderContextBuilder.php index 8a7bedc04..0b08bcc1a 100644 --- a/Context/Encoder/CsvEncoderContextBuilder.php +++ b/Context/Encoder/CsvEncoderContextBuilder.php @@ -11,6 +11,7 @@ namespace Symfony\Component\Serializer\Context\Encoder; +use Symfony\Component\Serializer\Context\ContextBuilderInterface; use Symfony\Component\Serializer\Context\ContextBuilderTrait; use Symfony\Component\Serializer\Encoder\CsvEncoder; use Symfony\Component\Serializer\Exception\InvalidArgumentException; @@ -20,7 +21,7 @@ * * @author Mathias Arlaud */ -final class CsvEncoderContextBuilder +final class CsvEncoderContextBuilder implements ContextBuilderInterface { use ContextBuilderTrait; diff --git a/Context/Encoder/JsonEncoderContextBuilder.php b/Context/Encoder/JsonEncoderContextBuilder.php index dfb43ac09..0ebd70269 100644 --- a/Context/Encoder/JsonEncoderContextBuilder.php +++ b/Context/Encoder/JsonEncoderContextBuilder.php @@ -11,6 +11,7 @@ namespace Symfony\Component\Serializer\Context\Encoder; +use Symfony\Component\Serializer\Context\ContextBuilderInterface; use Symfony\Component\Serializer\Context\ContextBuilderTrait; use Symfony\Component\Serializer\Encoder\JsonDecode; use Symfony\Component\Serializer\Encoder\JsonEncode; @@ -20,7 +21,7 @@ * * @author Mathias Arlaud */ -final class JsonEncoderContextBuilder +final class JsonEncoderContextBuilder implements ContextBuilderInterface { use ContextBuilderTrait; diff --git a/Context/Encoder/XmlEncoderContextBuilder.php b/Context/Encoder/XmlEncoderContextBuilder.php index d8273a722..3f8e92f4e 100644 --- a/Context/Encoder/XmlEncoderContextBuilder.php +++ b/Context/Encoder/XmlEncoderContextBuilder.php @@ -11,6 +11,7 @@ namespace Symfony\Component\Serializer\Context\Encoder; +use Symfony\Component\Serializer\Context\ContextBuilderInterface; use Symfony\Component\Serializer\Context\ContextBuilderTrait; use Symfony\Component\Serializer\Encoder\XmlEncoder; @@ -19,7 +20,7 @@ * * @author Mathias Arlaud */ -final class XmlEncoderContextBuilder +final class XmlEncoderContextBuilder implements ContextBuilderInterface { use ContextBuilderTrait; diff --git a/Context/Encoder/YamlEncoderContextBuilder.php b/Context/Encoder/YamlEncoderContextBuilder.php index aa8e554b7..81f6ad90d 100644 --- a/Context/Encoder/YamlEncoderContextBuilder.php +++ b/Context/Encoder/YamlEncoderContextBuilder.php @@ -11,6 +11,7 @@ namespace Symfony\Component\Serializer\Context\Encoder; +use Symfony\Component\Serializer\Context\ContextBuilderInterface; use Symfony\Component\Serializer\Context\ContextBuilderTrait; use Symfony\Component\Serializer\Encoder\YamlEncoder; @@ -19,7 +20,7 @@ * * @author Mathias Arlaud */ -final class YamlEncoderContextBuilder +final class YamlEncoderContextBuilder implements ContextBuilderInterface { use ContextBuilderTrait; diff --git a/Context/Normalizer/AbstractNormalizerContextBuilder.php b/Context/Normalizer/AbstractNormalizerContextBuilder.php index 4d65948a8..f494f060a 100644 --- a/Context/Normalizer/AbstractNormalizerContextBuilder.php +++ b/Context/Normalizer/AbstractNormalizerContextBuilder.php @@ -11,6 +11,7 @@ namespace Symfony\Component\Serializer\Context\Normalizer; +use Symfony\Component\Serializer\Context\ContextBuilderInterface; use Symfony\Component\Serializer\Context\ContextBuilderTrait; use Symfony\Component\Serializer\Exception\InvalidArgumentException; use Symfony\Component\Serializer\Normalizer\AbstractNormalizer; @@ -20,7 +21,7 @@ * * @author Mathias Arlaud */ -abstract class AbstractNormalizerContextBuilder +abstract class AbstractNormalizerContextBuilder implements ContextBuilderInterface { use ContextBuilderTrait; diff --git a/Context/Normalizer/ConstraintViolationListNormalizerContextBuilder.php b/Context/Normalizer/ConstraintViolationListNormalizerContextBuilder.php index 41dec70d4..fd1d7c4f5 100644 --- a/Context/Normalizer/ConstraintViolationListNormalizerContextBuilder.php +++ b/Context/Normalizer/ConstraintViolationListNormalizerContextBuilder.php @@ -11,6 +11,7 @@ namespace Symfony\Component\Serializer\Context\Normalizer; +use Symfony\Component\Serializer\Context\ContextBuilderInterface; use Symfony\Component\Serializer\Context\ContextBuilderTrait; use Symfony\Component\Serializer\Normalizer\ConstraintViolationListNormalizer; @@ -19,7 +20,7 @@ * * @author Mathias Arlaud */ -final class ConstraintViolationListNormalizerContextBuilder +final class ConstraintViolationListNormalizerContextBuilder implements ContextBuilderInterface { use ContextBuilderTrait; diff --git a/Context/Normalizer/DateIntervalNormalizerContextBuilder.php b/Context/Normalizer/DateIntervalNormalizerContextBuilder.php index 4f5558be5..98636577a 100644 --- a/Context/Normalizer/DateIntervalNormalizerContextBuilder.php +++ b/Context/Normalizer/DateIntervalNormalizerContextBuilder.php @@ -11,6 +11,7 @@ namespace Symfony\Component\Serializer\Context\Normalizer; +use Symfony\Component\Serializer\Context\ContextBuilderInterface; use Symfony\Component\Serializer\Context\ContextBuilderTrait; use Symfony\Component\Serializer\Normalizer\DateIntervalNormalizer; @@ -19,7 +20,7 @@ * * @author Mathias Arlaud */ -final class DateIntervalNormalizerContextBuilder +final class DateIntervalNormalizerContextBuilder implements ContextBuilderInterface { use ContextBuilderTrait; diff --git a/Context/Normalizer/DateTimeNormalizerContextBuilder.php b/Context/Normalizer/DateTimeNormalizerContextBuilder.php index 8e51bf419..99517afb1 100644 --- a/Context/Normalizer/DateTimeNormalizerContextBuilder.php +++ b/Context/Normalizer/DateTimeNormalizerContextBuilder.php @@ -11,6 +11,7 @@ namespace Symfony\Component\Serializer\Context\Normalizer; +use Symfony\Component\Serializer\Context\ContextBuilderInterface; use Symfony\Component\Serializer\Context\ContextBuilderTrait; use Symfony\Component\Serializer\Exception\InvalidArgumentException; use Symfony\Component\Serializer\Normalizer\DateTimeNormalizer; @@ -20,7 +21,7 @@ * * @author Mathias Arlaud */ -final class DateTimeNormalizerContextBuilder +final class DateTimeNormalizerContextBuilder implements ContextBuilderInterface { use ContextBuilderTrait; diff --git a/Context/Normalizer/FormErrorNormalizerContextBuilder.php b/Context/Normalizer/FormErrorNormalizerContextBuilder.php index deaf5c2a8..4cd2ddb88 100644 --- a/Context/Normalizer/FormErrorNormalizerContextBuilder.php +++ b/Context/Normalizer/FormErrorNormalizerContextBuilder.php @@ -11,6 +11,7 @@ namespace Symfony\Component\Serializer\Context\Normalizer; +use Symfony\Component\Serializer\Context\ContextBuilderInterface; use Symfony\Component\Serializer\Context\ContextBuilderTrait; use Symfony\Component\Serializer\Normalizer\FormErrorNormalizer; @@ -19,7 +20,7 @@ * * @author Mathias Arlaud */ -final class FormErrorNormalizerContextBuilder +final class FormErrorNormalizerContextBuilder implements ContextBuilderInterface { use ContextBuilderTrait; diff --git a/Context/Normalizer/ProblemNormalizerContextBuilder.php b/Context/Normalizer/ProblemNormalizerContextBuilder.php index ab448f361..b525a91ad 100644 --- a/Context/Normalizer/ProblemNormalizerContextBuilder.php +++ b/Context/Normalizer/ProblemNormalizerContextBuilder.php @@ -11,6 +11,7 @@ namespace Symfony\Component\Serializer\Context\Normalizer; +use Symfony\Component\Serializer\Context\ContextBuilderInterface; use Symfony\Component\Serializer\Context\ContextBuilderTrait; use Symfony\Component\Serializer\Normalizer\ProblemNormalizer; @@ -19,7 +20,7 @@ * * @author Mathias Arlaud */ -final class ProblemNormalizerContextBuilder +final class ProblemNormalizerContextBuilder implements ContextBuilderInterface { use ContextBuilderTrait; diff --git a/Context/Normalizer/UidNormalizerContextBuilder.php b/Context/Normalizer/UidNormalizerContextBuilder.php index c7386d047..1d889e502 100644 --- a/Context/Normalizer/UidNormalizerContextBuilder.php +++ b/Context/Normalizer/UidNormalizerContextBuilder.php @@ -11,6 +11,7 @@ namespace Symfony\Component\Serializer\Context\Normalizer; +use Symfony\Component\Serializer\Context\ContextBuilderInterface; use Symfony\Component\Serializer\Context\ContextBuilderTrait; use Symfony\Component\Serializer\Exception\InvalidArgumentException; use Symfony\Component\Serializer\Normalizer\UidNormalizer; @@ -20,7 +21,7 @@ * * @author Mathias Arlaud */ -final class UidNormalizerContextBuilder +final class UidNormalizerContextBuilder implements ContextBuilderInterface { use ContextBuilderTrait; diff --git a/Context/Normalizer/UnwrappingDenormalizerContextBuilder.php b/Context/Normalizer/UnwrappingDenormalizerContextBuilder.php index c09e6df04..f06f6ac77 100644 --- a/Context/Normalizer/UnwrappingDenormalizerContextBuilder.php +++ b/Context/Normalizer/UnwrappingDenormalizerContextBuilder.php @@ -13,6 +13,7 @@ use Symfony\Component\PropertyAccess\Exception\InvalidPropertyPathException; use Symfony\Component\PropertyAccess\PropertyPath; +use Symfony\Component\Serializer\Context\ContextBuilderInterface; use Symfony\Component\Serializer\Context\ContextBuilderTrait; use Symfony\Component\Serializer\Exception\InvalidArgumentException; use Symfony\Component\Serializer\Normalizer\UnwrappingDenormalizer; @@ -22,7 +23,7 @@ * * @author Mathias Arlaud */ -final class UnwrappingDenormalizerContextBuilder +final class UnwrappingDenormalizerContextBuilder implements ContextBuilderInterface { use ContextBuilderTrait; diff --git a/Context/SerializerContextBuilder.php b/Context/SerializerContextBuilder.php index 48ea999ec..a6359be98 100644 --- a/Context/SerializerContextBuilder.php +++ b/Context/SerializerContextBuilder.php @@ -19,7 +19,7 @@ * * @author Mathias Arlaud */ -final class SerializerContextBuilder +final class SerializerContextBuilder implements ContextBuilderInterface { use ContextBuilderTrait; diff --git a/Tests/Context/ContextBuilderTraitTest.php b/Tests/Context/ContextBuilderTraitTest.php index 4c25e17c0..17ad231a0 100644 --- a/Tests/Context/ContextBuilderTraitTest.php +++ b/Tests/Context/ContextBuilderTraitTest.php @@ -12,6 +12,7 @@ namespace Symfony\Component\Serializer\Tests\Context; use PHPUnit\Framework\TestCase; +use Symfony\Component\Serializer\Context\ContextBuilderInterface; use Symfony\Component\Serializer\Context\ContextBuilderTrait; /** @@ -21,13 +22,17 @@ class ContextBuilderTraitTest extends TestCase { public function testWithContext() { - $contextBuilder = new class() { + $contextBuilder = new class() implements ContextBuilderInterface { use ContextBuilderTrait; }; $context = $contextBuilder->withContext(['foo' => 'bar'])->toArray(); $this->assertSame(['foo' => 'bar'], $context); + + $withContextBuilderObject = $contextBuilder->withContext($contextBuilder->withContext(['foo' => 'bar']))->toArray(); + + $this->assertSame(['foo' => 'bar'], $withContextBuilderObject); } public function testWith() From e92f4ca8fc2eb569b65207942003a71d67d5e8e7 Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Sat, 7 May 2022 12:04:08 +0200 Subject: [PATCH 036/297] [Serializer] fix compatibility with psalm v4 --- DataCollector/SerializerDataCollector.php | 2 -- Debug/TraceableEncoder.php | 2 -- Debug/TraceableNormalizer.php | 2 -- Debug/TraceableSerializer.php | 42 ++++++++--------------- 4 files changed, 14 insertions(+), 34 deletions(-) diff --git a/DataCollector/SerializerDataCollector.php b/DataCollector/SerializerDataCollector.php index 57a8aecab..cfea563d0 100644 --- a/DataCollector/SerializerDataCollector.php +++ b/DataCollector/SerializerDataCollector.php @@ -21,8 +21,6 @@ /** * @author Mathias Arlaud * - * @final - * * @internal */ class SerializerDataCollector extends DataCollector implements LateDataCollectorInterface diff --git a/Debug/TraceableEncoder.php b/Debug/TraceableEncoder.php index cd4a351c6..c2927adf9 100644 --- a/Debug/TraceableEncoder.php +++ b/Debug/TraceableEncoder.php @@ -23,8 +23,6 @@ * * @author Mathias Arlaud * - * @final - * * @internal */ class TraceableEncoder implements EncoderInterface, DecoderInterface, SerializerAwareInterface diff --git a/Debug/TraceableNormalizer.php b/Debug/TraceableNormalizer.php index e32475e3c..58a055ecf 100644 --- a/Debug/TraceableNormalizer.php +++ b/Debug/TraceableNormalizer.php @@ -25,8 +25,6 @@ * * @author Mathias Arlaud * - * @final - * * @internal */ class TraceableNormalizer implements NormalizerInterface, DenormalizerInterface, SerializerAwareInterface, NormalizerAwareInterface, DenormalizerAwareInterface, CacheableSupportsMethodInterface diff --git a/Debug/TraceableSerializer.php b/Debug/TraceableSerializer.php index d98d0017b..557bf9128 100644 --- a/Debug/TraceableSerializer.php +++ b/Debug/TraceableSerializer.php @@ -23,15 +23,17 @@ * * @author Mathias Arlaud * - * @final * @internal */ class TraceableSerializer implements SerializerInterface, NormalizerInterface, DenormalizerInterface, EncoderInterface, DecoderInterface { public const DEBUG_TRACE_ID = 'debug_trace_id'; + /** + * @param SerializerInterface&NormalizerInterface&DenormalizerInterface&EncoderInterface&DecoderInterface $serializer + */ public function __construct( - private SerializerInterface&NormalizerInterface&DenormalizerInterface&EncoderInterface&DecoderInterface $serializer, + private SerializerInterface $serializer, private SerializerDataCollector $dataCollector, ) { } @@ -39,7 +41,7 @@ public function __construct( /** * {@inheritdoc} */ - final public function serialize(mixed $data, string $format, array $context = []): string + public function serialize(mixed $data, string $format, array $context = []): string { $context[self::DEBUG_TRACE_ID] = $traceId = uniqid(); @@ -55,7 +57,7 @@ final public function serialize(mixed $data, string $format, array $context = [] /** * {@inheritdoc} */ - final public function deserialize(mixed $data, string $type, string $format, array $context = []): mixed + public function deserialize(mixed $data, string $type, string $format, array $context = []): mixed { $context[self::DEBUG_TRACE_ID] = $traceId = uniqid(); @@ -71,7 +73,7 @@ final public function deserialize(mixed $data, string $type, string $format, arr /** * {@inheritdoc} */ - final public function normalize(mixed $object, string $format = null, array $context = []): array|string|int|float|bool|\ArrayObject|null + public function normalize(mixed $object, string $format = null, array $context = []): array|string|int|float|bool|\ArrayObject|null { $context[self::DEBUG_TRACE_ID] = $traceId = uniqid(); @@ -87,7 +89,7 @@ final public function normalize(mixed $object, string $format = null, array $con /** * {@inheritdoc} */ - final public function denormalize(mixed $data, string $type, string $format = null, array $context = []): mixed + public function denormalize(mixed $data, string $type, string $format = null, array $context = []): mixed { $context[self::DEBUG_TRACE_ID] = $traceId = uniqid(); @@ -103,7 +105,7 @@ final public function denormalize(mixed $data, string $type, string $format = nu /** * {@inheritdoc} */ - final public function encode(mixed $data, string $format, array $context = []): string + public function encode(mixed $data, string $format, array $context = []): string { $context[self::DEBUG_TRACE_ID] = $traceId = uniqid(); @@ -119,7 +121,7 @@ final public function encode(mixed $data, string $format, array $context = []): /** * {@inheritdoc} */ - final public function decode(string $data, string $format, array $context = []): mixed + public function decode(string $data, string $format, array $context = []): mixed { $context[self::DEBUG_TRACE_ID] = $traceId = uniqid(); @@ -134,49 +136,33 @@ final public function decode(string $data, string $format, array $context = []): /** * {@inheritdoc} - * - * @param array $context */ - final public function supportsNormalization(mixed $data, string $format = null /*, array $context = [] */): bool + public function supportsNormalization(mixed $data, string $format = null, array $context = []): bool { - $context = \func_num_args() > 2 ? \func_get_arg(2) : []; - return $this->serializer->supportsNormalization($data, $format, $context); } /** * {@inheritdoc} - * - * @param array $context */ - final public function supportsDenormalization(mixed $data, string $type, string $format = null /*, array $context = [] */): bool + public function supportsDenormalization(mixed $data, string $type, string $format = null, array $context = []): bool { - $context = \func_num_args() > 3 ? \func_get_arg(3) : []; - return $this->serializer->supportsDenormalization($data, $type, $format, $context); } /** * {@inheritdoc} - * - * @param array $context */ - final public function supportsEncoding(string $format /*, array $context = [] */): bool + public function supportsEncoding(string $format, array $context = []): bool { - $context = \func_num_args() > 1 ? \func_get_arg(1) : []; - return $this->serializer->supportsEncoding($format, $context); } /** * {@inheritdoc} - * - * @param array $context */ - final public function supportsDecoding(string $format /*, array $context = [] */): bool + public function supportsDecoding(string $format, array $context = []): bool { - $context = \func_num_args() > 1 ? \func_get_arg(1) : []; - return $this->serializer->supportsDecoding($format, $context); } } From d7f0d76e239f912d9ce1cf774b9abb76fd8cd859 Mon Sep 17 00:00:00 2001 From: Daniel Burger <48986191+danielburger1337@users.noreply.github.com> Date: Mon, 30 May 2022 18:45:24 +0200 Subject: [PATCH 037/297] [Serializer] Added missing __call to TraceableNormalizer and TraceableSerializer --- Debug/TraceableNormalizer.php | 8 ++++++++ Debug/TraceableSerializer.php | 8 ++++++++ 2 files changed, 16 insertions(+) diff --git a/Debug/TraceableNormalizer.php b/Debug/TraceableNormalizer.php index 58a055ecf..33e1b255a 100644 --- a/Debug/TraceableNormalizer.php +++ b/Debug/TraceableNormalizer.php @@ -150,4 +150,12 @@ public function hasCacheableSupportsMethod(): bool { return $this->normalizer instanceof CacheableSupportsMethodInterface && $this->normalizer->hasCacheableSupportsMethod(); } + + /** + * Proxies all method calls to the original normalizer. + */ + public function __call(string $method, array $arguments): mixed + { + return $this->normalizer->{$method}(...$arguments); + } } diff --git a/Debug/TraceableSerializer.php b/Debug/TraceableSerializer.php index 557bf9128..bc16bd7f8 100644 --- a/Debug/TraceableSerializer.php +++ b/Debug/TraceableSerializer.php @@ -165,4 +165,12 @@ public function supportsDecoding(string $format, array $context = []): bool { return $this->serializer->supportsDecoding($format, $context); } + + /** + * Proxies all method calls to the original serializer. + */ + public function __call(string $method, array $arguments): mixed + { + return $this->serializer->{$method}(...$arguments); + } } From 56da3815152e8ead4d91b839a73e1e23fde1e0ff Mon Sep 17 00:00:00 2001 From: Daniel Burger <48986191+danielburger1337@users.noreply.github.com> Date: Tue, 31 May 2022 17:48:08 +0200 Subject: [PATCH 038/297] Added missing __call to TraceableEncoder --- Debug/TraceableEncoder.php | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/Debug/TraceableEncoder.php b/Debug/TraceableEncoder.php index c2927adf9..9f58f2b12 100644 --- a/Debug/TraceableEncoder.php +++ b/Debug/TraceableEncoder.php @@ -121,4 +121,12 @@ public function needsNormalization(): bool { return !$this->encoder instanceof NormalizationAwareEncoder; } + + /** + * Proxies all method calls to the original encoder. + */ + public function __call(string $method, array $arguments): mixed + { + return $this->encoder->{$method}(...$arguments); + } } From f44d623c498e1135c73ea1302a6f67ef279ce1f5 Mon Sep 17 00:00:00 2001 From: Mathias Arlaud Date: Tue, 31 May 2022 18:08:20 +0200 Subject: [PATCH 039/297] [Serializer] Forget partially collected traces --- DataCollector/SerializerDataCollector.php | 4 ++++ .../SerializerDataCollectorTest.php | 21 +++++++++++++++++++ 2 files changed, 25 insertions(+) diff --git a/DataCollector/SerializerDataCollector.php b/DataCollector/SerializerDataCollector.php index cfea563d0..2cc4ba7a9 100644 --- a/DataCollector/SerializerDataCollector.php +++ b/DataCollector/SerializerDataCollector.php @@ -179,6 +179,10 @@ public function lateCollect(): void ]; foreach ($this->collected as $collected) { + if (!isset($collected['data'])) { + continue; + } + $data = [ 'data' => $this->cloneVar($collected['data']), 'dataType' => get_debug_type($collected['data']), diff --git a/Tests/DataCollector/SerializerDataCollectorTest.php b/Tests/DataCollector/SerializerDataCollectorTest.php index 7b22b6064..b929e2627 100644 --- a/Tests/DataCollector/SerializerDataCollectorTest.php +++ b/Tests/DataCollector/SerializerDataCollectorTest.php @@ -276,6 +276,27 @@ public function testReset() $this->assertSame([], $dataCollector->getData()); } + public function testDoNotCollectPartialTraces() + { + $dataCollector = new SerializerDataCollector(); + + $dataCollector->collectNormalization('traceIdOne', DateTimeNormalizer::class, 1.0); + $dataCollector->collectDenormalization('traceIdTwo', DateTimeNormalizer::class, 1.0); + $dataCollector->collectEncoding('traceIdThree', CsvEncoder::class, 10.0); + $dataCollector->collectDecoding('traceIdFour', JsonEncoder::class, 1.0); + + $dataCollector->lateCollect(); + + $data = $dataCollector->getData(); + + $this->assertSame([], $data['serialize']); + $this->assertSame([], $data['deserialize']); + $this->assertSame([], $data['normalize']); + $this->assertSame([], $data['denormalize']); + $this->assertSame([], $data['encode']); + $this->assertSame([], $data['decode']); + } + /** * Cast cloned vars to be able to test nested values. */ From ea7fe34da221b729aea1fa6c1ca76cccfecaff32 Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Tue, 31 May 2022 18:23:46 +0200 Subject: [PATCH 040/297] [Serializer] code cleanup --- Debug/TraceableEncoder.php | 12 ++---------- Debug/TraceableNormalizer.php | 12 ++---------- Tests/Debug/TraceableSerializerTest.php | 20 ++++---------------- 3 files changed, 8 insertions(+), 36 deletions(-) diff --git a/Debug/TraceableEncoder.php b/Debug/TraceableEncoder.php index c2927adf9..70d4452b8 100644 --- a/Debug/TraceableEncoder.php +++ b/Debug/TraceableEncoder.php @@ -55,17 +55,13 @@ public function encode(mixed $data, string $format, array $context = []): string /** * {@inheritDoc} - * - * @param array $context */ - public function supportsEncoding(string $format /*, array $context = [] */): bool + public function supportsEncoding(string $format, array $context = []): bool { if (!$this->encoder instanceof EncoderInterface) { return false; } - $context = \func_num_args() > 1 ? func_get_arg(1) : []; - return $this->encoder->supportsEncoding($format, $context); } @@ -91,17 +87,13 @@ public function decode(string $data, string $format, array $context = []): mixed /** * {@inheritDoc} - * - * @param array $context */ - public function supportsDecoding(string $format /*, array $context = [] */): bool + public function supportsDecoding(string $format, array $context = []): bool { if (!$this->encoder instanceof DecoderInterface) { return false; } - $context = \func_num_args() > 1 ? func_get_arg(1) : []; - return $this->encoder->supportsDecoding($format, $context); } diff --git a/Debug/TraceableNormalizer.php b/Debug/TraceableNormalizer.php index 33e1b255a..8e430e55d 100644 --- a/Debug/TraceableNormalizer.php +++ b/Debug/TraceableNormalizer.php @@ -57,17 +57,13 @@ public function normalize(mixed $object, string $format = null, array $context = /** * {@inheritDoc} - * - * @param array $context */ - public function supportsNormalization(mixed $data, string $format = null /*, array $context = [] */): bool + public function supportsNormalization(mixed $data, string $format = null, array $context = []): bool { if (!$this->normalizer instanceof NormalizerInterface) { return false; } - $context = \func_num_args() > 2 ? func_get_arg(2) : []; - return $this->normalizer->supportsNormalization($data, $format, $context); } @@ -93,17 +89,13 @@ public function denormalize(mixed $data, string $type, string $format = null, ar /** * {@inheritDoc} - * - * @param array $context */ - public function supportsDenormalization(mixed $data, string $type, string $format = null /*, array $context = [] */): bool + public function supportsDenormalization(mixed $data, string $type, string $format = null, array $context = []): bool { if (!$this->normalizer instanceof DenormalizerInterface) { return false; } - $context = \func_num_args() > 3 ? func_get_arg(3) : []; - return $this->normalizer->supportsDenormalization($data, $type, $format, $context); } diff --git a/Tests/Debug/TraceableSerializerTest.php b/Tests/Debug/TraceableSerializerTest.php index ae8a01623..00a1ef58a 100644 --- a/Tests/Debug/TraceableSerializerTest.php +++ b/Tests/Debug/TraceableSerializerTest.php @@ -145,10 +145,7 @@ public function normalize(mixed $object, string $format = null, array $context = return 'normalized'; } - /** - * @param array $context - */ - public function supportsNormalization(mixed $data, string $format = null /*, array $context = [] */): bool + public function supportsNormalization(mixed $data, string $format = null, array $context = []): bool { return true; } @@ -158,10 +155,7 @@ public function denormalize(mixed $data, string $type, string $format = null, ar return 'denormalized'; } - /** - * @param array $context - */ - public function supportsDenormalization(mixed $data, string $type, string $format = null /*, array $context = [] */): bool + public function supportsDenormalization(mixed $data, string $type, string $format = null, array $context = []): bool { return true; } @@ -171,10 +165,7 @@ public function encode(mixed $data, string $format, array $context = []): string return 'encoded'; } - /** - * @param array $context - */ - public function supportsEncoding(string $format /*, array $context = [] */): bool + public function supportsEncoding(string $format, array $context = []): bool { return true; } @@ -184,10 +175,7 @@ public function decode(string $data, string $format, array $context = []): mixed return 'decoded'; } - /** - * @param array $context - */ - public function supportsDecoding(string $format /*, array $context = [] */): bool + public function supportsDecoding(string $format, array $context = []): bool { return true; } From 64b6951f0424897c2275e50e569ac7bb4288664d Mon Sep 17 00:00:00 2001 From: Maxime Steinhausser Date: Thu, 2 Jun 2022 18:00:35 +0200 Subject: [PATCH 041/297] [Serializer][WebProfilerBundle] Collect & show caller source code --- DataCollector/SerializerDataCollector.php | 26 ++++---- Debug/TraceableSerializer.php | 49 ++++++++++++-- .../SerializerDataCollectorTest.php | 65 ++++++++++++------- 3 files changed, 97 insertions(+), 43 deletions(-) diff --git a/DataCollector/SerializerDataCollector.php b/DataCollector/SerializerDataCollector.php index 2cc4ba7a9..0d0ccc6d5 100644 --- a/DataCollector/SerializerDataCollector.php +++ b/DataCollector/SerializerDataCollector.php @@ -16,6 +16,7 @@ use Symfony\Component\HttpKernel\DataCollector\DataCollector; use Symfony\Component\HttpKernel\DataCollector\LateDataCollectorInterface; use Symfony\Component\Serializer\Debug\TraceableSerializer; +use Symfony\Component\Serializer\SerializerInterface; use Symfony\Component\VarDumper\Cloner\Data; /** @@ -70,68 +71,68 @@ public function getTotalTime(): float return $totalTime; } - public function collectSerialize(string $traceId, mixed $data, string $format, array $context, float $time): void + public function collectSerialize(string $traceId, mixed $data, string $format, array $context, float $time, array $caller): void { unset($context[TraceableSerializer::DEBUG_TRACE_ID]); $this->collected[$traceId] = array_merge( $this->collected[$traceId] ?? [], - compact('data', 'format', 'context', 'time'), + compact('data', 'format', 'context', 'time', 'caller'), ['method' => 'serialize'], ); } - public function collectDeserialize(string $traceId, mixed $data, string $type, string $format, array $context, float $time): void + public function collectDeserialize(string $traceId, mixed $data, string $type, string $format, array $context, float $time, array $caller): void { unset($context[TraceableSerializer::DEBUG_TRACE_ID]); $this->collected[$traceId] = array_merge( $this->collected[$traceId] ?? [], - compact('data', 'format', 'type', 'context', 'time'), + compact('data', 'format', 'type', 'context', 'time', 'caller'), ['method' => 'deserialize'], ); } - public function collectNormalize(string $traceId, mixed $data, ?string $format, array $context, float $time): void + public function collectNormalize(string $traceId, mixed $data, ?string $format, array $context, float $time, array $caller): void { unset($context[TraceableSerializer::DEBUG_TRACE_ID]); $this->collected[$traceId] = array_merge( $this->collected[$traceId] ?? [], - compact('data', 'format', 'context', 'time'), + compact('data', 'format', 'context', 'time', 'caller'), ['method' => 'normalize'], ); } - public function collectDenormalize(string $traceId, mixed $data, string $type, ?string $format, array $context, float $time): void + public function collectDenormalize(string $traceId, mixed $data, string $type, ?string $format, array $context, float $time, array $caller): void { unset($context[TraceableSerializer::DEBUG_TRACE_ID]); $this->collected[$traceId] = array_merge( $this->collected[$traceId] ?? [], - compact('data', 'format', 'type', 'context', 'time'), + compact('data', 'format', 'type', 'context', 'time', 'caller'), ['method' => 'denormalize'], ); } - public function collectEncode(string $traceId, mixed $data, ?string $format, array $context, float $time): void + public function collectEncode(string $traceId, mixed $data, ?string $format, array $context, float $time, array $caller): void { unset($context[TraceableSerializer::DEBUG_TRACE_ID]); $this->collected[$traceId] = array_merge( $this->collected[$traceId] ?? [], - compact('data', 'format', 'context', 'time'), + compact('data', 'format', 'context', 'time', 'caller'), ['method' => 'encode'], ); } - public function collectDecode(string $traceId, mixed $data, ?string $format, array $context, float $time): void + public function collectDecode(string $traceId, mixed $data, ?string $format, array $context, float $time, array $caller): void { unset($context[TraceableSerializer::DEBUG_TRACE_ID]); $this->collected[$traceId] = array_merge( $this->collected[$traceId] ?? [], - compact('data', 'format', 'context', 'time'), + compact('data', 'format', 'context', 'time', 'caller'), ['method' => 'decode'], ); } @@ -192,6 +193,7 @@ public function lateCollect(): void 'context' => $this->cloneVar($collected['context']), 'normalization' => [], 'encoding' => [], + 'caller' => $collected['caller'] ?? null, ]; if (isset($collected['normalization'])) { diff --git a/Debug/TraceableSerializer.php b/Debug/TraceableSerializer.php index bc16bd7f8..bcc1f71a6 100644 --- a/Debug/TraceableSerializer.php +++ b/Debug/TraceableSerializer.php @@ -49,7 +49,9 @@ public function serialize(mixed $data, string $format, array $context = []): str $result = $this->serializer->serialize($data, $format, $context); $time = microtime(true) - $startTime; - $this->dataCollector->collectSerialize($traceId, $data, $format, $context, $time); + $caller = $this->getCaller(__FUNCTION__, SerializerInterface::class); + + $this->dataCollector->collectSerialize($traceId, $data, $format, $context, $time, $caller); return $result; } @@ -65,7 +67,9 @@ public function deserialize(mixed $data, string $type, string $format, array $co $result = $this->serializer->deserialize($data, $type, $format, $context); $time = microtime(true) - $startTime; - $this->dataCollector->collectDeserialize($traceId, $data, $type, $format, $context, $time); + $caller = $this->getCaller(__FUNCTION__, SerializerInterface::class); + + $this->dataCollector->collectDeserialize($traceId, $data, $type, $format, $context, $time, $caller); return $result; } @@ -81,7 +85,9 @@ public function normalize(mixed $object, string $format = null, array $context = $result = $this->serializer->normalize($object, $format, $context); $time = microtime(true) - $startTime; - $this->dataCollector->collectNormalize($traceId, $object, $format, $context, $time); + $caller = $this->getCaller(__FUNCTION__, NormalizerInterface::class); + + $this->dataCollector->collectNormalize($traceId, $object, $format, $context, $time, $caller); return $result; } @@ -97,7 +103,9 @@ public function denormalize(mixed $data, string $type, string $format = null, ar $result = $this->serializer->denormalize($data, $type, $format, $context); $time = microtime(true) - $startTime; - $this->dataCollector->collectDenormalize($traceId, $data, $type, $format, $context, $time); + $caller = $this->getCaller(__FUNCTION__, DenormalizerInterface::class); + + $this->dataCollector->collectDenormalize($traceId, $data, $type, $format, $context, $time, $caller); return $result; } @@ -113,7 +121,9 @@ public function encode(mixed $data, string $format, array $context = []): string $result = $this->serializer->encode($data, $format, $context); $time = microtime(true) - $startTime; - $this->dataCollector->collectEncode($traceId, $data, $format, $context, $time); + $caller = $this->getCaller(__FUNCTION__, EncoderInterface::class); + + $this->dataCollector->collectEncode($traceId, $data, $format, $context, $time, $caller); return $result; } @@ -129,7 +139,9 @@ public function decode(string $data, string $format, array $context = []): mixed $result = $this->serializer->decode($data, $format, $context); $time = microtime(true) - $startTime; - $this->dataCollector->collectDecode($traceId, $data, $format, $context, $time); + $caller = $this->getCaller(__FUNCTION__, DecoderInterface::class); + + $this->dataCollector->collectDecode($traceId, $data, $format, $context, $time, $caller); return $result; } @@ -173,4 +185,29 @@ public function __call(string $method, array $arguments): mixed { return $this->serializer->{$method}(...$arguments); } + + private function getCaller(string $method, string $interface): array + { + $trace = debug_backtrace(\DEBUG_BACKTRACE_IGNORE_ARGS, 8); + + $file = $trace[0]['file']; + $line = $trace[0]['line']; + + for ($i = 1; $i < 8; ++$i) { + if (isset($trace[$i]['class'], $trace[$i]['function']) + && $method === $trace[$i]['function'] + && is_a($trace[$i]['class'], $interface, true) + ) { + $file = $trace[$i]['file']; + $line = $trace[$i]['line']; + + break; + } + } + + $name = str_replace('\\', '/', $file); + $name = substr($name, strrpos($name, '/') + 1); + + return compact('name', 'file', 'line'); + } } diff --git a/Tests/DataCollector/SerializerDataCollectorTest.php b/Tests/DataCollector/SerializerDataCollectorTest.php index b929e2627..aebde8efa 100644 --- a/Tests/DataCollector/SerializerDataCollectorTest.php +++ b/Tests/DataCollector/SerializerDataCollectorTest.php @@ -24,8 +24,9 @@ public function testCollectSerialize() { $dataCollector = new SerializerDataCollector(); - $dataCollector->collectSerialize('traceIdOne', 'data', 'format', ['foo' => 'bar'], 1.0); - $dataCollector->collectDeserialize('traceIdTwo', 'data', 'type', 'format', ['foo' => 'bar'], 1.0); + $caller = ['name' => 'Foo.php', 'file' => 'src/Foo.php', 'line' => 123]; + $dataCollector->collectSerialize('traceIdOne', 'data', 'format', ['foo' => 'bar'], 1.0, $caller); + $dataCollector->collectDeserialize('traceIdTwo', 'data', 'type', 'format', ['foo' => 'bar'], 1.0, $caller); $dataCollector->lateCollect(); $collectedData = $this->castCollectedData($dataCollector->getData()); @@ -39,6 +40,7 @@ public function testCollectSerialize() 'context' => ['foo' => 'bar'], 'normalization' => [], 'encoding' => [], + 'caller' => $caller, ]], $collectedData['serialize']); $this->assertSame([[ @@ -50,6 +52,7 @@ public function testCollectSerialize() 'context' => ['foo' => 'bar'], 'normalization' => [], 'encoding' => [], + 'caller' => $caller, ]], $collectedData['deserialize']); } @@ -57,8 +60,9 @@ public function testCollectNormalize() { $dataCollector = new SerializerDataCollector(); - $dataCollector->collectNormalize('traceIdOne', 'data', 'format', ['foo' => 'bar'], 1.0); - $dataCollector->collectDenormalize('traceIdTwo', 'data', 'type', 'format', ['foo' => 'bar'], 1.0); + $caller = ['name' => 'Foo.php', 'file' => 'src/Foo.php', 'line' => 123]; + $dataCollector->collectNormalize('traceIdOne', 'data', 'format', ['foo' => 'bar'], 1.0, $caller); + $dataCollector->collectDenormalize('traceIdTwo', 'data', 'type', 'format', ['foo' => 'bar'], 1.0, $caller); $dataCollector->lateCollect(); $collectedData = $this->castCollectedData($dataCollector->getData()); @@ -72,6 +76,7 @@ public function testCollectNormalize() 'context' => ['foo' => 'bar'], 'normalization' => [], 'encoding' => [], + 'caller' => $caller, ]], $collectedData['normalize']); $this->assertSame([[ @@ -83,6 +88,7 @@ public function testCollectNormalize() 'context' => ['foo' => 'bar'], 'normalization' => [], 'encoding' => [], + 'caller' => $caller, ]], $collectedData['denormalize']); } @@ -90,8 +96,9 @@ public function testCollectEncode() { $dataCollector = new SerializerDataCollector(); - $dataCollector->collectEncode('traceIdOne', 'data', 'format', ['foo' => 'bar'], 1.0); - $dataCollector->collectDecode('traceIdTwo', 'data', 'format', ['foo' => 'bar'], 1.0); + $caller = ['name' => 'Foo.php', 'file' => 'src/Foo.php', 'line' => 123]; + $dataCollector->collectEncode('traceIdOne', 'data', 'format', ['foo' => 'bar'], 1.0, $caller); + $dataCollector->collectDecode('traceIdTwo', 'data', 'format', ['foo' => 'bar'], 1.0, $caller); $dataCollector->lateCollect(); $collectedData = $this->castCollectedData($dataCollector->getData()); @@ -105,6 +112,7 @@ public function testCollectEncode() 'context' => ['foo' => 'bar'], 'normalization' => [], 'encoding' => [], + 'caller' => $caller, ]], $collectedData['encode']); $this->assertSame([[ @@ -116,6 +124,7 @@ public function testCollectEncode() 'context' => ['foo' => 'bar'], 'normalization' => [], 'encoding' => [], + 'caller' => $caller, ]], $collectedData['decode']); } @@ -123,8 +132,9 @@ public function testCollectNormalization() { $dataCollector = new SerializerDataCollector(); - $dataCollector->collectNormalize('traceIdOne', 'data', 'format', ['foo' => 'bar'], 20.0); - $dataCollector->collectDenormalize('traceIdTwo', 'data', 'type', 'format', ['foo' => 'bar'], 20.0); + $caller = ['name' => 'Foo.php', 'file' => 'src/Foo.php', 'line' => 123]; + $dataCollector->collectNormalize('traceIdOne', 'data', 'format', ['foo' => 'bar'], 20.0, $caller); + $dataCollector->collectDenormalize('traceIdTwo', 'data', 'type', 'format', ['foo' => 'bar'], 20.0, $caller); $dataCollector->collectNormalization('traceIdOne', DateTimeNormalizer::class, 1.0); $dataCollector->collectNormalization('traceIdOne', DateTimeNormalizer::class, 2.0); @@ -178,8 +188,9 @@ public function testCollectEncoding() { $dataCollector = new SerializerDataCollector(); - $dataCollector->collectEncode('traceIdOne', 'data', 'format', ['foo' => 'bar'], 20.0); - $dataCollector->collectDecode('traceIdTwo', 'data', 'format', ['foo' => 'bar'], 20.0); + $caller = ['name' => 'Foo.php', 'file' => 'src/Foo.php', 'line' => 123]; + $dataCollector->collectEncode('traceIdOne', 'data', 'format', ['foo' => 'bar'], 20.0, $caller); + $dataCollector->collectDecode('traceIdTwo', 'data', 'format', ['foo' => 'bar'], 20.0, $caller); $dataCollector->collectEncoding('traceIdOne', JsonEncoder::class, 1.0); $dataCollector->collectEncoding('traceIdOne', JsonEncoder::class, 2.0); @@ -233,13 +244,14 @@ public function testCountHandled() { $dataCollector = new SerializerDataCollector(); - $dataCollector->collectSerialize('traceIdOne', 'data', 'format', ['foo' => 'bar'], 1.0); - $dataCollector->collectDeserialize('traceIdTwo', 'data', 'type', 'format', ['foo' => 'bar'], 1.0); - $dataCollector->collectNormalize('traceIdThree', 'data', 'format', ['foo' => 'bar'], 20.0); - $dataCollector->collectDenormalize('traceIdFour', 'data', 'type', 'format', ['foo' => 'bar'], 20.0); - $dataCollector->collectEncode('traceIdFive', 'data', 'format', ['foo' => 'bar'], 20.0); - $dataCollector->collectDecode('traceIdSix', 'data', 'format', ['foo' => 'bar'], 20.0); - $dataCollector->collectSerialize('traceIdSeven', 'data', 'format', ['foo' => 'bar'], 1.0); + $caller = ['name' => 'Foo.php', 'file' => 'src/Foo.php', 'line' => 123]; + $dataCollector->collectSerialize('traceIdOne', 'data', 'format', ['foo' => 'bar'], 1.0, $caller); + $dataCollector->collectDeserialize('traceIdTwo', 'data', 'type', 'format', ['foo' => 'bar'], 1.0, $caller); + $dataCollector->collectNormalize('traceIdThree', 'data', 'format', ['foo' => 'bar'], 20.0, $caller); + $dataCollector->collectDenormalize('traceIdFour', 'data', 'type', 'format', ['foo' => 'bar'], 20.0, $caller); + $dataCollector->collectEncode('traceIdFive', 'data', 'format', ['foo' => 'bar'], 20.0, $caller); + $dataCollector->collectDecode('traceIdSix', 'data', 'format', ['foo' => 'bar'], 20.0, $caller); + $dataCollector->collectSerialize('traceIdSeven', 'data', 'format', ['foo' => 'bar'], 1.0, $caller); $dataCollector->lateCollect(); @@ -250,13 +262,15 @@ public function testGetTotalTime() { $dataCollector = new SerializerDataCollector(); - $dataCollector->collectSerialize('traceIdOne', 'data', 'format', ['foo' => 'bar'], 1.0); - $dataCollector->collectDeserialize('traceIdTwo', 'data', 'type', 'format', ['foo' => 'bar'], 2.0); - $dataCollector->collectNormalize('traceIdThree', 'data', 'format', ['foo' => 'bar'], 3.0); - $dataCollector->collectDenormalize('traceIdFour', 'data', 'type', 'format', ['foo' => 'bar'], 4.0); - $dataCollector->collectEncode('traceIdFive', 'data', 'format', ['foo' => 'bar'], 5.0); - $dataCollector->collectDecode('traceIdSix', 'data', 'format', ['foo' => 'bar'], 6.0); - $dataCollector->collectSerialize('traceIdSeven', 'data', 'format', ['foo' => 'bar'], 7.0); + $caller = ['name' => 'Foo.php', 'file' => 'src/Foo.php', 'line' => 123]; + + $dataCollector->collectSerialize('traceIdOne', 'data', 'format', ['foo' => 'bar'], 1.0, $caller); + $dataCollector->collectDeserialize('traceIdTwo', 'data', 'type', 'format', ['foo' => 'bar'], 2.0, $caller); + $dataCollector->collectNormalize('traceIdThree', 'data', 'format', ['foo' => 'bar'], 3.0, $caller); + $dataCollector->collectDenormalize('traceIdFour', 'data', 'type', 'format', ['foo' => 'bar'], 4.0, $caller); + $dataCollector->collectEncode('traceIdFive', 'data', 'format', ['foo' => 'bar'], 5.0, $caller); + $dataCollector->collectDecode('traceIdSix', 'data', 'format', ['foo' => 'bar'], 6.0, $caller); + $dataCollector->collectSerialize('traceIdSeven', 'data', 'format', ['foo' => 'bar'], 7.0, $caller); $dataCollector->lateCollect(); @@ -267,7 +281,8 @@ public function testReset() { $dataCollector = new SerializerDataCollector(); - $dataCollector->collectSerialize('traceIdOne', 'data', 'format', ['foo' => 'bar'], 1.0); + $caller = ['name' => 'Foo.php', 'file' => 'src/Foo.php', 'line' => 123]; + $dataCollector->collectSerialize('traceIdOne', 'data', 'format', ['foo' => 'bar'], 1.0, $caller); $dataCollector->lateCollect(); $this->assertNotSame([], $dataCollector->getData()); From da38f8ea49fa617411d20d28eddac6dc0e0b90ab Mon Sep 17 00:00:00 2001 From: Alexandre Daubois Date: Mon, 6 Jun 2022 14:15:47 +0200 Subject: [PATCH 042/297] [Serializer] Add support of true built-in type (from PHP 8.2) --- Normalizer/AbstractObjectNormalizer.php | 2 +- Tests/Fixtures/TrueBuiltInDummy.php | 17 +++++++++++++++++ Tests/SerializerTest.php | 14 ++++++++++++++ 3 files changed, 32 insertions(+), 1 deletion(-) create mode 100644 Tests/Fixtures/TrueBuiltInDummy.php diff --git a/Normalizer/AbstractObjectNormalizer.php b/Normalizer/AbstractObjectNormalizer.php index daf07f37a..1ee800248 100644 --- a/Normalizer/AbstractObjectNormalizer.php +++ b/Normalizer/AbstractObjectNormalizer.php @@ -565,7 +565,7 @@ private function validateAndDenormalize(array $types, string $currentClass, stri return (float) $data; } - if (Type::BUILTIN_TYPE_FALSE === $builtinType && false === $data) { + if ((Type::BUILTIN_TYPE_FALSE === $builtinType && false === $data) || (Type::BUILTIN_TYPE_TRUE === $builtinType && true === $data)) { return $data; } diff --git a/Tests/Fixtures/TrueBuiltInDummy.php b/Tests/Fixtures/TrueBuiltInDummy.php new file mode 100644 index 000000000..aa7991484 --- /dev/null +++ b/Tests/Fixtures/TrueBuiltInDummy.php @@ -0,0 +1,17 @@ + + * + * 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; + +class TrueBuiltInDummy +{ + public true $true = true; +} diff --git a/Tests/SerializerTest.php b/Tests/SerializerTest.php index 2f4fdc069..39552fdd7 100644 --- a/Tests/SerializerTest.php +++ b/Tests/SerializerTest.php @@ -63,6 +63,7 @@ use Symfony\Component\Serializer\Tests\Fixtures\Php74Full; use Symfony\Component\Serializer\Tests\Fixtures\Php80WithPromotedTypedConstructor; use Symfony\Component\Serializer\Tests\Fixtures\TraversableDummy; +use Symfony\Component\Serializer\Tests\Fixtures\TrueBuiltInDummy; use Symfony\Component\Serializer\Tests\Normalizer\TestDenormalizer; use Symfony\Component\Serializer\Tests\Normalizer\TestNormalizer; @@ -795,6 +796,19 @@ public function testFalseBuiltInTypes() $this->assertEquals(new FalseBuiltInDummy(), $actual); } + /** + * @requires PHP 8.2 + */ + public function testTrueBuiltInTypes() + { + $extractor = new PropertyInfoExtractor([], [new ReflectionExtractor()]); + $serializer = new Serializer([new ObjectNormalizer(null, null, null, $extractor)], ['json' => new JsonEncoder()]); + + $actual = $serializer->deserialize('{"true":true}', TrueBuiltInDummy::class, 'json'); + + $this->assertEquals(new TrueBuiltInDummy(), $actual); + } + private function serializerWithClassDiscriminator() { $classMetadataFactory = new ClassMetadataFactory(new AnnotationLoader(new AnnotationReader())); From ee470df50ea96d52b170779c75db276508ad30ae Mon Sep 17 00:00:00 2001 From: Daniel Badura Date: Wed, 15 Jun 2022 13:48:22 +0200 Subject: [PATCH 043/297] [Serializer] Provide context information from attribute for promoted properties --- Normalizer/AbstractNormalizer.php | 46 ++++++++++++++-- .../ContextDummyPromotedProperties.php | 53 +++++++++++++++++++ .../ContextDummyPromotedProperties.php | 45 ++++++++++++++++ Tests/Mapping/Loader/AnnotationLoaderTest.php | 8 +++ 4 files changed, 149 insertions(+), 3 deletions(-) create mode 100644 Tests/Fixtures/Annotations/ContextDummyPromotedProperties.php create mode 100644 Tests/Fixtures/Attributes/ContextDummyPromotedProperties.php diff --git a/Normalizer/AbstractNormalizer.php b/Normalizer/AbstractNormalizer.php index 44ba45f58..a31b10588 100644 --- a/Normalizer/AbstractNormalizer.php +++ b/Normalizer/AbstractNormalizer.php @@ -334,7 +334,8 @@ protected function instantiateObject(array &$data, string $class, array &$contex $params = []; foreach ($constructorParameters as $constructorParameter) { $paramName = $constructorParameter->name; - $key = $this->nameConverter ? $this->nameConverter->normalize($paramName, $class, $format, $context) : $paramName; + $attributeContext = $this->getAttributeDenormalizationContext($class, $paramName, $context); + $key = $this->nameConverter ? $this->nameConverter->normalize($paramName, $class, $format, $attributeContext) : $paramName; $allowed = false === $allowedAttributes || \in_array($paramName, $allowedAttributes); $ignored = !$this->isAllowedAttribute($class, $paramName, $format, $context); @@ -346,7 +347,7 @@ protected function instantiateObject(array &$data, string $class, array &$contex $variadicParameters = []; foreach ($data[$paramName] as $parameterData) { - $variadicParameters[] = $this->denormalizeParameter($reflectionClass, $constructorParameter, $paramName, $parameterData, $context, $format); + $variadicParameters[] = $this->denormalizeParameter($reflectionClass, $constructorParameter, $paramName, $parameterData, $attributeContext, $format); } $params = array_merge($params, $variadicParameters); @@ -363,7 +364,7 @@ protected function instantiateObject(array &$data, string $class, array &$contex // Don't run set for a parameter passed to the constructor try { - $params[] = $this->denormalizeParameter($reflectionClass, $constructorParameter, $paramName, $parameterData, $context, $format); + $params[] = $this->denormalizeParameter($reflectionClass, $constructorParameter, $paramName, $parameterData, $attributeContext, $format); } catch (NotNormalizableValueException $exception) { if (!isset($context['not_normalizable_value_exceptions'])) { throw $exception; @@ -485,4 +486,43 @@ final protected function applyCallbacks(mixed $value, object|string $object, str return $callback ? $callback($value, $object, $attribute, $format, $context) : $value; } + + /** + * Computes the normalization context merged with current one. Metadata always wins over global context, as more specific. + * + * @internal + */ + protected function getAttributeNormalizationContext(object $object, string $attribute, array $context): array + { + if (null === $metadata = $this->getAttributeMetadata($object, $attribute)) { + return $context; + } + + return array_merge($context, $metadata->getNormalizationContextForGroups($this->getGroups($context))); + } + + /** + * Computes the denormalization context merged with current one. Metadata always wins over global context, as more specific. + * + * @internal + */ + protected function getAttributeDenormalizationContext(string $class, string $attribute, array $context): array + { + $context['deserialization_path'] = ($context['deserialization_path'] ?? false) ? $context['deserialization_path'].'.'.$attribute : $attribute; + + if (null === $metadata = $this->getAttributeMetadata($class, $attribute)) { + return $context; + } + + return array_merge($context, $metadata->getDenormalizationContextForGroups($this->getGroups($context))); + } + + private function getAttributeMetadata($objectOrClass, string $attribute): ?AttributeMetadataInterface + { + if (!$this->classMetadataFactory) { + return null; + } + + return $this->classMetadataFactory->getMetadataFor($objectOrClass)->getAttributesMetadata()[$attribute] ?? null; + } } diff --git a/Tests/Fixtures/Annotations/ContextDummyPromotedProperties.php b/Tests/Fixtures/Annotations/ContextDummyPromotedProperties.php new file mode 100644 index 000000000..5aa108d1e --- /dev/null +++ b/Tests/Fixtures/Annotations/ContextDummyPromotedProperties.php @@ -0,0 +1,53 @@ + + * + * 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\Annotations; + +use Symfony\Component\Serializer\Annotation\Context; + +/** + * @author Maxime Steinhausser + */ +class ContextDummyPromotedProperties extends ContextDummyParent +{ + public function __construct( + /** + * @Context({ "foo" = "value", "bar" = "value", "nested" = { + * "nested_key" = "nested_value", + * }, "array": { "first", "second" } }) + * @Context({ "bar" = "value_for_group_a" }, groups = "a") + */ + public $foo, + + /** + * @Context( + * normalizationContext = { "format" = "d/m/Y" }, + * denormalizationContext = { "format" = "m-d-Y H:i" }, + * groups = {"a", "b"} + * ) + */ + public $bar, + + /** + * @Context(normalizationContext={ "prop" = "dummy_value" }) + */ + public $overriddenParentProperty, + ) { + } + + /** + * @Context({ "method" = "method_with_context" }) + */ + public function getMethodWithContext() + { + return 'method_with_context'; + } +} diff --git a/Tests/Fixtures/Attributes/ContextDummyPromotedProperties.php b/Tests/Fixtures/Attributes/ContextDummyPromotedProperties.php new file mode 100644 index 000000000..5dbc7d58e --- /dev/null +++ b/Tests/Fixtures/Attributes/ContextDummyPromotedProperties.php @@ -0,0 +1,45 @@ + + * + * 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\Attributes; + +use Symfony\Component\Serializer\Annotation\Context; + +/** + * @author Maxime Steinhausser + */ +class ContextDummyPromotedProperties extends ContextDummyParent +{ + public function __construct( + #[Context(['foo' => 'value', 'bar' => 'value', 'nested' => [ + 'nested_key' => 'nested_value', + ], 'array' => ['first', 'second']])] + #[Context(context: ['bar' => 'value_for_group_a'], groups: ['a'])] + public $foo, + + #[Context( + normalizationContext: ['format' => 'd/m/Y'], + denormalizationContext: ['format' => 'm-d-Y H:i'], + groups: ['a', 'b'], + )] + public $bar, + + #[Context(normalizationContext: ['prop' => 'dummy_value'])] + public $overriddenParentProperty, + ) { + } + + #[Context(['method' => 'method_with_context'])] + public function getMethodWithContext() + { + return 'method_with_context'; + } +} diff --git a/Tests/Mapping/Loader/AnnotationLoaderTest.php b/Tests/Mapping/Loader/AnnotationLoaderTest.php index 9245e1dcd..f349e9ece 100644 --- a/Tests/Mapping/Loader/AnnotationLoaderTest.php +++ b/Tests/Mapping/Loader/AnnotationLoaderTest.php @@ -120,6 +120,14 @@ public function testLoadContexts() $this->assertLoadedContexts($this->getNamespace().'\ContextDummy', $this->getNamespace().'\ContextDummyParent'); } + /** + * @requires PHP 8 + */ + public function testLoadContextsPropertiesPromoted() + { + $this->assertLoadedContexts($this->getNamespace().'\ContextDummyPromotedProperties', $this->getNamespace().'\ContextDummyParent'); + } + public function testThrowsOnContextOnInvalidMethod() { $class = $this->getNamespace().'\BadMethodContextDummy'; From 6dd6cb4f51c1b8fd52e29458be2808f403a01db5 Mon Sep 17 00:00:00 2001 From: Fabien Potencier Date: Sun, 3 Jul 2022 08:52:09 +0200 Subject: [PATCH 044/297] Remove unneeded annotation --- Tests/Mapping/Loader/AnnotationLoaderTest.php | 3 --- 1 file changed, 3 deletions(-) diff --git a/Tests/Mapping/Loader/AnnotationLoaderTest.php b/Tests/Mapping/Loader/AnnotationLoaderTest.php index f349e9ece..1c30aae30 100644 --- a/Tests/Mapping/Loader/AnnotationLoaderTest.php +++ b/Tests/Mapping/Loader/AnnotationLoaderTest.php @@ -120,9 +120,6 @@ public function testLoadContexts() $this->assertLoadedContexts($this->getNamespace().'\ContextDummy', $this->getNamespace().'\ContextDummyParent'); } - /** - * @requires PHP 8 - */ public function testLoadContextsPropertiesPromoted() { $this->assertLoadedContexts($this->getNamespace().'\ContextDummyPromotedProperties', $this->getNamespace().'\ContextDummyParent'); From 86fe6644ff5f56dc6b05c374017caee250573a7a Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Mon, 4 Jul 2022 17:15:44 +0200 Subject: [PATCH 045/297] [Serializer] fix AbstractObjectNormalizer --- Normalizer/AbstractNormalizer.php | 5 +++- Normalizer/AbstractObjectNormalizer.php | 35 ------------------------- 2 files changed, 4 insertions(+), 36 deletions(-) diff --git a/Normalizer/AbstractNormalizer.php b/Normalizer/AbstractNormalizer.php index a31b10588..e79e5c852 100644 --- a/Normalizer/AbstractNormalizer.php +++ b/Normalizer/AbstractNormalizer.php @@ -517,7 +517,10 @@ protected function getAttributeDenormalizationContext(string $class, string $att return array_merge($context, $metadata->getDenormalizationContextForGroups($this->getGroups($context))); } - private function getAttributeMetadata($objectOrClass, string $attribute): ?AttributeMetadataInterface + /** + * @internal + */ + protected function getAttributeMetadata(object|string $objectOrClass, string $attribute): ?AttributeMetadataInterface { if (!$this->classMetadataFactory) { return null; diff --git a/Normalizer/AbstractObjectNormalizer.php b/Normalizer/AbstractObjectNormalizer.php index 2113a2b53..98b0092f6 100644 --- a/Normalizer/AbstractObjectNormalizer.php +++ b/Normalizer/AbstractObjectNormalizer.php @@ -226,41 +226,6 @@ public function normalize(mixed $object, string $format = null, array $context = return $data; } - /** - * Computes the normalization context merged with current one. Metadata always wins over global context, as more specific. - */ - private function getAttributeNormalizationContext(object $object, string $attribute, array $context): array - { - if (null === $metadata = $this->getAttributeMetadata($object, $attribute)) { - return $context; - } - - return array_merge($context, $metadata->getNormalizationContextForGroups($this->getGroups($context))); - } - - /** - * Computes the denormalization context merged with current one. Metadata always wins over global context, as more specific. - */ - private function getAttributeDenormalizationContext(string $class, string $attribute, array $context): array - { - $context['deserialization_path'] = ($context['deserialization_path'] ?? false) ? $context['deserialization_path'].'.'.$attribute : $attribute; - - if (null === $metadata = $this->getAttributeMetadata($class, $attribute)) { - return $context; - } - - return array_merge($context, $metadata->getDenormalizationContextForGroups($this->getGroups($context))); - } - - private function getAttributeMetadata(object|string $objectOrClass, string $attribute): ?AttributeMetadataInterface - { - if (!$this->classMetadataFactory) { - return null; - } - - return $this->classMetadataFactory->getMetadataFor($objectOrClass)->getAttributesMetadata()[$attribute] ?? null; - } - /** * {@inheritdoc} */ From 916b51597dd226fdb492b647c41771f315ec9d4f Mon Sep 17 00:00:00 2001 From: Maxime Steinhausser Date: Mon, 4 Jul 2022 19:00:03 +0200 Subject: [PATCH 046/297] [Serializer] Cannot use @Context context in AdvancedNameConverterInterface::denormalize --- CHANGELOG.md | 5 +++++ Normalizer/AbstractNormalizer.php | 4 ++-- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c2b3ebfb3..d9d22407e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,11 @@ CHANGELOG ========= +6.2 +--- + +* Add support for constructor promoted properties to `Context` attribute + 6.1 --- diff --git a/Normalizer/AbstractNormalizer.php b/Normalizer/AbstractNormalizer.php index e79e5c852..391cdcb39 100644 --- a/Normalizer/AbstractNormalizer.php +++ b/Normalizer/AbstractNormalizer.php @@ -334,8 +334,8 @@ protected function instantiateObject(array &$data, string $class, array &$contex $params = []; foreach ($constructorParameters as $constructorParameter) { $paramName = $constructorParameter->name; - $attributeContext = $this->getAttributeDenormalizationContext($class, $paramName, $context); - $key = $this->nameConverter ? $this->nameConverter->normalize($paramName, $class, $format, $attributeContext) : $paramName; + $key = $this->nameConverter ? $this->nameConverter->normalize($paramName, $class, $format, $context) : $paramName; + $attributeContext = $this->getAttributeDenormalizationContext($class, $key, $context); $allowed = false === $allowedAttributes || \in_array($paramName, $allowedAttributes); $ignored = !$this->isAllowedAttribute($class, $paramName, $format, $context); From c7b1d042bab9abb7b0697f2c669fcf8dc652d628 Mon Sep 17 00:00:00 2001 From: Fabien Potencier Date: Wed, 20 Jul 2022 17:00:40 +0200 Subject: [PATCH 047/297] Fix CS --- Encoder/CsvEncoder.php | 4 ++-- Encoder/DecoderInterface.php | 4 ++-- Encoder/EncoderInterface.php | 2 +- Encoder/JsonDecode.php | 2 +- Encoder/JsonEncode.php | 2 +- Encoder/JsonEncoder.php | 4 ++-- Encoder/XmlEncoder.php | 4 ++-- Encoder/YamlEncoder.php | 4 ++-- Normalizer/AbstractObjectNormalizer.php | 5 +++-- Normalizer/ConstraintViolationListNormalizer.php | 2 +- Normalizer/CustomNormalizer.php | 4 ++-- Normalizer/DataUriNormalizer.php | 4 ++-- Normalizer/DateIntervalNormalizer.php | 4 ++-- Normalizer/DateTimeNormalizer.php | 4 ++-- Normalizer/DateTimeZoneNormalizer.php | 4 ++-- Normalizer/DenormalizerInterface.php | 2 +- Normalizer/GetSetMethodNormalizer.php | 4 ++-- Normalizer/JsonSerializableNormalizer.php | 4 ++-- Normalizer/NormalizerInterface.php | 2 +- Normalizer/ProblemNormalizer.php | 2 +- Normalizer/PropertyNormalizer.php | 4 ++-- 21 files changed, 36 insertions(+), 35 deletions(-) diff --git a/Encoder/CsvEncoder.php b/Encoder/CsvEncoder.php index f3e0d47d9..b2c6fcd81 100644 --- a/Encoder/CsvEncoder.php +++ b/Encoder/CsvEncoder.php @@ -127,7 +127,7 @@ public function encode(mixed $data, string $format, array $context = []): string * * @param array $context */ - public function supportsEncoding(string $format /*, array $context = [] */): bool + public function supportsEncoding(string $format /* , array $context = [] */): bool { return self::FORMAT === $format; } @@ -215,7 +215,7 @@ public function decode(string $data, string $format, array $context = []): mixed * * @param array $context */ - public function supportsDecoding(string $format /*, array $context = [] */): bool + public function supportsDecoding(string $format /* , array $context = [] */): bool { return self::FORMAT === $format; } diff --git a/Encoder/DecoderInterface.php b/Encoder/DecoderInterface.php index f38069e47..5014b9bd5 100644 --- a/Encoder/DecoderInterface.php +++ b/Encoder/DecoderInterface.php @@ -39,10 +39,10 @@ public function decode(string $data, string $format, array $context = []); /** * Checks whether the deserializer can decode from given format. * - * @param string $format Format name + * @param string $format Format name * @param array $context Options that decoders have access to * * @return bool */ - public function supportsDecoding(string $format /*, array $context = [] */); + public function supportsDecoding(string $format /* , array $context = [] */); } diff --git a/Encoder/EncoderInterface.php b/Encoder/EncoderInterface.php index 22da956d2..c913ac3fb 100644 --- a/Encoder/EncoderInterface.php +++ b/Encoder/EncoderInterface.php @@ -35,5 +35,5 @@ public function encode(mixed $data, string $format, array $context = []): string * @param string $format Format name * @param array $context Options that normalizers/encoders have access to */ - public function supportsEncoding(string $format /*, array $context = [] */): bool; + public function supportsEncoding(string $format /* , array $context = [] */): bool; } diff --git a/Encoder/JsonDecode.php b/Encoder/JsonDecode.php index f0f94f6d7..50d2d2e3f 100644 --- a/Encoder/JsonDecode.php +++ b/Encoder/JsonDecode.php @@ -98,7 +98,7 @@ public function decode(string $data, string $format, array $context = []): mixed * * @param array $context */ - public function supportsDecoding(string $format /*, array $context = [] */): bool + public function supportsDecoding(string $format /* , array $context = [] */): bool { return JsonEncoder::FORMAT === $format; } diff --git a/Encoder/JsonEncode.php b/Encoder/JsonEncode.php index 9a0a9393b..86baf9999 100644 --- a/Encoder/JsonEncode.php +++ b/Encoder/JsonEncode.php @@ -60,7 +60,7 @@ public function encode(mixed $data, string $format, array $context = []): string * * @param array $context */ - public function supportsEncoding(string $format /*, array $context = [] */): bool + public function supportsEncoding(string $format /* , array $context = [] */): bool { return JsonEncoder::FORMAT === $format; } diff --git a/Encoder/JsonEncoder.php b/Encoder/JsonEncoder.php index 2ce119bcb..e6ccbfba5 100644 --- a/Encoder/JsonEncoder.php +++ b/Encoder/JsonEncoder.php @@ -50,7 +50,7 @@ public function decode(string $data, string $format, array $context = []): mixed * * @param array $context */ - public function supportsEncoding(string $format /*, array $context = [] */): bool + public function supportsEncoding(string $format /* , array $context = [] */): bool { return self::FORMAT === $format; } @@ -60,7 +60,7 @@ public function supportsEncoding(string $format /*, array $context = [] */): boo * * @param array $context */ - public function supportsDecoding(string $format /*, array $context = [] */): bool + public function supportsDecoding(string $format /* , array $context = [] */): bool { return self::FORMAT === $format; } diff --git a/Encoder/XmlEncoder.php b/Encoder/XmlEncoder.php index 839233c38..258a3239e 100644 --- a/Encoder/XmlEncoder.php +++ b/Encoder/XmlEncoder.php @@ -169,7 +169,7 @@ public function decode(string $data, string $format, array $context = []): mixed * * @param array $context */ - public function supportsEncoding(string $format /*, array $context = [] */): bool + public function supportsEncoding(string $format /* , array $context = [] */): bool { return self::FORMAT === $format; } @@ -179,7 +179,7 @@ public function supportsEncoding(string $format /*, array $context = [] */): boo * * @param array $context */ - public function supportsDecoding(string $format /*, array $context = [] */): bool + public function supportsDecoding(string $format /* , array $context = [] */): bool { return self::FORMAT === $format; } diff --git a/Encoder/YamlEncoder.php b/Encoder/YamlEncoder.php index 8bd19cb86..ecb9815ee 100644 --- a/Encoder/YamlEncoder.php +++ b/Encoder/YamlEncoder.php @@ -70,7 +70,7 @@ public function encode(mixed $data, string $format, array $context = []): string * * @param array $context */ - public function supportsEncoding(string $format /*, array $context = [] */): bool + public function supportsEncoding(string $format /* , array $context = [] */): bool { return self::FORMAT === $format || self::ALTERNATIVE_FORMAT === $format; } @@ -90,7 +90,7 @@ public function decode(string $data, string $format, array $context = []): mixed * * @param array $context */ - public function supportsDecoding(string $format /*, array $context = [] */): bool + public function supportsDecoding(string $format /* , array $context = [] */): bool { return self::FORMAT === $format || self::ALTERNATIVE_FORMAT === $format; } diff --git a/Normalizer/AbstractObjectNormalizer.php b/Normalizer/AbstractObjectNormalizer.php index 5d126b9cc..5e15ee383 100644 --- a/Normalizer/AbstractObjectNormalizer.php +++ b/Normalizer/AbstractObjectNormalizer.php @@ -138,7 +138,7 @@ public function __construct(ClassMetadataFactoryInterface $classMetadataFactory * * @param array $context */ - public function supportsNormalization(mixed $data, string $format = null /*, array $context = [] */) + public function supportsNormalization(mixed $data, string $format = null /* , array $context = [] */) { return \is_object($data) && !$data instanceof \Traversable; } @@ -340,7 +340,7 @@ abstract protected function getAttributeValue(object $object, string $attribute, * * @param array $context */ - public function supportsDenormalization(mixed $data, string $type, string $format = null /*, array $context = [] */) + public function supportsDenormalization(mixed $data, string $type, string $format = null /* , array $context = [] */) { return class_exists($type) || (interface_exists($type, false) && $this->classDiscriminatorResolver && null !== $this->classDiscriminatorResolver->getMappingForClass($type)); } @@ -509,6 +509,7 @@ private function validateAndDenormalize(array $types, string $currentClass, stri if (is_numeric($data)) { return (float) $data; } + return match ($data) { 'NaN' => \NAN, 'INF' => \INF, diff --git a/Normalizer/ConstraintViolationListNormalizer.php b/Normalizer/ConstraintViolationListNormalizer.php index 2ac3c3681..8af6fd7cd 100644 --- a/Normalizer/ConstraintViolationListNormalizer.php +++ b/Normalizer/ConstraintViolationListNormalizer.php @@ -109,7 +109,7 @@ public function normalize(mixed $object, string $format = null, array $context = * * @param array $context */ - public function supportsNormalization(mixed $data, string $format = null /*, array $context = [] */): bool + public function supportsNormalization(mixed $data, string $format = null /* , array $context = [] */): bool { return $data instanceof ConstraintViolationListInterface; } diff --git a/Normalizer/CustomNormalizer.php b/Normalizer/CustomNormalizer.php index d12361d50..7714af313 100644 --- a/Normalizer/CustomNormalizer.php +++ b/Normalizer/CustomNormalizer.php @@ -48,7 +48,7 @@ public function denormalize(mixed $data, string $type, string $format = null, ar * @param string $format The format being (de-)serialized from or into * @param array $context */ - public function supportsNormalization(mixed $data, string $format = null /*, array $context = [] */): bool + public function supportsNormalization(mixed $data, string $format = null /* , array $context = [] */): bool { return $data instanceof NormalizableInterface; } @@ -61,7 +61,7 @@ public function supportsNormalization(mixed $data, string $format = null /*, arr * @param string $format The format being deserialized from * @param array $context */ - public function supportsDenormalization(mixed $data, string $type, string $format = null /*, array $context = [] */): bool + public function supportsDenormalization(mixed $data, string $type, string $format = null /* , array $context = [] */): bool { return is_subclass_of($type, DenormalizableInterface::class); } diff --git a/Normalizer/DataUriNormalizer.php b/Normalizer/DataUriNormalizer.php index 675b9a13f..ccfab6dbc 100644 --- a/Normalizer/DataUriNormalizer.php +++ b/Normalizer/DataUriNormalizer.php @@ -76,7 +76,7 @@ public function normalize(mixed $object, string $format = null, array $context = * * @param array $context */ - public function supportsNormalization(mixed $data, string $format = null /*, array $context = [] */): bool + public function supportsNormalization(mixed $data, string $format = null /* , array $context = [] */): bool { return $data instanceof \SplFileInfo; } @@ -122,7 +122,7 @@ public function denormalize(mixed $data, string $type, string $format = null, ar * * @param array $context */ - public function supportsDenormalization(mixed $data, string $type, string $format = null /*, array $context = [] */): bool + public function supportsDenormalization(mixed $data, string $type, string $format = null /* , array $context = [] */): bool { return isset(self::SUPPORTED_TYPES[$type]); } diff --git a/Normalizer/DateIntervalNormalizer.php b/Normalizer/DateIntervalNormalizer.php index 9a7aa0496..2c97eb30e 100644 --- a/Normalizer/DateIntervalNormalizer.php +++ b/Normalizer/DateIntervalNormalizer.php @@ -52,7 +52,7 @@ public function normalize(mixed $object, string $format = null, array $context = * * @param array $context */ - public function supportsNormalization(mixed $data, string $format = null /*, array $context = [] */): bool + public function supportsNormalization(mixed $data, string $format = null /* , array $context = [] */): bool { return $data instanceof \DateInterval; } @@ -122,7 +122,7 @@ public function denormalize(mixed $data, string $type, string $format = null, ar * * @param array $context */ - public function supportsDenormalization(mixed $data, string $type, string $format = null /*, array $context = [] */): bool + public function supportsDenormalization(mixed $data, string $type, string $format = null /* , array $context = [] */): bool { return \DateInterval::class === $type; } diff --git a/Normalizer/DateTimeNormalizer.php b/Normalizer/DateTimeNormalizer.php index ea7e30f9e..49ef7f425 100644 --- a/Normalizer/DateTimeNormalizer.php +++ b/Normalizer/DateTimeNormalizer.php @@ -74,7 +74,7 @@ public function normalize(mixed $object, string $format = null, array $context = * * @param array $context */ - public function supportsNormalization(mixed $data, string $format = null /*, array $context = [] */): bool + public function supportsNormalization(mixed $data, string $format = null /* , array $context = [] */): bool { return $data instanceof \DateTimeInterface; } @@ -117,7 +117,7 @@ public function denormalize(mixed $data, string $type, string $format = null, ar * * @param array $context */ - public function supportsDenormalization(mixed $data, string $type, string $format = null /*, array $context = [] */): bool + public function supportsDenormalization(mixed $data, string $type, string $format = null /* , array $context = [] */): bool { return isset(self::SUPPORTED_TYPES[$type]); } diff --git a/Normalizer/DateTimeZoneNormalizer.php b/Normalizer/DateTimeZoneNormalizer.php index 89adcb56f..62d5c9e0b 100644 --- a/Normalizer/DateTimeZoneNormalizer.php +++ b/Normalizer/DateTimeZoneNormalizer.php @@ -41,7 +41,7 @@ public function normalize(mixed $object, string $format = null, array $context = * * @param array $context */ - public function supportsNormalization(mixed $data, string $format = null /*, array $context = [] */): bool + public function supportsNormalization(mixed $data, string $format = null /* , array $context = [] */): bool { return $data instanceof \DateTimeZone; } @@ -69,7 +69,7 @@ public function denormalize(mixed $data, string $type, string $format = null, ar * * @param array $context */ - public function supportsDenormalization(mixed $data, string $type, string $format = null /*, array $context = [] */): bool + public function supportsDenormalization(mixed $data, string $type, string $format = null /* , array $context = [] */): bool { return \DateTimeZone::class === $type; } diff --git a/Normalizer/DenormalizerInterface.php b/Normalizer/DenormalizerInterface.php index 1c708738a..ae3adbfe3 100644 --- a/Normalizer/DenormalizerInterface.php +++ b/Normalizer/DenormalizerInterface.php @@ -56,5 +56,5 @@ public function denormalize(mixed $data, string $type, string $format = null, ar * * @return bool */ - public function supportsDenormalization(mixed $data, string $type, string $format = null /*, array $context = [] */); + public function supportsDenormalization(mixed $data, string $type, string $format = null /* , array $context = [] */); } diff --git a/Normalizer/GetSetMethodNormalizer.php b/Normalizer/GetSetMethodNormalizer.php index 7e42144f6..edbf08e29 100644 --- a/Normalizer/GetSetMethodNormalizer.php +++ b/Normalizer/GetSetMethodNormalizer.php @@ -41,7 +41,7 @@ class GetSetMethodNormalizer extends AbstractObjectNormalizer * * @param array $context */ - public function supportsNormalization(mixed $data, string $format = null /*, array $context = [] */): bool + public function supportsNormalization(mixed $data, string $format = null /* , array $context = [] */): bool { return parent::supportsNormalization($data, $format) && $this->supports(\get_class($data)); } @@ -51,7 +51,7 @@ public function supportsNormalization(mixed $data, string $format = null /*, arr * * @param array $context */ - public function supportsDenormalization(mixed $data, string $type, string $format = null /*, array $context = [] */): bool + public function supportsDenormalization(mixed $data, string $type, string $format = null /* , array $context = [] */): bool { return parent::supportsDenormalization($data, $type, $format) && $this->supports($type); } diff --git a/Normalizer/JsonSerializableNormalizer.php b/Normalizer/JsonSerializableNormalizer.php index 40d769caa..cc50e898b 100644 --- a/Normalizer/JsonSerializableNormalizer.php +++ b/Normalizer/JsonSerializableNormalizer.php @@ -46,7 +46,7 @@ public function normalize(mixed $object, string $format = null, array $context = * * @param array $context */ - public function supportsNormalization(mixed $data, string $format = null /*, array $context = [] */): bool + public function supportsNormalization(mixed $data, string $format = null /* , array $context = [] */): bool { return $data instanceof \JsonSerializable; } @@ -56,7 +56,7 @@ public function supportsNormalization(mixed $data, string $format = null /*, arr * * @param array $context */ - public function supportsDenormalization(mixed $data, string $type, string $format = null /*, array $context = [] */): bool + public function supportsDenormalization(mixed $data, string $type, string $format = null /* , array $context = [] */): bool { return false; } diff --git a/Normalizer/NormalizerInterface.php b/Normalizer/NormalizerInterface.php index 741f19e50..691e9c70f 100644 --- a/Normalizer/NormalizerInterface.php +++ b/Normalizer/NormalizerInterface.php @@ -47,5 +47,5 @@ public function normalize(mixed $object, string $format = null, array $context = * * @return bool */ - public function supportsNormalization(mixed $data, string $format = null /*, array $context = [] */); + public function supportsNormalization(mixed $data, string $format = null /* , array $context = [] */); } diff --git a/Normalizer/ProblemNormalizer.php b/Normalizer/ProblemNormalizer.php index be543ec9b..7898d991f 100644 --- a/Normalizer/ProblemNormalizer.php +++ b/Normalizer/ProblemNormalizer.php @@ -71,7 +71,7 @@ public function normalize(mixed $object, string $format = null, array $context = * * @param array $context */ - public function supportsNormalization(mixed $data, string $format = null /*, array $context = [] */): bool + public function supportsNormalization(mixed $data, string $format = null /* , array $context = [] */): bool { return $data instanceof FlattenException; } diff --git a/Normalizer/PropertyNormalizer.php b/Normalizer/PropertyNormalizer.php index 1c68896aa..1143bbc34 100644 --- a/Normalizer/PropertyNormalizer.php +++ b/Normalizer/PropertyNormalizer.php @@ -37,7 +37,7 @@ class PropertyNormalizer extends AbstractObjectNormalizer * * @param array $context */ - public function supportsNormalization(mixed $data, string $format = null /*, array $context = [] */): bool + public function supportsNormalization(mixed $data, string $format = null /* , array $context = [] */): bool { return parent::supportsNormalization($data, $format) && $this->supports(\get_class($data)); } @@ -47,7 +47,7 @@ public function supportsNormalization(mixed $data, string $format = null /*, arr * * @param array $context */ - public function supportsDenormalization(mixed $data, string $type, string $format = null /*, array $context = [] */): bool + public function supportsDenormalization(mixed $data, string $type, string $format = null /* , array $context = [] */): bool { return parent::supportsDenormalization($data, $type, $format) && $this->supports($type); } From 5f1acdd2737aca5bfe6a079bd9576396dc5c361c Mon Sep 17 00:00:00 2001 From: Antoine Lamirault Date: Mon, 3 Jan 2022 21:23:28 +0100 Subject: [PATCH 048/297] Add visibility context option in PropertyNormalizer --- CHANGELOG.md | 2 + .../PropertyNormalizerContextBuilder.php | 9 ++++ Normalizer/PropertyNormalizer.php | 45 +++++++++++++++-- .../PropertyNormalizerContextBuilderTest.php | 40 ++++++++++++++++ Tests/Normalizer/PropertyNormalizerTest.php | 48 +++++++++++++++++++ 5 files changed, 140 insertions(+), 4 deletions(-) create mode 100644 Tests/Context/Normalizer/PropertyNormalizerContextBuilderTest.php diff --git a/CHANGELOG.md b/CHANGELOG.md index d9d22407e..31934dd32 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,8 @@ CHANGELOG --- * Add support for constructor promoted properties to `Context` attribute +* Add context option `PropertyNormalizer::NORMALIZE_VISIBILITY` with bitmask flags `PropertyNormalizer::NORMALIZE_PUBLIC`, `PropertyNormalizer::NORMALIZE_PROTECTED`, `PropertyNormalizer::NORMALIZE_PRIVATE` +* Add method `withNormalizeVisibility` to `PropertyNormalizerContextBuilder` 6.1 --- diff --git a/Context/Normalizer/PropertyNormalizerContextBuilder.php b/Context/Normalizer/PropertyNormalizerContextBuilder.php index e0a8f2f91..71d5ed045 100644 --- a/Context/Normalizer/PropertyNormalizerContextBuilder.php +++ b/Context/Normalizer/PropertyNormalizerContextBuilder.php @@ -11,6 +11,8 @@ namespace Symfony\Component\Serializer\Context\Normalizer; +use Symfony\Component\Serializer\Normalizer\PropertyNormalizer; + /** * A helper providing autocompletion for available PropertyNormalizer options. * @@ -18,4 +20,11 @@ */ final class PropertyNormalizerContextBuilder extends AbstractObjectNormalizerContextBuilder { + /** + * Configures whether fields should be output based on visibility. + */ + public function withNormalizeVisibility(int $normalizeVisibility): static + { + return $this->with(PropertyNormalizer::NORMALIZE_VISIBILITY, $normalizeVisibility); + } } diff --git a/Normalizer/PropertyNormalizer.php b/Normalizer/PropertyNormalizer.php index 1143bbc34..bd537a1b1 100644 --- a/Normalizer/PropertyNormalizer.php +++ b/Normalizer/PropertyNormalizer.php @@ -12,6 +12,10 @@ namespace Symfony\Component\Serializer\Normalizer; use Symfony\Component\PropertyAccess\Exception\UninitializedPropertyException; +use Symfony\Component\PropertyInfo\PropertyTypeExtractorInterface; +use Symfony\Component\Serializer\Mapping\ClassDiscriminatorResolverInterface; +use Symfony\Component\Serializer\Mapping\Factory\ClassMetadataFactoryInterface; +use Symfony\Component\Serializer\NameConverter\NameConverterInterface; /** * Converts between objects and arrays by mapping properties. @@ -32,6 +36,24 @@ */ class PropertyNormalizer extends AbstractObjectNormalizer { + public const NORMALIZE_PUBLIC = 1; + public const NORMALIZE_PROTECTED = 2; + public const NORMALIZE_PRIVATE = 4; + + /** + * Flag to control whether fields should be output based on visibility. + */ + public const NORMALIZE_VISIBILITY = 'normalize_visibility'; + + public function __construct(ClassMetadataFactoryInterface $classMetadataFactory = null, NameConverterInterface $nameConverter = null, PropertyTypeExtractorInterface $propertyTypeExtractor = null, ClassDiscriminatorResolverInterface $classDiscriminatorResolver = null, callable $objectClassResolver = null, array $defaultContext = []) + { + parent::__construct($classMetadataFactory, $nameConverter, $propertyTypeExtractor, $classDiscriminatorResolver, $objectClassResolver, $defaultContext); + + if (!isset($this->defaultContext[self::NORMALIZE_VISIBILITY])) { + $this->defaultContext[self::NORMALIZE_VISIBILITY] = self::NORMALIZE_PUBLIC | self::NORMALIZE_PROTECTED | self::NORMALIZE_PRIVATE; + } + } + /** * {@inheritdoc} * @@ -90,14 +112,29 @@ protected function isAllowedAttribute(object|string $classOrObject, string $attr try { $reflectionProperty = $this->getReflectionProperty($classOrObject, $attribute); - if ($reflectionProperty->isStatic()) { - return false; - } } catch (\ReflectionException) { return false; } - return true; + if ($reflectionProperty->isStatic()) { + return false; + } + + $normalizeVisibility = $context[self::NORMALIZE_VISIBILITY] ?? $this->defaultContext[self::NORMALIZE_VISIBILITY]; + + if ((self::NORMALIZE_PUBLIC & $normalizeVisibility) && $reflectionProperty->isPublic()) { + return true; + } + + if ((self::NORMALIZE_PROTECTED & $normalizeVisibility) && $reflectionProperty->isProtected()) { + return true; + } + + if ((self::NORMALIZE_PRIVATE & $normalizeVisibility) && $reflectionProperty->isPrivate()) { + return true; + } + + return false; } /** diff --git a/Tests/Context/Normalizer/PropertyNormalizerContextBuilderTest.php b/Tests/Context/Normalizer/PropertyNormalizerContextBuilderTest.php new file mode 100644 index 000000000..41f0b8990 --- /dev/null +++ b/Tests/Context/Normalizer/PropertyNormalizerContextBuilderTest.php @@ -0,0 +1,40 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Serializer\Tests\Context\Normalizer; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\Serializer\Context\Normalizer\PropertyNormalizerContextBuilder; +use Symfony\Component\Serializer\Normalizer\PropertyNormalizer; + +/** + * @author Antoine Lamirault + */ +class PropertyNormalizerContextBuilderTest extends TestCase +{ + private PropertyNormalizerContextBuilder $contextBuilder; + + protected function setUp(): void + { + $this->contextBuilder = new PropertyNormalizerContextBuilder(); + } + + public function testWithNormalizeVisibility() + { + $context = $this->contextBuilder + ->withNormalizeVisibility(PropertyNormalizer::NORMALIZE_PUBLIC | PropertyNormalizer::NORMALIZE_PROTECTED) + ->toArray(); + + $this->assertSame([ + PropertyNormalizer::NORMALIZE_VISIBILITY => PropertyNormalizer::NORMALIZE_PUBLIC | PropertyNormalizer::NORMALIZE_PROTECTED, + ], $context); + } +} diff --git a/Tests/Normalizer/PropertyNormalizerTest.php b/Tests/Normalizer/PropertyNormalizerTest.php index b7993e59f..2b9a111ed 100644 --- a/Tests/Normalizer/PropertyNormalizerTest.php +++ b/Tests/Normalizer/PropertyNormalizerTest.php @@ -122,6 +122,54 @@ public function testNormalizeObjectWithLazyProperties() ); } + public function testNormalizeOnlyPublic() + { + $obj = new PropertyDummy(); + $obj->foo = 'foo'; + $obj->setBar('bar'); + $obj->setCamelCase('camelcase'); + $this->assertEquals( + ['foo' => 'foo'], + $this->normalizer->normalize($obj, 'any', ['normalize_visibility' => PropertyNormalizer::NORMALIZE_PUBLIC]) + ); + } + + public function testNormalizeOnlyProtected() + { + $obj = new PropertyDummy(); + $obj->foo = 'foo'; + $obj->setBar('bar'); + $obj->setCamelCase('camelcase'); + $this->assertEquals( + ['camelCase' => 'camelcase'], + $this->normalizer->normalize($obj, 'any', ['normalize_visibility' => PropertyNormalizer::NORMALIZE_PROTECTED]) + ); + } + + public function testNormalizeOnlyPrivate() + { + $obj = new PropertyDummy(); + $obj->foo = 'foo'; + $obj->setBar('bar'); + $obj->setCamelCase('camelcase'); + $this->assertEquals( + ['bar' => 'bar'], + $this->normalizer->normalize($obj, 'any', ['normalize_visibility' => PropertyNormalizer::NORMALIZE_PRIVATE]) + ); + } + + public function testNormalizePublicAndProtected() + { + $obj = new PropertyDummy(); + $obj->foo = 'foo'; + $obj->setBar('bar'); + $obj->setCamelCase('camelcase'); + $this->assertEquals( + ['foo' => 'foo', 'camelCase' => 'camelcase'], + $this->normalizer->normalize($obj, 'any', ['normalize_visibility' => PropertyNormalizer::NORMALIZE_PUBLIC | PropertyNormalizer::NORMALIZE_PROTECTED]) + ); + } + public function testDenormalize() { $obj = $this->normalizer->denormalize( From ca142a076cbf46ad8702ae627a44d834cf152e97 Mon Sep 17 00:00:00 2001 From: Maarten de Boer Date: Mon, 12 Apr 2021 17:21:52 +0200 Subject: [PATCH 049/297] Improve some PHPdocs based on existing Symfony stubs in PHPstan and Psalm --- SerializerInterface.php | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/SerializerInterface.php b/SerializerInterface.php index d78e83d75..eb5ee000e 100644 --- a/SerializerInterface.php +++ b/SerializerInterface.php @@ -19,14 +19,20 @@ interface SerializerInterface /** * Serializes data in the appropriate format. * - * @param mixed $data Any data - * @param string $format Format name - * @param array $context Options normalizers/encoders have access to + * @param array $context Options normalizers/encoders have access to */ public function serialize(mixed $data, string $format, array $context = []): string; /** * Deserializes data into the given type. + * + * @template TObject of object + * @template TType of string|class-string + * + * @param TType $type + * @param array $context + * + * @psalm-return (TType is class-string ? TObject : mixed) */ public function deserialize(mixed $data, string $type, string $format, array $context = []): mixed; } From 2c59be38d343e2ed4d4ea3c05965c2ae41c7109f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gabriel=20Ostroluck=C3=BD?= Date: Fri, 29 Jul 2022 00:06:07 +0200 Subject: [PATCH 050/297] [Serializer] Fix wrong needsNormalization in TraceableEncoder --- Debug/TraceableEncoder.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Debug/TraceableEncoder.php b/Debug/TraceableEncoder.php index f494cc57c..8105ba7b3 100644 --- a/Debug/TraceableEncoder.php +++ b/Debug/TraceableEncoder.php @@ -14,9 +14,9 @@ use Symfony\Component\Serializer\DataCollector\SerializerDataCollector; use Symfony\Component\Serializer\Encoder\DecoderInterface; use Symfony\Component\Serializer\Encoder\EncoderInterface; +use Symfony\Component\Serializer\Encoder\NormalizationAwareInterface; use Symfony\Component\Serializer\SerializerAwareInterface; use Symfony\Component\Serializer\SerializerInterface; -use Symfony\Component\Serializer\Tests\Encoder\NormalizationAwareEncoder; /** * Collects some data about encoding. @@ -111,7 +111,7 @@ public function setSerializer(SerializerInterface $serializer) public function needsNormalization(): bool { - return !$this->encoder instanceof NormalizationAwareEncoder; + return !$this->encoder instanceof NormalizationAwareInterface; } /** From b7bf506a19d6fb52f1832a3cc69b30ff21693560 Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Tue, 2 Aug 2022 11:31:21 +0200 Subject: [PATCH 051/297] [Serializer] Revert deprecation of `ContextAwareEncoderInterface` and `ContextAwareDecoderInterface` --- CHANGELOG.md | 2 -- Encoder/ChainDecoder.php | 6 +++++- Encoder/ChainEncoder.php | 6 +++++- Encoder/ContextAwareDecoderInterface.php | 2 -- Encoder/ContextAwareEncoderInterface.php | 2 -- Encoder/CsvEncoder.php | 8 ++------ Encoder/DecoderInterface.php | 5 ++--- Encoder/EncoderInterface.php | 5 ++--- Encoder/JsonDecode.php | 4 +--- Encoder/JsonEncode.php | 4 +--- Encoder/JsonEncoder.php | 8 ++------ Encoder/XmlEncoder.php | 8 ++------ Encoder/YamlEncoder.php | 8 ++------ Tests/Encoder/ChainDecoderTest.php | 3 ++- Tests/Encoder/ChainEncoderTest.php | 5 +++-- 15 files changed, 29 insertions(+), 47 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c2b3ebfb3..0a475a0ea 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,8 +9,6 @@ CHANGELOG * Set `Context` annotation as not final * Deprecate `ContextAwareNormalizerInterface`, use `NormalizerInterface` instead * Deprecate `ContextAwareDenormalizerInterface`, use `DenormalizerInterface` instead - * Deprecate `ContextAwareEncoderInterface`, use `EncoderInterface` instead - * Deprecate `ContextAwareDecoderInterface`, use `DecoderInterface` instead * Deprecate supporting denormalization for `AbstractUid` in `UidNormalizer`, use one of `AbstractUid` child class instead * Deprecate denormalizing to an abstract class in `UidNormalizer` * Add support for `can*()` methods to `ObjectNormalizer` diff --git a/Encoder/ChainDecoder.php b/Encoder/ChainDecoder.php index 0f7f94fad..7a01938ab 100644 --- a/Encoder/ChainDecoder.php +++ b/Encoder/ChainDecoder.php @@ -67,9 +67,13 @@ private function getDecoder(string $format, array $context): DecoderInterface return $this->decoders[$this->decoderByFormat[$format]]; } + $cache = true; foreach ($this->decoders as $i => $decoder) { + $cache = $cache && !$decoder instanceof ContextAwareDecoderInterface; if ($decoder->supportsDecoding($format, $context)) { - $this->decoderByFormat[$format] = $i; + if ($cache) { + $this->decoderByFormat[$format] = $i; + } return $decoder; } diff --git a/Encoder/ChainEncoder.php b/Encoder/ChainEncoder.php index 70d7c9fd1..061a9cf74 100644 --- a/Encoder/ChainEncoder.php +++ b/Encoder/ChainEncoder.php @@ -90,9 +90,13 @@ private function getEncoder(string $format, array $context): EncoderInterface return $this->encoders[$this->encoderByFormat[$format]]; } + $cache = true; foreach ($this->encoders as $i => $encoder) { + $cache = $cache && !$encoder instanceof ContextAwareEncoderInterface; if ($encoder->supportsEncoding($format, $context)) { - $this->encoderByFormat[$format] = $i; + if ($cache) { + $this->encoderByFormat[$format] = $i; + } return $encoder; } diff --git a/Encoder/ContextAwareDecoderInterface.php b/Encoder/ContextAwareDecoderInterface.php index 910b26bac..6ac2e38cc 100644 --- a/Encoder/ContextAwareDecoderInterface.php +++ b/Encoder/ContextAwareDecoderInterface.php @@ -15,8 +15,6 @@ * Adds the support of an extra $context parameter for the supportsDecoding method. * * @author Kévin Dunglas - * - * @deprecated since symfony/serializer 6.1, use DecoderInterface instead */ interface ContextAwareDecoderInterface extends DecoderInterface { diff --git a/Encoder/ContextAwareEncoderInterface.php b/Encoder/ContextAwareEncoderInterface.php index f828f87a4..832b600ee 100644 --- a/Encoder/ContextAwareEncoderInterface.php +++ b/Encoder/ContextAwareEncoderInterface.php @@ -15,8 +15,6 @@ * Adds the support of an extra $context parameter for the supportsEncoding method. * * @author Kévin Dunglas - * - * @deprecated since symfony/serializer 6.1, use EncoderInterface instead */ interface ContextAwareEncoderInterface extends EncoderInterface { diff --git a/Encoder/CsvEncoder.php b/Encoder/CsvEncoder.php index b2c6fcd81..a3733a53d 100644 --- a/Encoder/CsvEncoder.php +++ b/Encoder/CsvEncoder.php @@ -124,10 +124,8 @@ public function encode(mixed $data, string $format, array $context = []): string /** * {@inheritdoc} - * - * @param array $context */ - public function supportsEncoding(string $format /* , array $context = [] */): bool + public function supportsEncoding(string $format): bool { return self::FORMAT === $format; } @@ -212,10 +210,8 @@ public function decode(string $data, string $format, array $context = []): mixed /** * {@inheritdoc} - * - * @param array $context */ - public function supportsDecoding(string $format /* , array $context = [] */): bool + public function supportsDecoding(string $format): bool { return self::FORMAT === $format; } diff --git a/Encoder/DecoderInterface.php b/Encoder/DecoderInterface.php index 5014b9bd5..84a84ad1f 100644 --- a/Encoder/DecoderInterface.php +++ b/Encoder/DecoderInterface.php @@ -39,10 +39,9 @@ public function decode(string $data, string $format, array $context = []); /** * Checks whether the deserializer can decode from given format. * - * @param string $format Format name - * @param array $context Options that decoders have access to + * @param string $format Format name * * @return bool */ - public function supportsDecoding(string $format /* , array $context = [] */); + public function supportsDecoding(string $format); } diff --git a/Encoder/EncoderInterface.php b/Encoder/EncoderInterface.php index c913ac3fb..e0f303b1e 100644 --- a/Encoder/EncoderInterface.php +++ b/Encoder/EncoderInterface.php @@ -32,8 +32,7 @@ public function encode(mixed $data, string $format, array $context = []): string /** * Checks whether the serializer can encode to given format. * - * @param string $format Format name - * @param array $context Options that normalizers/encoders have access to + * @param string $format Format name */ - public function supportsEncoding(string $format /* , array $context = [] */): bool; + public function supportsEncoding(string $format): bool; } diff --git a/Encoder/JsonDecode.php b/Encoder/JsonDecode.php index 50d2d2e3f..ad094afac 100644 --- a/Encoder/JsonDecode.php +++ b/Encoder/JsonDecode.php @@ -95,10 +95,8 @@ public function decode(string $data, string $format, array $context = []): mixed /** * {@inheritdoc} - * - * @param array $context */ - public function supportsDecoding(string $format /* , array $context = [] */): bool + public function supportsDecoding(string $format): bool { return JsonEncoder::FORMAT === $format; } diff --git a/Encoder/JsonEncode.php b/Encoder/JsonEncode.php index 86baf9999..23d0fdd96 100644 --- a/Encoder/JsonEncode.php +++ b/Encoder/JsonEncode.php @@ -57,10 +57,8 @@ public function encode(mixed $data, string $format, array $context = []): string /** * {@inheritdoc} - * - * @param array $context */ - public function supportsEncoding(string $format /* , array $context = [] */): bool + public function supportsEncoding(string $format): bool { return JsonEncoder::FORMAT === $format; } diff --git a/Encoder/JsonEncoder.php b/Encoder/JsonEncoder.php index e6ccbfba5..d17ef0492 100644 --- a/Encoder/JsonEncoder.php +++ b/Encoder/JsonEncoder.php @@ -47,20 +47,16 @@ public function decode(string $data, string $format, array $context = []): mixed /** * {@inheritdoc} - * - * @param array $context */ - public function supportsEncoding(string $format /* , array $context = [] */): bool + public function supportsEncoding(string $format): bool { return self::FORMAT === $format; } /** * {@inheritdoc} - * - * @param array $context */ - public function supportsDecoding(string $format /* , array $context = [] */): bool + public function supportsDecoding(string $format): bool { return self::FORMAT === $format; } diff --git a/Encoder/XmlEncoder.php b/Encoder/XmlEncoder.php index a47e6d695..2474f4439 100644 --- a/Encoder/XmlEncoder.php +++ b/Encoder/XmlEncoder.php @@ -166,20 +166,16 @@ public function decode(string $data, string $format, array $context = []): mixed /** * {@inheritdoc} - * - * @param array $context */ - public function supportsEncoding(string $format /* , array $context = [] */): bool + public function supportsEncoding(string $format): bool { return self::FORMAT === $format; } /** * {@inheritdoc} - * - * @param array $context */ - public function supportsDecoding(string $format /* , array $context = [] */): bool + public function supportsDecoding(string $format): bool { return self::FORMAT === $format; } diff --git a/Encoder/YamlEncoder.php b/Encoder/YamlEncoder.php index ecb9815ee..51f600786 100644 --- a/Encoder/YamlEncoder.php +++ b/Encoder/YamlEncoder.php @@ -67,10 +67,8 @@ public function encode(mixed $data, string $format, array $context = []): string /** * {@inheritdoc} - * - * @param array $context */ - public function supportsEncoding(string $format /* , array $context = [] */): bool + public function supportsEncoding(string $format): bool { return self::FORMAT === $format || self::ALTERNATIVE_FORMAT === $format; } @@ -87,10 +85,8 @@ public function decode(string $data, string $format, array $context = []): mixed /** * {@inheritdoc} - * - * @param array $context */ - public function supportsDecoding(string $format /* , array $context = [] */): bool + public function supportsDecoding(string $format): bool { return self::FORMAT === $format || self::ALTERNATIVE_FORMAT === $format; } diff --git a/Tests/Encoder/ChainDecoderTest.php b/Tests/Encoder/ChainDecoderTest.php index 5cac8d99a..a181bb145 100644 --- a/Tests/Encoder/ChainDecoderTest.php +++ b/Tests/Encoder/ChainDecoderTest.php @@ -13,6 +13,7 @@ use PHPUnit\Framework\TestCase; use Symfony\Component\Serializer\Encoder\ChainDecoder; +use Symfony\Component\Serializer\Encoder\ContextAwareDecoderInterface; use Symfony\Component\Serializer\Encoder\DecoderInterface; use Symfony\Component\Serializer\Exception\RuntimeException; @@ -28,7 +29,7 @@ class ChainDecoderTest extends TestCase protected function setUp(): void { - $this->decoder1 = $this->createMock(DecoderInterface::class); + $this->decoder1 = $this->createMock(ContextAwareDecoderInterface::class); $this->decoder1 ->method('supportsDecoding') ->willReturnMap([ diff --git a/Tests/Encoder/ChainEncoderTest.php b/Tests/Encoder/ChainEncoderTest.php index 848087145..227e251c1 100644 --- a/Tests/Encoder/ChainEncoderTest.php +++ b/Tests/Encoder/ChainEncoderTest.php @@ -14,6 +14,7 @@ use PHPUnit\Framework\TestCase; use Symfony\Component\Serializer\Debug\TraceableEncoder; use Symfony\Component\Serializer\Encoder\ChainEncoder; +use Symfony\Component\Serializer\Encoder\ContextAwareEncoderInterface; use Symfony\Component\Serializer\Encoder\EncoderInterface; use Symfony\Component\Serializer\Encoder\NormalizationAwareInterface; use Symfony\Component\Serializer\Exception\RuntimeException; @@ -30,7 +31,7 @@ class ChainEncoderTest extends TestCase protected function setUp(): void { - $this->encoder1 = $this->createMock(EncoderInterface::class); + $this->encoder1 = $this->createMock(ContextAwareEncoderInterface::class); $this->encoder1 ->method('supportsEncoding') ->willReturnMap([ @@ -106,7 +107,7 @@ public function testNeedsNormalizationTraceableEncoder() class NormalizationAwareEncoder implements EncoderInterface, NormalizationAwareInterface { - public function supportsEncoding(string $format, array $context = []): bool + public function supportsEncoding(string $format): bool { return true; } From bd92a7eaf489c70c2bf7407b7ea3b8d15d763507 Mon Sep 17 00:00:00 2001 From: David Buchmann Date: Fri, 5 Aug 2022 14:59:23 +0200 Subject: [PATCH 052/297] enable JSON_PRESERVE_ZERO_FRACTION by default this makes json output float values that happen to be full numbers with a `.0`, so `4.0` or `0.0` instead of `4` resp `0`. that in turn helps when consuming the json in a type-safe language. --- Encoder/JsonEncode.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Encoder/JsonEncode.php b/Encoder/JsonEncode.php index 86baf9999..898b69db8 100644 --- a/Encoder/JsonEncode.php +++ b/Encoder/JsonEncode.php @@ -23,7 +23,7 @@ class JsonEncode implements EncoderInterface public const OPTIONS = 'json_encode_options'; private $defaultContext = [ - self::OPTIONS => 0, + self::OPTIONS => \JSON_PRESERVE_ZERO_FRACTION, ]; public function __construct(array $defaultContext = []) From 348bea6a4fa81c84ec508eddec7f688fdb769389 Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Wed, 17 Aug 2022 17:14:22 +0200 Subject: [PATCH 053/297] Fix merge --- Normalizer/BackedEnumNormalizer.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Normalizer/BackedEnumNormalizer.php b/Normalizer/BackedEnumNormalizer.php index cb79a0294..1fdcf5b31 100644 --- a/Normalizer/BackedEnumNormalizer.php +++ b/Normalizer/BackedEnumNormalizer.php @@ -37,7 +37,7 @@ public function normalize(mixed $object, string $format = null, array $context = /** * {@inheritdoc} */ - public function supportsNormalization($data, string $format = null): bool + public function supportsNormalization(mixed $data, string $format = null): bool { return $data instanceof \BackedEnum; } From aa35a62ff74ff1502f74ac8d1463675eabb65352 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gr=C3=A9goire=20Pineau?= Date: Thu, 25 Aug 2022 16:59:21 +0200 Subject: [PATCH 054/297] [CS] Remove @inheritdoc PHPDoc --- .../CompiledClassMetadataCacheWarmer.php | 6 --- DataCollector/SerializerDataCollector.php | 9 ---- Debug/TraceableEncoder.php | 15 ------ Debug/TraceableNormalizer.php | 24 --------- Debug/TraceableSerializer.php | 30 ----------- Encoder/ChainDecoder.php | 6 --- Encoder/ChainEncoder.php | 6 --- Encoder/ContextAwareDecoderInterface.php | 2 - Encoder/ContextAwareEncoderInterface.php | 2 - Encoder/CsvEncoder.php | 10 ---- Encoder/JsonDecode.php | 2 - Encoder/JsonEncode.php | 5 -- Encoder/JsonEncoder.php | 10 ---- Encoder/XmlEncoder.php | 10 ---- Encoder/YamlEncoder.php | 10 ---- Extractor/ObjectPropertyListExtractor.php | 3 -- Mapping/AttributeMetadata.php | 50 ------------------- .../ClassDiscriminatorFromClassMetadata.php | 9 ---- Mapping/ClassMetadata.php | 23 --------- Mapping/Factory/CacheClassMetadataFactory.php | 6 --- Mapping/Factory/ClassMetadataFactory.php | 6 --- .../Factory/CompiledClassMetadataFactory.php | 6 --- Mapping/Loader/AnnotationLoader.php | 3 -- Mapping/Loader/LoaderChain.php | 3 -- Mapping/Loader/XmlFileLoader.php | 3 -- Mapping/Loader/YamlFileLoader.php | 3 -- .../AdvancedNameConverterInterface.php | 6 --- .../CamelCaseToSnakeCaseNameConverter.php | 6 --- NameConverter/MetadataAwareNameConverter.php | 6 --- Normalizer/AbstractNormalizer.php | 3 -- Normalizer/AbstractObjectNormalizer.php | 15 ------ Normalizer/ArrayDenormalizer.php | 8 --- Normalizer/BackedEnumNormalizer.php | 14 ------ .../ConstraintViolationListNormalizer.php | 8 --- .../ContextAwareDenormalizerInterface.php | 2 - .../ContextAwareNormalizerInterface.php | 2 - Normalizer/CustomNormalizer.php | 9 ---- Normalizer/DataUriNormalizer.php | 12 ----- Normalizer/DateIntervalNormalizer.php | 11 ---- Normalizer/DateTimeNormalizer.php | 11 ---- Normalizer/DateTimeZoneNormalizer.php | 11 ---- Normalizer/FormErrorNormalizer.php | 9 ---- Normalizer/GetSetMethodNormalizer.php | 16 ------ Normalizer/JsonSerializableNormalizer.php | 13 ----- Normalizer/MimeMessageNormalizer.php | 15 ------ Normalizer/ObjectNormalizer.php | 15 ------ Normalizer/ProblemNormalizer.php | 8 --- Normalizer/PropertyNormalizer.php | 19 ------- Normalizer/UidNormalizer.php | 14 ------ Normalizer/UnwrappingDenormalizer.php | 9 ---- Serializer.php | 29 ----------- Tests/Fixtures/AbstractNormalizerDummy.php | 15 ------ .../Fixtures/StaticConstructorNormalizer.php | 3 -- .../AbstractObjectNormalizerTest.php | 8 --- Tests/Normalizer/TestDenormalizer.php | 6 --- Tests/Normalizer/TestNormalizer.php | 6 --- 56 files changed, 571 deletions(-) diff --git a/CacheWarmer/CompiledClassMetadataCacheWarmer.php b/CacheWarmer/CompiledClassMetadataCacheWarmer.php index 39a85aeeb..565320af9 100644 --- a/CacheWarmer/CompiledClassMetadataCacheWarmer.php +++ b/CacheWarmer/CompiledClassMetadataCacheWarmer.php @@ -37,9 +37,6 @@ public function __construct(array $classesToCompile, ClassMetadataFactoryInterfa $this->filesystem = $filesystem; } - /** - * {@inheritdoc} - */ public function warmUp(string $cacheDir): array { $metadatas = []; @@ -55,9 +52,6 @@ public function warmUp(string $cacheDir): array return []; } - /** - * {@inheritdoc} - */ public function isOptional(): bool { return true; diff --git a/DataCollector/SerializerDataCollector.php b/DataCollector/SerializerDataCollector.php index 6067ece88..9dd1752fa 100644 --- a/DataCollector/SerializerDataCollector.php +++ b/DataCollector/SerializerDataCollector.php @@ -33,17 +33,11 @@ public function reset(): void $this->collected = []; } - /** - * {@inheritDoc} - */ public function collect(Request $request, Response $response, \Throwable $exception = null): void { // Everything is collected during the request, and formatted on kernel terminate. } - /** - * {@inheritDoc} - */ public function getName(): string { return 'serializer'; @@ -164,9 +158,6 @@ public function collectDecoding(string $traceId, string $encoder, float $time): $this->collected[$traceId]['encoding'][] = compact('encoder', 'method', 'time'); } - /** - * {@inheritDoc} - */ public function lateCollect(): void { $this->data = [ diff --git a/Debug/TraceableEncoder.php b/Debug/TraceableEncoder.php index 8105ba7b3..73c645c6c 100644 --- a/Debug/TraceableEncoder.php +++ b/Debug/TraceableEncoder.php @@ -33,9 +33,6 @@ public function __construct( ) { } - /** - * {@inheritDoc} - */ public function encode(mixed $data, string $format, array $context = []): string { if (!$this->encoder instanceof EncoderInterface) { @@ -53,9 +50,6 @@ public function encode(mixed $data, string $format, array $context = []): string return $encoded; } - /** - * {@inheritDoc} - */ public function supportsEncoding(string $format, array $context = []): bool { if (!$this->encoder instanceof EncoderInterface) { @@ -65,9 +59,6 @@ public function supportsEncoding(string $format, array $context = []): bool return $this->encoder->supportsEncoding($format, $context); } - /** - * {@inheritDoc} - */ public function decode(string $data, string $format, array $context = []): mixed { if (!$this->encoder instanceof DecoderInterface) { @@ -85,9 +76,6 @@ public function decode(string $data, string $format, array $context = []): mixed return $encoded; } - /** - * {@inheritDoc} - */ public function supportsDecoding(string $format, array $context = []): bool { if (!$this->encoder instanceof DecoderInterface) { @@ -97,9 +85,6 @@ public function supportsDecoding(string $format, array $context = []): bool return $this->encoder->supportsDecoding($format, $context); } - /** - * {@inheritDoc} - */ public function setSerializer(SerializerInterface $serializer) { if (!$this->encoder instanceof SerializerAwareInterface) { diff --git a/Debug/TraceableNormalizer.php b/Debug/TraceableNormalizer.php index 8e430e55d..be214ce69 100644 --- a/Debug/TraceableNormalizer.php +++ b/Debug/TraceableNormalizer.php @@ -35,9 +35,6 @@ public function __construct( ) { } - /** - * {@inheritDoc} - */ public function normalize(mixed $object, string $format = null, array $context = []): array|string|int|float|bool|\ArrayObject|null { if (!$this->normalizer instanceof NormalizerInterface) { @@ -55,9 +52,6 @@ public function normalize(mixed $object, string $format = null, array $context = return $normalized; } - /** - * {@inheritDoc} - */ public function supportsNormalization(mixed $data, string $format = null, array $context = []): bool { if (!$this->normalizer instanceof NormalizerInterface) { @@ -67,9 +61,6 @@ public function supportsNormalization(mixed $data, string $format = null, array return $this->normalizer->supportsNormalization($data, $format, $context); } - /** - * {@inheritDoc} - */ public function denormalize(mixed $data, string $type, string $format = null, array $context = []): mixed { if (!$this->normalizer instanceof DenormalizerInterface) { @@ -87,9 +78,6 @@ public function denormalize(mixed $data, string $type, string $format = null, ar return $denormalized; } - /** - * {@inheritDoc} - */ public function supportsDenormalization(mixed $data, string $type, string $format = null, array $context = []): bool { if (!$this->normalizer instanceof DenormalizerInterface) { @@ -99,9 +87,6 @@ public function supportsDenormalization(mixed $data, string $type, string $forma return $this->normalizer->supportsDenormalization($data, $type, $format, $context); } - /** - * {@inheritDoc} - */ public function setSerializer(SerializerInterface $serializer) { if (!$this->normalizer instanceof SerializerAwareInterface) { @@ -111,9 +96,6 @@ public function setSerializer(SerializerInterface $serializer) $this->normalizer->setSerializer($serializer); } - /** - * {@inheritDoc} - */ public function setNormalizer(NormalizerInterface $normalizer) { if (!$this->normalizer instanceof NormalizerAwareInterface) { @@ -123,9 +105,6 @@ public function setNormalizer(NormalizerInterface $normalizer) $this->normalizer->setNormalizer($normalizer); } - /** - * {@inheritDoc} - */ public function setDenormalizer(DenormalizerInterface $denormalizer) { if (!$this->normalizer instanceof DenormalizerAwareInterface) { @@ -135,9 +114,6 @@ public function setDenormalizer(DenormalizerInterface $denormalizer) $this->normalizer->setDenormalizer($denormalizer); } - /** - * {@inheritDoc} - */ public function hasCacheableSupportsMethod(): bool { return $this->normalizer instanceof CacheableSupportsMethodInterface && $this->normalizer->hasCacheableSupportsMethod(); diff --git a/Debug/TraceableSerializer.php b/Debug/TraceableSerializer.php index bcc1f71a6..01ae79584 100644 --- a/Debug/TraceableSerializer.php +++ b/Debug/TraceableSerializer.php @@ -38,9 +38,6 @@ public function __construct( ) { } - /** - * {@inheritdoc} - */ public function serialize(mixed $data, string $format, array $context = []): string { $context[self::DEBUG_TRACE_ID] = $traceId = uniqid(); @@ -56,9 +53,6 @@ public function serialize(mixed $data, string $format, array $context = []): str return $result; } - /** - * {@inheritdoc} - */ public function deserialize(mixed $data, string $type, string $format, array $context = []): mixed { $context[self::DEBUG_TRACE_ID] = $traceId = uniqid(); @@ -74,9 +68,6 @@ public function deserialize(mixed $data, string $type, string $format, array $co return $result; } - /** - * {@inheritdoc} - */ public function normalize(mixed $object, string $format = null, array $context = []): array|string|int|float|bool|\ArrayObject|null { $context[self::DEBUG_TRACE_ID] = $traceId = uniqid(); @@ -92,9 +83,6 @@ public function normalize(mixed $object, string $format = null, array $context = return $result; } - /** - * {@inheritdoc} - */ public function denormalize(mixed $data, string $type, string $format = null, array $context = []): mixed { $context[self::DEBUG_TRACE_ID] = $traceId = uniqid(); @@ -110,9 +98,6 @@ public function denormalize(mixed $data, string $type, string $format = null, ar return $result; } - /** - * {@inheritdoc} - */ public function encode(mixed $data, string $format, array $context = []): string { $context[self::DEBUG_TRACE_ID] = $traceId = uniqid(); @@ -128,9 +113,6 @@ public function encode(mixed $data, string $format, array $context = []): string return $result; } - /** - * {@inheritdoc} - */ public function decode(string $data, string $format, array $context = []): mixed { $context[self::DEBUG_TRACE_ID] = $traceId = uniqid(); @@ -146,33 +128,21 @@ public function decode(string $data, string $format, array $context = []): mixed return $result; } - /** - * {@inheritdoc} - */ public function supportsNormalization(mixed $data, string $format = null, array $context = []): bool { return $this->serializer->supportsNormalization($data, $format, $context); } - /** - * {@inheritdoc} - */ public function supportsDenormalization(mixed $data, string $type, string $format = null, array $context = []): bool { return $this->serializer->supportsDenormalization($data, $type, $format, $context); } - /** - * {@inheritdoc} - */ public function supportsEncoding(string $format, array $context = []): bool { return $this->serializer->supportsEncoding($format, $context); } - /** - * {@inheritdoc} - */ public function supportsDecoding(string $format, array $context = []): bool { return $this->serializer->supportsDecoding($format, $context); diff --git a/Encoder/ChainDecoder.php b/Encoder/ChainDecoder.php index 0f7f94fad..dc998fcba 100644 --- a/Encoder/ChainDecoder.php +++ b/Encoder/ChainDecoder.php @@ -32,17 +32,11 @@ public function __construct(array $decoders = []) $this->decoders = $decoders; } - /** - * {@inheritdoc} - */ final public function decode(string $data, string $format, array $context = []): mixed { return $this->getDecoder($format, $context)->decode($data, $format, $context); } - /** - * {@inheritdoc} - */ public function supportsDecoding(string $format, array $context = []): bool { try { diff --git a/Encoder/ChainEncoder.php b/Encoder/ChainEncoder.php index 70d7c9fd1..132061aa1 100644 --- a/Encoder/ChainEncoder.php +++ b/Encoder/ChainEncoder.php @@ -33,17 +33,11 @@ public function __construct(array $encoders = []) $this->encoders = $encoders; } - /** - * {@inheritdoc} - */ final public function encode(mixed $data, string $format, array $context = []): string { return $this->getEncoder($format, $context)->encode($data, $format, $context); } - /** - * {@inheritdoc} - */ public function supportsEncoding(string $format, array $context = []): bool { try { diff --git a/Encoder/ContextAwareDecoderInterface.php b/Encoder/ContextAwareDecoderInterface.php index 910b26bac..6fc81c124 100644 --- a/Encoder/ContextAwareDecoderInterface.php +++ b/Encoder/ContextAwareDecoderInterface.php @@ -21,8 +21,6 @@ interface ContextAwareDecoderInterface extends DecoderInterface { /** - * {@inheritdoc} - * * @param array $context options that decoders have access to */ public function supportsDecoding(string $format, array $context = []): bool; diff --git a/Encoder/ContextAwareEncoderInterface.php b/Encoder/ContextAwareEncoderInterface.php index f828f87a4..f5b313f41 100644 --- a/Encoder/ContextAwareEncoderInterface.php +++ b/Encoder/ContextAwareEncoderInterface.php @@ -21,8 +21,6 @@ interface ContextAwareEncoderInterface extends EncoderInterface { /** - * {@inheritdoc} - * * @param array $context options that encoders have access to */ public function supportsEncoding(string $format, array $context = []): bool; diff --git a/Encoder/CsvEncoder.php b/Encoder/CsvEncoder.php index b2c6fcd81..68d11c84b 100644 --- a/Encoder/CsvEncoder.php +++ b/Encoder/CsvEncoder.php @@ -56,9 +56,6 @@ public function __construct(array $defaultContext = []) $this->defaultContext = array_merge($this->defaultContext, $defaultContext); } - /** - * {@inheritdoc} - */ public function encode(mixed $data, string $format, array $context = []): string { $handle = fopen('php://temp,', 'w+'); @@ -123,8 +120,6 @@ public function encode(mixed $data, string $format, array $context = []): string } /** - * {@inheritdoc} - * * @param array $context */ public function supportsEncoding(string $format /* , array $context = [] */): bool @@ -132,9 +127,6 @@ public function supportsEncoding(string $format /* , array $context = [] */): bo return self::FORMAT === $format; } - /** - * {@inheritdoc} - */ public function decode(string $data, string $format, array $context = []): mixed { $handle = fopen('php://temp', 'r+'); @@ -211,8 +203,6 @@ public function decode(string $data, string $format, array $context = []): mixed } /** - * {@inheritdoc} - * * @param array $context */ public function supportsDecoding(string $format /* , array $context = [] */): bool diff --git a/Encoder/JsonDecode.php b/Encoder/JsonDecode.php index 50d2d2e3f..9c307bb31 100644 --- a/Encoder/JsonDecode.php +++ b/Encoder/JsonDecode.php @@ -94,8 +94,6 @@ public function decode(string $data, string $format, array $context = []): mixed } /** - * {@inheritdoc} - * * @param array $context */ public function supportsDecoding(string $format /* , array $context = [] */): bool diff --git a/Encoder/JsonEncode.php b/Encoder/JsonEncode.php index 898b69db8..da61e01c0 100644 --- a/Encoder/JsonEncode.php +++ b/Encoder/JsonEncode.php @@ -31,9 +31,6 @@ public function __construct(array $defaultContext = []) $this->defaultContext = array_merge($this->defaultContext, $defaultContext); } - /** - * {@inheritdoc} - */ public function encode(mixed $data, string $format, array $context = []): string { $options = $context[self::OPTIONS] ?? $this->defaultContext[self::OPTIONS]; @@ -56,8 +53,6 @@ public function encode(mixed $data, string $format, array $context = []): string } /** - * {@inheritdoc} - * * @param array $context */ public function supportsEncoding(string $format /* , array $context = [] */): bool diff --git a/Encoder/JsonEncoder.php b/Encoder/JsonEncoder.php index e6ccbfba5..82ea08125 100644 --- a/Encoder/JsonEncoder.php +++ b/Encoder/JsonEncoder.php @@ -29,25 +29,17 @@ public function __construct(JsonEncode $encodingImpl = null, JsonDecode $decodin $this->decodingImpl = $decodingImpl ?? new JsonDecode([JsonDecode::ASSOCIATIVE => true]); } - /** - * {@inheritdoc} - */ public function encode(mixed $data, string $format, array $context = []): string { return $this->encodingImpl->encode($data, self::FORMAT, $context); } - /** - * {@inheritdoc} - */ public function decode(string $data, string $format, array $context = []): mixed { return $this->decodingImpl->decode($data, self::FORMAT, $context); } /** - * {@inheritdoc} - * * @param array $context */ public function supportsEncoding(string $format /* , array $context = [] */): bool @@ -56,8 +48,6 @@ public function supportsEncoding(string $format /* , array $context = [] */): bo } /** - * {@inheritdoc} - * * @param array $context */ public function supportsDecoding(string $format /* , array $context = [] */): bool diff --git a/Encoder/XmlEncoder.php b/Encoder/XmlEncoder.php index a47e6d695..e64441ffc 100644 --- a/Encoder/XmlEncoder.php +++ b/Encoder/XmlEncoder.php @@ -68,9 +68,6 @@ public function __construct(array $defaultContext = []) $this->defaultContext = array_merge($this->defaultContext, $defaultContext); } - /** - * {@inheritdoc} - */ public function encode(mixed $data, string $format, array $context = []): string { $encoderIgnoredNodeTypes = $context[self::ENCODER_IGNORED_NODE_TYPES] ?? $this->defaultContext[self::ENCODER_IGNORED_NODE_TYPES]; @@ -94,9 +91,6 @@ public function encode(mixed $data, string $format, array $context = []): string return $dom->saveXML($ignorePiNode ? $dom->documentElement : null); } - /** - * {@inheritdoc} - */ public function decode(string $data, string $format, array $context = []): mixed { if ('' === trim($data)) { @@ -165,8 +159,6 @@ public function decode(string $data, string $format, array $context = []): mixed } /** - * {@inheritdoc} - * * @param array $context */ public function supportsEncoding(string $format /* , array $context = [] */): bool @@ -175,8 +167,6 @@ public function supportsEncoding(string $format /* , array $context = [] */): bo } /** - * {@inheritdoc} - * * @param array $context */ public function supportsDecoding(string $format /* , array $context = [] */): bool diff --git a/Encoder/YamlEncoder.php b/Encoder/YamlEncoder.php index ecb9815ee..0ee184560 100644 --- a/Encoder/YamlEncoder.php +++ b/Encoder/YamlEncoder.php @@ -51,9 +51,6 @@ public function __construct(Dumper $dumper = null, Parser $parser = null, array $this->defaultContext = array_merge($this->defaultContext, $defaultContext); } - /** - * {@inheritdoc} - */ public function encode(mixed $data, string $format, array $context = []): string { $context = array_merge($this->defaultContext, $context); @@ -66,8 +63,6 @@ public function encode(mixed $data, string $format, array $context = []): string } /** - * {@inheritdoc} - * * @param array $context */ public function supportsEncoding(string $format /* , array $context = [] */): bool @@ -75,9 +70,6 @@ public function supportsEncoding(string $format /* , array $context = [] */): bo return self::FORMAT === $format || self::ALTERNATIVE_FORMAT === $format; } - /** - * {@inheritdoc} - */ public function decode(string $data, string $format, array $context = []): mixed { $context = array_merge($this->defaultContext, $context); @@ -86,8 +78,6 @@ public function decode(string $data, string $format, array $context = []): mixed } /** - * {@inheritdoc} - * * @param array $context */ public function supportsDecoding(string $format /* , array $context = [] */): bool diff --git a/Extractor/ObjectPropertyListExtractor.php b/Extractor/ObjectPropertyListExtractor.php index 1f9fc6f7c..0524b0380 100644 --- a/Extractor/ObjectPropertyListExtractor.php +++ b/Extractor/ObjectPropertyListExtractor.php @@ -27,9 +27,6 @@ public function __construct(PropertyListExtractorInterface $propertyListExtracto $this->objectClassResolver = $objectClassResolver; } - /** - * {@inheritdoc} - */ public function getProperties(object $object, array $context = []): ?array { $class = $this->objectClassResolver ? ($this->objectClassResolver)($object) : \get_class($object); diff --git a/Mapping/AttributeMetadata.php b/Mapping/AttributeMetadata.php index 3097b52c4..7c644c272 100644 --- a/Mapping/AttributeMetadata.php +++ b/Mapping/AttributeMetadata.php @@ -12,8 +12,6 @@ namespace Symfony\Component\Serializer\Mapping; /** - * {@inheritdoc} - * * @author Kévin Dunglas */ class AttributeMetadata implements AttributeMetadataInterface @@ -82,17 +80,11 @@ public function __construct(string $name) $this->name = $name; } - /** - * {@inheritdoc} - */ public function getName(): string { return $this->name; } - /** - * {@inheritdoc} - */ public function addGroup(string $group) { if (!\in_array($group, $this->groups)) { @@ -100,73 +92,46 @@ public function addGroup(string $group) } } - /** - * {@inheritdoc} - */ public function getGroups(): array { return $this->groups; } - /** - * {@inheritdoc} - */ public function setMaxDepth(?int $maxDepth) { $this->maxDepth = $maxDepth; } - /** - * {@inheritdoc} - */ public function getMaxDepth(): ?int { return $this->maxDepth; } - /** - * {@inheritdoc} - */ public function setSerializedName(string $serializedName = null) { $this->serializedName = $serializedName; } - /** - * {@inheritdoc} - */ public function getSerializedName(): ?string { return $this->serializedName; } - /** - * {@inheritdoc} - */ public function setIgnore(bool $ignore) { $this->ignore = $ignore; } - /** - * {@inheritdoc} - */ public function isIgnored(): bool { return $this->ignore; } - /** - * {@inheritdoc} - */ public function getNormalizationContexts(): array { return $this->normalizationContexts; } - /** - * {@inheritdoc} - */ public function getNormalizationContextForGroups(array $groups): array { $contexts = []; @@ -177,9 +142,6 @@ public function getNormalizationContextForGroups(array $groups): array return array_merge($this->normalizationContexts['*'] ?? [], ...$contexts); } - /** - * {@inheritdoc} - */ public function setNormalizationContextForGroups(array $context, array $groups = []): void { if (!$groups) { @@ -191,17 +153,11 @@ public function setNormalizationContextForGroups(array $context, array $groups = } } - /** - * {@inheritdoc} - */ public function getDenormalizationContexts(): array { return $this->denormalizationContexts; } - /** - * {@inheritdoc} - */ public function getDenormalizationContextForGroups(array $groups): array { $contexts = []; @@ -212,9 +168,6 @@ public function getDenormalizationContextForGroups(array $groups): array return array_merge($this->denormalizationContexts['*'] ?? [], ...$contexts); } - /** - * {@inheritdoc} - */ public function setDenormalizationContextForGroups(array $context, array $groups = []): void { if (!$groups) { @@ -226,9 +179,6 @@ public function setDenormalizationContextForGroups(array $context, array $groups } } - /** - * {@inheritdoc} - */ public function merge(AttributeMetadataInterface $attributeMetadata) { foreach ($attributeMetadata->getGroups() as $group) { diff --git a/Mapping/ClassDiscriminatorFromClassMetadata.php b/Mapping/ClassDiscriminatorFromClassMetadata.php index 2e46fecc8..8b9b02896 100644 --- a/Mapping/ClassDiscriminatorFromClassMetadata.php +++ b/Mapping/ClassDiscriminatorFromClassMetadata.php @@ -29,9 +29,6 @@ public function __construct(ClassMetadataFactoryInterface $classMetadataFactory) $this->classMetadataFactory = $classMetadataFactory; } - /** - * {@inheritdoc} - */ public function getMappingForClass(string $class): ?ClassDiscriminatorMapping { if ($this->classMetadataFactory->hasMetadataFor($class)) { @@ -41,9 +38,6 @@ public function getMappingForClass(string $class): ?ClassDiscriminatorMapping return null; } - /** - * {@inheritdoc} - */ public function getMappingForMappedObject(object|string $object): ?ClassDiscriminatorMapping { if ($this->classMetadataFactory->hasMetadataFor($object)) { @@ -62,9 +56,6 @@ public function getMappingForMappedObject(object|string $object): ?ClassDiscrimi return $this->mappingForMappedObjectCache[$cacheKey]; } - /** - * {@inheritdoc} - */ public function getTypeForMappedObject(object|string $object): ?string { if (null === $mapping = $this->getMappingForMappedObject($object)) { diff --git a/Mapping/ClassMetadata.php b/Mapping/ClassMetadata.php index ace7ba329..d27ee31a3 100644 --- a/Mapping/ClassMetadata.php +++ b/Mapping/ClassMetadata.php @@ -12,8 +12,6 @@ namespace Symfony\Component\Serializer\Mapping; /** - * {@inheritdoc} - * * @author Kévin Dunglas */ class ClassMetadata implements ClassMetadataInterface @@ -57,33 +55,21 @@ public function __construct(string $class, ClassDiscriminatorMapping $classDiscr $this->classDiscriminatorMapping = $classDiscriminatorMapping; } - /** - * {@inheritdoc} - */ public function getName(): string { return $this->name; } - /** - * {@inheritdoc} - */ public function addAttributeMetadata(AttributeMetadataInterface $attributeMetadata) { $this->attributesMetadata[$attributeMetadata->getName()] = $attributeMetadata; } - /** - * {@inheritdoc} - */ public function getAttributesMetadata(): array { return $this->attributesMetadata; } - /** - * {@inheritdoc} - */ public function merge(ClassMetadataInterface $classMetadata) { foreach ($classMetadata->getAttributesMetadata() as $attributeMetadata) { @@ -95,9 +81,6 @@ public function merge(ClassMetadataInterface $classMetadata) } } - /** - * {@inheritdoc} - */ public function getReflectionClass(): \ReflectionClass { if (!$this->reflClass) { @@ -107,17 +90,11 @@ public function getReflectionClass(): \ReflectionClass return $this->reflClass; } - /** - * {@inheritdoc} - */ public function getClassDiscriminatorMapping(): ?ClassDiscriminatorMapping { return $this->classDiscriminatorMapping; } - /** - * {@inheritdoc} - */ public function setClassDiscriminatorMapping(ClassDiscriminatorMapping $mapping = null) { $this->classDiscriminatorMapping = $mapping; diff --git a/Mapping/Factory/CacheClassMetadataFactory.php b/Mapping/Factory/CacheClassMetadataFactory.php index e7edc4d37..c3adbeec1 100644 --- a/Mapping/Factory/CacheClassMetadataFactory.php +++ b/Mapping/Factory/CacheClassMetadataFactory.php @@ -41,9 +41,6 @@ public function __construct(ClassMetadataFactoryInterface $decorated, CacheItemP $this->cacheItemPool = $cacheItemPool; } - /** - * {@inheritdoc} - */ public function getMetadataFor(string|object $value): ClassMetadataInterface { $class = $this->getClass($value); @@ -65,9 +62,6 @@ public function getMetadataFor(string|object $value): ClassMetadataInterface return $this->loadedClasses[$class] = $metadata; } - /** - * {@inheritdoc} - */ public function hasMetadataFor(mixed $value): bool { return $this->decorated->hasMetadataFor($value); diff --git a/Mapping/Factory/ClassMetadataFactory.php b/Mapping/Factory/ClassMetadataFactory.php index 59fb589f0..9b2cf1cb7 100644 --- a/Mapping/Factory/ClassMetadataFactory.php +++ b/Mapping/Factory/ClassMetadataFactory.php @@ -36,9 +36,6 @@ public function __construct(LoaderInterface $loader) $this->loader = $loader; } - /** - * {@inheritdoc} - */ public function getMetadataFor(string|object $value): ClassMetadataInterface { $class = $this->getClass($value); @@ -65,9 +62,6 @@ public function getMetadataFor(string|object $value): ClassMetadataInterface return $this->loadedClasses[$class] = $classMetadata; } - /** - * {@inheritdoc} - */ public function hasMetadataFor(mixed $value): bool { return \is_object($value) || (\is_string($value) && (class_exists($value) || interface_exists($value, false))); diff --git a/Mapping/Factory/CompiledClassMetadataFactory.php b/Mapping/Factory/CompiledClassMetadataFactory.php index 8b0f0aeff..5bac97581 100644 --- a/Mapping/Factory/CompiledClassMetadataFactory.php +++ b/Mapping/Factory/CompiledClassMetadataFactory.php @@ -42,9 +42,6 @@ public function __construct(string $compiledClassMetadataFile, ClassMetadataFact $this->classMetadataFactory = $classMetadataFactory; } - /** - * {@inheritdoc} - */ public function getMetadataFor(string|object $value): ClassMetadataInterface { $className = \is_object($value) ? \get_class($value) : $value; @@ -70,9 +67,6 @@ public function getMetadataFor(string|object $value): ClassMetadataInterface return $this->loadedClasses[$className]; } - /** - * {@inheritdoc} - */ public function hasMetadataFor(mixed $value): bool { $className = \is_object($value) ? \get_class($value) : $value; diff --git a/Mapping/Loader/AnnotationLoader.php b/Mapping/Loader/AnnotationLoader.php index 0e534a480..25d4beaf6 100644 --- a/Mapping/Loader/AnnotationLoader.php +++ b/Mapping/Loader/AnnotationLoader.php @@ -48,9 +48,6 @@ public function __construct(Reader $reader = null) $this->reader = $reader; } - /** - * {@inheritdoc} - */ public function loadClassMetadata(ClassMetadataInterface $classMetadata): bool { $reflectionClass = $classMetadata->getReflectionClass(); diff --git a/Mapping/Loader/LoaderChain.php b/Mapping/Loader/LoaderChain.php index c2b3423c8..c25f5fa96 100644 --- a/Mapping/Loader/LoaderChain.php +++ b/Mapping/Loader/LoaderChain.php @@ -47,9 +47,6 @@ public function __construct(array $loaders) $this->loaders = $loaders; } - /** - * {@inheritdoc} - */ public function loadClassMetadata(ClassMetadataInterface $metadata): bool { $success = false; diff --git a/Mapping/Loader/XmlFileLoader.php b/Mapping/Loader/XmlFileLoader.php index 8f7e7cf55..6d6ae16a9 100644 --- a/Mapping/Loader/XmlFileLoader.php +++ b/Mapping/Loader/XmlFileLoader.php @@ -31,9 +31,6 @@ class XmlFileLoader extends FileLoader */ private $classes; - /** - * {@inheritdoc} - */ public function loadClassMetadata(ClassMetadataInterface $classMetadata): bool { if (null === $this->classes) { diff --git a/Mapping/Loader/YamlFileLoader.php b/Mapping/Loader/YamlFileLoader.php index 6e77a989d..907d33844 100644 --- a/Mapping/Loader/YamlFileLoader.php +++ b/Mapping/Loader/YamlFileLoader.php @@ -34,9 +34,6 @@ class YamlFileLoader extends FileLoader */ private $classes; - /** - * {@inheritdoc} - */ public function loadClassMetadata(ClassMetadataInterface $classMetadata): bool { if (null === $this->classes) { diff --git a/NameConverter/AdvancedNameConverterInterface.php b/NameConverter/AdvancedNameConverterInterface.php index 208d649f7..4e7ed504f 100644 --- a/NameConverter/AdvancedNameConverterInterface.php +++ b/NameConverter/AdvancedNameConverterInterface.php @@ -18,13 +18,7 @@ */ interface AdvancedNameConverterInterface extends NameConverterInterface { - /** - * {@inheritdoc} - */ public function normalize(string $propertyName, string $class = null, string $format = null, array $context = []): string; - /** - * {@inheritdoc} - */ public function denormalize(string $propertyName, string $class = null, string $format = null, array $context = []): string; } diff --git a/NameConverter/CamelCaseToSnakeCaseNameConverter.php b/NameConverter/CamelCaseToSnakeCaseNameConverter.php index 84f3f5c8c..14c2e8a28 100644 --- a/NameConverter/CamelCaseToSnakeCaseNameConverter.php +++ b/NameConverter/CamelCaseToSnakeCaseNameConverter.php @@ -31,9 +31,6 @@ public function __construct(array $attributes = null, bool $lowerCamelCase = tru $this->lowerCamelCase = $lowerCamelCase; } - /** - * {@inheritdoc} - */ public function normalize(string $propertyName): string { if (null === $this->attributes || \in_array($propertyName, $this->attributes)) { @@ -43,9 +40,6 @@ public function normalize(string $propertyName): string return $propertyName; } - /** - * {@inheritdoc} - */ public function denormalize(string $propertyName): string { $camelCasedName = preg_replace_callback('/(^|_|\.)+(.)/', function ($match) { diff --git a/NameConverter/MetadataAwareNameConverter.php b/NameConverter/MetadataAwareNameConverter.php index 7ce17cc39..24c9991e1 100644 --- a/NameConverter/MetadataAwareNameConverter.php +++ b/NameConverter/MetadataAwareNameConverter.php @@ -38,9 +38,6 @@ public function __construct(ClassMetadataFactoryInterface $metadataFactory, Name $this->fallbackNameConverter = $fallbackNameConverter; } - /** - * {@inheritdoc} - */ public function normalize(string $propertyName, string $class = null, string $format = null, array $context = []): string { if (null === $class) { @@ -54,9 +51,6 @@ public function normalize(string $propertyName, string $class = null, string $fo return self::$normalizeCache[$class][$propertyName] ?? $this->normalizeFallback($propertyName, $class, $format, $context); } - /** - * {@inheritdoc} - */ public function denormalize(string $propertyName, string $class = null, string $format = null, array $context = []): string { if (null === $class) { diff --git a/Normalizer/AbstractNormalizer.php b/Normalizer/AbstractNormalizer.php index e426d8707..cb6890d3c 100644 --- a/Normalizer/AbstractNormalizer.php +++ b/Normalizer/AbstractNormalizer.php @@ -150,9 +150,6 @@ public function __construct(ClassMetadataFactoryInterface $classMetadataFactory } } - /** - * {@inheritdoc} - */ public function hasCacheableSupportsMethod(): bool { return false; diff --git a/Normalizer/AbstractObjectNormalizer.php b/Normalizer/AbstractObjectNormalizer.php index 714fb10e3..1a499a049 100644 --- a/Normalizer/AbstractObjectNormalizer.php +++ b/Normalizer/AbstractObjectNormalizer.php @@ -134,8 +134,6 @@ public function __construct(ClassMetadataFactoryInterface $classMetadataFactory } /** - * {@inheritdoc} - * * @param array $context */ public function supportsNormalization(mixed $data, string $format = null /* , array $context = [] */) @@ -143,9 +141,6 @@ public function supportsNormalization(mixed $data, string $format = null /* , ar return \is_object($data) && !$data instanceof \Traversable; } - /** - * {@inheritdoc} - */ public function normalize(mixed $object, string $format = null, array $context = []) { if (!isset($context['cache_key'])) { @@ -226,9 +221,6 @@ public function normalize(mixed $object, string $format = null, array $context = return $data; } - /** - * {@inheritdoc} - */ protected function instantiateObject(array &$data, string $class, array &$context, \ReflectionClass $reflectionClass, array|bool $allowedAttributes, string $format = null) { if ($this->classDiscriminatorResolver && $mapping = $this->classDiscriminatorResolver->getMappingForClass($class)) { @@ -301,8 +293,6 @@ abstract protected function extractAttributes(object $object, string $format = n abstract protected function getAttributeValue(object $object, string $attribute, string $format = null, array $context = []); /** - * {@inheritdoc} - * * @param array $context */ public function supportsDenormalization(mixed $data, string $type, string $format = null /* , array $context = [] */) @@ -310,9 +300,6 @@ public function supportsDenormalization(mixed $data, string $type, string $forma return class_exists($type) || (interface_exists($type, false) && $this->classDiscriminatorResolver && null !== $this->classDiscriminatorResolver->getMappingForClass($type)); } - /** - * {@inheritdoc} - */ public function denormalize(mixed $data, string $type, string $format = null, array $context = []) { if (!isset($context['cache_key'])) { @@ -688,8 +675,6 @@ private function isMaxDepthReached(array $attributesMetadata, string $class, str * * We must not mix up the attribute cache between parent and children. * - * {@inheritdoc} - * * @internal */ protected function createChildContext(array $parentContext, string $attribute, ?string $format): array diff --git a/Normalizer/ArrayDenormalizer.php b/Normalizer/ArrayDenormalizer.php index 8fa797487..a88beba7a 100644 --- a/Normalizer/ArrayDenormalizer.php +++ b/Normalizer/ArrayDenormalizer.php @@ -28,8 +28,6 @@ class ArrayDenormalizer implements ContextAwareDenormalizerInterface, Denormaliz use DenormalizerAwareTrait; /** - * {@inheritdoc} - * * @throws NotNormalizableValueException */ public function denormalize(mixed $data, string $type, string $format = null, array $context = []): array @@ -61,9 +59,6 @@ public function denormalize(mixed $data, string $type, string $format = null, ar return $data; } - /** - * {@inheritdoc} - */ public function supportsDenormalization(mixed $data, string $type, string $format = null, array $context = []): bool { if (null === $this->denormalizer) { @@ -74,9 +69,6 @@ public function supportsDenormalization(mixed $data, string $type, string $forma && $this->denormalizer->supportsDenormalization($data, substr($type, 0, -2), $format, $context); } - /** - * {@inheritdoc} - */ public function hasCacheableSupportsMethod(): bool { return $this->denormalizer instanceof CacheableSupportsMethodInterface && $this->denormalizer->hasCacheableSupportsMethod(); diff --git a/Normalizer/BackedEnumNormalizer.php b/Normalizer/BackedEnumNormalizer.php index 8aa242d67..26943377b 100644 --- a/Normalizer/BackedEnumNormalizer.php +++ b/Normalizer/BackedEnumNormalizer.php @@ -22,9 +22,6 @@ */ final class BackedEnumNormalizer implements NormalizerInterface, DenormalizerInterface, CacheableSupportsMethodInterface { - /** - * {@inheritdoc} - */ public function normalize(mixed $object, string $format = null, array $context = []): int|string { if (!$object instanceof \BackedEnum) { @@ -34,17 +31,12 @@ public function normalize(mixed $object, string $format = null, array $context = return $object->value; } - /** - * {@inheritdoc} - */ public function supportsNormalization(mixed $data, string $format = null, array $context = []): bool { return $data instanceof \BackedEnum; } /** - * {@inheritdoc} - * * @throws NotNormalizableValueException */ public function denormalize(mixed $data, string $type, string $format = null, array $context = []): mixed @@ -64,17 +56,11 @@ public function denormalize(mixed $data, string $type, string $format = null, ar } } - /** - * {@inheritdoc} - */ public function supportsDenormalization(mixed $data, string $type, string $format = null, array $context = []): bool { return is_subclass_of($type, \BackedEnum::class); } - /** - * {@inheritdoc} - */ public function hasCacheableSupportsMethod(): bool { return true; diff --git a/Normalizer/ConstraintViolationListNormalizer.php b/Normalizer/ConstraintViolationListNormalizer.php index 8af6fd7cd..0300bd847 100644 --- a/Normalizer/ConstraintViolationListNormalizer.php +++ b/Normalizer/ConstraintViolationListNormalizer.php @@ -39,9 +39,6 @@ public function __construct(array $defaultContext = [], NameConverterInterface $ $this->nameConverter = $nameConverter; } - /** - * {@inheritdoc} - */ public function normalize(mixed $object, string $format = null, array $context = []): array { if (\array_key_exists(self::PAYLOAD_FIELDS, $context)) { @@ -105,8 +102,6 @@ public function normalize(mixed $object, string $format = null, array $context = } /** - * {@inheritdoc} - * * @param array $context */ public function supportsNormalization(mixed $data, string $format = null /* , array $context = [] */): bool @@ -114,9 +109,6 @@ public function supportsNormalization(mixed $data, string $format = null /* , ar return $data instanceof ConstraintViolationListInterface; } - /** - * {@inheritdoc} - */ public function hasCacheableSupportsMethod(): bool { return __CLASS__ === static::class; diff --git a/Normalizer/ContextAwareDenormalizerInterface.php b/Normalizer/ContextAwareDenormalizerInterface.php index 38c07a268..a02951093 100644 --- a/Normalizer/ContextAwareDenormalizerInterface.php +++ b/Normalizer/ContextAwareDenormalizerInterface.php @@ -21,8 +21,6 @@ interface ContextAwareDenormalizerInterface extends DenormalizerInterface { /** - * {@inheritdoc} - * * @param array $context options that denormalizers have access to */ public function supportsDenormalization(mixed $data, string $type, string $format = null, array $context = []): bool; diff --git a/Normalizer/ContextAwareNormalizerInterface.php b/Normalizer/ContextAwareNormalizerInterface.php index 6f85225bd..44f2f0219 100644 --- a/Normalizer/ContextAwareNormalizerInterface.php +++ b/Normalizer/ContextAwareNormalizerInterface.php @@ -21,8 +21,6 @@ interface ContextAwareNormalizerInterface extends NormalizerInterface { /** - * {@inheritdoc} - * * @param array $context options that normalizers have access to */ public function supportsNormalization(mixed $data, string $format = null, array $context = []): bool; diff --git a/Normalizer/CustomNormalizer.php b/Normalizer/CustomNormalizer.php index 7714af313..63e7dbc5c 100644 --- a/Normalizer/CustomNormalizer.php +++ b/Normalizer/CustomNormalizer.php @@ -22,17 +22,11 @@ class CustomNormalizer implements NormalizerInterface, DenormalizerInterface, Se use ObjectToPopulateTrait; use SerializerAwareTrait; - /** - * {@inheritdoc} - */ public function normalize(mixed $object, string $format = null, array $context = []): array|string|int|float|bool|\ArrayObject|null { return $object->normalize($this->serializer, $format, $context); } - /** - * {@inheritdoc} - */ public function denormalize(mixed $data, string $type, string $format = null, array $context = []): mixed { $object = $this->extractObjectToPopulate($type, $context) ?? new $type(); @@ -66,9 +60,6 @@ public function supportsDenormalization(mixed $data, string $type, string $forma return is_subclass_of($type, DenormalizableInterface::class); } - /** - * {@inheritdoc} - */ public function hasCacheableSupportsMethod(): bool { return __CLASS__ === static::class; diff --git a/Normalizer/DataUriNormalizer.php b/Normalizer/DataUriNormalizer.php index ccfab6dbc..b0cecd9e8 100644 --- a/Normalizer/DataUriNormalizer.php +++ b/Normalizer/DataUriNormalizer.php @@ -45,9 +45,6 @@ public function __construct(MimeTypeGuesserInterface $mimeTypeGuesser = null) $this->mimeTypeGuesser = $mimeTypeGuesser; } - /** - * {@inheritdoc} - */ public function normalize(mixed $object, string $format = null, array $context = []): string { if (!$object instanceof \SplFileInfo) { @@ -72,8 +69,6 @@ public function normalize(mixed $object, string $format = null, array $context = } /** - * {@inheritdoc} - * * @param array $context */ public function supportsNormalization(mixed $data, string $format = null /* , array $context = [] */): bool @@ -82,8 +77,6 @@ public function supportsNormalization(mixed $data, string $format = null /* , ar } /** - * {@inheritdoc} - * * Regex adapted from Brian Grinstead code. * * @see https://gist.github.com/bgrins/6194623 @@ -118,8 +111,6 @@ public function denormalize(mixed $data, string $type, string $format = null, ar } /** - * {@inheritdoc} - * * @param array $context */ public function supportsDenormalization(mixed $data, string $type, string $format = null /* , array $context = [] */): bool @@ -127,9 +118,6 @@ public function supportsDenormalization(mixed $data, string $type, string $forma return isset(self::SUPPORTED_TYPES[$type]); } - /** - * {@inheritdoc} - */ public function hasCacheableSupportsMethod(): bool { return __CLASS__ === static::class; diff --git a/Normalizer/DateIntervalNormalizer.php b/Normalizer/DateIntervalNormalizer.php index 2c97eb30e..bff7bcabb 100644 --- a/Normalizer/DateIntervalNormalizer.php +++ b/Normalizer/DateIntervalNormalizer.php @@ -34,8 +34,6 @@ public function __construct(array $defaultContext = []) } /** - * {@inheritdoc} - * * @throws InvalidArgumentException */ public function normalize(mixed $object, string $format = null, array $context = []): string @@ -48,8 +46,6 @@ public function normalize(mixed $object, string $format = null, array $context = } /** - * {@inheritdoc} - * * @param array $context */ public function supportsNormalization(mixed $data, string $format = null /* , array $context = [] */): bool @@ -57,17 +53,12 @@ public function supportsNormalization(mixed $data, string $format = null /* , ar return $data instanceof \DateInterval; } - /** - * {@inheritdoc} - */ public function hasCacheableSupportsMethod(): bool { return __CLASS__ === static::class; } /** - * {@inheritdoc} - * * @throws InvalidArgumentException * @throws UnexpectedValueException */ @@ -118,8 +109,6 @@ public function denormalize(mixed $data, string $type, string $format = null, ar } /** - * {@inheritdoc} - * * @param array $context */ public function supportsDenormalization(mixed $data, string $type, string $format = null /* , array $context = [] */): bool diff --git a/Normalizer/DateTimeNormalizer.php b/Normalizer/DateTimeNormalizer.php index 140dddbc2..715a56d5b 100644 --- a/Normalizer/DateTimeNormalizer.php +++ b/Normalizer/DateTimeNormalizer.php @@ -48,8 +48,6 @@ public function setDefaultContext(array $defaultContext): void } /** - * {@inheritdoc} - * * @throws InvalidArgumentException */ public function normalize(mixed $object, string $format = null, array $context = []): string @@ -70,8 +68,6 @@ public function normalize(mixed $object, string $format = null, array $context = } /** - * {@inheritdoc} - * * @param array $context */ public function supportsNormalization(mixed $data, string $format = null /* , array $context = [] */): bool @@ -80,8 +76,6 @@ public function supportsNormalization(mixed $data, string $format = null /* , ar } /** - * {@inheritdoc} - * * @throws NotNormalizableValueException */ public function denormalize(mixed $data, string $type, string $format = null, array $context = []): \DateTimeInterface @@ -123,8 +117,6 @@ public function denormalize(mixed $data, string $type, string $format = null, ar } /** - * {@inheritdoc} - * * @param array $context */ public function supportsDenormalization(mixed $data, string $type, string $format = null /* , array $context = [] */): bool @@ -132,9 +124,6 @@ public function supportsDenormalization(mixed $data, string $type, string $forma return isset(self::SUPPORTED_TYPES[$type]); } - /** - * {@inheritdoc} - */ public function hasCacheableSupportsMethod(): bool { return __CLASS__ === static::class; diff --git a/Normalizer/DateTimeZoneNormalizer.php b/Normalizer/DateTimeZoneNormalizer.php index 62d5c9e0b..6309ce335 100644 --- a/Normalizer/DateTimeZoneNormalizer.php +++ b/Normalizer/DateTimeZoneNormalizer.php @@ -23,8 +23,6 @@ class DateTimeZoneNormalizer implements NormalizerInterface, DenormalizerInterface, CacheableSupportsMethodInterface { /** - * {@inheritdoc} - * * @throws InvalidArgumentException */ public function normalize(mixed $object, string $format = null, array $context = []): string @@ -37,8 +35,6 @@ public function normalize(mixed $object, string $format = null, array $context = } /** - * {@inheritdoc} - * * @param array $context */ public function supportsNormalization(mixed $data, string $format = null /* , array $context = [] */): bool @@ -47,8 +43,6 @@ public function supportsNormalization(mixed $data, string $format = null /* , ar } /** - * {@inheritdoc} - * * @throws NotNormalizableValueException */ public function denormalize(mixed $data, string $type, string $format = null, array $context = []): \DateTimeZone @@ -65,8 +59,6 @@ public function denormalize(mixed $data, string $type, string $format = null, ar } /** - * {@inheritdoc} - * * @param array $context */ public function supportsDenormalization(mixed $data, string $type, string $format = null /* , array $context = [] */): bool @@ -74,9 +66,6 @@ public function supportsDenormalization(mixed $data, string $type, string $forma return \DateTimeZone::class === $type; } - /** - * {@inheritdoc} - */ public function hasCacheableSupportsMethod(): bool { return __CLASS__ === static::class; diff --git a/Normalizer/FormErrorNormalizer.php b/Normalizer/FormErrorNormalizer.php index 0ffa9f072..637baa47d 100644 --- a/Normalizer/FormErrorNormalizer.php +++ b/Normalizer/FormErrorNormalizer.php @@ -22,9 +22,6 @@ final class FormErrorNormalizer implements NormalizerInterface, CacheableSupport public const TYPE = 'type'; public const CODE = 'status_code'; - /** - * {@inheritdoc} - */ public function normalize(mixed $object, string $format = null, array $context = []): array { $data = [ @@ -41,9 +38,6 @@ public function normalize(mixed $object, string $format = null, array $context = return $data; } - /** - * {@inheritdoc} - */ public function supportsNormalization(mixed $data, string $format = null, array $context = []): bool { return $data instanceof FormInterface && $data->isSubmitted() && !$data->isValid(); @@ -82,9 +76,6 @@ private function convertFormChildrenToArray(FormInterface $data): array return $children; } - /** - * {@inheritdoc} - */ public function hasCacheableSupportsMethod(): bool { return __CLASS__ === static::class; diff --git a/Normalizer/GetSetMethodNormalizer.php b/Normalizer/GetSetMethodNormalizer.php index edbf08e29..a0b6ee780 100644 --- a/Normalizer/GetSetMethodNormalizer.php +++ b/Normalizer/GetSetMethodNormalizer.php @@ -37,8 +37,6 @@ class GetSetMethodNormalizer extends AbstractObjectNormalizer private static $setterAccessibleCache = []; /** - * {@inheritdoc} - * * @param array $context */ public function supportsNormalization(mixed $data, string $format = null /* , array $context = [] */): bool @@ -47,8 +45,6 @@ public function supportsNormalization(mixed $data, string $format = null /* , ar } /** - * {@inheritdoc} - * * @param array $context */ public function supportsDenormalization(mixed $data, string $type, string $format = null /* , array $context = [] */): bool @@ -56,9 +52,6 @@ public function supportsDenormalization(mixed $data, string $type, string $forma return parent::supportsDenormalization($data, $type, $format) && $this->supports($type); } - /** - * {@inheritdoc} - */ public function hasCacheableSupportsMethod(): bool { return __CLASS__ === static::class; @@ -98,9 +91,6 @@ private function isGetMethod(\ReflectionMethod $method): bool ; } - /** - * {@inheritdoc} - */ protected function extractAttributes(object $object, string $format = null, array $context = []): array { $reflectionObject = new \ReflectionObject($object); @@ -122,9 +112,6 @@ protected function extractAttributes(object $object, string $format = null, arra return $attributes; } - /** - * {@inheritdoc} - */ protected function getAttributeValue(object $object, string $attribute, string $format = null, array $context = []): mixed { $ucfirsted = ucfirst($attribute); @@ -147,9 +134,6 @@ protected function getAttributeValue(object $object, string $attribute, string $ return null; } - /** - * {@inheritdoc} - */ protected function setAttributeValue(object $object, string $attribute, mixed $value, string $format = null, array $context = []) { $setter = 'set'.ucfirst($attribute); diff --git a/Normalizer/JsonSerializableNormalizer.php b/Normalizer/JsonSerializableNormalizer.php index cc50e898b..b81385bc8 100644 --- a/Normalizer/JsonSerializableNormalizer.php +++ b/Normalizer/JsonSerializableNormalizer.php @@ -21,9 +21,6 @@ */ class JsonSerializableNormalizer extends AbstractNormalizer { - /** - * {@inheritdoc} - */ public function normalize(mixed $object, string $format = null, array $context = []): array|string|int|float|bool|\ArrayObject|null { if ($this->isCircularReference($object, $context)) { @@ -42,8 +39,6 @@ public function normalize(mixed $object, string $format = null, array $context = } /** - * {@inheritdoc} - * * @param array $context */ public function supportsNormalization(mixed $data, string $format = null /* , array $context = [] */): bool @@ -52,8 +47,6 @@ public function supportsNormalization(mixed $data, string $format = null /* , ar } /** - * {@inheritdoc} - * * @param array $context */ public function supportsDenormalization(mixed $data, string $type, string $format = null /* , array $context = [] */): bool @@ -61,17 +54,11 @@ public function supportsDenormalization(mixed $data, string $type, string $forma return false; } - /** - * {@inheritdoc} - */ public function denormalize(mixed $data, string $type, string $format = null, array $context = []): mixed { throw new LogicException(sprintf('Cannot denormalize with "%s".', \JsonSerializable::class)); } - /** - * {@inheritdoc} - */ public function hasCacheableSupportsMethod(): bool { return __CLASS__ === static::class; diff --git a/Normalizer/MimeMessageNormalizer.php b/Normalizer/MimeMessageNormalizer.php index fea86e863..1a3155818 100644 --- a/Normalizer/MimeMessageNormalizer.php +++ b/Normalizer/MimeMessageNormalizer.php @@ -48,9 +48,6 @@ public function setSerializer(SerializerInterface $serializer) $this->normalizer->setSerializer($serializer); } - /** - * {@inheritdoc} - */ public function normalize(mixed $object, string $format = null, array $context = []): array|string|int|float|bool|\ArrayObject|null { if ($object instanceof Headers) { @@ -72,9 +69,6 @@ public function normalize(mixed $object, string $format = null, array $context = return $this->normalizer->normalize($object, $format, $context); } - /** - * {@inheritdoc} - */ public function denormalize(mixed $data, string $type, string $format = null, array $context = []): mixed { if (Headers::class === $type) { @@ -96,25 +90,16 @@ public function denormalize(mixed $data, string $type, string $format = null, ar return $this->normalizer->denormalize($data, $type, $format, $context); } - /** - * {@inheritdoc} - */ public function supportsNormalization(mixed $data, string $format = null, array $context = []): bool { return $data instanceof Message || $data instanceof Headers || $data instanceof HeaderInterface || $data instanceof Address || $data instanceof AbstractPart; } - /** - * {@inheritdoc} - */ public function supportsDenormalization(mixed $data, string $type, string $format = null, array $context = []): bool { return is_a($type, Message::class, true) || Headers::class === $type || AbstractPart::class === $type; } - /** - * {@inheritdoc} - */ public function hasCacheableSupportsMethod(): bool { return __CLASS__ === static::class; diff --git a/Normalizer/ObjectNormalizer.php b/Normalizer/ObjectNormalizer.php index 263959c1d..5851bfe9a 100644 --- a/Normalizer/ObjectNormalizer.php +++ b/Normalizer/ObjectNormalizer.php @@ -49,17 +49,11 @@ public function __construct(ClassMetadataFactoryInterface $classMetadataFactory }; } - /** - * {@inheritdoc} - */ public function hasCacheableSupportsMethod(): bool { return __CLASS__ === static::class; } - /** - * {@inheritdoc} - */ protected function extractAttributes(object $object, string $format = null, array $context = []): array { if (\stdClass::class === \get_class($object)) { @@ -123,9 +117,6 @@ protected function extractAttributes(object $object, string $format = null, arra return array_keys($attributes); } - /** - * {@inheritdoc} - */ protected function getAttributeValue(object $object, string $attribute, string $format = null, array $context = []): mixed { $cacheKey = \get_class($object); @@ -140,9 +131,6 @@ protected function getAttributeValue(object $object, string $attribute, string $ return $attribute === $this->discriminatorCache[$cacheKey] ? $this->classDiscriminatorResolver->getTypeForMappedObject($object) : $this->propertyAccessor->getValue($object, $attribute); } - /** - * {@inheritdoc} - */ protected function setAttributeValue(object $object, string $attribute, mixed $value, string $format = null, array $context = []) { try { @@ -152,9 +140,6 @@ protected function setAttributeValue(object $object, string $attribute, mixed $v } } - /** - * {@inheritdoc} - */ protected function getAllowedAttributes(string|object $classOrObject, array $context, bool $attributesAsString = false): array|bool { if (false === $allowedAttributes = parent::getAllowedAttributes($classOrObject, $context, $attributesAsString)) { diff --git a/Normalizer/ProblemNormalizer.php b/Normalizer/ProblemNormalizer.php index 7898d991f..59b8427ab 100644 --- a/Normalizer/ProblemNormalizer.php +++ b/Normalizer/ProblemNormalizer.php @@ -40,9 +40,6 @@ public function __construct(bool $debug = false, array $defaultContext = []) $this->defaultContext = $defaultContext + $this->defaultContext; } - /** - * {@inheritdoc} - */ public function normalize(mixed $object, string $format = null, array $context = []): array { if (!$object instanceof FlattenException) { @@ -67,8 +64,6 @@ public function normalize(mixed $object, string $format = null, array $context = } /** - * {@inheritdoc} - * * @param array $context */ public function supportsNormalization(mixed $data, string $format = null /* , array $context = [] */): bool @@ -76,9 +71,6 @@ public function supportsNormalization(mixed $data, string $format = null /* , ar return $data instanceof FlattenException; } - /** - * {@inheritdoc} - */ public function hasCacheableSupportsMethod(): bool { return true; diff --git a/Normalizer/PropertyNormalizer.php b/Normalizer/PropertyNormalizer.php index bd537a1b1..365b9f56c 100644 --- a/Normalizer/PropertyNormalizer.php +++ b/Normalizer/PropertyNormalizer.php @@ -55,8 +55,6 @@ public function __construct(ClassMetadataFactoryInterface $classMetadataFactory } /** - * {@inheritdoc} - * * @param array $context */ public function supportsNormalization(mixed $data, string $format = null /* , array $context = [] */): bool @@ -65,8 +63,6 @@ public function supportsNormalization(mixed $data, string $format = null /* , ar } /** - * {@inheritdoc} - * * @param array $context */ public function supportsDenormalization(mixed $data, string $type, string $format = null /* , array $context = [] */): bool @@ -74,9 +70,6 @@ public function supportsDenormalization(mixed $data, string $type, string $forma return parent::supportsDenormalization($data, $type, $format) && $this->supports($type); } - /** - * {@inheritdoc} - */ public function hasCacheableSupportsMethod(): bool { return __CLASS__ === static::class; @@ -101,9 +94,6 @@ private function supports(string $class): bool return false; } - /** - * {@inheritdoc} - */ protected function isAllowedAttribute(object|string $classOrObject, string $attribute, string $format = null, array $context = []): bool { if (!parent::isAllowedAttribute($classOrObject, $attribute, $format, $context)) { @@ -137,9 +127,6 @@ protected function isAllowedAttribute(object|string $classOrObject, string $attr return false; } - /** - * {@inheritdoc} - */ protected function extractAttributes(object $object, string $format = null, array $context = []): array { $reflectionObject = new \ReflectionObject($object); @@ -158,9 +145,6 @@ protected function extractAttributes(object $object, string $format = null, arra return array_unique($attributes); } - /** - * {@inheritdoc} - */ protected function getAttributeValue(object $object, string $attribute, string $format = null, array $context = []): mixed { try { @@ -187,9 +171,6 @@ protected function getAttributeValue(object $object, string $attribute, string $ return $reflectionProperty->getValue($object); } - /** - * {@inheritdoc} - */ protected function setAttributeValue(object $object, string $attribute, mixed $value, string $format = null, array $context = []) { try { diff --git a/Normalizer/UidNormalizer.php b/Normalizer/UidNormalizer.php index 264ddd801..5ab7427af 100644 --- a/Normalizer/UidNormalizer.php +++ b/Normalizer/UidNormalizer.php @@ -42,8 +42,6 @@ public function __construct(array $defaultContext = []) } /** - * {@inheritdoc} - * * @param AbstractUid $object */ public function normalize(mixed $object, string $format = null, array $context = []): array|string|int|float|bool|\ArrayObject|null @@ -62,17 +60,11 @@ public function normalize(mixed $object, string $format = null, array $context = throw new LogicException(sprintf('The "%s" format is not valid.', $context[self::NORMALIZATION_FORMAT_KEY] ?? $this->defaultContext[self::NORMALIZATION_FORMAT_KEY])); } - /** - * {@inheritdoc} - */ public function supportsNormalization(mixed $data, string $format = null, array $context = []): bool { return $data instanceof AbstractUid; } - /** - * {@inheritdoc} - */ public function denormalize(mixed $data, string $type, string $format = null, array $context = []): mixed { try { @@ -94,9 +86,6 @@ public function denormalize(mixed $data, string $type, string $format = null, ar } } - /** - * {@inheritdoc} - */ public function supportsDenormalization(mixed $data, string $type, string $format = null, array $context = []): bool { if (AbstractUid::class === $type) { @@ -108,9 +97,6 @@ public function supportsDenormalization(mixed $data, string $type, string $forma return is_subclass_of($type, AbstractUid::class, true); } - /** - * {@inheritdoc} - */ public function hasCacheableSupportsMethod(): bool { return __CLASS__ === static::class; diff --git a/Normalizer/UnwrappingDenormalizer.php b/Normalizer/UnwrappingDenormalizer.php index 152cfa080..2eff8b775 100644 --- a/Normalizer/UnwrappingDenormalizer.php +++ b/Normalizer/UnwrappingDenormalizer.php @@ -32,9 +32,6 @@ public function __construct(PropertyAccessorInterface $propertyAccessor = null) $this->propertyAccessor = $propertyAccessor ?? PropertyAccess::createPropertyAccessor(); } - /** - * {@inheritdoc} - */ public function denormalize(mixed $data, string $class, string $format = null, array $context = []): mixed { $propertyPath = $context[self::UNWRAP_PATH]; @@ -51,17 +48,11 @@ public function denormalize(mixed $data, string $class, string $format = null, a return $this->serializer->denormalize($data, $class, $format, $context); } - /** - * {@inheritdoc} - */ public function supportsDenormalization(mixed $data, string $type, string $format = null, array $context = []): bool { return \array_key_exists(self::UNWRAP_PATH, $context) && !isset($context['unwrapped']); } - /** - * {@inheritdoc} - */ public function hasCacheableSupportsMethod(): bool { return $this->serializer instanceof CacheableSupportsMethodInterface && $this->serializer->hasCacheableSupportsMethod(); diff --git a/Serializer.php b/Serializer.php index 5962fdd69..7896129b2 100644 --- a/Serializer.php +++ b/Serializer.php @@ -121,9 +121,6 @@ public function __construct(array $normalizers = [], array $encoders = []) $this->decoder = new ChainDecoder($decoders); } - /** - * {@inheritdoc} - */ final public function serialize(mixed $data, string $format, array $context = []): string { if (!$this->supportsEncoding($format, $context)) { @@ -137,9 +134,6 @@ final public function serialize(mixed $data, string $format, array $context = [] return $this->encode($data, $format, $context); } - /** - * {@inheritdoc} - */ final public function deserialize(mixed $data, string $type, string $format, array $context = []): mixed { if (!$this->supportsDecoding($format, $context)) { @@ -151,9 +145,6 @@ final public function deserialize(mixed $data, string $type, string $format, arr return $this->denormalize($data, $type, $format, $context); } - /** - * {@inheritdoc} - */ public function normalize(mixed $data, string $format = null, array $context = []): array|string|int|float|bool|\ArrayObject|null { // If a normalizer supports the given data, use it @@ -194,8 +185,6 @@ public function normalize(mixed $data, string $format = null, array $context = [ } /** - * {@inheritdoc} - * * @throws NotNormalizableValueException */ public function denormalize(mixed $data, string $type, string $format = null, array $context = []): mixed @@ -238,17 +227,11 @@ public function denormalize(mixed $data, string $type, string $format = null, ar return $normalizer->denormalize($data, $type, $format, $context); } - /** - * {@inheritdoc} - */ public function supportsNormalization(mixed $data, string $format = null, array $context = []): bool { return null !== $this->getNormalizer($data, $format, $context); } - /** - * {@inheritdoc} - */ public function supportsDenormalization(mixed $data, string $type, string $format = null, array $context = []): bool { return isset(self::SCALAR_TYPES[$type]) || null !== $this->getDenormalizer($data, $type, $format, $context); @@ -329,33 +312,21 @@ private function getDenormalizer(mixed $data, string $class, ?string $format, ar return null; } - /** - * {@inheritdoc} - */ final public function encode(mixed $data, string $format, array $context = []): string { return $this->encoder->encode($data, $format, $context); } - /** - * {@inheritdoc} - */ final public function decode(string $data, string $format, array $context = []): mixed { return $this->decoder->decode($data, $format, $context); } - /** - * {@inheritdoc} - */ public function supportsEncoding(string $format, array $context = []): bool { return $this->encoder->supportsEncoding($format, $context); } - /** - * {@inheritdoc} - */ public function supportsDecoding(string $format, array $context = []): bool { return $this->decoder->supportsDecoding($format, $context); diff --git a/Tests/Fixtures/AbstractNormalizerDummy.php b/Tests/Fixtures/AbstractNormalizerDummy.php index afdfdec16..82586062b 100644 --- a/Tests/Fixtures/AbstractNormalizerDummy.php +++ b/Tests/Fixtures/AbstractNormalizerDummy.php @@ -20,39 +20,24 @@ */ class AbstractNormalizerDummy extends AbstractNormalizer { - /** - * {@inheritdoc} - */ public function getAllowedAttributes(string|object $classOrObject, array $context, bool $attributesAsString = false): array|bool { return parent::getAllowedAttributes($classOrObject, $context, $attributesAsString); } - /** - * {@inheritdoc} - */ public function normalize(mixed $object, string $format = null, array $context = []): array|string|int|float|bool|\ArrayObject|null { } - /** - * {@inheritdoc} - */ public function supportsNormalization(mixed $data, string $format = null, array $context = []): bool { return true; } - /** - * {@inheritdoc} - */ public function denormalize(mixed $data, string $type, string $format = null, array $context = []): mixed { } - /** - * {@inheritdoc} - */ public function supportsDenormalization(mixed $data, string $type, string $format = null, array $context = []): bool { return true; diff --git a/Tests/Fixtures/StaticConstructorNormalizer.php b/Tests/Fixtures/StaticConstructorNormalizer.php index 209a07d2c..44a367e92 100644 --- a/Tests/Fixtures/StaticConstructorNormalizer.php +++ b/Tests/Fixtures/StaticConstructorNormalizer.php @@ -18,9 +18,6 @@ */ class StaticConstructorNormalizer extends ObjectNormalizer { - /** - * {@inheritdoc} - */ protected function getConstructor(array &$data, string $class, array &$context, \ReflectionClass $reflectionClass, array|bool $allowedAttributes): ?\ReflectionMethod { if (is_a($class, StaticConstructorDummy::class, true)) { diff --git a/Tests/Normalizer/AbstractObjectNormalizerTest.php b/Tests/Normalizer/AbstractObjectNormalizerTest.php index fe006c187..b34d40d69 100644 --- a/Tests/Normalizer/AbstractObjectNormalizerTest.php +++ b/Tests/Normalizer/AbstractObjectNormalizerTest.php @@ -592,8 +592,6 @@ class ArrayDenormalizerDummy implements DenormalizerInterface, SerializerAwareIn private $serializer; /** - * {@inheritdoc} - * * @throws NotNormalizableValueException */ public function denormalize($data, string $type, string $format = null, array $context = []): mixed @@ -608,18 +606,12 @@ public function denormalize($data, string $type, string $format = null, array $c return $data; } - /** - * {@inheritdoc} - */ public function supportsDenormalization($data, string $type, string $format = null, array $context = []): bool { return str_ends_with($type, '[]') && $this->serializer->supportsDenormalization($data, substr($type, 0, -2), $format, $context); } - /** - * {@inheritdoc} - */ public function setSerializer(SerializerInterface $serializer) { $this->serializer = $serializer; diff --git a/Tests/Normalizer/TestDenormalizer.php b/Tests/Normalizer/TestDenormalizer.php index cef09715d..ee9f2da0e 100644 --- a/Tests/Normalizer/TestDenormalizer.php +++ b/Tests/Normalizer/TestDenormalizer.php @@ -20,16 +20,10 @@ */ class TestDenormalizer implements DenormalizerInterface { - /** - * {@inheritdoc} - */ public function denormalize($data, string $type, string $format = null, array $context = []): mixed { } - /** - * {@inheritdoc} - */ public function supportsDenormalization($data, string $type, string $format = null, array $context = []): bool { return true; diff --git a/Tests/Normalizer/TestNormalizer.php b/Tests/Normalizer/TestNormalizer.php index f3b604bfe..5d941e7a5 100644 --- a/Tests/Normalizer/TestNormalizer.php +++ b/Tests/Normalizer/TestNormalizer.php @@ -20,17 +20,11 @@ */ class TestNormalizer implements NormalizerInterface { - /** - * {@inheritdoc} - */ public function normalize($object, string $format = null, array $context = []): array|string|int|float|bool|\ArrayObject|null { return null; } - /** - * {@inheritdoc} - */ public function supportsNormalization($data, string $format = null, array $context = []): bool { return true; From 3996defa1936d36517c43451cd694f8e24e7d75d Mon Sep 17 00:00:00 2001 From: Antoine Lamirault Date: Tue, 23 Aug 2022 21:46:41 +0200 Subject: [PATCH 055/297] Remove usage of empty function when possible --- Annotation/Groups.php | 2 +- Normalizer/FormErrorNormalizer.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Annotation/Groups.php b/Annotation/Groups.php index 7f377c8fb..2069d678c 100644 --- a/Annotation/Groups.php +++ b/Annotation/Groups.php @@ -37,7 +37,7 @@ public function __construct(string|array $groups) { $this->groups = (array) $groups; - if (empty($this->groups)) { + if (!$this->groups) { throw new InvalidArgumentException(sprintf('Parameter of annotation "%s" cannot be empty.', static::class)); } diff --git a/Normalizer/FormErrorNormalizer.php b/Normalizer/FormErrorNormalizer.php index 0ffa9f072..d6dad602e 100644 --- a/Normalizer/FormErrorNormalizer.php +++ b/Normalizer/FormErrorNormalizer.php @@ -72,7 +72,7 @@ private function convertFormChildrenToArray(FormInterface $data): array 'errors' => $this->convertFormErrorsToArray($child), ]; - if (!empty($child->all())) { + if ($child->all()) { $childData['children'] = $this->convertFormChildrenToArray($child); } From 1e69e2fc852463dd5262189027b9542d7a10688f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois-Xavier=20de=20Guillebon?= Date: Fri, 26 Aug 2022 16:19:22 +0200 Subject: [PATCH 056/297] Replace get_class() calls by ::class --- Extractor/ObjectPropertyListExtractor.php | 2 +- Mapping/ClassDiscriminatorFromClassMetadata.php | 2 +- Mapping/Factory/ClassResolverTrait.php | 2 +- Mapping/Factory/CompiledClassMetadataFactory.php | 4 ++-- Normalizer/AbstractObjectNormalizer.php | 6 +++--- Normalizer/GetSetMethodNormalizer.php | 2 +- Normalizer/MimeMessageNormalizer.php | 2 +- Normalizer/ObjectNormalizer.php | 8 ++++---- Normalizer/PropertyNormalizer.php | 4 ++-- Serializer.php | 2 +- Tests/Debug/TraceableEncoderTest.php | 4 ++-- Tests/Debug/TraceableNormalizerTest.php | 4 ++-- Tests/Extractor/ObjectPropertyListExtractorTest.php | 2 +- Tests/Mapping/Factory/CacheMetadataFactoryTest.php | 2 +- Tests/Normalizer/Features/CallbacksTestTrait.php | 4 ++-- Tests/Normalizer/Features/CircularReferenceTestTrait.php | 8 ++++---- Tests/Normalizer/PropertyNormalizerTest.php | 2 +- 17 files changed, 30 insertions(+), 30 deletions(-) diff --git a/Extractor/ObjectPropertyListExtractor.php b/Extractor/ObjectPropertyListExtractor.php index 0524b0380..9b7344627 100644 --- a/Extractor/ObjectPropertyListExtractor.php +++ b/Extractor/ObjectPropertyListExtractor.php @@ -29,7 +29,7 @@ public function __construct(PropertyListExtractorInterface $propertyListExtracto public function getProperties(object $object, array $context = []): ?array { - $class = $this->objectClassResolver ? ($this->objectClassResolver)($object) : \get_class($object); + $class = $this->objectClassResolver ? ($this->objectClassResolver)($object) : $object::class; return $this->propertyListExtractor->getProperties($class, $context); } diff --git a/Mapping/ClassDiscriminatorFromClassMetadata.php b/Mapping/ClassDiscriminatorFromClassMetadata.php index 8b9b02896..5359408dd 100644 --- a/Mapping/ClassDiscriminatorFromClassMetadata.php +++ b/Mapping/ClassDiscriminatorFromClassMetadata.php @@ -48,7 +48,7 @@ public function getMappingForMappedObject(object|string $object): ?ClassDiscrimi } } - $cacheKey = \is_object($object) ? \get_class($object) : $object; + $cacheKey = \is_object($object) ? $object::class : $object; if (!\array_key_exists($cacheKey, $this->mappingForMappedObjectCache)) { $this->mappingForMappedObjectCache[$cacheKey] = $this->resolveMappingForMappedObject($object); } diff --git a/Mapping/Factory/ClassResolverTrait.php b/Mapping/Factory/ClassResolverTrait.php index 05ce80922..7af722d55 100644 --- a/Mapping/Factory/ClassResolverTrait.php +++ b/Mapping/Factory/ClassResolverTrait.php @@ -37,6 +37,6 @@ private function getClass(object|string $value): string return ltrim($value, '\\'); } - return \get_class($value); + return $value::class; } } diff --git a/Mapping/Factory/CompiledClassMetadataFactory.php b/Mapping/Factory/CompiledClassMetadataFactory.php index 5bac97581..670a93587 100644 --- a/Mapping/Factory/CompiledClassMetadataFactory.php +++ b/Mapping/Factory/CompiledClassMetadataFactory.php @@ -44,7 +44,7 @@ public function __construct(string $compiledClassMetadataFile, ClassMetadataFact public function getMetadataFor(string|object $value): ClassMetadataInterface { - $className = \is_object($value) ? \get_class($value) : $value; + $className = \is_object($value) ? $value::class : $value; if (!isset($this->compiledClassMetadata[$className])) { return $this->classMetadataFactory->getMetadataFor($value); @@ -69,7 +69,7 @@ public function getMetadataFor(string|object $value): ClassMetadataInterface public function hasMetadataFor(mixed $value): bool { - $className = \is_object($value) ? \get_class($value) : $value; + $className = \is_object($value) ? $value::class : $value; return isset($this->compiledClassMetadata[$className]) || $this->classMetadataFactory->hasMetadataFor($value); } diff --git a/Normalizer/AbstractObjectNormalizer.php b/Normalizer/AbstractObjectNormalizer.php index 1a499a049..59704c6d6 100644 --- a/Normalizer/AbstractObjectNormalizer.php +++ b/Normalizer/AbstractObjectNormalizer.php @@ -156,7 +156,7 @@ public function normalize(mixed $object, string $format = null, array $context = $data = []; $stack = []; $attributes = $this->getAttributes($object, $format, $context); - $class = $this->objectClassResolver ? ($this->objectClassResolver)($object) : \get_class($object); + $class = $this->objectClassResolver ? ($this->objectClassResolver)($object) : $object::class; $attributesMetadata = $this->classMetadataFactory?->getMetadataFor($class)->getAttributesMetadata(); if (isset($context[self::MAX_DEPTH_HANDLER])) { $maxDepthHandler = $context[self::MAX_DEPTH_HANDLER]; @@ -248,7 +248,7 @@ protected function instantiateObject(array &$data, string $class, array &$contex */ protected function getAttributes(object $object, ?string $format, array $context): array { - $class = $this->objectClassResolver ? ($this->objectClassResolver)($object) : \get_class($object); + $class = $this->objectClassResolver ? ($this->objectClassResolver)($object) : $object::class; $key = $class.'-'.$context['cache_key']; if (isset($this->attributesCache[$key])) { @@ -318,7 +318,7 @@ public function denormalize(mixed $data, string $type, string $format = null, ar $reflectionClass = new \ReflectionClass($type); $object = $this->instantiateObject($normalizedData, $type, $context, $reflectionClass, $allowedAttributes, $format); - $resolvedClass = $this->objectClassResolver ? ($this->objectClassResolver)($object) : \get_class($object); + $resolvedClass = $this->objectClassResolver ? ($this->objectClassResolver)($object) : $object::class; foreach ($normalizedData as $attribute => $value) { if ($this->nameConverter) { diff --git a/Normalizer/GetSetMethodNormalizer.php b/Normalizer/GetSetMethodNormalizer.php index a0b6ee780..08ccfd1bd 100644 --- a/Normalizer/GetSetMethodNormalizer.php +++ b/Normalizer/GetSetMethodNormalizer.php @@ -137,7 +137,7 @@ protected function getAttributeValue(object $object, string $attribute, string $ protected function setAttributeValue(object $object, string $attribute, mixed $value, string $format = null, array $context = []) { $setter = 'set'.ucfirst($attribute); - $key = \get_class($object).':'.$setter; + $key = $object::class.':'.$setter; if (!isset(self::$setterAccessibleCache[$key])) { self::$setterAccessibleCache[$key] = \is_callable([$object, $setter]) && !(new \ReflectionMethod($object, $setter))->isStatic(); diff --git a/Normalizer/MimeMessageNormalizer.php b/Normalizer/MimeMessageNormalizer.php index 1a3155818..f112b3e2c 100644 --- a/Normalizer/MimeMessageNormalizer.php +++ b/Normalizer/MimeMessageNormalizer.php @@ -61,7 +61,7 @@ public function normalize(mixed $object, string $format = null, array $context = if ($object instanceof AbstractPart) { $ret = $this->normalizer->normalize($object, $format, $context); - $ret['class'] = \get_class($object); + $ret['class'] = $object::class; return $ret; } diff --git a/Normalizer/ObjectNormalizer.php b/Normalizer/ObjectNormalizer.php index 5851bfe9a..b949193e4 100644 --- a/Normalizer/ObjectNormalizer.php +++ b/Normalizer/ObjectNormalizer.php @@ -45,7 +45,7 @@ public function __construct(ClassMetadataFactoryInterface $classMetadataFactory $this->propertyAccessor = $propertyAccessor ?: PropertyAccess::createPropertyAccessor(); $this->objectClassResolver = $objectClassResolver ?? function ($class) { - return \is_object($class) ? \get_class($class) : $class; + return \is_object($class) ? $class::class : $class; }; } @@ -56,7 +56,7 @@ public function hasCacheableSupportsMethod(): bool protected function extractAttributes(object $object, string $format = null, array $context = []): array { - if (\stdClass::class === \get_class($object)) { + if (\stdClass::class === $object::class) { return array_keys((array) $object); } @@ -119,7 +119,7 @@ protected function extractAttributes(object $object, string $format = null, arra protected function getAttributeValue(object $object, string $attribute, string $format = null, array $context = []): mixed { - $cacheKey = \get_class($object); + $cacheKey = $object::class; if (!\array_key_exists($cacheKey, $this->discriminatorCache)) { $this->discriminatorCache[$cacheKey] = null; if (null !== $this->classDiscriminatorResolver) { @@ -147,7 +147,7 @@ protected function getAllowedAttributes(string|object $classOrObject, array $con } if (null !== $this->classDiscriminatorResolver) { - $class = \is_object($classOrObject) ? \get_class($classOrObject) : $classOrObject; + $class = \is_object($classOrObject) ? $classOrObject::class : $classOrObject; if (null !== $discriminatorMapping = $this->classDiscriminatorResolver->getMappingForMappedObject($classOrObject)) { $allowedAttributes[] = $attributesAsString ? $discriminatorMapping->getTypeProperty() : new AttributeMetadata($discriminatorMapping->getTypeProperty()); } diff --git a/Normalizer/PropertyNormalizer.php b/Normalizer/PropertyNormalizer.php index 365b9f56c..144f1c448 100644 --- a/Normalizer/PropertyNormalizer.php +++ b/Normalizer/PropertyNormalizer.php @@ -59,7 +59,7 @@ public function __construct(ClassMetadataFactoryInterface $classMetadataFactory */ public function supportsNormalization(mixed $data, string $format = null /* , array $context = [] */): bool { - return parent::supportsNormalization($data, $format) && $this->supports(\get_class($data)); + return parent::supportsNormalization($data, $format) && $this->supports($data::class); } /** @@ -164,7 +164,7 @@ protected function getAttributeValue(object $object, string $attribute, string $ || ($reflectionProperty->isProtected() && !\array_key_exists("\0*\0{$reflectionProperty->name}", $propertyValues)) || ($reflectionProperty->isPrivate() && !\array_key_exists("\0{$reflectionProperty->class}\0{$reflectionProperty->name}", $propertyValues)) ) { - throw new UninitializedPropertyException(sprintf('The property "%s::$%s" is not initialized.', \get_class($object), $reflectionProperty->name)); + throw new UninitializedPropertyException(sprintf('The property "%s::$%s" is not initialized.', $object::class, $reflectionProperty->name)); } } diff --git a/Serializer.php b/Serializer.php index 7896129b2..85a3ac5b5 100644 --- a/Serializer.php +++ b/Serializer.php @@ -246,7 +246,7 @@ public function supportsDenormalization(mixed $data, string $type, string $forma */ private function getNormalizer(mixed $data, ?string $format, array $context): ?NormalizerInterface { - $type = \is_object($data) ? \get_class($data) : 'native-'.\gettype($data); + $type = \is_object($data) ? $data::class : 'native-'.\gettype($data); if (!isset($this->normalizerCache[$format][$type])) { $this->normalizerCache[$format][$type] = []; diff --git a/Tests/Debug/TraceableEncoderTest.php b/Tests/Debug/TraceableEncoderTest.php index aa2393bbe..9c1fddc7d 100644 --- a/Tests/Debug/TraceableEncoderTest.php +++ b/Tests/Debug/TraceableEncoderTest.php @@ -50,11 +50,11 @@ public function testCollectEncodingData() $dataCollector ->expects($this->once()) ->method('collectEncoding') - ->with($this->isType('string'), \get_class($encoder), $this->isType('float')); + ->with($this->isType('string'), $encoder::class, $this->isType('float')); $dataCollector ->expects($this->once()) ->method('collectDecoding') - ->with($this->isType('string'), \get_class($decoder), $this->isType('float')); + ->with($this->isType('string'), $decoder::class, $this->isType('float')); (new TraceableEncoder($encoder, $dataCollector))->encode('data', 'format', [TraceableSerializer::DEBUG_TRACE_ID => 'debug']); (new TraceableEncoder($decoder, $dataCollector))->decode('data', 'format', [TraceableSerializer::DEBUG_TRACE_ID => 'debug']); diff --git a/Tests/Debug/TraceableNormalizerTest.php b/Tests/Debug/TraceableNormalizerTest.php index dc99a03c0..481639fb9 100644 --- a/Tests/Debug/TraceableNormalizerTest.php +++ b/Tests/Debug/TraceableNormalizerTest.php @@ -50,11 +50,11 @@ public function testCollectNormalizationData() $dataCollector ->expects($this->once()) ->method('collectNormalization') - ->with($this->isType('string'), \get_class($normalizer), $this->isType('float')); + ->with($this->isType('string'), $normalizer::class, $this->isType('float')); $dataCollector ->expects($this->once()) ->method('collectDenormalization') - ->with($this->isType('string'), \get_class($denormalizer), $this->isType('float')); + ->with($this->isType('string'), $denormalizer::class, $this->isType('float')); (new TraceableNormalizer($normalizer, $dataCollector))->normalize('data', 'format', [TraceableSerializer::DEBUG_TRACE_ID => 'debug']); (new TraceableNormalizer($denormalizer, $dataCollector))->denormalize('data', 'type', 'format', [TraceableSerializer::DEBUG_TRACE_ID => 'debug']); diff --git a/Tests/Extractor/ObjectPropertyListExtractorTest.php b/Tests/Extractor/ObjectPropertyListExtractorTest.php index 65e776684..d639bf733 100644 --- a/Tests/Extractor/ObjectPropertyListExtractorTest.php +++ b/Tests/Extractor/ObjectPropertyListExtractorTest.php @@ -26,7 +26,7 @@ public function testGetPropertiesWithoutObjectClassResolver() $propertyListExtractor = $this->createMock(PropertyListExtractorInterface::class); $propertyListExtractor->expects($this->once()) ->method('getProperties') - ->with(\get_class($object), $context) + ->with($object::class, $context) ->willReturn($properties); $this->assertSame( diff --git a/Tests/Mapping/Factory/CacheMetadataFactoryTest.php b/Tests/Mapping/Factory/CacheMetadataFactoryTest.php index 74fc10069..9525ca605 100644 --- a/Tests/Mapping/Factory/CacheMetadataFactoryTest.php +++ b/Tests/Mapping/Factory/CacheMetadataFactoryTest.php @@ -70,7 +70,7 @@ public function testAnonymousClass() $anonymousObject = new class() { }; - $metadata = new ClassMetadata(\get_class($anonymousObject)); + $metadata = new ClassMetadata($anonymousObject::class); $decorated = $this->createMock(ClassMetadataFactoryInterface::class); $decorated ->expects($this->once()) diff --git a/Tests/Normalizer/Features/CallbacksTestTrait.php b/Tests/Normalizer/Features/CallbacksTestTrait.php index db7b226c3..a8ef81bb6 100644 --- a/Tests/Normalizer/Features/CallbacksTestTrait.php +++ b/Tests/Normalizer/Features/CallbacksTestTrait.php @@ -107,8 +107,8 @@ public function __construct() } }; - $obj = $normalizer->denormalize(['foo' => $valueBar], \get_class($objWithNoConstructorArgument), 'any', ['callbacks' => $callbacks]); - $this->assertInstanceof(\get_class($objWithNoConstructorArgument), $obj); + $obj = $normalizer->denormalize(['foo' => $valueBar], $objWithNoConstructorArgument::class, 'any', ['callbacks' => $callbacks]); + $this->assertInstanceof($objWithNoConstructorArgument::class, $obj); $this->assertEquals($result->getBar(), $obj->getBar()); } diff --git a/Tests/Normalizer/Features/CircularReferenceTestTrait.php b/Tests/Normalizer/Features/CircularReferenceTestTrait.php index 1996e80e9..1407c8242 100644 --- a/Tests/Normalizer/Features/CircularReferenceTestTrait.php +++ b/Tests/Normalizer/Features/CircularReferenceTestTrait.php @@ -42,7 +42,7 @@ public function testUnableToNormalizeCircularReference(array $defaultContext, ar $obj = $this->getSelfReferencingModel(); $this->expectException(CircularReferenceException::class); - $this->expectExceptionMessage(sprintf('A circular reference has been detected when serializing the object of class "%s" (configured limit: %d).', \get_class($obj), $expectedLimit)); + $this->expectExceptionMessage(sprintf('A circular reference has been detected when serializing the object of class "%s" (configured limit: %d).', $obj::class, $expectedLimit)); $normalizer->normalize($obj, null, $context); } @@ -51,15 +51,15 @@ public function testCircularReferenceHandler() $normalizer = $this->getNormalizerForCircularReference([]); $obj = $this->getSelfReferencingModel(); - $expected = ['me' => \get_class($obj)]; + $expected = ['me' => $obj::class]; $context = [ 'circular_reference_handler' => function ($actualObj, string $format, array $context) use ($obj) { - $this->assertInstanceOf(\get_class($obj), $actualObj); + $this->assertInstanceOf($obj::class, $actualObj); $this->assertSame('test', $format); $this->assertArrayHasKey('foo', $context); - return \get_class($actualObj); + return $actualObj::class; }, 'foo' => 'bar', ]; diff --git a/Tests/Normalizer/PropertyNormalizerTest.php b/Tests/Normalizer/PropertyNormalizerTest.php index 2b9a111ed..f1401158f 100644 --- a/Tests/Normalizer/PropertyNormalizerTest.php +++ b/Tests/Normalizer/PropertyNormalizerTest.php @@ -453,7 +453,7 @@ public function testMultiDimensionObject() RootDummy::class, 'any' ); - $this->assertEquals(\get_class($root), RootDummy::class); + $this->assertEquals($root::class, RootDummy::class); // children (two dimension array) $this->assertCount(1, $root->children); From 78703a626838837a32ac7031a4b662d68820531b Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Fri, 2 Sep 2022 16:55:07 +0200 Subject: [PATCH 057/297] CS fixes --- Context/Normalizer/AbstractNormalizerContextBuilder.php | 2 +- Normalizer/AbstractNormalizer.php | 4 ++-- Normalizer/GetSetMethodNormalizer.php | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Context/Normalizer/AbstractNormalizerContextBuilder.php b/Context/Normalizer/AbstractNormalizerContextBuilder.php index f494f060a..670543540 100644 --- a/Context/Normalizer/AbstractNormalizerContextBuilder.php +++ b/Context/Normalizer/AbstractNormalizerContextBuilder.php @@ -158,7 +158,7 @@ public function withCircularReferenceHandler(?callable $circularReferenceHandler * Note: The behaviour for nested structures is different from ATTRIBUTES * for historical reason. Aligning the behaviour would be a BC break. * - * @param list|null $attributes + * @param list|null $ignoredAttributes */ public function withIgnoredAttributes(?array $ignoredAttributes): static { diff --git a/Normalizer/AbstractNormalizer.php b/Normalizer/AbstractNormalizer.php index cb6890d3c..12c778cb8 100644 --- a/Normalizer/AbstractNormalizer.php +++ b/Normalizer/AbstractNormalizer.php @@ -205,9 +205,9 @@ protected function handleCircularReference(object $object, string $format = null * * @param bool $attributesAsString If false, return an array of {@link AttributeMetadataInterface} * - * @throws LogicException if the 'allow_extra_attributes' context variable is false and no class metadata factory is provided - * * @return string[]|AttributeMetadataInterface[]|bool + * + * @throws LogicException if the 'allow_extra_attributes' context variable is false and no class metadata factory is provided */ protected function getAllowedAttributes(string|object $classOrObject, array $context, bool $attributesAsString = false) { diff --git a/Normalizer/GetSetMethodNormalizer.php b/Normalizer/GetSetMethodNormalizer.php index 08ccfd1bd..ba39b41ff 100644 --- a/Normalizer/GetSetMethodNormalizer.php +++ b/Normalizer/GetSetMethodNormalizer.php @@ -41,7 +41,7 @@ class GetSetMethodNormalizer extends AbstractObjectNormalizer */ public function supportsNormalization(mixed $data, string $format = null /* , array $context = [] */): bool { - return parent::supportsNormalization($data, $format) && $this->supports(\get_class($data)); + return parent::supportsNormalization($data, $format) && $this->supports($data::class); } /** From fda0885a73d7521b19b839d76acbe646d763ad9f Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Mon, 12 Sep 2022 14:32:45 +0200 Subject: [PATCH 058/297] Remove all "nullable-by-default-value" setters --- CHANGELOG.md | 9 ++++++--- Mapping/AttributeMetadata.php | 4 ++++ Mapping/AttributeMetadataInterface.php | 2 +- Mapping/ClassMetadata.php | 3 +++ Mapping/ClassMetadataInterface.php | 2 +- 5 files changed, 15 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f2ced0039..9175ac1e4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,9 +4,12 @@ CHANGELOG 6.2 --- -* Add support for constructor promoted properties to `Context` attribute -* Add context option `PropertyNormalizer::NORMALIZE_VISIBILITY` with bitmask flags `PropertyNormalizer::NORMALIZE_PUBLIC`, `PropertyNormalizer::NORMALIZE_PROTECTED`, `PropertyNormalizer::NORMALIZE_PRIVATE` -* Add method `withNormalizeVisibility` to `PropertyNormalizerContextBuilder` + * Add support for constructor promoted properties to `Context` attribute + * Add context option `PropertyNormalizer::NORMALIZE_VISIBILITY` with bitmask flags `PropertyNormalizer::NORMALIZE_PUBLIC`, `PropertyNormalizer::NORMALIZE_PROTECTED`, `PropertyNormalizer::NORMALIZE_PRIVATE` + * Add method `withNormalizeVisibility` to `PropertyNormalizerContextBuilder` + * Deprecate calling `AttributeMetadata::setSerializedName()`, `ClassMetadata::setClassDiscriminatorMapping()` without arguments + * Change the signature of `AttributeMetadataInterface::setSerializedName()` to `setSerializedName(?string)` + * Change the signature of `ClassMetadataInterface::setClassDiscriminatorMapping()` to `setClassDiscriminatorMapping(?ClassDiscriminatorMapping)` 6.1 --- diff --git a/Mapping/AttributeMetadata.php b/Mapping/AttributeMetadata.php index 7c644c272..180909b9f 100644 --- a/Mapping/AttributeMetadata.php +++ b/Mapping/AttributeMetadata.php @@ -109,6 +109,10 @@ public function getMaxDepth(): ?int public function setSerializedName(string $serializedName = null) { + if (1 > \func_num_args()) { + trigger_deprecation('symfony/serializer', '6.2', 'Calling "%s()" without any arguments is deprecated, pass null explicitly instead.', __METHOD__); + } + $this->serializedName = $serializedName; } diff --git a/Mapping/AttributeMetadataInterface.php b/Mapping/AttributeMetadataInterface.php index 492beb1b4..6a215e0ea 100644 --- a/Mapping/AttributeMetadataInterface.php +++ b/Mapping/AttributeMetadataInterface.php @@ -52,7 +52,7 @@ public function getMaxDepth(): ?int; /** * Sets the serialization name for this attribute. */ - public function setSerializedName(string $serializedName = null); + public function setSerializedName(?string $serializedName); /** * Gets the serialization name for this attribute. diff --git a/Mapping/ClassMetadata.php b/Mapping/ClassMetadata.php index d27ee31a3..cb166b212 100644 --- a/Mapping/ClassMetadata.php +++ b/Mapping/ClassMetadata.php @@ -97,6 +97,9 @@ public function getClassDiscriminatorMapping(): ?ClassDiscriminatorMapping public function setClassDiscriminatorMapping(ClassDiscriminatorMapping $mapping = null) { + if (1 > \func_num_args()) { + trigger_deprecation('symfony/serializer', '6.2', 'Calling "%s()" without any arguments is deprecated, pass null explicitly instead.', __METHOD__); + } $this->classDiscriminatorMapping = $mapping; } diff --git a/Mapping/ClassMetadataInterface.php b/Mapping/ClassMetadataInterface.php index e0a445d6a..14ab4bb41 100644 --- a/Mapping/ClassMetadataInterface.php +++ b/Mapping/ClassMetadataInterface.php @@ -53,5 +53,5 @@ public function getReflectionClass(): \ReflectionClass; public function getClassDiscriminatorMapping(): ?ClassDiscriminatorMapping; - public function setClassDiscriminatorMapping(ClassDiscriminatorMapping $mapping = null); + public function setClassDiscriminatorMapping(?ClassDiscriminatorMapping $mapping); } From 9caf30e24d07f5dce97b2276505095746aea6b77 Mon Sep 17 00:00:00 2001 From: Christian Flothmann Date: Sat, 17 Sep 2022 09:57:35 +0200 Subject: [PATCH 059/297] remove no longer needed PHP version requirements from tests --- Tests/SerializerTest.php | 6 ------ 1 file changed, 6 deletions(-) diff --git a/Tests/SerializerTest.php b/Tests/SerializerTest.php index 9fb1519b8..bbc3d81cb 100644 --- a/Tests/SerializerTest.php +++ b/Tests/SerializerTest.php @@ -1169,9 +1169,6 @@ public function testCollectDenormalizationErrorsWithConstructor(?ClassMetadataFa $this->assertSame($expected, $exceptionsAsArray); } - /** - * @requires PHP 8.1 - */ public function testCollectDenormalizationErrorsWithEnumConstructor() { $serializer = new Serializer( @@ -1209,9 +1206,6 @@ public function testCollectDenormalizationErrorsWithEnumConstructor() $this->assertSame($expected, $exceptionsAsArray); } - /** - * @requires PHP 8.1 - */ public function testNoCollectDenormalizationErrorsWithWrongEnum() { $serializer = new Serializer( From 8123526d83192ed6981ae56d7204544e2f62de3e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A9vin=20Dunglas?= Date: Sat, 17 Sep 2022 15:14:51 +0200 Subject: [PATCH 060/297] [Serializer] Improve performance of AbstractObjectDenormalizer with XML/CSV --- Normalizer/AbstractObjectNormalizer.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Normalizer/AbstractObjectNormalizer.php b/Normalizer/AbstractObjectNormalizer.php index 59704c6d6..febe70bb4 100644 --- a/Normalizer/AbstractObjectNormalizer.php +++ b/Normalizer/AbstractObjectNormalizer.php @@ -451,7 +451,7 @@ private function validateAndDenormalize(array $types, string $currentClass, stri } break; case Type::BUILTIN_TYPE_INT: - if (ctype_digit($data) || '-' === $data[0] && ctype_digit(substr($data, 1))) { + if (ctype_digit('-' === $data[0] ? substr($data, 1) : $data)) { $data = (int) $data; } else { throw NotNormalizableValueException::createForUnexpectedDataType(sprintf('The type of the "%s" attribute for class "%s" must be int ("%s" given).', $attribute, $currentClass, $data), $data, [Type::BUILTIN_TYPE_INT], $context['deserialization_path'] ?? null); From 7c17b69a26db50cdc6e41b9ff885b2aa8151e0db Mon Sep 17 00:00:00 2001 From: tigitz Date: Wed, 21 Sep 2022 21:07:39 +0200 Subject: [PATCH 061/297] Convert switch cases to match expression --- Normalizer/UidNormalizer.php | 19 +++++++------------ 1 file changed, 7 insertions(+), 12 deletions(-) diff --git a/Normalizer/UidNormalizer.php b/Normalizer/UidNormalizer.php index 5ab7427af..ea86f3754 100644 --- a/Normalizer/UidNormalizer.php +++ b/Normalizer/UidNormalizer.php @@ -46,18 +46,13 @@ public function __construct(array $defaultContext = []) */ public function normalize(mixed $object, string $format = null, array $context = []): array|string|int|float|bool|\ArrayObject|null { - switch ($context[self::NORMALIZATION_FORMAT_KEY] ?? $this->defaultContext[self::NORMALIZATION_FORMAT_KEY]) { - case self::NORMALIZATION_FORMAT_CANONICAL: - return (string) $object; - case self::NORMALIZATION_FORMAT_BASE58: - return $object->toBase58(); - case self::NORMALIZATION_FORMAT_BASE32: - return $object->toBase32(); - case self::NORMALIZATION_FORMAT_RFC4122: - return $object->toRfc4122(); - } - - throw new LogicException(sprintf('The "%s" format is not valid.', $context[self::NORMALIZATION_FORMAT_KEY] ?? $this->defaultContext[self::NORMALIZATION_FORMAT_KEY])); + return match ($context[self::NORMALIZATION_FORMAT_KEY] ?? $this->defaultContext[self::NORMALIZATION_FORMAT_KEY]) { + self::NORMALIZATION_FORMAT_CANONICAL => (string) $object, + self::NORMALIZATION_FORMAT_BASE58 => $object->toBase58(), + self::NORMALIZATION_FORMAT_BASE32 => $object->toBase32(), + self::NORMALIZATION_FORMAT_RFC4122 => $object->toRfc4122(), + default => throw new LogicException(sprintf('The "%s" format is not valid.', $context[self::NORMALIZATION_FORMAT_KEY] ?? $this->defaultContext[self::NORMALIZATION_FORMAT_KEY])), + }; } public function supportsNormalization(mixed $data, string $format = null, array $context = []): bool From a48fc79d15491224d7d7abbb0f4c635052d8171f Mon Sep 17 00:00:00 2001 From: Fabien Potencier Date: Wed, 31 Aug 2022 08:44:02 +0200 Subject: [PATCH 062/297] [Mime] Simplify adding Parts to an Email --- Normalizer/MimeMessageNormalizer.php | 1 + 1 file changed, 1 insertion(+) diff --git a/Normalizer/MimeMessageNormalizer.php b/Normalizer/MimeMessageNormalizer.php index f112b3e2c..5f599ba65 100644 --- a/Normalizer/MimeMessageNormalizer.php +++ b/Normalizer/MimeMessageNormalizer.php @@ -62,6 +62,7 @@ public function normalize(mixed $object, string $format = null, array $context = if ($object instanceof AbstractPart) { $ret = $this->normalizer->normalize($object, $format, $context); $ret['class'] = $object::class; + unset($ret['seekable'], $ret['cid']); return $ret; } From 29673ae57ff33ae058f57cc7aeb59ff04f796052 Mon Sep 17 00:00:00 2001 From: David Buchmann Date: Wed, 10 Aug 2022 17:09:47 +0200 Subject: [PATCH 063/297] [Serializer] Add context option to configure the indentation of nested nodes for YamlEncoder --- CHANGELOG.md | 1 + Context/Encoder/YamlEncoderContextBuilder.php | 4 ++++ Encoder/JsonEncode.php | 3 +++ Encoder/YamlEncoder.php | 16 +++++++++++++++- Tests/Encoder/YamlEncoderTest.php | 15 ++++++++++++--- 5 files changed, 35 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9175ac1e4..21cbb4256 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ CHANGELOG * Deprecate calling `AttributeMetadata::setSerializedName()`, `ClassMetadata::setClassDiscriminatorMapping()` without arguments * Change the signature of `AttributeMetadataInterface::setSerializedName()` to `setSerializedName(?string)` * Change the signature of `ClassMetadataInterface::setClassDiscriminatorMapping()` to `setClassDiscriminatorMapping(?ClassDiscriminatorMapping)` + * Add option YamlEncoder::YAML_INDENTATION to YamlEncoder constructor options to configure additional indentation for each level of nesting. This allows configuring indentation in the service configuration. 6.1 --- diff --git a/Context/Encoder/YamlEncoderContextBuilder.php b/Context/Encoder/YamlEncoderContextBuilder.php index 81f6ad90d..8bff9de93 100644 --- a/Context/Encoder/YamlEncoderContextBuilder.php +++ b/Context/Encoder/YamlEncoderContextBuilder.php @@ -18,6 +18,10 @@ /** * A helper providing autocompletion for available YamlEncoder options. * + * Note that the "indentation" setting is not offered in this builder because + * it can only be set during the construction of the YamlEncoder, but not per + * call. + * * @author Mathias Arlaud */ final class YamlEncoderContextBuilder implements ContextBuilderInterface diff --git a/Encoder/JsonEncode.php b/Encoder/JsonEncode.php index 49117e81a..2f7c81575 100644 --- a/Encoder/JsonEncode.php +++ b/Encoder/JsonEncode.php @@ -20,6 +20,9 @@ */ class JsonEncode implements EncoderInterface { + /** + * Configure the JSON flags bitmask. + */ public const OPTIONS = 'json_encode_options'; private $defaultContext = [ diff --git a/Encoder/YamlEncoder.php b/Encoder/YamlEncoder.php index 7addd10bb..8d559a57b 100644 --- a/Encoder/YamlEncoder.php +++ b/Encoder/YamlEncoder.php @@ -28,7 +28,17 @@ class YamlEncoder implements EncoderInterface, DecoderInterface public const PRESERVE_EMPTY_OBJECTS = 'preserve_empty_objects'; + /** + * Override the amount of spaces to use for indentation of nested nodes. + * + * This option only works in the constructor, not in calls to `encode`. + */ + public const YAML_INDENTATION = 'yaml_indentation'; + public const YAML_INLINE = 'yaml_inline'; + /** + * Initial indentation for root element. + */ public const YAML_INDENT = 'yaml_indent'; public const YAML_FLAGS = 'yaml_flags'; @@ -46,8 +56,12 @@ public function __construct(Dumper $dumper = null, Parser $parser = null, array throw new RuntimeException('The YamlEncoder class requires the "Yaml" component. Install "symfony/yaml" to use it.'); } - $this->dumper = $dumper ?? new Dumper(); + if (!$dumper) { + $dumper = \array_key_exists(self::YAML_INDENTATION, $defaultContext) ? new Dumper($defaultContext[self::YAML_INDENTATION]) : new Dumper(); + } + $this->dumper = $dumper; $this->parser = $parser ?? new Parser(); + unset($defaultContext[self::YAML_INDENTATION]); $this->defaultContext = array_merge($this->defaultContext, $defaultContext); } diff --git a/Tests/Encoder/YamlEncoderTest.php b/Tests/Encoder/YamlEncoderTest.php index 708d3dc62..33ee49f5d 100644 --- a/Tests/Encoder/YamlEncoderTest.php +++ b/Tests/Encoder/YamlEncoderTest.php @@ -13,8 +13,6 @@ use PHPUnit\Framework\TestCase; use Symfony\Component\Serializer\Encoder\YamlEncoder; -use Symfony\Component\Yaml\Dumper; -use Symfony\Component\Yaml\Parser; use Symfony\Component\Yaml\Yaml; /** @@ -58,9 +56,20 @@ public function testSupportsDecoding() $this->assertFalse($encoder->supportsDecoding('json')); } + public function testIndentation() + { + $encoder = new YamlEncoder(null, null, [YamlEncoder::YAML_INLINE => 100, YamlEncoder::YAML_INDENTATION => 7]); + + $expected = <<<'END' +foo: + bar: baz +END; + $this->assertSame($expected."\n", $encoder->encode(['foo' => ['bar' => 'baz']], 'yaml')); + } + public function testContext() { - $encoder = new YamlEncoder(new Dumper(), new Parser(), [YamlEncoder::YAML_INLINE => 1, YamlEncoder::YAML_INDENT => 4, YamlEncoder::YAML_FLAGS => Yaml::DUMP_OBJECT | Yaml::PARSE_OBJECT]); + $encoder = new YamlEncoder(null, null, [YamlEncoder::YAML_INLINE => 1, YamlEncoder::YAML_INDENT => 4, YamlEncoder::YAML_FLAGS => Yaml::DUMP_OBJECT | Yaml::PARSE_OBJECT]); $obj = new \stdClass(); $obj->bar = 2; From bf2cb074de0cbd5a9b8bc6b23c8067de1c9ca6e3 Mon Sep 17 00:00:00 2001 From: Mathias Arlaud Date: Fri, 9 Sep 2022 10:10:01 +0200 Subject: [PATCH 064/297] [Serializer] fixed traceable decoration priorities --- DependencyInjection/SerializerPass.php | 4 ++-- Tests/DependencyInjection/SerializerPassTest.php | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/DependencyInjection/SerializerPass.php b/DependencyInjection/SerializerPass.php index 00bd0eea0..930462175 100644 --- a/DependencyInjection/SerializerPass.php +++ b/DependencyInjection/SerializerPass.php @@ -44,7 +44,7 @@ public function process(ContainerBuilder $container) if ($container->getParameter('kernel.debug') && $container->hasDefinition('serializer.data_collector')) { foreach (array_keys($normalizers) as $normalizer) { $container->register('debug.'.$normalizer, TraceableNormalizer::class) - ->setDecoratedService($normalizer, null, 255) + ->setDecoratedService($normalizer) ->setArguments([new Reference('debug.'.$normalizer.'.inner'), new Reference('serializer.data_collector')]); } } @@ -59,7 +59,7 @@ public function process(ContainerBuilder $container) if ($container->getParameter('kernel.debug') && $container->hasDefinition('serializer.data_collector')) { foreach (array_keys($encoders) as $encoder) { $container->register('debug.'.$encoder, TraceableEncoder::class) - ->setDecoratedService($encoder, null, 255) + ->setDecoratedService($encoder) ->setArguments([new Reference('debug.'.$encoder.'.inner'), new Reference('serializer.data_collector')]); } } diff --git a/Tests/DependencyInjection/SerializerPassTest.php b/Tests/DependencyInjection/SerializerPassTest.php index 92c258a82..abb1ade96 100644 --- a/Tests/DependencyInjection/SerializerPassTest.php +++ b/Tests/DependencyInjection/SerializerPassTest.php @@ -108,12 +108,12 @@ public function testNormalizersAndEncodersAreDecoredAndOrderedWhenCollectingData $traceableEncoderDefinition = $container->getDefinition('debug.e'); $this->assertEquals(TraceableNormalizer::class, $traceableNormalizerDefinition->getClass()); - $this->assertEquals(['n', null, 255], $traceableNormalizerDefinition->getDecoratedService()); + $this->assertEquals(['n', null, 0], $traceableNormalizerDefinition->getDecoratedService()); $this->assertEquals(new Reference('debug.n.inner'), $traceableNormalizerDefinition->getArgument(0)); $this->assertEquals(new Reference('serializer.data_collector'), $traceableNormalizerDefinition->getArgument(1)); $this->assertEquals(TraceableEncoder::class, $traceableEncoderDefinition->getClass()); - $this->assertEquals(['e', null, 255], $traceableEncoderDefinition->getDecoratedService()); + $this->assertEquals(['e', null, 0], $traceableEncoderDefinition->getDecoratedService()); $this->assertEquals(new Reference('debug.e.inner'), $traceableEncoderDefinition->getArgument(0)); $this->assertEquals(new Reference('serializer.data_collector'), $traceableEncoderDefinition->getArgument(1)); } From f22044db3f3ec161584fe72c07dbefa626563db5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tobias=20B=C3=B6nner?= Date: Fri, 24 Sep 2021 17:04:39 +0200 Subject: [PATCH 065/297] [Serializer] Add `SerializedPath` annotation to flatten nested attributes --- Annotation/SerializedName.php | 4 +- Annotation/SerializedPath.php | 45 ++++ CHANGELOG.md | 1 + Mapping/AttributeMetadata.php | 32 ++- Mapping/AttributeMetadataInterface.php | 6 + .../Factory/ClassMetadataFactoryCompiler.php | 1 + Mapping/Loader/AnnotationLoader.php | 10 + Mapping/Loader/XmlFileLoader.php | 10 + Mapping/Loader/YamlFileLoader.php | 12 +- .../serializer-mapping-1.0.xsd | 7 + NameConverter/MetadataAwareNameConverter.php | 9 + Normalizer/AbstractObjectNormalizer.php | 79 ++++++- Tests/Annotation/SerializedNameTest.php | 8 +- Tests/Annotation/SerializedPathTest.php | 39 ++++ .../Annotations/SerializedPathDummy.php | 35 ++++ .../Attributes/SerializedPathDummy.php | 31 +++ Tests/Fixtures/serialization.xml | 5 + Tests/Fixtures/serialization.yml | 6 + Tests/Mapping/AttributeMetadataTest.php | 15 ++ .../ClassMetadataFactoryCompilerTest.php | 36 ++-- Tests/Mapping/Loader/AnnotationLoaderTest.php | 11 + Tests/Mapping/Loader/XmlFileLoaderTest.php | 10 + Tests/Mapping/Loader/YamlFileLoaderTest.php | 11 + .../MetadataAwareNameConverterTest.php | 30 +++ .../AbstractObjectNormalizerTest.php | 193 +++++++++++++++++- 25 files changed, 612 insertions(+), 34 deletions(-) create mode 100644 Annotation/SerializedPath.php create mode 100644 Tests/Annotation/SerializedPathTest.php create mode 100644 Tests/Fixtures/Annotations/SerializedPathDummy.php create mode 100644 Tests/Fixtures/Attributes/SerializedPathDummy.php diff --git a/Annotation/SerializedName.php b/Annotation/SerializedName.php index 93438cdfb..b6c6027e8 100644 --- a/Annotation/SerializedName.php +++ b/Annotation/SerializedName.php @@ -27,8 +27,8 @@ final class SerializedName { public function __construct(private string $serializedName) { - if (empty($serializedName)) { - throw new InvalidArgumentException(sprintf('Parameter of annotation "%s" must be a non-empty string.', static::class)); + if ('' === $serializedName) { + throw new InvalidArgumentException(sprintf('Parameter of annotation "%s" must be a non-empty string.', self::class)); } } diff --git a/Annotation/SerializedPath.php b/Annotation/SerializedPath.php new file mode 100644 index 000000000..88ee8e47d --- /dev/null +++ b/Annotation/SerializedPath.php @@ -0,0 +1,45 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Serializer\Annotation; + +use Symfony\Component\PropertyAccess\Exception\InvalidPropertyPathException; +use Symfony\Component\PropertyAccess\PropertyPath; +use Symfony\Component\Serializer\Exception\InvalidArgumentException; + +/** + * Annotation class for @SerializedPath(). + * + * @Annotation + * @NamedArgumentConstructor + * @Target({"PROPERTY", "METHOD"}) + * + * @author Tobias Bönner + */ +#[\Attribute(\Attribute::TARGET_METHOD | \Attribute::TARGET_PROPERTY)] +final class SerializedPath +{ + private PropertyPath $serializedPath; + + public function __construct(string $serializedPath) + { + try { + $this->serializedPath = new PropertyPath($serializedPath); + } catch (InvalidPropertyPathException $pathException) { + throw new InvalidArgumentException(sprintf('Parameter of annotation "%s" must be a valid property path.', self::class)); + } + } + + public function getSerializedPath(): PropertyPath + { + return $this->serializedPath; + } +} diff --git a/CHANGELOG.md b/CHANGELOG.md index 21cbb4256..3342ada2f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,7 @@ CHANGELOG * Change the signature of `AttributeMetadataInterface::setSerializedName()` to `setSerializedName(?string)` * Change the signature of `ClassMetadataInterface::setClassDiscriminatorMapping()` to `setClassDiscriminatorMapping(?ClassDiscriminatorMapping)` * Add option YamlEncoder::YAML_INDENTATION to YamlEncoder constructor options to configure additional indentation for each level of nesting. This allows configuring indentation in the service configuration. + * Add `SerializedPath` annotation to flatten nested attributes 6.1 --- diff --git a/Mapping/AttributeMetadata.php b/Mapping/AttributeMetadata.php index 180909b9f..22cc711a7 100644 --- a/Mapping/AttributeMetadata.php +++ b/Mapping/AttributeMetadata.php @@ -11,6 +11,8 @@ namespace Symfony\Component\Serializer\Mapping; +use Symfony\Component\PropertyAccess\PropertyPath; + /** * @author Kévin Dunglas */ @@ -48,6 +50,13 @@ class AttributeMetadata implements AttributeMetadataInterface */ public $serializedName; + /** + * @internal This property is public in order to reduce the size of the + * class' serialized representation. Do not access it. Use + * {@link getSerializedPath()} instead. + */ + public ?PropertyPath $serializedPath = null; + /** * @var bool * @@ -121,6 +130,16 @@ public function getSerializedName(): ?string return $this->serializedName; } + public function setSerializedPath(PropertyPath $serializedPath = null): void + { + $this->serializedPath = $serializedPath; + } + + public function getSerializedPath(): ?PropertyPath + { + return $this->serializedPath; + } + public function setIgnore(bool $ignore) { $this->ignore = $ignore; @@ -190,14 +209,9 @@ public function merge(AttributeMetadataInterface $attributeMetadata) } // Overwrite only if not defined - if (null === $this->maxDepth) { - $this->maxDepth = $attributeMetadata->getMaxDepth(); - } - - // Overwrite only if not defined - if (null === $this->serializedName) { - $this->serializedName = $attributeMetadata->getSerializedName(); - } + $this->maxDepth ??= $attributeMetadata->getMaxDepth(); + $this->serializedName ??= $attributeMetadata->getSerializedName(); + $this->serializedPath ??= $attributeMetadata->getSerializedPath(); // Overwrite only if both contexts are empty if (!$this->normalizationContexts && !$this->denormalizationContexts) { @@ -217,6 +231,6 @@ public function merge(AttributeMetadataInterface $attributeMetadata) */ public function __sleep(): array { - return ['name', 'groups', 'maxDepth', 'serializedName', 'ignore', 'normalizationContexts', 'denormalizationContexts']; + return ['name', 'groups', 'maxDepth', 'serializedName', 'serializedPath', 'ignore', 'normalizationContexts', 'denormalizationContexts']; } } diff --git a/Mapping/AttributeMetadataInterface.php b/Mapping/AttributeMetadataInterface.php index 6a215e0ea..67ca8d3c0 100644 --- a/Mapping/AttributeMetadataInterface.php +++ b/Mapping/AttributeMetadataInterface.php @@ -11,6 +11,8 @@ namespace Symfony\Component\Serializer\Mapping; +use Symfony\Component\PropertyAccess\PropertyPath; + /** * Stores metadata needed for serializing and deserializing attributes. * @@ -59,6 +61,10 @@ public function setSerializedName(?string $serializedName); */ public function getSerializedName(): ?string; + public function setSerializedPath(?PropertyPath $serializedPath): void; + + public function getSerializedPath(): ?PropertyPath; + /** * Sets if this attribute must be ignored or not. */ diff --git a/Mapping/Factory/ClassMetadataFactoryCompiler.php b/Mapping/Factory/ClassMetadataFactoryCompiler.php index 81dd4b932..f01fe9ce2 100644 --- a/Mapping/Factory/ClassMetadataFactoryCompiler.php +++ b/Mapping/Factory/ClassMetadataFactoryCompiler.php @@ -48,6 +48,7 @@ private function generateDeclaredClassMetadata(array $classMetadatas): string $attributeMetadata->getGroups(), $attributeMetadata->getMaxDepth(), $attributeMetadata->getSerializedName(), + $attributeMetadata->getSerializedPath(), ]; } diff --git a/Mapping/Loader/AnnotationLoader.php b/Mapping/Loader/AnnotationLoader.php index 25d4beaf6..cfcee8bd5 100644 --- a/Mapping/Loader/AnnotationLoader.php +++ b/Mapping/Loader/AnnotationLoader.php @@ -18,6 +18,7 @@ use Symfony\Component\Serializer\Annotation\Ignore; use Symfony\Component\Serializer\Annotation\MaxDepth; use Symfony\Component\Serializer\Annotation\SerializedName; +use Symfony\Component\Serializer\Annotation\SerializedPath; use Symfony\Component\Serializer\Exception\MappingException; use Symfony\Component\Serializer\Mapping\AttributeMetadata; use Symfony\Component\Serializer\Mapping\AttributeMetadataInterface; @@ -38,6 +39,7 @@ class AnnotationLoader implements LoaderInterface Ignore::class, MaxDepth::class, SerializedName::class, + SerializedPath::class, Context::class, ]; @@ -81,6 +83,8 @@ public function loadClassMetadata(ClassMetadataInterface $classMetadata): bool $attributesMetadata[$property->name]->setMaxDepth($annotation->getMaxDepth()); } elseif ($annotation instanceof SerializedName) { $attributesMetadata[$property->name]->setSerializedName($annotation->getSerializedName()); + } elseif ($annotation instanceof SerializedPath) { + $attributesMetadata[$property->name]->setSerializedPath($annotation->getSerializedPath()); } elseif ($annotation instanceof Ignore) { $attributesMetadata[$property->name]->setIgnore(true); } elseif ($annotation instanceof Context) { @@ -134,6 +138,12 @@ public function loadClassMetadata(ClassMetadataInterface $classMetadata): bool } $attributeMetadata->setSerializedName($annotation->getSerializedName()); + } elseif ($annotation instanceof SerializedPath) { + if (!$accessorOrMutator) { + throw new MappingException(sprintf('SerializedPath on "%s::%s()" cannot be added. SerializedPath can only be added on methods beginning with "get", "is", "has" or "set".', $className, $method->name)); + } + + $attributeMetadata->setSerializedPath($annotation->getSerializedPath()); } elseif ($annotation instanceof Ignore) { if (!$accessorOrMutator) { throw new MappingException(sprintf('Ignore on "%s::%s()" cannot be added. Ignore can only be added on methods beginning with "get", "is", "has" or "set".', $className, $method->name)); diff --git a/Mapping/Loader/XmlFileLoader.php b/Mapping/Loader/XmlFileLoader.php index 6d6ae16a9..3dc3b96c6 100644 --- a/Mapping/Loader/XmlFileLoader.php +++ b/Mapping/Loader/XmlFileLoader.php @@ -12,6 +12,8 @@ namespace Symfony\Component\Serializer\Mapping\Loader; use Symfony\Component\Config\Util\XmlUtils; +use Symfony\Component\PropertyAccess\Exception\InvalidPropertyPathException; +use Symfony\Component\PropertyAccess\PropertyPath; use Symfony\Component\Serializer\Exception\MappingException; use Symfony\Component\Serializer\Mapping\AttributeMetadata; use Symfony\Component\Serializer\Mapping\ClassDiscriminatorMapping; @@ -68,6 +70,14 @@ public function loadClassMetadata(ClassMetadataInterface $classMetadata): bool $attributeMetadata->setSerializedName((string) $attribute['serialized-name']); } + if (isset($attribute['serialized-path'])) { + try { + $attributeMetadata->setSerializedPath(new PropertyPath((string) $attribute['serialized-path'])); + } catch (InvalidPropertyPathException) { + throw new MappingException(sprintf('The "serialized-path" value must be a valid property path for the attribute "%s" of the class "%s".', $attributeName, $classMetadata->getName())); + } + } + if (isset($attribute['ignore'])) { $attributeMetadata->setIgnore(XmlUtils::phpize($attribute['ignore'])); } diff --git a/Mapping/Loader/YamlFileLoader.php b/Mapping/Loader/YamlFileLoader.php index 907d33844..0fdfcc511 100644 --- a/Mapping/Loader/YamlFileLoader.php +++ b/Mapping/Loader/YamlFileLoader.php @@ -11,6 +11,8 @@ namespace Symfony\Component\Serializer\Mapping\Loader; +use Symfony\Component\PropertyAccess\Exception\InvalidPropertyPathException; +use Symfony\Component\PropertyAccess\PropertyPath; use Symfony\Component\Serializer\Exception\MappingException; use Symfony\Component\Serializer\Mapping\AttributeMetadata; use Symfony\Component\Serializer\Mapping\ClassDiscriminatorMapping; @@ -84,13 +86,21 @@ public function loadClassMetadata(ClassMetadataInterface $classMetadata): bool } if (isset($data['serialized_name'])) { - if (!\is_string($data['serialized_name']) || empty($data['serialized_name'])) { + if (!\is_string($data['serialized_name']) || '' === $data['serialized_name']) { throw new MappingException(sprintf('The "serialized_name" value must be a non-empty string in "%s" for the attribute "%s" of the class "%s".', $this->file, $attribute, $classMetadata->getName())); } $attributeMetadata->setSerializedName($data['serialized_name']); } + if (isset($data['serialized_path'])) { + try { + $attributeMetadata->setSerializedPath(new PropertyPath((string) $data['serialized_path'])); + } catch (InvalidPropertyPathException) { + throw new MappingException(sprintf('The "serialized_path" value must be a valid property path in "%s" for the attribute "%s" of the class "%s".', $this->file, $attribute, $classMetadata->getName())); + } + } + if (isset($data['ignore'])) { if (!\is_bool($data['ignore'])) { throw new MappingException(sprintf('The "ignore" value must be a boolean in "%s" for the attribute "%s" of the class "%s".', $this->file, $attribute, $classMetadata->getName())); diff --git a/Mapping/Loader/schema/dic/serializer-mapping/serializer-mapping-1.0.xsd b/Mapping/Loader/schema/dic/serializer-mapping/serializer-mapping-1.0.xsd index 0228e41ce..f5f6cca9f 100644 --- a/Mapping/Loader/schema/dic/serializer-mapping/serializer-mapping-1.0.xsd +++ b/Mapping/Loader/schema/dic/serializer-mapping/serializer-mapping-1.0.xsd @@ -81,6 +81,13 @@ + + + + + + + diff --git a/NameConverter/MetadataAwareNameConverter.php b/NameConverter/MetadataAwareNameConverter.php index 24c9991e1..920e81869 100644 --- a/NameConverter/MetadataAwareNameConverter.php +++ b/NameConverter/MetadataAwareNameConverter.php @@ -11,6 +11,7 @@ namespace Symfony\Component\Serializer\NameConverter; +use Symfony\Component\Serializer\Exception\LogicException; use Symfony\Component\Serializer\Mapping\Factory\ClassMetadataFactoryInterface; use Symfony\Component\Serializer\Normalizer\AbstractNormalizer; @@ -76,6 +77,10 @@ private function getCacheValueForNormalization(string $propertyName, string $cla return null; } + if (null !== $attributesMetadata[$propertyName]->getSerializedName() && null !== $attributesMetadata[$propertyName]->getSerializedPath()) { + throw new LogicException(sprintf('Found SerializedName and SerializedPath annotations on property "%s" of class "%s".', $propertyName, $class)); + } + return $attributesMetadata[$propertyName]->getSerializedName() ?? null; } @@ -113,6 +118,10 @@ private function getCacheValueForAttributesMetadata(string $class, array $contex continue; } + if (null !== $metadata->getSerializedName() && null !== $metadata->getSerializedPath()) { + throw new LogicException(sprintf('Found SerializedName and SerializedPath annotations on property "%s" of class "%s".', $name, $class)); + } + $groups = $metadata->getGroups(); if (!$groups && ($context[AbstractNormalizer::GROUPS] ?? [])) { continue; diff --git a/Normalizer/AbstractObjectNormalizer.php b/Normalizer/AbstractObjectNormalizer.php index febe70bb4..7c4c5fb41 100644 --- a/Normalizer/AbstractObjectNormalizer.php +++ b/Normalizer/AbstractObjectNormalizer.php @@ -14,6 +14,7 @@ use Symfony\Component\PropertyAccess\Exception\InvalidArgumentException; use Symfony\Component\PropertyAccess\Exception\NoSuchPropertyException; use Symfony\Component\PropertyAccess\Exception\UninitializedPropertyException; +use Symfony\Component\PropertyAccess\PropertyAccess; use Symfony\Component\PropertyInfo\PropertyTypeExtractorInterface; use Symfony\Component\PropertyInfo\Type; use Symfony\Component\Serializer\Encoder\CsvEncoder; @@ -26,6 +27,7 @@ use Symfony\Component\Serializer\Mapping\AttributeMetadataInterface; use Symfony\Component\Serializer\Mapping\ClassDiscriminatorFromClassMetadata; use Symfony\Component\Serializer\Mapping\ClassDiscriminatorResolverInterface; +use Symfony\Component\Serializer\Mapping\ClassMetadataInterface; use Symfony\Component\Serializer\Mapping\Factory\ClassMetadataFactoryInterface; use Symfony\Component\Serializer\NameConverter\NameConverterInterface; @@ -157,6 +159,7 @@ public function normalize(mixed $object, string $format = null, array $context = $stack = []; $attributes = $this->getAttributes($object, $format, $context); $class = $this->objectClassResolver ? ($this->objectClassResolver)($object) : $object::class; + $classMetadata = $this->classMetadataFactory?->getMetadataFor($class); $attributesMetadata = $this->classMetadataFactory?->getMetadataFor($class)->getAttributesMetadata(); if (isset($context[self::MAX_DEPTH_HANDLER])) { $maxDepthHandler = $context[self::MAX_DEPTH_HANDLER]; @@ -199,7 +202,7 @@ public function normalize(mixed $object, string $format = null, array $context = $stack[$attribute] = $attributeValue; } - $data = $this->updateData($data, $attribute, $attributeValue, $class, $format, $attributeContext); + $data = $this->updateData($data, $attribute, $attributeValue, $class, $format, $attributeContext, $attributesMetadata, $classMetadata); } foreach ($stack as $attribute => $attributeValue) { @@ -210,7 +213,7 @@ public function normalize(mixed $object, string $format = null, array $context = $attributeContext = $this->getAttributeNormalizationContext($object, $attribute, $context); $childContext = $this->createChildContext($attributeContext, $attribute, $format); - $data = $this->updateData($data, $attribute, $this->serializer->normalize($attributeValue, $format, $childContext), $class, $format, $attributeContext); + $data = $this->updateData($data, $attribute, $this->serializer->normalize($attributeValue, $format, $childContext), $class, $format, $attributeContext, $attributesMetadata, $classMetadata); } $preserveEmptyObjects = $context[self::PRESERVE_EMPTY_OBJECTS] ?? $this->defaultContext[self::PRESERVE_EMPTY_OBJECTS] ?? false; @@ -320,9 +323,26 @@ public function denormalize(mixed $data, string $type, string $format = null, ar $object = $this->instantiateObject($normalizedData, $type, $context, $reflectionClass, $allowedAttributes, $format); $resolvedClass = $this->objectClassResolver ? ($this->objectClassResolver)($object) : $object::class; + $nestedAttributes = $this->getNestedAttributes($resolvedClass); + $nestedData = []; + $propertyAccessor = PropertyAccess::createPropertyAccessor(); + foreach ($nestedAttributes as $property => $serializedPath) { + if (null === $value = $propertyAccessor->getValue($normalizedData, $serializedPath)) { + continue; + } + $nestedData[$property] = $value; + $normalizedData = $this->removeNestedValue($serializedPath->getElements(), $normalizedData); + } + + $normalizedData = array_merge($normalizedData, $nestedData); + foreach ($normalizedData as $attribute => $value) { if ($this->nameConverter) { + $notConverted = $attribute; $attribute = $this->nameConverter->denormalize($attribute, $resolvedClass, $format, $context); + if (isset($nestedData[$notConverted]) && !isset($nestedData[$attribute])) { + throw new LogicException(sprintf('Duplicate values for key "%s" found. One value is set via the SerializedPath annotation: "%s", the other one is set via the SerializedName annotation: "%s".', $notConverted, implode('->', $nestedAttributes[$notConverted]->getElements()), $attribute)); + } } $attributeContext = $this->getAttributeDenormalizationContext($resolvedClass, $attribute, $context); @@ -623,12 +643,22 @@ private function getTypes(string $currentClass, string $attribute): ?array /** * Sets an attribute and apply the name converter if necessary. */ - private function updateData(array $data, string $attribute, mixed $attributeValue, string $class, ?string $format, array $context): array + private function updateData(array $data, string $attribute, mixed $attributeValue, string $class, ?string $format, array $context, ?array $attributesMetadata, ?ClassMetadataInterface $classMetadata): array { if (null === $attributeValue && ($context[self::SKIP_NULL_VALUES] ?? $this->defaultContext[self::SKIP_NULL_VALUES] ?? false)) { return $data; } + if (null !== $classMetadata && null !== $serializedPath = ($attributesMetadata[$attribute] ?? null)?->getSerializedPath()) { + $propertyAccessor = PropertyAccess::createPropertyAccessor(); + if ($propertyAccessor->isReadable($data, $serializedPath) && null !== $propertyAccessor->getValue($data, $serializedPath)) { + throw new LogicException(sprintf('The element you are trying to set is already populated: "%s".', (string) $serializedPath)); + } + $propertyAccessor->setValue($data, $serializedPath, $attributeValue); + + return $data; + } + if ($this->nameConverter) { $attribute = $this->nameConverter->normalize($attribute, $class, $format, $context); } @@ -719,4 +749,47 @@ private function isUninitializedValueError(\Error $e): bool return str_starts_with($e->getMessage(), 'Typed property') && str_ends_with($e->getMessage(), 'must not be accessed before initialization'); } + + /** + * Returns all attributes with a SerializedPath annotation and the respective path. + */ + private function getNestedAttributes(string $class): array + { + if (!$this->classMetadataFactory || !$this->classMetadataFactory->hasMetadataFor($class)) { + return []; + } + + $properties = []; + $serializedPaths = []; + $classMetadata = $this->classMetadataFactory->getMetadataFor($class); + foreach ($classMetadata->getAttributesMetadata() as $name => $metadata) { + if (!$serializedPath = $metadata->getSerializedPath()) { + continue; + } + $serializedPath = $metadata->getSerializedPath(); + $pathIdentifier = implode(',', $serializedPath->getElements()); + if (isset($serializedPaths[$pathIdentifier])) { + throw new LogicException(sprintf('Duplicate serialized path: "%s" used for properties "%s" and "%s".', $pathIdentifier, $serializedPaths[$pathIdentifier], $name)); + } + $serializedPaths[$pathIdentifier] = $name; + $properties[$name] = $serializedPath; + } + + return $properties; + } + + private function removeNestedValue(array $path, array $data): array + { + $element = array_shift($path); + if ([] === $path) { + unset($data[$element]); + } else { + $data[$element] = $this->removeNestedValue($path, $data[$element]); + if ([] === $data[$element]) { + unset($data[$element]); + } + } + + return $data; + } } diff --git a/Tests/Annotation/SerializedNameTest.php b/Tests/Annotation/SerializedNameTest.php index 4e81ad59d..f4dd82d7f 100644 --- a/Tests/Annotation/SerializedNameTest.php +++ b/Tests/Annotation/SerializedNameTest.php @@ -20,16 +20,12 @@ */ class SerializedNameTest extends TestCase { - /** - * @testWith [""] - * [0] - */ - public function testNotAStringSerializedNameParameter($value) + public function testNotAStringSerializedNameParameter() { $this->expectException(InvalidArgumentException::class); $this->expectExceptionMessage('Parameter of annotation "Symfony\Component\Serializer\Annotation\SerializedName" must be a non-empty string.'); - new SerializedName($value); + new SerializedName(''); } public function testSerializedNameParameters() diff --git a/Tests/Annotation/SerializedPathTest.php b/Tests/Annotation/SerializedPathTest.php new file mode 100644 index 000000000..58a057bd3 --- /dev/null +++ b/Tests/Annotation/SerializedPathTest.php @@ -0,0 +1,39 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Serializer\Tests\Annotation; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\PropertyAccess\PropertyPath; +use Symfony\Component\Serializer\Annotation\SerializedPath; +use Symfony\Component\Serializer\Exception\InvalidArgumentException; + +/** + * @author Tobias Bönner + */ +class SerializedPathTest extends TestCase +{ + public function testEmptyStringSerializedPathParameter() + { + $this->expectException(InvalidArgumentException::class); + $this->expectExceptionMessage('Parameter of annotation "Symfony\Component\Serializer\Annotation\SerializedPath" must be a valid property path.'); + + new SerializedPath(''); + } + + public function testSerializedPath() + { + $path = '[one][two]'; + $serializedPath = new SerializedPath($path); + $propertyPath = new PropertyPath($path); + $this->assertEquals($propertyPath, $serializedPath->getSerializedPath()); + } +} diff --git a/Tests/Fixtures/Annotations/SerializedPathDummy.php b/Tests/Fixtures/Annotations/SerializedPathDummy.php new file mode 100644 index 000000000..cd50b81c3 --- /dev/null +++ b/Tests/Fixtures/Annotations/SerializedPathDummy.php @@ -0,0 +1,35 @@ + + * + * 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\Annotations; + +use Symfony\Component\Serializer\Annotation\SerializedPath; + +/** + * @author Tobias Bönner + */ +class SerializedPathDummy +{ + /** + * @SerializedPath("[one][two]") + */ + public $three; + + public $seven; + + /** + * @SerializedPath("[three][four]") + */ + public function getSeven() + { + return $this->seven; + } +} diff --git a/Tests/Fixtures/Attributes/SerializedPathDummy.php b/Tests/Fixtures/Attributes/SerializedPathDummy.php new file mode 100644 index 000000000..fc5d9f64a --- /dev/null +++ b/Tests/Fixtures/Attributes/SerializedPathDummy.php @@ -0,0 +1,31 @@ + + * + * 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\Attributes; + +use Symfony\Component\Serializer\Annotation\SerializedPath; + +/** + * @author Tobias Bönner + */ +class SerializedPathDummy +{ + #[SerializedPath('[one][two]')] + public $three; + + public $seven; + + #[SerializedPath('[three][four]')] + public function getSeven() + { + return $this->seven; + } +} diff --git a/Tests/Fixtures/serialization.xml b/Tests/Fixtures/serialization.xml index 69243cfdd..4890f56bf 100644 --- a/Tests/Fixtures/serialization.xml +++ b/Tests/Fixtures/serialization.xml @@ -25,6 +25,11 @@ + + + + + diff --git a/Tests/Fixtures/serialization.yml b/Tests/Fixtures/serialization.yml index 80100e826..7519b979e 100644 --- a/Tests/Fixtures/serialization.yml +++ b/Tests/Fixtures/serialization.yml @@ -16,6 +16,12 @@ serialized_name: 'baz' bar: serialized_name: 'qux' +'Symfony\Component\Serializer\Tests\Fixtures\Annotations\SerializedPathDummy': + attributes: + three: + serialized_path: '[one][two]' + seven: + serialized_path: '[three][four]' 'Symfony\Component\Serializer\Tests\Fixtures\Annotations\AbstractDummy': discriminator_map: type_property: type diff --git a/Tests/Mapping/AttributeMetadataTest.php b/Tests/Mapping/AttributeMetadataTest.php index 8fc4b8b49..1f1b291be 100644 --- a/Tests/Mapping/AttributeMetadataTest.php +++ b/Tests/Mapping/AttributeMetadataTest.php @@ -12,6 +12,7 @@ namespace Symfony\Component\Serializer\Tests\Mapping; use PHPUnit\Framework\TestCase; +use Symfony\Component\PropertyAccess\PropertyPath; use Symfony\Component\Serializer\Mapping\AttributeMetadata; use Symfony\Component\Serializer\Mapping\AttributeMetadataInterface; @@ -58,6 +59,15 @@ public function testSerializedName() $this->assertEquals('serialized_name', $attributeMetadata->getSerializedName()); } + public function testSerializedPath() + { + $attributeMetadata = new AttributeMetadata('path'); + $serializedPath = new PropertyPath('[serialized][path]'); + $attributeMetadata->setSerializedPath($serializedPath); + + $this->assertEquals($serializedPath, $attributeMetadata->getSerializedPath()); + } + public function testIgnore() { $attributeMetadata = new AttributeMetadata('ignored'); @@ -119,6 +129,7 @@ public function testGetContextsForGroups() public function testMerge() { + $serializedPath = new PropertyPath('[a4][a5]'); $attributeMetadata1 = new AttributeMetadata('a1'); $attributeMetadata1->addGroup('a'); $attributeMetadata1->addGroup('b'); @@ -128,6 +139,7 @@ public function testMerge() $attributeMetadata2->addGroup('c'); $attributeMetadata2->setMaxDepth(2); $attributeMetadata2->setSerializedName('a3'); + $attributeMetadata2->setSerializedPath($serializedPath); $attributeMetadata2->setNormalizationContextForGroups(['foo' => 'bar'], ['a']); $attributeMetadata2->setDenormalizationContextForGroups(['baz' => 'qux'], ['c']); @@ -138,6 +150,7 @@ public function testMerge() $this->assertEquals(['a', 'b', 'c'], $attributeMetadata1->getGroups()); $this->assertEquals(2, $attributeMetadata1->getMaxDepth()); $this->assertEquals('a3', $attributeMetadata1->getSerializedName()); + $this->assertEquals($serializedPath, $attributeMetadata1->getSerializedPath()); $this->assertSame(['a' => ['foo' => 'bar']], $attributeMetadata1->getNormalizationContexts()); $this->assertSame(['c' => ['baz' => 'qux']], $attributeMetadata1->getDenormalizationContexts()); $this->assertTrue($attributeMetadata1->isIgnored()); @@ -166,6 +179,8 @@ public function testSerialize() $attributeMetadata->addGroup('b'); $attributeMetadata->setMaxDepth(3); $attributeMetadata->setSerializedName('serialized_name'); + $serializedPath = new PropertyPath('[serialized][path]'); + $attributeMetadata->setSerializedPath($serializedPath); $serialized = serialize($attributeMetadata); $this->assertEquals($attributeMetadata, unserialize($serialized)); diff --git a/Tests/Mapping/Factory/ClassMetadataFactoryCompilerTest.php b/Tests/Mapping/Factory/ClassMetadataFactoryCompilerTest.php index 6d562e30f..5ce1931ba 100644 --- a/Tests/Mapping/Factory/ClassMetadataFactoryCompilerTest.php +++ b/Tests/Mapping/Factory/ClassMetadataFactoryCompilerTest.php @@ -18,6 +18,7 @@ use Symfony\Component\Serializer\Mapping\Loader\AnnotationLoader; use Symfony\Component\Serializer\Tests\Fixtures\Annotations\MaxDepthDummy; use Symfony\Component\Serializer\Tests\Fixtures\Annotations\SerializedNameDummy; +use Symfony\Component\Serializer\Tests\Fixtures\Annotations\SerializedPathDummy; use Symfony\Component\Serializer\Tests\Fixtures\Dummy; final class ClassMetadataFactoryCompilerTest extends TestCase @@ -44,25 +45,27 @@ public function testItDumpMetadata() $dummyMetadata = $classMetatadataFactory->getMetadataFor(Dummy::class); $maxDepthDummyMetadata = $classMetatadataFactory->getMetadataFor(MaxDepthDummy::class); $serializedNameDummyMetadata = $classMetatadataFactory->getMetadataFor(SerializedNameDummy::class); + $serializedPathDummyMetadata = $classMetatadataFactory->getMetadataFor(SerializedPathDummy::class); $code = (new ClassMetadataFactoryCompiler())->compile([ $dummyMetadata, $maxDepthDummyMetadata, $serializedNameDummyMetadata, + $serializedPathDummyMetadata, ]); file_put_contents($this->dumpPath, $code); $compiledMetadata = require $this->dumpPath; - $this->assertCount(3, $compiledMetadata); + $this->assertCount(4, $compiledMetadata); $this->assertArrayHasKey(Dummy::class, $compiledMetadata); $this->assertEquals([ [ - 'foo' => [[], null, null], - 'bar' => [[], null, null], - 'baz' => [[], null, null], - 'qux' => [[], null, null], + 'foo' => [[], null, null, null], + 'bar' => [[], null, null, null], + 'baz' => [[], null, null, null], + 'qux' => [[], null, null, null], ], null, ], $compiledMetadata[Dummy::class]); @@ -70,9 +73,9 @@ public function testItDumpMetadata() $this->assertArrayHasKey(MaxDepthDummy::class, $compiledMetadata); $this->assertEquals([ [ - 'foo' => [[], 2, null], - 'bar' => [[], 3, null], - 'child' => [[], null, null], + 'foo' => [[], 2, null, null], + 'bar' => [[], 3, null, null], + 'child' => [[], null, null, null], ], null, ], $compiledMetadata[MaxDepthDummy::class]); @@ -80,12 +83,21 @@ public function testItDumpMetadata() $this->assertArrayHasKey(SerializedNameDummy::class, $compiledMetadata); $this->assertEquals([ [ - 'foo' => [[], null, 'baz'], - 'bar' => [[], null, 'qux'], - 'quux' => [[], null, null], - 'child' => [[], null, null], + 'foo' => [[], null, 'baz', null], + 'bar' => [[], null, 'qux', null], + 'quux' => [[], null, null, null], + 'child' => [[], null, null, null], ], null, ], $compiledMetadata[SerializedNameDummy::class]); + + $this->assertArrayHasKey(SerializedPathDummy::class, $compiledMetadata); + $this->assertEquals([ + [ + 'three' => [[], null, null, '[one][two]'], + 'seven' => [[], null, null, '[three][four]'], + ], + null, + ], $compiledMetadata[SerializedPathDummy::class]); } } diff --git a/Tests/Mapping/Loader/AnnotationLoaderTest.php b/Tests/Mapping/Loader/AnnotationLoaderTest.php index 0df08f9dc..0747ca3f5 100644 --- a/Tests/Mapping/Loader/AnnotationLoaderTest.php +++ b/Tests/Mapping/Loader/AnnotationLoaderTest.php @@ -12,6 +12,7 @@ namespace Symfony\Component\Serializer\Tests\Mapping\Loader; use PHPUnit\Framework\TestCase; +use Symfony\Component\PropertyAccess\PropertyPath; use Symfony\Component\Serializer\Exception\MappingException; use Symfony\Component\Serializer\Mapping\AttributeMetadata; use Symfony\Component\Serializer\Mapping\ClassDiscriminatorMapping; @@ -92,6 +93,16 @@ public function testLoadSerializedName() $this->assertEquals('qux', $attributesMetadata['bar']->getSerializedName()); } + public function testLoadSerializedPath() + { + $classMetadata = new ClassMetadata($this->getNamespace().'\SerializedPathDummy'); + $this->loader->loadClassMetadata($classMetadata); + + $attributesMetadata = $classMetadata->getAttributesMetadata(); + $this->assertEquals(new PropertyPath('[one][two]'), $attributesMetadata['three']->getSerializedPath()); + $this->assertEquals(new PropertyPath('[three][four]'), $attributesMetadata['seven']->getSerializedPath()); + } + public function testLoadClassMetadataAndMerge() { $classMetadata = new ClassMetadata($this->getNamespace().'\GroupDummy'); diff --git a/Tests/Mapping/Loader/XmlFileLoaderTest.php b/Tests/Mapping/Loader/XmlFileLoaderTest.php index 47d6305a8..b1e9ed722 100644 --- a/Tests/Mapping/Loader/XmlFileLoaderTest.php +++ b/Tests/Mapping/Loader/XmlFileLoaderTest.php @@ -84,6 +84,16 @@ public function testSerializedName() $this->assertEquals('qux', $attributesMetadata['bar']->getSerializedName()); } + public function testSerializedPath() + { + $classMetadata = new ClassMetadata('Symfony\Component\Serializer\Tests\Fixtures\Annotations\SerializedPathDummy'); + $this->loader->loadClassMetadata($classMetadata); + + $attributesMetadata = $classMetadata->getAttributesMetadata(); + $this->assertEquals('[one][two]', $attributesMetadata['three']->getSerializedPath()); + $this->assertEquals('[three][four]', $attributesMetadata['seven']->getSerializedPath()); + } + public function testLoadDiscriminatorMap() { $classMetadata = new ClassMetadata(AbstractDummy::class); diff --git a/Tests/Mapping/Loader/YamlFileLoaderTest.php b/Tests/Mapping/Loader/YamlFileLoaderTest.php index aa235762b..bbe0a99ae 100644 --- a/Tests/Mapping/Loader/YamlFileLoaderTest.php +++ b/Tests/Mapping/Loader/YamlFileLoaderTest.php @@ -12,6 +12,7 @@ namespace Symfony\Component\Serializer\Tests\Mapping\Loader; use PHPUnit\Framework\TestCase; +use Symfony\Component\PropertyAccess\PropertyPath; use Symfony\Component\Serializer\Exception\MappingException; use Symfony\Component\Serializer\Mapping\AttributeMetadata; use Symfony\Component\Serializer\Mapping\ClassDiscriminatorMapping; @@ -97,6 +98,16 @@ public function testSerializedName() $this->assertEquals('qux', $attributesMetadata['bar']->getSerializedName()); } + public function testSerializedPath() + { + $classMetadata = new ClassMetadata('Symfony\Component\Serializer\Tests\Fixtures\Annotations\SerializedPathDummy'); + $this->loader->loadClassMetadata($classMetadata); + + $attributesMetadata = $classMetadata->getAttributesMetadata(); + $this->assertEquals(new PropertyPath('[one][two]'), $attributesMetadata['three']->getSerializedPath()); + $this->assertEquals(new PropertyPath('[three][four]'), $attributesMetadata['seven']->getSerializedPath()); + } + public function testLoadDiscriminatorMap() { $classMetadata = new ClassMetadata(AbstractDummy::class); diff --git a/Tests/NameConverter/MetadataAwareNameConverterTest.php b/Tests/NameConverter/MetadataAwareNameConverterTest.php index 119edfbfb..6cd01bbc1 100644 --- a/Tests/NameConverter/MetadataAwareNameConverterTest.php +++ b/Tests/NameConverter/MetadataAwareNameConverterTest.php @@ -13,6 +13,9 @@ use Doctrine\Common\Annotations\AnnotationReader; use PHPUnit\Framework\TestCase; +use Symfony\Component\Serializer\Annotation\SerializedName; +use Symfony\Component\Serializer\Annotation\SerializedPath; +use Symfony\Component\Serializer\Exception\LogicException; use Symfony\Component\Serializer\Mapping\Factory\ClassMetadataFactory; use Symfony\Component\Serializer\Mapping\Factory\ClassMetadataFactoryInterface; use Symfony\Component\Serializer\Mapping\Loader\AnnotationLoader; @@ -163,4 +166,31 @@ public function testDenormalizeWithCacheContext() $this->assertEquals('buzForExport', $nameConverter->denormalize('buz', OtherSerializedNameDummy::class, null, ['groups' => ['b']])); $this->assertEquals('buz', $nameConverter->denormalize('buz', OtherSerializedNameDummy::class)); } + + public function testDenormalizeWithNestedPathAndName() + { + $classMetadataFactory = new ClassMetadataFactory(new AnnotationLoader(new AnnotationReader())); + $nameConverter = new MetadataAwareNameConverter($classMetadataFactory); + $this->expectException(LogicException::class); + $this->expectExceptionMessage('Found SerializedName and SerializedPath annotations on property "foo" of class "Symfony\Component\Serializer\Tests\NameConverter\NestedPathAndName".'); + $nameConverter->denormalize('foo', NestedPathAndName::class); + } + + public function testNormalizeWithNestedPathAndName() + { + $classMetadataFactory = new ClassMetadataFactory(new AnnotationLoader(new AnnotationReader())); + $nameConverter = new MetadataAwareNameConverter($classMetadataFactory); + $this->expectException(LogicException::class); + $this->expectExceptionMessage('Found SerializedName and SerializedPath annotations on property "foo" of class "Symfony\Component\Serializer\Tests\NameConverter\NestedPathAndName".'); + $nameConverter->normalize('foo', NestedPathAndName::class); + } +} + +class NestedPathAndName +{ + /** + * @SerializedName("five") + * @SerializedPath("[one][two][three]") + */ + public $foo; } diff --git a/Tests/Normalizer/AbstractObjectNormalizerTest.php b/Tests/Normalizer/AbstractObjectNormalizerTest.php index b34d40d69..9c8ba3ffc 100644 --- a/Tests/Normalizer/AbstractObjectNormalizerTest.php +++ b/Tests/Normalizer/AbstractObjectNormalizerTest.php @@ -16,6 +16,8 @@ use Symfony\Component\PropertyInfo\Extractor\PhpDocExtractor; use Symfony\Component\PropertyInfo\Extractor\ReflectionExtractor; use Symfony\Component\PropertyInfo\Type; +use Symfony\Component\Serializer\Annotation\SerializedName; +use Symfony\Component\Serializer\Annotation\SerializedPath; use Symfony\Component\Serializer\Exception\ExtraAttributesException; use Symfony\Component\Serializer\Exception\InvalidArgumentException; use Symfony\Component\Serializer\Exception\LogicException; @@ -28,6 +30,7 @@ use Symfony\Component\Serializer\Mapping\Factory\ClassMetadataFactory; use Symfony\Component\Serializer\Mapping\Factory\ClassMetadataFactoryInterface; use Symfony\Component\Serializer\Mapping\Loader\AnnotationLoader; +use Symfony\Component\Serializer\NameConverter\MetadataAwareNameConverter; use Symfony\Component\Serializer\Normalizer\AbstractObjectNormalizer; use Symfony\Component\Serializer\Normalizer\DenormalizerInterface; use Symfony\Component\Serializer\Normalizer\ObjectNormalizer; @@ -103,6 +106,132 @@ public function testDenormalizeWithExtraAttributesAndNoGroupsWithMetadataFactory ); } + public function testDenormalizeWithDuplicateNestedAttributes() + { + $this->expectException(LogicException::class); + $this->expectExceptionMessage('Duplicate serialized path: "one,two,three" used for properties "foo" and "bar".'); + $normalizer = new AbstractObjectNormalizerWithMetadata(); + $normalizer->denormalize([], DuplicateValueNestedDummy::class, 'any'); + } + + public function testDenormalizeWithNestedAttributesWithoutMetadata() + { + $normalizer = new AbstractObjectNormalizerDummy(); + $data = [ + 'one' => [ + 'two' => [ + 'three' => 'foo', + ], + 'four' => 'quux', + ], + 'foo' => 'notfoo', + 'baz' => 'baz', + ]; + $test = $normalizer->denormalize($data, NestedDummy::class, 'any'); + $this->assertSame('notfoo', $test->foo); + $this->assertSame('baz', $test->baz); + $this->assertNull($test->notfoo); + } + + public function testDenormalizeWithNestedAttributes() + { + $normalizer = new AbstractObjectNormalizerWithMetadata(); + $data = [ + 'one' => [ + 'two' => [ + 'three' => 'foo', + ], + 'four' => 'quux', + ], + 'foo' => 'notfoo', + 'baz' => 'baz', + ]; + $test = $normalizer->denormalize($data, NestedDummy::class, 'any'); + $this->assertSame('baz', $test->baz); + $this->assertSame('foo', $test->foo); + $this->assertSame('quux', $test->quux); + $this->assertSame('notfoo', $test->notfoo); + } + + public function testDenormalizeWithNestedAttributesDuplicateKeys() + { + $this->expectException(LogicException::class); + $this->expectExceptionMessage('Duplicate values for key "quux" found. One value is set via the SerializedPath annotation: "one->four", the other one is set via the SerializedName annotation: "notquux".'); + $normalizer = new AbstractObjectNormalizerWithMetadata(); + $data = [ + 'one' => [ + 'four' => 'quux', + ], + 'quux' => 'notquux', + ]; + $normalizer->denormalize($data, DuplicateKeyNestedDummy::class, 'any'); + } + + public function testNormalizeWithNestedAttributesMixingArrayTypes() + { + $this->expectException(LogicException::class); + $this->expectExceptionMessage('The element you are trying to set is already populated: "[one][two]"'); + $foobar = new AlreadyPopulatedNestedDummy(); + $foobar->foo = 'foo'; + $foobar->bar = 'bar'; + $classMetadataFactory = new ClassMetadataFactory(new AnnotationLoader(new AnnotationReader())); + $normalizer = new ObjectNormalizer($classMetadataFactory, new MetadataAwareNameConverter($classMetadataFactory)); + $normalizer->normalize($foobar, 'any'); + } + + public function testNormalizeWithNestedAttributesElementAlreadySet() + { + $this->expectException(LogicException::class); + $this->expectExceptionMessage('The element you are trying to set is already populated: "[one][two][three]"'); + $foobar = new DuplicateValueNestedDummy(); + $foobar->foo = 'foo'; + $foobar->bar = 'bar'; + $classMetadataFactory = new ClassMetadataFactory(new AnnotationLoader(new AnnotationReader())); + $normalizer = new ObjectNormalizer($classMetadataFactory, new MetadataAwareNameConverter($classMetadataFactory)); + $normalizer->normalize($foobar, 'any'); + } + + public function testNormalizeWithNestedAttributes() + { + $foobar = new NestedDummy(); + $foobar->foo = 'foo'; + $foobar->quux = 'quux'; + $foobar->baz = 'baz'; + $foobar->notfoo = 'notfoo'; + $data = [ + 'one' => [ + 'two' => [ + 'three' => 'foo', + ], + 'four' => 'quux', + ], + 'foo' => 'notfoo', + 'baz' => 'baz', + ]; + $classMetadataFactory = new ClassMetadataFactory(new AnnotationLoader(new AnnotationReader())); + $normalizer = new ObjectNormalizer($classMetadataFactory, new MetadataAwareNameConverter($classMetadataFactory)); + $test = $normalizer->normalize($foobar, 'any'); + $this->assertSame($data, $test); + } + + public function testNormalizeWithNestedAttributesWithoutMetadata() + { + $foobar = new NestedDummy(); + $foobar->foo = 'foo'; + $foobar->quux = 'quux'; + $foobar->baz = 'baz'; + $foobar->notfoo = 'notfoo'; + $data = [ + 'foo' => 'foo', + 'quux' => 'quux', + 'notfoo' => 'notfoo', + 'baz' => 'baz', + ]; + $normalizer = new ObjectNormalizer(); + $test = $normalizer->normalize($foobar, 'any'); + $this->assertSame($data, $test); + } + public function testDenormalizeCollectionDecodedFromXmlWithOneChild() { $denormalizer = $this->getDenormalizerForDummyCollection(); @@ -436,11 +565,73 @@ class EmptyDummy { } +class AlreadyPopulatedNestedDummy +{ + /** + * @SerializedPath("[one][two][three]") + */ + public $foo; + + /** + * @SerializedPath("[one][two]") + */ + public $bar; +} + +class DuplicateValueNestedDummy +{ + /** + * @SerializedPath("[one][two][three]") + */ + public $foo; + + /** + * @SerializedPath("[one][two][three]") + */ + public $bar; + + public $baz; +} + +class NestedDummy +{ + /** + * @SerializedPath("[one][two][three]") + */ + public $foo; + + /** + * @SerializedPath("[one][four]") + */ + public $quux; + + /** + * @SerializedPath("[foo]") + */ + public $notfoo; + + public $baz; +} + +class DuplicateKeyNestedDummy +{ + /** + * @SerializedPath("[one][four]") + */ + public $quux; + + /** + * @SerializedName("quux") + */ + public $notquux; +} + class AbstractObjectNormalizerWithMetadata extends AbstractObjectNormalizer { public function __construct() { - parent::__construct(new ClassMetadataFactory(new AnnotationLoader(new AnnotationReader()))); + $classMetadataFactory = new ClassMetadataFactory(new AnnotationLoader(new AnnotationReader())); + parent::__construct($classMetadataFactory, new MetadataAwareNameConverter($classMetadataFactory)); } protected function extractAttributes(object $object, string $format = null, array $context = []): array From cf8df288db70003681b729b87a022d436b81035f Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Tue, 1 Nov 2022 22:49:27 +0100 Subject: [PATCH 066/297] Use ??= more --- Mapping/Loader/XmlFileLoader.php | 12 ++---------- Mapping/Loader/YamlFileLoader.php | 16 +++------------- 2 files changed, 5 insertions(+), 23 deletions(-) diff --git a/Mapping/Loader/XmlFileLoader.php b/Mapping/Loader/XmlFileLoader.php index 3dc3b96c6..ebaa4655d 100644 --- a/Mapping/Loader/XmlFileLoader.php +++ b/Mapping/Loader/XmlFileLoader.php @@ -35,11 +35,7 @@ class XmlFileLoader extends FileLoader public function loadClassMetadata(ClassMetadataInterface $classMetadata): bool { - if (null === $this->classes) { - $this->classes = $this->getClassesFromXml(); - } - - if (!$this->classes) { + if (!$this->classes ??= $this->getClassesFromXml()) { return false; } @@ -128,11 +124,7 @@ public function loadClassMetadata(ClassMetadataInterface $classMetadata): bool */ public function getMappedClasses(): array { - if (null === $this->classes) { - $this->classes = $this->getClassesFromXml(); - } - - return array_keys($this->classes); + return array_keys($this->classes ??= $this->getClassesFromXml()); } /** diff --git a/Mapping/Loader/YamlFileLoader.php b/Mapping/Loader/YamlFileLoader.php index 0fdfcc511..fd73864fd 100644 --- a/Mapping/Loader/YamlFileLoader.php +++ b/Mapping/Loader/YamlFileLoader.php @@ -38,11 +38,7 @@ class YamlFileLoader extends FileLoader public function loadClassMetadata(ClassMetadataInterface $classMetadata): bool { - if (null === $this->classes) { - $this->classes = $this->getClassesFromYaml(); - } - - if (!$this->classes) { + if (!$this->classes ??= $this->getClassesFromYaml()) { return false; } @@ -153,11 +149,7 @@ public function loadClassMetadata(ClassMetadataInterface $classMetadata): bool */ public function getMappedClasses(): array { - if (null === $this->classes) { - $this->classes = $this->getClassesFromYaml(); - } - - return array_keys($this->classes); + return array_keys($this->classes ??= $this->getClassesFromYaml()); } private function getClassesFromYaml(): array @@ -166,9 +158,7 @@ private function getClassesFromYaml(): array throw new MappingException(sprintf('This is not a local file "%s".', $this->file)); } - if (null === $this->yamlParser) { - $this->yamlParser = new Parser(); - } + $this->yamlParser ??= new Parser(); $classes = $this->yamlParser->parseFile($this->file, Yaml::PARSE_CONSTANT); From f5a54dd73ec9fdf340808e11e7c03ea618d6cb34 Mon Sep 17 00:00:00 2001 From: Yannick Ihmels Date: Tue, 8 Nov 2022 21:58:02 +0100 Subject: [PATCH 067/297] Add encoder option for saving options --- CHANGELOG.md | 5 +++++ Encoder/XmlEncoder.php | 11 +++++++++-- Tests/Encoder/XmlEncoderTest.php | 4 +++- 3 files changed, 17 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3342ada2f..d7799bb50 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,11 @@ CHANGELOG ========= +6.3 +--- + + * Add `XmlEncoder::SAVE_OPTIONS` context option + 6.2 --- diff --git a/Encoder/XmlEncoder.php b/Encoder/XmlEncoder.php index 0132f1ebf..2207012ca 100644 --- a/Encoder/XmlEncoder.php +++ b/Encoder/XmlEncoder.php @@ -44,9 +44,15 @@ class XmlEncoder implements EncoderInterface, DecoderInterface, NormalizationAwa public const FORMAT_OUTPUT = 'xml_format_output'; /** - * A bit field of LIBXML_* constants. + * A bit field of LIBXML_* constants for loading XML documents. */ public const LOAD_OPTIONS = 'load_options'; + + /** + * A bit field of LIBXML_* constants for saving XML documents. + */ + public const SAVE_OPTIONS = 'save_options'; + public const REMOVE_EMPTY_TAGS = 'remove_empty_tags'; public const ROOT_NODE_NAME = 'xml_root_node_name'; public const STANDALONE = 'xml_standalone'; @@ -58,6 +64,7 @@ class XmlEncoder implements EncoderInterface, DecoderInterface, NormalizationAwa self::DECODER_IGNORED_NODE_TYPES => [\XML_PI_NODE, \XML_COMMENT_NODE], self::ENCODER_IGNORED_NODE_TYPES => [], self::LOAD_OPTIONS => \LIBXML_NONET | \LIBXML_NOBLANKS, + self::SAVE_OPTIONS => 0, self::REMOVE_EMPTY_TAGS => false, self::ROOT_NODE_NAME => 'response', self::TYPE_CAST_ATTRIBUTES => true, @@ -88,7 +95,7 @@ public function encode(mixed $data, string $format, array $context = []): string $this->appendNode($dom, $data, $format, $context, $xmlRootNodeName); } - return $dom->saveXML($ignorePiNode ? $dom->documentElement : null); + return $dom->saveXML($ignorePiNode ? $dom->documentElement : null, $context[self::SAVE_OPTIONS] ?? $this->defaultContext[self::SAVE_OPTIONS]); } public function decode(string $data, string $format, array $context = []): mixed diff --git a/Tests/Encoder/XmlEncoderTest.php b/Tests/Encoder/XmlEncoderTest.php index 77b220d58..11e7bef3e 100644 --- a/Tests/Encoder/XmlEncoderTest.php +++ b/Tests/Encoder/XmlEncoderTest.php @@ -189,12 +189,13 @@ public function testEncodeNotRemovingEmptyTags() public function testContext() { - $array = ['person' => ['name' => 'George Abitbol']]; + $array = ['person' => ['name' => 'George Abitbol', 'age' => null]]; $expected = <<<'XML' George Abitbol + @@ -202,6 +203,7 @@ public function testContext() $context = [ 'xml_format_output' => true, + 'save_options' => \LIBXML_NOEMPTYTAG, ]; $this->assertSame($expected, $this->encoder->encode($array, 'xml', $context)); From d0d49de4ecb314b7a0051931d1ccf703d9fc721a Mon Sep 17 00:00:00 2001 From: tigitz Date: Fri, 30 Sep 2022 23:29:24 +0200 Subject: [PATCH 068/297] Leverage class name literal on object --- Debug/TraceableEncoder.php | 4 ++-- Debug/TraceableNormalizer.php | 4 ++-- Tests/Normalizer/CustomNormalizerTest.php | 4 ++-- Tests/Normalizer/ObjectNormalizerTest.php | 4 ++-- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/Debug/TraceableEncoder.php b/Debug/TraceableEncoder.php index 73c645c6c..35fc462d0 100644 --- a/Debug/TraceableEncoder.php +++ b/Debug/TraceableEncoder.php @@ -44,7 +44,7 @@ public function encode(mixed $data, string $format, array $context = []): string $time = microtime(true) - $startTime; if ($traceId = ($context[TraceableSerializer::DEBUG_TRACE_ID] ?? null)) { - $this->dataCollector->collectEncoding($traceId, \get_class($this->encoder), $time); + $this->dataCollector->collectEncoding($traceId, $this->encoder::class, $time); } return $encoded; @@ -70,7 +70,7 @@ public function decode(string $data, string $format, array $context = []): mixed $time = microtime(true) - $startTime; if ($traceId = ($context[TraceableSerializer::DEBUG_TRACE_ID] ?? null)) { - $this->dataCollector->collectDecoding($traceId, \get_class($this->encoder), $time); + $this->dataCollector->collectDecoding($traceId, $this->encoder::class, $time); } return $encoded; diff --git a/Debug/TraceableNormalizer.php b/Debug/TraceableNormalizer.php index be214ce69..fc801ff8a 100644 --- a/Debug/TraceableNormalizer.php +++ b/Debug/TraceableNormalizer.php @@ -46,7 +46,7 @@ public function normalize(mixed $object, string $format = null, array $context = $time = microtime(true) - $startTime; if ($traceId = ($context[TraceableSerializer::DEBUG_TRACE_ID] ?? null)) { - $this->dataCollector->collectNormalization($traceId, \get_class($this->normalizer), $time); + $this->dataCollector->collectNormalization($traceId, $this->normalizer::class, $time); } return $normalized; @@ -72,7 +72,7 @@ public function denormalize(mixed $data, string $type, string $format = null, ar $time = microtime(true) - $startTime; if ($traceId = ($context[TraceableSerializer::DEBUG_TRACE_ID] ?? null)) { - $this->dataCollector->collectDenormalization($traceId, \get_class($this->normalizer), $time); + $this->dataCollector->collectDenormalization($traceId, $this->normalizer::class, $time); } return $denormalized; diff --git a/Tests/Normalizer/CustomNormalizerTest.php b/Tests/Normalizer/CustomNormalizerTest.php index e3f13fc1f..d69ee8e40 100644 --- a/Tests/Normalizer/CustomNormalizerTest.php +++ b/Tests/Normalizer/CustomNormalizerTest.php @@ -50,11 +50,11 @@ public function testSerialize() public function testDeserialize() { - $obj = $this->normalizer->denormalize('foo', \get_class(new ScalarDummy()), 'xml'); + $obj = $this->normalizer->denormalize('foo', (new ScalarDummy())::class, 'xml'); $this->assertEquals('foo', $obj->xmlFoo); $this->assertNull($obj->foo); - $obj = $this->normalizer->denormalize('foo', \get_class(new ScalarDummy()), 'json'); + $obj = $this->normalizer->denormalize('foo', (new ScalarDummy())::class, 'json'); $this->assertEquals('foo', $obj->foo); $this->assertNull($obj->xmlFoo); } diff --git a/Tests/Normalizer/ObjectNormalizerTest.php b/Tests/Normalizer/ObjectNormalizerTest.php index f7b5dc88d..396a2a04b 100644 --- a/Tests/Normalizer/ObjectNormalizerTest.php +++ b/Tests/Normalizer/ObjectNormalizerTest.php @@ -736,10 +736,10 @@ public function testDoesntHaveIssuesWithUnionConstTypes() $normalizer = new ObjectNormalizer(null, null, null, $extractor); $serializer = new Serializer([new ArrayDenormalizer(), new DateTimeNormalizer(), $normalizer]); - $this->assertSame('bar', $serializer->denormalize(['foo' => 'bar'], \get_class(new class() { + $this->assertSame('bar', $serializer->denormalize(['foo' => 'bar'], (new class() { /** @var self::*|null */ public $foo; - }))->foo); + })::class)->foo); } public function testExtractAttributesRespectsFormat() From 2b6a60d084e08f1ea2090f4bf33c20721ee8b7ab Mon Sep 17 00:00:00 2001 From: Nicolas PHILIPPE Date: Thu, 15 Dec 2022 13:11:53 +0100 Subject: [PATCH 069/297] [Serializer] fix context attribute with serializedName --- Normalizer/AbstractNormalizer.php | 2 +- .../AbstractObjectNormalizerTest.php | 17 +++++++++++ .../ObjectDummyWithContextAttribute.php | 29 +++++++++++++++++++ 3 files changed, 47 insertions(+), 1 deletion(-) create mode 100644 Tests/Normalizer/Features/ObjectDummyWithContextAttribute.php diff --git a/Normalizer/AbstractNormalizer.php b/Normalizer/AbstractNormalizer.php index 12c778cb8..829e17840 100644 --- a/Normalizer/AbstractNormalizer.php +++ b/Normalizer/AbstractNormalizer.php @@ -331,8 +331,8 @@ protected function instantiateObject(array &$data, string $class, array &$contex $params = []; foreach ($constructorParameters as $constructorParameter) { $paramName = $constructorParameter->name; + $attributeContext = $this->getAttributeDenormalizationContext($class, $paramName, $context); $key = $this->nameConverter ? $this->nameConverter->normalize($paramName, $class, $format, $context) : $paramName; - $attributeContext = $this->getAttributeDenormalizationContext($class, $key, $context); $allowed = false === $allowedAttributes || \in_array($paramName, $allowedAttributes); $ignored = !$this->isAllowedAttribute($class, $paramName, $format, $context); diff --git a/Tests/Normalizer/AbstractObjectNormalizerTest.php b/Tests/Normalizer/AbstractObjectNormalizerTest.php index 9c8ba3ffc..4cc668658 100644 --- a/Tests/Normalizer/AbstractObjectNormalizerTest.php +++ b/Tests/Normalizer/AbstractObjectNormalizerTest.php @@ -15,6 +15,7 @@ use PHPUnit\Framework\TestCase; use Symfony\Component\PropertyInfo\Extractor\PhpDocExtractor; use Symfony\Component\PropertyInfo\Extractor\ReflectionExtractor; +use Symfony\Component\PropertyInfo\PropertyInfoExtractor; use Symfony\Component\PropertyInfo\Type; use Symfony\Component\Serializer\Annotation\SerializedName; use Symfony\Component\Serializer\Annotation\SerializedPath; @@ -32,6 +33,7 @@ use Symfony\Component\Serializer\Mapping\Loader\AnnotationLoader; use Symfony\Component\Serializer\NameConverter\MetadataAwareNameConverter; use Symfony\Component\Serializer\Normalizer\AbstractObjectNormalizer; +use Symfony\Component\Serializer\Normalizer\DateTimeNormalizer; use Symfony\Component\Serializer\Normalizer\DenormalizerInterface; use Symfony\Component\Serializer\Normalizer\ObjectNormalizer; use Symfony\Component\Serializer\Serializer; @@ -41,6 +43,7 @@ use Symfony\Component\Serializer\Tests\Fixtures\Annotations\AbstractDummyFirstChild; use Symfony\Component\Serializer\Tests\Fixtures\Annotations\AbstractDummySecondChild; use Symfony\Component\Serializer\Tests\Fixtures\DummySecondChildQuux; +use Symfony\Component\Serializer\Tests\Normalizer\Features\ObjectDummyWithContextAttribute; class AbstractObjectNormalizerTest extends TestCase { @@ -525,6 +528,20 @@ public function testDenormalizeRecursiveWithObjectAttributeWithStringValue() $this->assertInstanceOf(ObjectInner::class, $obj->getInner()); } + + public function testDenormalizeUsesContextAttributeForPropertiesInConstructorWithSeralizedName() + { + $classMetadataFactory = new ClassMetadataFactory(new AnnotationLoader(new AnnotationReader())); + + $extractor = new PropertyInfoExtractor([], [new PhpDocExtractor(), new ReflectionExtractor()]); + $normalizer = new ObjectNormalizer($classMetadataFactory, new MetadataAwareNameConverter($classMetadataFactory), null, $extractor); + $serializer = new Serializer([new DateTimeNormalizer([DateTimeNormalizer::FORMAT_KEY => 'd-m-Y']), $normalizer]); + + /** @var ObjectDummyWithContextAttribute $obj */ + $obj = $serializer->denormalize(['property_with_serialized_name' => '01-02-2022', 'propertyWithoutSerializedName' => '01-02-2022'], ObjectDummyWithContextAttribute::class); + + $this->assertSame($obj->propertyWithSerializedName->format('Y-m-d'), $obj->propertyWithoutSerializedName->format('Y-m-d')); + } } class AbstractObjectNormalizerDummy extends AbstractObjectNormalizer diff --git a/Tests/Normalizer/Features/ObjectDummyWithContextAttribute.php b/Tests/Normalizer/Features/ObjectDummyWithContextAttribute.php new file mode 100644 index 000000000..b846f042f --- /dev/null +++ b/Tests/Normalizer/Features/ObjectDummyWithContextAttribute.php @@ -0,0 +1,29 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Serializer\Tests\Normalizer\Features; + +use Symfony\Component\Serializer\Annotation\Context; +use Symfony\Component\Serializer\Annotation\SerializedName; +use Symfony\Component\Serializer\Normalizer\DateTimeNormalizer; + +final class ObjectDummyWithContextAttribute +{ + public function __construct( + #[Context([DateTimeNormalizer::FORMAT_KEY => 'm-d-Y'])] + #[SerializedName('property_with_serialized_name')] + public \DateTimeImmutable $propertyWithSerializedName, + + #[Context([DateTimeNormalizer::FORMAT_KEY => 'm-d-Y'])] + public \DateTimeImmutable $propertyWithoutSerializedName, + ) { + } +} From c9d243d74fed2a9bc902eceb741066f4a7fe3878 Mon Sep 17 00:00:00 2001 From: Gert de Pagter Date: Sun, 8 Jan 2023 11:12:05 +0100 Subject: [PATCH 070/297] [Serializer] Fix SerializerInterface for PHPStan --- SerializerInterface.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/SerializerInterface.php b/SerializerInterface.php index eb5ee000e..b883dbea5 100644 --- a/SerializerInterface.php +++ b/SerializerInterface.php @@ -33,6 +33,8 @@ public function serialize(mixed $data, string $format, array $context = []): str * @param array $context * * @psalm-return (TType is class-string ? TObject : mixed) + * + * @phpstan-return ($type is class-string ? TObject : mixed) */ public function deserialize(mixed $data, string $type, string $format, array $context = []): mixed; } From a43bac4481b85613c18e4041edfd329b16462a25 Mon Sep 17 00:00:00 2001 From: tigitz Date: Sun, 1 Jan 2023 19:45:34 +0100 Subject: [PATCH 071/297] Leverage arrow function syntax for closure --- .../CamelCaseToSnakeCaseNameConverter.php | 4 +- Normalizer/ObjectNormalizer.php | 4 +- Tests/Annotation/ContextTest.php | 10 +-- .../MetadataAwareNameConverterTest.php | 8 +-- .../Features/CallbacksTestTrait.php | 8 +-- Tests/Normalizer/ObjectNormalizerTest.php | 4 +- Tests/SerializerTest.php | 68 ++++++++----------- 7 files changed, 40 insertions(+), 66 deletions(-) diff --git a/NameConverter/CamelCaseToSnakeCaseNameConverter.php b/NameConverter/CamelCaseToSnakeCaseNameConverter.php index 14c2e8a28..de34f5b57 100644 --- a/NameConverter/CamelCaseToSnakeCaseNameConverter.php +++ b/NameConverter/CamelCaseToSnakeCaseNameConverter.php @@ -42,9 +42,7 @@ public function normalize(string $propertyName): string public function denormalize(string $propertyName): string { - $camelCasedName = preg_replace_callback('/(^|_|\.)+(.)/', function ($match) { - return ('.' === $match[1] ? '_' : '').strtoupper($match[2]); - }, $propertyName); + $camelCasedName = preg_replace_callback('/(^|_|\.)+(.)/', fn ($match) => ('.' === $match[1] ? '_' : '').strtoupper($match[2]), $propertyName); if ($this->lowerCamelCase) { $camelCasedName = lcfirst($camelCasedName); diff --git a/Normalizer/ObjectNormalizer.php b/Normalizer/ObjectNormalizer.php index b949193e4..37d3204aa 100644 --- a/Normalizer/ObjectNormalizer.php +++ b/Normalizer/ObjectNormalizer.php @@ -44,9 +44,7 @@ public function __construct(ClassMetadataFactoryInterface $classMetadataFactory $this->propertyAccessor = $propertyAccessor ?: PropertyAccess::createPropertyAccessor(); - $this->objectClassResolver = $objectClassResolver ?? function ($class) { - return \is_object($class) ? $class::class : $class; - }; + $this->objectClassResolver = $objectClassResolver ?? fn ($class) => \is_object($class) ? $class::class : $class; } public function hasCacheableSupportsMethod(): bool diff --git a/Tests/Annotation/ContextTest.php b/Tests/Annotation/ContextTest.php index 9d5dd0098..6b3a3f0c7 100644 --- a/Tests/Annotation/ContextTest.php +++ b/Tests/Annotation/ContextTest.php @@ -76,7 +76,7 @@ public function testValidInputs(callable $factory, string $expectedDump) public function provideValidInputs(): iterable { yield 'named arguments: with context option' => [ - function () { return new Context(context: ['foo' => 'bar']); }, + fn () => new Context(context: ['foo' => 'bar']), << 'bar']); }, ]; yield 'named arguments: with normalization context option' => [ - function () { return new Context(normalizationContext: ['foo' => 'bar']); }, + fn () => new Context(normalizationContext: ['foo' => 'bar']), << 'bar']); }, ]; yield 'named arguments: with denormalization context option' => [ - function () { return new Context(denormalizationContext: ['foo' => 'bar']); }, + fn () => new Context(denormalizationContext: ['foo' => 'bar']), << 'bar']); }, ]; yield 'named arguments: with groups option as string' => [ - function () { return new Context(context: ['foo' => 'bar'], groups: 'a'); }, + fn () => new Context(context: ['foo' => 'bar'], groups: 'a'), << 'bar'], groups: 'a'); }, ]; yield 'named arguemnts: with groups option as array' => [ - function () { return new Context(context: ['foo' => 'bar'], groups: ['a', 'b']); }, + fn () => new Context(context: ['foo' => 'bar'], groups: ['a', 'b']), <<createMock(NameConverterInterface::class); $fallback ->method('normalize') - ->willReturnCallback(function ($propertyName) { - return strtoupper($propertyName); - }) + ->willReturnCallback(strtoupper(...)) ; $nameConverter = new MetadataAwareNameConverter($classMetadataFactory, $fallback); @@ -90,9 +88,7 @@ public function testDenormalizeWithFallback($expected, $propertyName) $fallback = $this->createMock(NameConverterInterface::class); $fallback ->method('denormalize') - ->willReturnCallback(function ($propertyName) { - return strtolower($propertyName); - }) + ->willReturnCallback(strtolower(...)) ; $nameConverter = new MetadataAwareNameConverter($classMetadataFactory, $fallback); diff --git a/Tests/Normalizer/Features/CallbacksTestTrait.php b/Tests/Normalizer/Features/CallbacksTestTrait.php index a8ef81bb6..e9a9721fd 100644 --- a/Tests/Normalizer/Features/CallbacksTestTrait.php +++ b/Tests/Normalizer/Features/CallbacksTestTrait.php @@ -180,9 +180,7 @@ public function provideNormalizeCallbacks() ], 'Count a property' => [ [ - 'bar' => function (array $bars) { - return \count($bars); - }, + 'bar' => fn (array $bars) => \count($bars), ], [new CallbacksObject(), new CallbacksObject()], ['bar' => 2, 'foo' => null], @@ -244,9 +242,7 @@ public function provideDenormalizeCallbacks(): array ], 'Count a property' => [ [ - 'bar' => function (array $bars) { - return \count($bars); - }, + 'bar' => fn (array $bars) => \count($bars), ], [new CallbacksObject(), new CallbacksObject()], new CallbacksObject(2), diff --git a/Tests/Normalizer/ObjectNormalizerTest.php b/Tests/Normalizer/ObjectNormalizerTest.php index 396a2a04b..47747c6db 100644 --- a/Tests/Normalizer/ObjectNormalizerTest.php +++ b/Tests/Normalizer/ObjectNormalizerTest.php @@ -826,9 +826,7 @@ public function testDefaultObjectClassResolver() public function testObjectClassResolver() { - $classResolver = function ($object) { - return ObjectDummy::class; - }; + $classResolver = fn ($object) => ObjectDummy::class; $normalizer = new ObjectNormalizer(null, null, null, null, null, $classResolver); diff --git a/Tests/SerializerTest.php b/Tests/SerializerTest.php index 255abb386..eed0c3290 100644 --- a/Tests/SerializerTest.php +++ b/Tests/SerializerTest.php @@ -154,9 +154,7 @@ public function testNormalizeWithSupportOnData() { $normalizer1 = $this->createMock(NormalizerInterface::class); $normalizer1->method('supportsNormalization') - ->willReturnCallback(function ($data, $format) { - return isset($data->test); - }); + ->willReturnCallback(fn ($data, $format) => isset($data->test)); $normalizer1->method('normalize')->willReturn('test1'); $normalizer2 = $this->createMock(NormalizerInterface::class); @@ -177,9 +175,7 @@ public function testDenormalizeWithSupportOnData() { $denormalizer1 = $this->createMock(DenormalizerInterface::class); $denormalizer1->method('supportsDenormalization') - ->willReturnCallback(function ($data, $type, $format) { - return isset($data['test1']); - }); + ->willReturnCallback(fn ($data, $type, $format) => isset($data['test1'])); $denormalizer1->method('denormalize')->willReturn('test1'); $denormalizer2 = $this->createMock(DenormalizerInterface::class); @@ -898,15 +894,13 @@ public function testCollectDenormalizationErrors(?ClassMetadataFactory $classMet $this->assertInstanceOf(Php74Full::class, $th->getData()); - $exceptionsAsArray = array_map(function (NotNormalizableValueException $e): array { - return [ - 'currentType' => $e->getCurrentType(), - 'expectedTypes' => $e->getExpectedTypes(), - 'path' => $e->getPath(), - 'useMessageForUser' => $e->canUseMessageForUser(), - 'message' => $e->getMessage(), - ]; - }, $th->getErrors()); + $exceptionsAsArray = array_map(fn (NotNormalizableValueException $e): array => [ + 'currentType' => $e->getCurrentType(), + 'expectedTypes' => $e->getExpectedTypes(), + 'path' => $e->getPath(), + 'useMessageForUser' => $e->canUseMessageForUser(), + 'message' => $e->getMessage(), + ], $th->getErrors()); $expected = [ [ @@ -1096,15 +1090,13 @@ public function testCollectDenormalizationErrors2(?ClassMetadataFactory $classMe $this->assertInstanceOf(Php74Full::class, $th->getData()[0]); $this->assertInstanceOf(Php74Full::class, $th->getData()[1]); - $exceptionsAsArray = array_map(function (NotNormalizableValueException $e): array { - return [ - 'currentType' => $e->getCurrentType(), - 'expectedTypes' => $e->getExpectedTypes(), - 'path' => $e->getPath(), - 'useMessageForUser' => $e->canUseMessageForUser(), - 'message' => $e->getMessage(), - ]; - }, $th->getErrors()); + $exceptionsAsArray = array_map(fn (NotNormalizableValueException $e): array => [ + 'currentType' => $e->getCurrentType(), + 'expectedTypes' => $e->getExpectedTypes(), + 'path' => $e->getPath(), + 'useMessageForUser' => $e->canUseMessageForUser(), + 'message' => $e->getMessage(), + ], $th->getErrors()); $expected = [ [ @@ -1158,15 +1150,13 @@ public function testCollectDenormalizationErrorsWithConstructor(?ClassMetadataFa $this->assertInstanceOf(Php80WithPromotedTypedConstructor::class, $th->getData()); - $exceptionsAsArray = array_map(function (NotNormalizableValueException $e): array { - return [ - 'currentType' => $e->getCurrentType(), - 'expectedTypes' => $e->getExpectedTypes(), - 'path' => $e->getPath(), - 'useMessageForUser' => $e->canUseMessageForUser(), - 'message' => $e->getMessage(), - ]; - }, $th->getErrors()); + $exceptionsAsArray = array_map(fn (NotNormalizableValueException $e): array => [ + 'currentType' => $e->getCurrentType(), + 'expectedTypes' => $e->getExpectedTypes(), + 'path' => $e->getPath(), + 'useMessageForUser' => $e->canUseMessageForUser(), + 'message' => $e->getMessage(), + ], $th->getErrors()); $expected = [ [ @@ -1201,13 +1191,11 @@ public function testCollectDenormalizationErrorsWithEnumConstructor() $this->assertInstanceOf(PartialDenormalizationException::class, $th); } - $exceptionsAsArray = array_map(function (NotNormalizableValueException $e): array { - return [ - 'currentType' => $e->getCurrentType(), - 'useMessageForUser' => $e->canUseMessageForUser(), - 'message' => $e->getMessage(), - ]; - }, $th->getErrors()); + $exceptionsAsArray = array_map(fn (NotNormalizableValueException $e): array => [ + 'currentType' => $e->getCurrentType(), + 'useMessageForUser' => $e->canUseMessageForUser(), + 'message' => $e->getMessage(), + ], $th->getErrors()); $expected = [ [ From 957307976b9401998a55201e2c801fb1e6b68848 Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Fri, 13 Jan 2023 10:29:14 +0100 Subject: [PATCH 072/297] Fix merge --- Tests/NameConverter/MetadataAwareNameConverterTest.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Tests/NameConverter/MetadataAwareNameConverterTest.php b/Tests/NameConverter/MetadataAwareNameConverterTest.php index f0663c08e..c64ac1218 100644 --- a/Tests/NameConverter/MetadataAwareNameConverterTest.php +++ b/Tests/NameConverter/MetadataAwareNameConverterTest.php @@ -58,7 +58,7 @@ public function testNormalizeWithFallback($propertyName, $expected) $fallback = $this->createMock(NameConverterInterface::class); $fallback ->method('normalize') - ->willReturnCallback(strtoupper(...)) + ->willReturnCallback(fn ($propertyName) => strtoupper($propertyName)) ; $nameConverter = new MetadataAwareNameConverter($classMetadataFactory, $fallback); @@ -88,7 +88,7 @@ public function testDenormalizeWithFallback($expected, $propertyName) $fallback = $this->createMock(NameConverterInterface::class); $fallback ->method('denormalize') - ->willReturnCallback(strtolower(...)) + ->willReturnCallback(fn ($propertyName) => strtolower($propertyName)) ; $nameConverter = new MetadataAwareNameConverter($classMetadataFactory, $fallback); From 03b530e044ba307302879c01edd4f57f196d347d Mon Sep 17 00:00:00 2001 From: HypeMC Date: Sat, 21 Jan 2023 14:00:36 +0100 Subject: [PATCH 073/297] [Serializer] Replace the MissingConstructorArgumentsException class with MissingConstructorArgumentException --- CHANGELOG.md | 1 + .../MissingConstructorArgumentException.php | 41 +++++++++++++++++++ .../MissingConstructorArgumentsException.php | 10 +++++ Normalizer/AbstractNormalizer.php | 8 ++-- Normalizer/AbstractObjectNormalizer.php | 6 +-- .../ConstructorArgumentsTestTrait.php | 13 ++++-- 6 files changed, 68 insertions(+), 11 deletions(-) create mode 100644 Exception/MissingConstructorArgumentException.php diff --git a/CHANGELOG.md b/CHANGELOG.md index d7799bb50..19eaa6a9c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,7 @@ CHANGELOG --- * Add `XmlEncoder::SAVE_OPTIONS` context option + * Deprecate `MissingConstructorArgumentsException` in favor of `MissingConstructorArgumentException` 6.2 --- diff --git a/Exception/MissingConstructorArgumentException.php b/Exception/MissingConstructorArgumentException.php new file mode 100644 index 000000000..3fdfaf605 --- /dev/null +++ b/Exception/MissingConstructorArgumentException.php @@ -0,0 +1,41 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Serializer\Exception; + +class MissingConstructorArgumentException extends MissingConstructorArgumentsException +{ + private string $class; + private string $missingArgument; + + /** + * @param class-string $class + */ + public function __construct(string $class, string $missingArgument, int $code = 0, \Throwable $previous = null) + { + $this->class = $class; + $this->missingArgument = $missingArgument; + + $message = sprintf('Cannot create an instance of "%s" from serialized data because its constructor requires parameter "%s" to be present.', $class, $missingArgument); + + parent::__construct($message, $code, $previous, [$missingArgument]); + } + + public function getClass(): string + { + return $this->class; + } + + public function getMissingArgument(): string + { + return $this->missingArgument; + } +} diff --git a/Exception/MissingConstructorArgumentsException.php b/Exception/MissingConstructorArgumentsException.php index fe984a291..a5a71d00c 100644 --- a/Exception/MissingConstructorArgumentsException.php +++ b/Exception/MissingConstructorArgumentsException.php @@ -12,6 +12,8 @@ namespace Symfony\Component\Serializer\Exception; /** + * @deprecated since Symfony 6.3, use {@see MissingConstructorArgumentException} instead + * * @author Maxime VEBER */ class MissingConstructorArgumentsException extends RuntimeException @@ -23,16 +25,24 @@ class MissingConstructorArgumentsException extends RuntimeException public function __construct(string $message, int $code = 0, \Throwable $previous = null, array $missingArguments = []) { + if (!$this instanceof MissingConstructorArgumentException) { + trigger_deprecation('symfony/serializer', '6.3', 'The "%s" class is deprecated, use "%s" instead.', __CLASS__, MissingConstructorArgumentException::class); + } + $this->missingArguments = $missingArguments; parent::__construct($message, $code, $previous); } /** + * @deprecated since Symfony 6.3, use {@see MissingConstructorArgumentException::getMissingArgument()} instead + * * @return string[] */ public function getMissingConstructorArguments(): array { + trigger_deprecation('symfony/serializer', '6.3', 'The "%s()" method is deprecated, use "%s::getMissingArgument()" instead.', __METHOD__, MissingConstructorArgumentException::class); + return $this->missingArguments; } } diff --git a/Normalizer/AbstractNormalizer.php b/Normalizer/AbstractNormalizer.php index 829e17840..52e985815 100644 --- a/Normalizer/AbstractNormalizer.php +++ b/Normalizer/AbstractNormalizer.php @@ -14,7 +14,7 @@ use Symfony\Component\Serializer\Exception\CircularReferenceException; use Symfony\Component\Serializer\Exception\InvalidArgumentException; use Symfony\Component\Serializer\Exception\LogicException; -use Symfony\Component\Serializer\Exception\MissingConstructorArgumentsException; +use Symfony\Component\Serializer\Exception\MissingConstructorArgumentException; use Symfony\Component\Serializer\Exception\NotNormalizableValueException; use Symfony\Component\Serializer\Exception\RuntimeException; use Symfony\Component\Serializer\Mapping\AttributeMetadataInterface; @@ -308,7 +308,7 @@ protected function getConstructor(array &$data, string $class, array &$context, * @return object * * @throws RuntimeException - * @throws MissingConstructorArgumentsException + * @throws MissingConstructorArgumentException */ protected function instantiateObject(array &$data, string $class, array &$context, \ReflectionClass $reflectionClass, array|bool $allowedAttributes, string $format = null) { @@ -381,7 +381,7 @@ protected function instantiateObject(array &$data, string $class, array &$contex $params[] = null; } else { if (!isset($context['not_normalizable_value_exceptions'])) { - 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]); + throw new MissingConstructorArgumentException($class, $constructorParameter->name); } $exception = NotNormalizableValueException::createForUnexpectedDataType( @@ -425,7 +425,7 @@ protected function denormalizeParameter(\ReflectionClass $class, \ReflectionPara } } catch (\ReflectionException $e) { throw new RuntimeException(sprintf('Could not determine the class of the parameter "%s".', $parameterName), 0, $e); - } catch (MissingConstructorArgumentsException $e) { + } catch (MissingConstructorArgumentException $e) { if (!$parameter->getType()->allowsNull()) { throw $e; } diff --git a/Normalizer/AbstractObjectNormalizer.php b/Normalizer/AbstractObjectNormalizer.php index 7c4c5fb41..4a02c05a8 100644 --- a/Normalizer/AbstractObjectNormalizer.php +++ b/Normalizer/AbstractObjectNormalizer.php @@ -22,7 +22,7 @@ use Symfony\Component\Serializer\Encoder\XmlEncoder; use Symfony\Component\Serializer\Exception\ExtraAttributesException; use Symfony\Component\Serializer\Exception\LogicException; -use Symfony\Component\Serializer\Exception\MissingConstructorArgumentsException; +use Symfony\Component\Serializer\Exception\MissingConstructorArgumentException; use Symfony\Component\Serializer\Exception\NotNormalizableValueException; use Symfony\Component\Serializer\Mapping\AttributeMetadataInterface; use Symfony\Component\Serializer\Mapping\ClassDiscriminatorFromClassMetadata; @@ -417,7 +417,7 @@ abstract protected function setAttributeValue(object $object, string $attribute, * * @throws NotNormalizableValueException * @throws ExtraAttributesException - * @throws MissingConstructorArgumentsException + * @throws MissingConstructorArgumentException * @throws LogicException */ private function validateAndDenormalize(array $types, string $currentClass, string $attribute, mixed $data, ?string $format, array $context): mixed @@ -565,7 +565,7 @@ private function validateAndDenormalize(array $types, string $currentClass, stri } $extraAttributesException ??= $e; - } catch (MissingConstructorArgumentsException $e) { + } catch (MissingConstructorArgumentException $e) { if (!$isUnionType) { throw $e; } diff --git a/Tests/Normalizer/Features/ConstructorArgumentsTestTrait.php b/Tests/Normalizer/Features/ConstructorArgumentsTestTrait.php index 306c571f9..9489136be 100644 --- a/Tests/Normalizer/Features/ConstructorArgumentsTestTrait.php +++ b/Tests/Normalizer/Features/ConstructorArgumentsTestTrait.php @@ -11,7 +11,7 @@ namespace Symfony\Component\Serializer\Tests\Normalizer\Features; -use Symfony\Component\Serializer\Exception\MissingConstructorArgumentsException; +use Symfony\Component\Serializer\Exception\MissingConstructorArgumentException; use Symfony\Component\Serializer\Normalizer\DenormalizerInterface; use Symfony\Component\Serializer\Tests\Fixtures\NotSerializedConstructorArgumentDummy; @@ -63,8 +63,13 @@ public function testConstructorWithMissingData() $normalizer = $this->getDenormalizerForConstructArguments(); - $this->expectException(MissingConstructorArgumentsException::class); - $this->expectExceptionMessage('Cannot create an instance of "'.ConstructorArgumentsObject::class.'" from serialized data because its constructor requires parameter "bar" to be present.'); - $normalizer->denormalize($data, ConstructorArgumentsObject::class); + try { + $normalizer->denormalize($data, ConstructorArgumentsObject::class); + self::fail(sprintf('Failed asserting that exception of type "%s" is thrown.', MissingConstructorArgumentException::class)); + } catch (MissingConstructorArgumentException $e) { + self::assertSame(sprintf('Cannot create an instance of "%s" from serialized data because its constructor requires parameter "bar" to be present.', ConstructorArgumentsObject::class), $e->getMessage()); + self::assertSame(ConstructorArgumentsObject::class, $e->getClass()); + self::assertSame('bar', $e->getMissingArgument()); + } } } From bdb8c3335cf5d926a1df4949c6bf591b6d4603f0 Mon Sep 17 00:00:00 2001 From: Oskar Stark Date: Mon, 6 Feb 2023 06:06:48 +0100 Subject: [PATCH 074/297] [PHPUnit 10] Use `TestCase` suffix for abstract tests in `/Tests/` --- .../{AnnotationLoaderTest.php => AnnotationLoaderTestCase.php} | 2 +- Tests/Mapping/Loader/AnnotationLoaderWithAttributesTest.php | 2 +- .../Loader/AnnotationLoaderWithDoctrineAnnotationsTest.php | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) rename Tests/Mapping/Loader/{AnnotationLoaderTest.php => AnnotationLoaderTestCase.php} (99%) diff --git a/Tests/Mapping/Loader/AnnotationLoaderTest.php b/Tests/Mapping/Loader/AnnotationLoaderTestCase.php similarity index 99% rename from Tests/Mapping/Loader/AnnotationLoaderTest.php rename to Tests/Mapping/Loader/AnnotationLoaderTestCase.php index 0747ca3f5..de7accd84 100644 --- a/Tests/Mapping/Loader/AnnotationLoaderTest.php +++ b/Tests/Mapping/Loader/AnnotationLoaderTestCase.php @@ -25,7 +25,7 @@ /** * @author Kévin Dunglas */ -abstract class AnnotationLoaderTest extends TestCase +abstract class AnnotationLoaderTestCase extends TestCase { use ContextMappingTestTrait; diff --git a/Tests/Mapping/Loader/AnnotationLoaderWithAttributesTest.php b/Tests/Mapping/Loader/AnnotationLoaderWithAttributesTest.php index 0983620b8..89e94b663 100644 --- a/Tests/Mapping/Loader/AnnotationLoaderWithAttributesTest.php +++ b/Tests/Mapping/Loader/AnnotationLoaderWithAttributesTest.php @@ -15,7 +15,7 @@ use Symfony\Component\Serializer\Mapping\ClassMetadata; use Symfony\Component\Serializer\Mapping\Loader\AnnotationLoader; -class AnnotationLoaderWithAttributesTest extends AnnotationLoaderTest +class AnnotationLoaderWithAttributesTest extends AnnotationLoaderTestCase { protected function createLoader(): AnnotationLoader { diff --git a/Tests/Mapping/Loader/AnnotationLoaderWithDoctrineAnnotationsTest.php b/Tests/Mapping/Loader/AnnotationLoaderWithDoctrineAnnotationsTest.php index 37675a49d..cfb1547c5 100644 --- a/Tests/Mapping/Loader/AnnotationLoaderWithDoctrineAnnotationsTest.php +++ b/Tests/Mapping/Loader/AnnotationLoaderWithDoctrineAnnotationsTest.php @@ -14,7 +14,7 @@ use Doctrine\Common\Annotations\AnnotationReader; use Symfony\Component\Serializer\Mapping\Loader\AnnotationLoader; -class AnnotationLoaderWithDoctrineAnnotationsTest extends AnnotationLoaderTest +class AnnotationLoaderWithDoctrineAnnotationsTest extends AnnotationLoaderTestCase { protected function createLoader(): AnnotationLoader { From 53ad0f34e1dc97483cf0db12aa1ad2520f1f915d Mon Sep 17 00:00:00 2001 From: Thomas Calvet Date: Thu, 9 Feb 2023 18:28:49 +0100 Subject: [PATCH 075/297] Use xxh128 instead of md5 --- NameConverter/MetadataAwareNameConverter.php | 2 +- Normalizer/AbstractObjectNormalizer.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/NameConverter/MetadataAwareNameConverter.php b/NameConverter/MetadataAwareNameConverter.php index 920e81869..881d334bd 100644 --- a/NameConverter/MetadataAwareNameConverter.php +++ b/NameConverter/MetadataAwareNameConverter.php @@ -142,6 +142,6 @@ private function getCacheKey(string $class, array $context): string return $class.'-'.$context['cache_key']; } - return $class.md5(serialize($context[AbstractNormalizer::GROUPS] ?? [])); + return $class.hash('xxh128', serialize($context[AbstractNormalizer::GROUPS] ?? [])); } } diff --git a/Normalizer/AbstractObjectNormalizer.php b/Normalizer/AbstractObjectNormalizer.php index 7c4c5fb41..e64cfc880 100644 --- a/Normalizer/AbstractObjectNormalizer.php +++ b/Normalizer/AbstractObjectNormalizer.php @@ -730,7 +730,7 @@ private function getCacheKey(?string $format, array $context): bool|string unset($context['cache_key']); // avoid artificially different keys try { - return md5($format.serialize([ + return hash('xxh128', $format.serialize([ 'context' => $context, 'ignored' => $context[self::IGNORED_ATTRIBUTES] ?? $this->defaultContext[self::IGNORED_ATTRIBUTES], ])); From 78688a062f6be3c8467eefe11006ee34ac7cd846 Mon Sep 17 00:00:00 2001 From: Wouter de Jong Date: Mon, 13 Feb 2023 00:00:11 +0100 Subject: [PATCH 076/297] Add missing PHPdoc return types --- Annotation/MaxDepth.php | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Annotation/MaxDepth.php b/Annotation/MaxDepth.php index 7ecefb8e3..43785b292 100644 --- a/Annotation/MaxDepth.php +++ b/Annotation/MaxDepth.php @@ -32,6 +32,9 @@ public function __construct(private int $maxDepth) } } + /** + * @return int + */ public function getMaxDepth() { return $this->maxDepth; From 2659ca360e8b6b5b0610bf6e266d10237d32680c Mon Sep 17 00:00:00 2001 From: Wouter de Jong Date: Mon, 13 Feb 2023 00:00:27 +0100 Subject: [PATCH 077/297] Add PHP types to private methods and functions --- Mapping/ClassDiscriminatorFromClassMetadata.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Mapping/ClassDiscriminatorFromClassMetadata.php b/Mapping/ClassDiscriminatorFromClassMetadata.php index 5359408dd..708d58e03 100644 --- a/Mapping/ClassDiscriminatorFromClassMetadata.php +++ b/Mapping/ClassDiscriminatorFromClassMetadata.php @@ -65,7 +65,7 @@ public function getTypeForMappedObject(object|string $object): ?string return $mapping->getMappedObjectType($object); } - private function resolveMappingForMappedObject(object|string $object) + private function resolveMappingForMappedObject(object|string $object): ?ClassDiscriminatorMapping { $reflectionClass = new \ReflectionClass($object); if ($parentClass = $reflectionClass->getParentClass()) { From fada431eca1dc0f376dc96e88f67c61b359c01ec Mon Sep 17 00:00:00 2001 From: Wouter de Jong Date: Sun, 12 Feb 2023 23:57:18 +0100 Subject: [PATCH 078/297] Add void return types --- Debug/TraceableEncoder.php | 2 +- Debug/TraceableNormalizer.php | 6 +++--- DependencyInjection/SerializerPass.php | 3 +++ Encoder/CsvEncoder.php | 2 +- Mapping/AttributeMetadata.php | 15 +++++++++++++++ Mapping/ClassMetadata.php | 9 +++++++++ Normalizer/DenormalizerAwareTrait.php | 3 +++ Normalizer/GetSetMethodNormalizer.php | 3 +++ Normalizer/MimeMessageNormalizer.php | 2 +- Normalizer/NormalizerAwareTrait.php | 3 +++ Normalizer/ObjectNormalizer.php | 3 +++ Normalizer/PropertyNormalizer.php | 3 +++ SerializerAwareTrait.php | 3 +++ 13 files changed, 51 insertions(+), 6 deletions(-) diff --git a/Debug/TraceableEncoder.php b/Debug/TraceableEncoder.php index 35fc462d0..0795d14ca 100644 --- a/Debug/TraceableEncoder.php +++ b/Debug/TraceableEncoder.php @@ -85,7 +85,7 @@ public function supportsDecoding(string $format, array $context = []): bool return $this->encoder->supportsDecoding($format, $context); } - public function setSerializer(SerializerInterface $serializer) + public function setSerializer(SerializerInterface $serializer): void { if (!$this->encoder instanceof SerializerAwareInterface) { return; diff --git a/Debug/TraceableNormalizer.php b/Debug/TraceableNormalizer.php index fc801ff8a..ca41fc6a8 100644 --- a/Debug/TraceableNormalizer.php +++ b/Debug/TraceableNormalizer.php @@ -87,7 +87,7 @@ public function supportsDenormalization(mixed $data, string $type, string $forma return $this->normalizer->supportsDenormalization($data, $type, $format, $context); } - public function setSerializer(SerializerInterface $serializer) + public function setSerializer(SerializerInterface $serializer): void { if (!$this->normalizer instanceof SerializerAwareInterface) { return; @@ -96,7 +96,7 @@ public function setSerializer(SerializerInterface $serializer) $this->normalizer->setSerializer($serializer); } - public function setNormalizer(NormalizerInterface $normalizer) + public function setNormalizer(NormalizerInterface $normalizer): void { if (!$this->normalizer instanceof NormalizerAwareInterface) { return; @@ -105,7 +105,7 @@ public function setNormalizer(NormalizerInterface $normalizer) $this->normalizer->setNormalizer($normalizer); } - public function setDenormalizer(DenormalizerInterface $denormalizer) + public function setDenormalizer(DenormalizerInterface $denormalizer): void { if (!$this->normalizer instanceof DenormalizerAwareInterface) { return; diff --git a/DependencyInjection/SerializerPass.php b/DependencyInjection/SerializerPass.php index 930462175..71376d496 100644 --- a/DependencyInjection/SerializerPass.php +++ b/DependencyInjection/SerializerPass.php @@ -31,6 +31,9 @@ class SerializerPass implements CompilerPassInterface { use PriorityTaggedServiceTrait; + /** + * @return void + */ public function process(ContainerBuilder $container) { if (!$container->hasDefinition('serializer')) { diff --git a/Encoder/CsvEncoder.php b/Encoder/CsvEncoder.php index c1893789a..d8cc6e3f3 100644 --- a/Encoder/CsvEncoder.php +++ b/Encoder/CsvEncoder.php @@ -207,7 +207,7 @@ public function supportsDecoding(string $format): bool /** * Flattens an array and generates keys including the path. */ - private function flatten(iterable $array, array &$result, string $keySeparator, string $parentKey = '', bool $escapeFormulas = false) + private function flatten(iterable $array, array &$result, string $keySeparator, string $parentKey = '', bool $escapeFormulas = false): void { foreach ($array as $key => $value) { if (is_iterable($value)) { diff --git a/Mapping/AttributeMetadata.php b/Mapping/AttributeMetadata.php index 22cc711a7..aeee5ac68 100644 --- a/Mapping/AttributeMetadata.php +++ b/Mapping/AttributeMetadata.php @@ -94,6 +94,9 @@ public function getName(): string return $this->name; } + /** + * @return void + */ public function addGroup(string $group) { if (!\in_array($group, $this->groups)) { @@ -106,6 +109,9 @@ public function getGroups(): array return $this->groups; } + /** + * @return void + */ public function setMaxDepth(?int $maxDepth) { $this->maxDepth = $maxDepth; @@ -116,6 +122,9 @@ public function getMaxDepth(): ?int return $this->maxDepth; } + /** + * @return void + */ public function setSerializedName(string $serializedName = null) { if (1 > \func_num_args()) { @@ -140,6 +149,9 @@ public function getSerializedPath(): ?PropertyPath return $this->serializedPath; } + /** + * @return void + */ public function setIgnore(bool $ignore) { $this->ignore = $ignore; @@ -202,6 +214,9 @@ public function setDenormalizationContextForGroups(array $context, array $groups } } + /** + * @return void + */ public function merge(AttributeMetadataInterface $attributeMetadata) { foreach ($attributeMetadata->getGroups() as $group) { diff --git a/Mapping/ClassMetadata.php b/Mapping/ClassMetadata.php index cb166b212..fc6336ebd 100644 --- a/Mapping/ClassMetadata.php +++ b/Mapping/ClassMetadata.php @@ -60,6 +60,9 @@ public function getName(): string return $this->name; } + /** + * @return void + */ public function addAttributeMetadata(AttributeMetadataInterface $attributeMetadata) { $this->attributesMetadata[$attributeMetadata->getName()] = $attributeMetadata; @@ -70,6 +73,9 @@ public function getAttributesMetadata(): array return $this->attributesMetadata; } + /** + * @return void + */ public function merge(ClassMetadataInterface $classMetadata) { foreach ($classMetadata->getAttributesMetadata() as $attributeMetadata) { @@ -95,6 +101,9 @@ public function getClassDiscriminatorMapping(): ?ClassDiscriminatorMapping return $this->classDiscriminatorMapping; } + /** + * @return void + */ public function setClassDiscriminatorMapping(ClassDiscriminatorMapping $mapping = null) { if (1 > \func_num_args()) { diff --git a/Normalizer/DenormalizerAwareTrait.php b/Normalizer/DenormalizerAwareTrait.php index 82dba8f18..c5cc86ecf 100644 --- a/Normalizer/DenormalizerAwareTrait.php +++ b/Normalizer/DenormalizerAwareTrait.php @@ -21,6 +21,9 @@ trait DenormalizerAwareTrait */ protected $denormalizer; + /** + * @return void + */ public function setDenormalizer(DenormalizerInterface $denormalizer) { $this->denormalizer = $denormalizer; diff --git a/Normalizer/GetSetMethodNormalizer.php b/Normalizer/GetSetMethodNormalizer.php index 54b2f7c9d..e08dd5d9e 100644 --- a/Normalizer/GetSetMethodNormalizer.php +++ b/Normalizer/GetSetMethodNormalizer.php @@ -134,6 +134,9 @@ protected function getAttributeValue(object $object, string $attribute, string $ return null; } + /** + * @return void + */ protected function setAttributeValue(object $object, string $attribute, mixed $value, string $format = null, array $context = []) { $setter = 'set'.ucfirst($attribute); diff --git a/Normalizer/MimeMessageNormalizer.php b/Normalizer/MimeMessageNormalizer.php index 63f677039..a442fc54d 100644 --- a/Normalizer/MimeMessageNormalizer.php +++ b/Normalizer/MimeMessageNormalizer.php @@ -42,7 +42,7 @@ public function __construct(PropertyNormalizer $normalizer) $this->headersProperty = new \ReflectionProperty(Headers::class, 'headers'); } - public function setSerializer(SerializerInterface $serializer) + public function setSerializer(SerializerInterface $serializer): void { $this->serializer = $serializer; $this->normalizer->setSerializer($serializer); diff --git a/Normalizer/NormalizerAwareTrait.php b/Normalizer/NormalizerAwareTrait.php index a8835c93d..40a4fa0e8 100644 --- a/Normalizer/NormalizerAwareTrait.php +++ b/Normalizer/NormalizerAwareTrait.php @@ -21,6 +21,9 @@ trait NormalizerAwareTrait */ protected $normalizer; + /** + * @return void + */ public function setNormalizer(NormalizerInterface $normalizer) { $this->normalizer = $normalizer; diff --git a/Normalizer/ObjectNormalizer.php b/Normalizer/ObjectNormalizer.php index 37d3204aa..8018cb7a4 100644 --- a/Normalizer/ObjectNormalizer.php +++ b/Normalizer/ObjectNormalizer.php @@ -129,6 +129,9 @@ protected function getAttributeValue(object $object, string $attribute, string $ return $attribute === $this->discriminatorCache[$cacheKey] ? $this->classDiscriminatorResolver->getTypeForMappedObject($object) : $this->propertyAccessor->getValue($object, $attribute); } + /** + * @return void + */ protected function setAttributeValue(object $object, string $attribute, mixed $value, string $format = null, array $context = []) { try { diff --git a/Normalizer/PropertyNormalizer.php b/Normalizer/PropertyNormalizer.php index 144f1c448..3dd734055 100644 --- a/Normalizer/PropertyNormalizer.php +++ b/Normalizer/PropertyNormalizer.php @@ -171,6 +171,9 @@ protected function getAttributeValue(object $object, string $attribute, string $ return $reflectionProperty->getValue($object); } + /** + * @return void + */ protected function setAttributeValue(object $object, string $attribute, mixed $value, string $format = null, array $context = []) { try { diff --git a/SerializerAwareTrait.php b/SerializerAwareTrait.php index 99f4d0f3e..183556874 100644 --- a/SerializerAwareTrait.php +++ b/SerializerAwareTrait.php @@ -21,6 +21,9 @@ trait SerializerAwareTrait */ protected $serializer; + /** + * @return void + */ public function setSerializer(SerializerInterface $serializer) { $this->serializer = $serializer; From 6749e0b6a7c6bc9c30d5da498acf2418f7488caf Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Tue, 14 Feb 2023 09:53:37 +0100 Subject: [PATCH 079/297] Fix merge --- Tests/Annotation/ContextTest.php | 2 +- Tests/Context/Encoder/CsvEncoderContextBuilderTest.php | 2 +- Tests/Context/Encoder/JsonEncoderContextBuilderTest.php | 2 +- Tests/Context/Encoder/XmlEncoderContextBuilderTest.php | 2 +- Tests/Context/Encoder/YamlEncoderContextBuilderTest.php | 2 +- .../Normalizer/AbstractNormalizerContextBuilderTest.php | 2 +- .../Normalizer/AbstractObjectNormalizerContextBuilderTest.php | 4 ++-- .../ConstraintViolationListNormalizerContextBuilderTest.php | 2 +- .../Normalizer/DateIntervalNormalizerContextBuilderTest.php | 2 +- .../Normalizer/DateTimeNormalizerContextBuilderTest.php | 2 +- .../Normalizer/FormErrorNormalizerContextBuilderTest.php | 2 +- .../Normalizer/ProblemNormalizerContextBuilderTest.php | 2 +- Tests/Context/Normalizer/UidNormalizerContextBuilderTest.php | 2 +- .../Normalizer/UnwrappingDenormalizerContextBuilderTest.php | 2 +- Tests/Context/SerializerContextBuilderTest.php | 2 +- 15 files changed, 16 insertions(+), 16 deletions(-) diff --git a/Tests/Annotation/ContextTest.php b/Tests/Annotation/ContextTest.php index 9d5dd0098..afa5f292b 100644 --- a/Tests/Annotation/ContextTest.php +++ b/Tests/Annotation/ContextTest.php @@ -73,7 +73,7 @@ public function testValidInputs(callable $factory, string $expectedDump) $this->assertDumpEquals($expectedDump, $factory()); } - public function provideValidInputs(): iterable + public static function provideValidInputs(): iterable { yield 'named arguments: with context option' => [ function () { return new Context(context: ['foo' => 'bar']); }, diff --git a/Tests/Context/Encoder/CsvEncoderContextBuilderTest.php b/Tests/Context/Encoder/CsvEncoderContextBuilderTest.php index 47403888c..c71d41b63 100644 --- a/Tests/Context/Encoder/CsvEncoderContextBuilderTest.php +++ b/Tests/Context/Encoder/CsvEncoderContextBuilderTest.php @@ -54,7 +54,7 @@ public function testWithers(array $values) /** * @return iterable|}> */ - public function withersDataProvider(): iterable + public static function withersDataProvider(): iterable { yield 'With values' => [[ CsvEncoder::DELIMITER_KEY => ';', diff --git a/Tests/Context/Encoder/JsonEncoderContextBuilderTest.php b/Tests/Context/Encoder/JsonEncoderContextBuilderTest.php index 6f06525cd..9cabbf17a 100644 --- a/Tests/Context/Encoder/JsonEncoderContextBuilderTest.php +++ b/Tests/Context/Encoder/JsonEncoderContextBuilderTest.php @@ -48,7 +48,7 @@ public function testWithers(array $values) /** * @return iterable|}> */ - public function withersDataProvider(): iterable + public static function withersDataProvider(): iterable { yield 'With values' => [[ JsonEncode::OPTIONS => \JSON_PRETTY_PRINT, diff --git a/Tests/Context/Encoder/XmlEncoderContextBuilderTest.php b/Tests/Context/Encoder/XmlEncoderContextBuilderTest.php index f4b836c73..c730695d8 100644 --- a/Tests/Context/Encoder/XmlEncoderContextBuilderTest.php +++ b/Tests/Context/Encoder/XmlEncoderContextBuilderTest.php @@ -54,7 +54,7 @@ public function testWithers(array $values) /** * @return iterable|}> */ - public function withersDataProvider(): iterable + public static function withersDataProvider(): iterable { yield 'With values' => [[ XmlEncoder::AS_COLLECTION => true, diff --git a/Tests/Context/Encoder/YamlEncoderContextBuilderTest.php b/Tests/Context/Encoder/YamlEncoderContextBuilderTest.php index 72e650a83..86371bf4d 100644 --- a/Tests/Context/Encoder/YamlEncoderContextBuilderTest.php +++ b/Tests/Context/Encoder/YamlEncoderContextBuilderTest.php @@ -47,7 +47,7 @@ public function testWithers(array $values) /** * @return iterable|}> */ - public function withersDataProvider(): iterable + public static function withersDataProvider(): iterable { yield 'With values' => [[ YamlEncoder::YAML_INDENT => 4, diff --git a/Tests/Context/Normalizer/AbstractNormalizerContextBuilderTest.php b/Tests/Context/Normalizer/AbstractNormalizerContextBuilderTest.php index ffc9969b5..4c36a8ff9 100644 --- a/Tests/Context/Normalizer/AbstractNormalizerContextBuilderTest.php +++ b/Tests/Context/Normalizer/AbstractNormalizerContextBuilderTest.php @@ -53,7 +53,7 @@ public function testWithers(array $values) /** * @return iterable|}> */ - public function withersDataProvider(): iterable + public static function withersDataProvider(): iterable { yield 'With values' => [[ AbstractNormalizer::CIRCULAR_REFERENCE_LIMIT => 12, diff --git a/Tests/Context/Normalizer/AbstractObjectNormalizerContextBuilderTest.php b/Tests/Context/Normalizer/AbstractObjectNormalizerContextBuilderTest.php index b9e0cf0f6..410f2972b 100644 --- a/Tests/Context/Normalizer/AbstractObjectNormalizerContextBuilderTest.php +++ b/Tests/Context/Normalizer/AbstractObjectNormalizerContextBuilderTest.php @@ -53,7 +53,7 @@ public function testWithers(array $values) /** * @return iterable|}> */ - public function withersDataProvider(): iterable + public static function withersDataProvider(): iterable { yield 'With values' => [[ AbstractObjectNormalizer::ENABLE_MAX_DEPTH => true, @@ -99,7 +99,7 @@ public function testValidateDepthKeyPattern(string $pattern, bool $expectExcepti /** * @return iterable */ - public function validateDepthKeyPatternDataProvider(): iterable + public static function validateDepthKeyPatternDataProvider(): iterable { yield ['depth_%s::%s', false]; yield ['%%%s %%s %%%%%s', false]; diff --git a/Tests/Context/Normalizer/ConstraintViolationListNormalizerContextBuilderTest.php b/Tests/Context/Normalizer/ConstraintViolationListNormalizerContextBuilderTest.php index c3d09b064..df1a0ced7 100644 --- a/Tests/Context/Normalizer/ConstraintViolationListNormalizerContextBuilderTest.php +++ b/Tests/Context/Normalizer/ConstraintViolationListNormalizerContextBuilderTest.php @@ -48,7 +48,7 @@ public function testWithers(array $values) /** * @return iterable}> */ - public function withersDataProvider(): iterable + public static function withersDataProvider(): iterable { yield 'With values' => [[ ConstraintViolationListNormalizer::INSTANCE => new \stdClass(), diff --git a/Tests/Context/Normalizer/DateIntervalNormalizerContextBuilderTest.php b/Tests/Context/Normalizer/DateIntervalNormalizerContextBuilderTest.php index b76da9d50..018d34abf 100644 --- a/Tests/Context/Normalizer/DateIntervalNormalizerContextBuilderTest.php +++ b/Tests/Context/Normalizer/DateIntervalNormalizerContextBuilderTest.php @@ -44,7 +44,7 @@ public function testWithers(array $values) /** * @return iterable}> */ - public function withersDataProvider(): iterable + public static function withersDataProvider(): iterable { yield 'With values' => [[ DateIntervalNormalizer::FORMAT_KEY => 'format', diff --git a/Tests/Context/Normalizer/DateTimeNormalizerContextBuilderTest.php b/Tests/Context/Normalizer/DateTimeNormalizerContextBuilderTest.php index aa8070541..8ab41f949 100644 --- a/Tests/Context/Normalizer/DateTimeNormalizerContextBuilderTest.php +++ b/Tests/Context/Normalizer/DateTimeNormalizerContextBuilderTest.php @@ -46,7 +46,7 @@ public function testWithers(array $values) /** * @return iterable}> */ - public function withersDataProvider(): iterable + public static function withersDataProvider(): iterable { yield 'With values' => [[ DateTimeNormalizer::FORMAT_KEY => 'format', diff --git a/Tests/Context/Normalizer/FormErrorNormalizerContextBuilderTest.php b/Tests/Context/Normalizer/FormErrorNormalizerContextBuilderTest.php index 42915b55c..0557c2548 100644 --- a/Tests/Context/Normalizer/FormErrorNormalizerContextBuilderTest.php +++ b/Tests/Context/Normalizer/FormErrorNormalizerContextBuilderTest.php @@ -46,7 +46,7 @@ public function testWithers(array $values) /** * @return iterable}> */ - public function withersDataProvider(): iterable + public static function withersDataProvider(): iterable { yield 'With values' => [[ FormErrorNormalizer::TITLE => 'title', diff --git a/Tests/Context/Normalizer/ProblemNormalizerContextBuilderTest.php b/Tests/Context/Normalizer/ProblemNormalizerContextBuilderTest.php index 68f49dac9..3e9821d17 100644 --- a/Tests/Context/Normalizer/ProblemNormalizerContextBuilderTest.php +++ b/Tests/Context/Normalizer/ProblemNormalizerContextBuilderTest.php @@ -46,7 +46,7 @@ public function testWithers(array $values) /** * @return iterable}> */ - public function withersDataProvider(): iterable + public static function withersDataProvider(): iterable { yield 'With values' => [[ ProblemNormalizer::TITLE => 'title', diff --git a/Tests/Context/Normalizer/UidNormalizerContextBuilderTest.php b/Tests/Context/Normalizer/UidNormalizerContextBuilderTest.php index 95964f27b..6a3855704 100644 --- a/Tests/Context/Normalizer/UidNormalizerContextBuilderTest.php +++ b/Tests/Context/Normalizer/UidNormalizerContextBuilderTest.php @@ -45,7 +45,7 @@ public function testWithers(array $values) /** * @return iterable}> */ - public function withersDataProvider(): iterable + public static function withersDataProvider(): iterable { yield 'With values' => [[ UidNormalizer::NORMALIZATION_FORMAT_KEY => UidNormalizer::NORMALIZATION_FORMAT_BASE32, diff --git a/Tests/Context/Normalizer/UnwrappingDenormalizerContextBuilderTest.php b/Tests/Context/Normalizer/UnwrappingDenormalizerContextBuilderTest.php index bf43399ef..5307618fe 100644 --- a/Tests/Context/Normalizer/UnwrappingDenormalizerContextBuilderTest.php +++ b/Tests/Context/Normalizer/UnwrappingDenormalizerContextBuilderTest.php @@ -45,7 +45,7 @@ public function testWithers(array $values) /** * @return iterable}> */ - public function withersDataProvider(): iterable + public static function withersDataProvider(): iterable { yield 'With values' => [[ UnwrappingDenormalizer::UNWRAP_PATH => 'foo', diff --git a/Tests/Context/SerializerContextBuilderTest.php b/Tests/Context/SerializerContextBuilderTest.php index ca13c6530..a417869f1 100644 --- a/Tests/Context/SerializerContextBuilderTest.php +++ b/Tests/Context/SerializerContextBuilderTest.php @@ -46,7 +46,7 @@ public function testWithers(array $values) /** * @return iterable}> */ - public function withersDataProvider(): iterable + public static function withersDataProvider(): iterable { yield 'With values' => [[ Serializer::EMPTY_ARRAY_AS_OBJECT => true, From f2bccdfb6f13b631cd54ca2df342e1e5dd7e236a Mon Sep 17 00:00:00 2001 From: Antoine Lamirault Date: Tue, 21 Feb 2023 19:48:11 +0100 Subject: [PATCH 080/297] Remove unused local variable --- Normalizer/AbstractObjectNormalizer.php | 1 - 1 file changed, 1 deletion(-) diff --git a/Normalizer/AbstractObjectNormalizer.php b/Normalizer/AbstractObjectNormalizer.php index e64cfc880..d3d8b872c 100644 --- a/Normalizer/AbstractObjectNormalizer.php +++ b/Normalizer/AbstractObjectNormalizer.php @@ -766,7 +766,6 @@ private function getNestedAttributes(string $class): array if (!$serializedPath = $metadata->getSerializedPath()) { continue; } - $serializedPath = $metadata->getSerializedPath(); $pathIdentifier = implode(',', $serializedPath->getElements()); if (isset($serializedPaths[$pathIdentifier])) { throw new LogicException(sprintf('Duplicate serialized path: "%s" used for properties "%s" and "%s".', $pathIdentifier, $serializedPaths[$pathIdentifier], $name)); From 30a16f452c023cb136550ebec6d3b5c86e6c40c9 Mon Sep 17 00:00:00 2001 From: Nicolas PHILIPPE Date: Thu, 29 Dec 2022 16:57:55 +0100 Subject: [PATCH 081/297] [Serializer] add a context to allow invalid values in BackedEnumNormalizer --- CHANGELOG.md | 1 + .../BackedEnumNormalizerContextBuilder.php | 35 +++++++++++++++++++ Normalizer/BackedEnumNormalizer.php | 17 +++++++++ ...BackedEnumNormalizerContextBuilderTest.php | 35 +++++++++++++++++++ Tests/Normalizer/BackedEnumNormalizerTest.php | 15 ++++++++ 5 files changed, 103 insertions(+) create mode 100644 Context/Normalizer/BackedEnumNormalizerContextBuilder.php create mode 100644 Tests/Context/Normalizer/BackedEnumNormalizerContextBuilderTest.php diff --git a/CHANGELOG.md b/CHANGELOG.md index 19eaa6a9c..12269eda7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,7 @@ CHANGELOG --- * Add `XmlEncoder::SAVE_OPTIONS` context option + * Add `BackedEnumNormalizer::ALLOW_INVALID_VALUES` context option * Deprecate `MissingConstructorArgumentsException` in favor of `MissingConstructorArgumentException` 6.2 diff --git a/Context/Normalizer/BackedEnumNormalizerContextBuilder.php b/Context/Normalizer/BackedEnumNormalizerContextBuilder.php new file mode 100644 index 000000000..ca1a4f50a --- /dev/null +++ b/Context/Normalizer/BackedEnumNormalizerContextBuilder.php @@ -0,0 +1,35 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Serializer\Context\Normalizer; + +use Symfony\Component\Serializer\Context\ContextBuilderInterface; +use Symfony\Component\Serializer\Context\ContextBuilderTrait; +use Symfony\Component\Serializer\Normalizer\BackedEnumNormalizer; + +/** + * A helper providing autocompletion for available BackedEnumNormalizer options. + * + * @author Nicolas PHILIPPE + */ +final class BackedEnumNormalizerContextBuilder implements ContextBuilderInterface +{ + use ContextBuilderTrait; + + /** + * Configures if invalid values are allowed in denormalization. + * They will be denormalized into `null` values. + */ + public function withAllowInvalidValues(bool $allowInvalidValues): static + { + return $this->with(BackedEnumNormalizer::ALLOW_INVALID_VALUES, $allowInvalidValues); + } +} diff --git a/Normalizer/BackedEnumNormalizer.php b/Normalizer/BackedEnumNormalizer.php index 26943377b..2281849ad 100644 --- a/Normalizer/BackedEnumNormalizer.php +++ b/Normalizer/BackedEnumNormalizer.php @@ -22,6 +22,11 @@ */ final class BackedEnumNormalizer implements NormalizerInterface, DenormalizerInterface, CacheableSupportsMethodInterface { + /** + * If true, will denormalize any invalid value into null. + */ + public const ALLOW_INVALID_VALUES = 'allow_invalid_values'; + public function normalize(mixed $object, string $format = null, array $context = []): int|string { if (!$object instanceof \BackedEnum) { @@ -45,6 +50,18 @@ public function denormalize(mixed $data, string $type, string $format = null, ar throw new InvalidArgumentException('The data must belong to a backed enumeration.'); } + if ($context[self::ALLOW_INVALID_VALUES] ?? false) { + if (null === $data || (!\is_int($data) && !\is_string($data))) { + return null; + } + + try { + return $type::tryFrom($data); + } catch (\TypeError) { + return null; + } + } + if (!\is_int($data) && !\is_string($data)) { throw NotNormalizableValueException::createForUnexpectedDataType('The data is neither an integer nor a string, you should pass an integer or a string that can be parsed as an enumeration case of type '.$type.'.', $data, [Type::BUILTIN_TYPE_INT, Type::BUILTIN_TYPE_STRING], $context['deserialization_path'] ?? null, true); } diff --git a/Tests/Context/Normalizer/BackedEnumNormalizerContextBuilderTest.php b/Tests/Context/Normalizer/BackedEnumNormalizerContextBuilderTest.php new file mode 100644 index 000000000..94d98776f --- /dev/null +++ b/Tests/Context/Normalizer/BackedEnumNormalizerContextBuilderTest.php @@ -0,0 +1,35 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Serializer\Tests\Context\Normalizer; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\Serializer\Context\Normalizer\BackedEnumNormalizerContextBuilder; +use Symfony\Component\Serializer\Normalizer\BackedEnumNormalizer; + +class BackedEnumNormalizerContextBuilderTest extends TestCase +{ + private BackedEnumNormalizerContextBuilder $contextBuilder; + + protected function setUp(): void + { + $this->contextBuilder = new BackedEnumNormalizerContextBuilder(); + } + + public function testWithers() + { + $context = $this->contextBuilder->withAllowInvalidValues(true)->toArray(); + self::assertSame([BackedEnumNormalizer::ALLOW_INVALID_VALUES => true], $context); + + $context = $this->contextBuilder->withAllowInvalidValues(false)->toArray(); + self::assertSame([BackedEnumNormalizer::ALLOW_INVALID_VALUES => false], $context); + } +} diff --git a/Tests/Normalizer/BackedEnumNormalizerTest.php b/Tests/Normalizer/BackedEnumNormalizerTest.php index b0063da5f..aa0cefe0b 100644 --- a/Tests/Normalizer/BackedEnumNormalizerTest.php +++ b/Tests/Normalizer/BackedEnumNormalizerTest.php @@ -114,4 +114,19 @@ public function testSupportsNormalizationShouldFailOnAnyPHPVersionForNonEnumObje { $this->assertFalse($this->normalizer->supportsNormalization(new \stdClass())); } + + public function testItUsesTryFromIfContextIsPassed() + { + $this->assertNull($this->normalizer->denormalize(1, IntegerBackedEnumDummy::class, null, [BackedEnumNormalizer::ALLOW_INVALID_VALUES => true])); + $this->assertNull($this->normalizer->denormalize('', IntegerBackedEnumDummy::class, null, [BackedEnumNormalizer::ALLOW_INVALID_VALUES => true])); + $this->assertNull($this->normalizer->denormalize(null, IntegerBackedEnumDummy::class, null, [BackedEnumNormalizer::ALLOW_INVALID_VALUES => true])); + + $this->assertSame(IntegerBackedEnumDummy::SUCCESS, $this->normalizer->denormalize(200, IntegerBackedEnumDummy::class, null, [BackedEnumNormalizer::ALLOW_INVALID_VALUES => true])); + + $this->assertNull($this->normalizer->denormalize(1, StringBackedEnumDummy::class, null, [BackedEnumNormalizer::ALLOW_INVALID_VALUES => true])); + $this->assertNull($this->normalizer->denormalize('foo', StringBackedEnumDummy::class, null, [BackedEnumNormalizer::ALLOW_INVALID_VALUES => true])); + $this->assertNull($this->normalizer->denormalize(null, StringBackedEnumDummy::class, null, [BackedEnumNormalizer::ALLOW_INVALID_VALUES => true])); + + $this->assertSame(StringBackedEnumDummy::GET, $this->normalizer->denormalize('GET', StringBackedEnumDummy::class, null, [BackedEnumNormalizer::ALLOW_INVALID_VALUES => true])); + } } From 27f4cbac982f0a29c26196b02037e96f1466a2eb Mon Sep 17 00:00:00 2001 From: Tugdual Saunier Date: Thu, 2 Feb 2023 11:05:14 -0500 Subject: [PATCH 082/297] [Serializer] Add methods `getSupportedTypes` to allow better performance --- CHANGELOG.md | 2 + Debug/TraceableNormalizer.php | 15 +++ Debug/TraceableSerializer.php | 10 ++ Normalizer/AbstractNormalizer.php | 5 + Normalizer/AbstractObjectNormalizer.php | 2 +- Normalizer/ArrayDenormalizer.php | 15 +++ Normalizer/BackedEnumNormalizer.php | 12 +++ .../CacheableSupportsMethodInterface.php | 2 + .../ConstraintViolationListNormalizer.php | 12 +++ Normalizer/CustomNormalizer.php | 12 +++ Normalizer/DataUriNormalizer.php | 14 +++ Normalizer/DateIntervalNormalizer.php | 12 +++ Normalizer/DateTimeNormalizer.php | 14 +++ Normalizer/DateTimeZoneNormalizer.php | 12 +++ Normalizer/DenormalizerInterface.php | 28 ++++- Normalizer/FormErrorNormalizer.php | 12 +++ Normalizer/GetSetMethodNormalizer.php | 10 ++ Normalizer/JsonSerializableNormalizer.php | 12 +++ Normalizer/MimeMessageNormalizer.php | 16 +++ Normalizer/NormalizerInterface.php | 24 ++++- Normalizer/ObjectNormalizer.php | 10 ++ Normalizer/ProblemNormalizer.php | 12 +++ Normalizer/PropertyNormalizer.php | 10 ++ Normalizer/UidNormalizer.php | 12 +++ Normalizer/UnwrappingDenormalizer.php | 10 ++ Serializer.php | 69 ++++++++++-- Tests/Debug/TraceableNormalizerTest.php | 12 ++- Tests/Debug/TraceableSerializerTest.php | 5 + Tests/Encoder/XmlEncoderTest.php | 5 + Tests/Fixtures/AbstractNormalizerDummy.php | 5 + Tests/Fixtures/EnvelopeNormalizer.php | 7 ++ Tests/Fixtures/EnvelopedMessageNormalizer.php | 7 ++ .../UpcomingDenormalizerInterface.php | 20 ++++ .../Fixtures/UpcomingNormalizerInterface.php | 20 ++++ .../AbstractObjectNormalizerTest.php | 25 +++++ Tests/Normalizer/ArrayDenormalizerTest.php | 15 +-- Tests/Normalizer/TestDenormalizer.php | 5 + Tests/Normalizer/TestNormalizer.php | 5 + Tests/SerializerTest.php | 100 +++++++++++++++--- 39 files changed, 552 insertions(+), 43 deletions(-) create mode 100644 Tests/Fixtures/UpcomingDenormalizerInterface.php create mode 100644 Tests/Fixtures/UpcomingNormalizerInterface.php diff --git a/CHANGELOG.md b/CHANGELOG.md index 12269eda7..99f68f71f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,7 +6,9 @@ CHANGELOG * Add `XmlEncoder::SAVE_OPTIONS` context option * Add `BackedEnumNormalizer::ALLOW_INVALID_VALUES` context option + * Add method `getSupportedTypes(?string $format)` to `NormalizerInterface` and `DenormalizerInterface` * Deprecate `MissingConstructorArgumentsException` in favor of `MissingConstructorArgumentException` + * Deprecate `CacheableSupportsMethodInterface` in favor of the new `getSupportedTypes(?string $format)` methods 6.2 --- diff --git a/Debug/TraceableNormalizer.php b/Debug/TraceableNormalizer.php index ca41fc6a8..8a00056f1 100644 --- a/Debug/TraceableNormalizer.php +++ b/Debug/TraceableNormalizer.php @@ -35,6 +35,16 @@ public function __construct( ) { } + public function getSupportedTypes(?string $format): ?array + { + // @deprecated remove condition in 7.0 + if (!method_exists($this->normalizer, 'getSupportedTypes')) { + return null; + } + + return $this->normalizer->getSupportedTypes($format); + } + public function normalize(mixed $object, string $format = null, array $context = []): array|string|int|float|bool|\ArrayObject|null { if (!$this->normalizer instanceof NormalizerInterface) { @@ -114,8 +124,13 @@ public function setDenormalizer(DenormalizerInterface $denormalizer): void $this->normalizer->setDenormalizer($denormalizer); } + /** + * @deprecated since Symfony 6.3, use "getSupportedTypes()" instead + */ public function hasCacheableSupportsMethod(): bool { + trigger_deprecation('symfony/serializer', '6.3', 'The "%s()" method is deprecated, use "getSupportedTypes()" instead.', __METHOD__); + return $this->normalizer instanceof CacheableSupportsMethodInterface && $this->normalizer->hasCacheableSupportsMethod(); } diff --git a/Debug/TraceableSerializer.php b/Debug/TraceableSerializer.php index 01ae79584..7aa1cb01f 100644 --- a/Debug/TraceableSerializer.php +++ b/Debug/TraceableSerializer.php @@ -128,6 +128,16 @@ public function decode(string $data, string $format, array $context = []): mixed return $result; } + public function getSupportedTypes(?string $format): ?array + { + // @deprecated remove condition in 7.0 + if (!method_exists($this->serializer, 'getSupportedTypes')) { + return null; + } + + return $this->serializer->getSupportedTypes($format); + } + public function supportsNormalization(mixed $data, string $format = null, array $context = []): bool { return $this->serializer->supportsNormalization($data, $format, $context); diff --git a/Normalizer/AbstractNormalizer.php b/Normalizer/AbstractNormalizer.php index 52e985815..7d138b0b2 100644 --- a/Normalizer/AbstractNormalizer.php +++ b/Normalizer/AbstractNormalizer.php @@ -150,8 +150,13 @@ public function __construct(ClassMetadataFactoryInterface $classMetadataFactory } } + /** + * @deprecated since Symfony 6.3, use "getSupportedTypes()" instead + */ public function hasCacheableSupportsMethod(): bool { + trigger_deprecation('symfony/serializer', '6.3', 'The "%s()" method is deprecated, use "getSupportedTypes()" instead.', __METHOD__); + return false; } diff --git a/Normalizer/AbstractObjectNormalizer.php b/Normalizer/AbstractObjectNormalizer.php index a02a46b94..75fe3a5cb 100644 --- a/Normalizer/AbstractObjectNormalizer.php +++ b/Normalizer/AbstractObjectNormalizer.php @@ -300,7 +300,7 @@ abstract protected function getAttributeValue(object $object, string $attribute, */ public function supportsDenormalization(mixed $data, string $type, string $format = null /* , array $context = [] */) { - return class_exists($type) || (interface_exists($type, false) && $this->classDiscriminatorResolver && null !== $this->classDiscriminatorResolver->getMappingForClass($type)); + return class_exists($type) || (interface_exists($type, false) && null !== $this->classDiscriminatorResolver?->getMappingForClass($type)); } public function denormalize(mixed $data, string $type, string $format = null, array $context = []) diff --git a/Normalizer/ArrayDenormalizer.php b/Normalizer/ArrayDenormalizer.php index a88beba7a..71ee54fdc 100644 --- a/Normalizer/ArrayDenormalizer.php +++ b/Normalizer/ArrayDenormalizer.php @@ -27,6 +27,16 @@ class ArrayDenormalizer implements ContextAwareDenormalizerInterface, Denormaliz { use DenormalizerAwareTrait; + public function getSupportedTypes(?string $format): ?array + { + // @deprecated remove condition in 7.0 + if (!method_exists($this->denormalizer, 'getSupportedTypes')) { + return null; + } + + return $this->denormalizer->getSupportedTypes($format); + } + /** * @throws NotNormalizableValueException */ @@ -69,8 +79,13 @@ public function supportsDenormalization(mixed $data, string $type, string $forma && $this->denormalizer->supportsDenormalization($data, substr($type, 0, -2), $format, $context); } + /** + * @deprecated since Symfony 6.3, use "getSupportedTypes()" instead + */ public function hasCacheableSupportsMethod(): bool { + trigger_deprecation('symfony/serializer', '6.3', 'The "%s()" method is deprecated, use "getSupportedTypes()" instead.', __METHOD__); + return $this->denormalizer instanceof CacheableSupportsMethodInterface && $this->denormalizer->hasCacheableSupportsMethod(); } } diff --git a/Normalizer/BackedEnumNormalizer.php b/Normalizer/BackedEnumNormalizer.php index 2281849ad..32ee831c8 100644 --- a/Normalizer/BackedEnumNormalizer.php +++ b/Normalizer/BackedEnumNormalizer.php @@ -27,6 +27,13 @@ final class BackedEnumNormalizer implements NormalizerInterface, DenormalizerInt */ public const ALLOW_INVALID_VALUES = 'allow_invalid_values'; + public function getSupportedTypes(?string $format): array + { + return [ + \BackedEnum::class => true, + ]; + } + public function normalize(mixed $object, string $format = null, array $context = []): int|string { if (!$object instanceof \BackedEnum) { @@ -78,8 +85,13 @@ public function supportsDenormalization(mixed $data, string $type, string $forma return is_subclass_of($type, \BackedEnum::class); } + /** + * @deprecated since Symfony 6.3, use "getSupportedTypes()" instead + */ public function hasCacheableSupportsMethod(): bool { + trigger_deprecation('symfony/serializer', '6.3', 'The "%s()" method is deprecated, use "getSupportedTypes()" instead.', __METHOD__); + return true; } } diff --git a/Normalizer/CacheableSupportsMethodInterface.php b/Normalizer/CacheableSupportsMethodInterface.php index 3a55f653b..ea2df15b2 100644 --- a/Normalizer/CacheableSupportsMethodInterface.php +++ b/Normalizer/CacheableSupportsMethodInterface.php @@ -19,6 +19,8 @@ * supports*() methods will be cached by type and format. * * @author Kévin Dunglas + * + * @deprecated since Symfony 6.3, implement "getSupportedTypes(?string $format)" instead */ interface CacheableSupportsMethodInterface { diff --git a/Normalizer/ConstraintViolationListNormalizer.php b/Normalizer/ConstraintViolationListNormalizer.php index 0300bd847..7d0804c18 100644 --- a/Normalizer/ConstraintViolationListNormalizer.php +++ b/Normalizer/ConstraintViolationListNormalizer.php @@ -39,6 +39,13 @@ public function __construct(array $defaultContext = [], NameConverterInterface $ $this->nameConverter = $nameConverter; } + public function getSupportedTypes(?string $format): ?array + { + return [ + ConstraintViolationListInterface::class => __CLASS__ === static::class, + ]; + } + public function normalize(mixed $object, string $format = null, array $context = []): array { if (\array_key_exists(self::PAYLOAD_FIELDS, $context)) { @@ -109,8 +116,13 @@ public function supportsNormalization(mixed $data, string $format = null /* , ar return $data instanceof ConstraintViolationListInterface; } + /** + * @deprecated since Symfony 6.3, use "getSupportedTypes()" instead + */ public function hasCacheableSupportsMethod(): bool { + trigger_deprecation('symfony/serializer', '6.3', 'The "%s()" method is deprecated, use "getSupportedTypes()" instead.', __METHOD__); + return __CLASS__ === static::class; } } diff --git a/Normalizer/CustomNormalizer.php b/Normalizer/CustomNormalizer.php index c498b1935..97cc865b9 100644 --- a/Normalizer/CustomNormalizer.php +++ b/Normalizer/CustomNormalizer.php @@ -22,6 +22,13 @@ class CustomNormalizer implements NormalizerInterface, DenormalizerInterface, Se use ObjectToPopulateTrait; use SerializerAwareTrait; + public function getSupportedTypes(?string $format): array + { + return [ + NormalizableInterface::class => __CLASS__ === static::class, + ]; + } + public function normalize(mixed $object, string $format = null, array $context = []): array|string|int|float|bool|\ArrayObject|null { return $object->normalize($this->serializer, $format, $context); @@ -60,8 +67,13 @@ public function supportsDenormalization(mixed $data, string $type, string $forma return is_subclass_of($type, DenormalizableInterface::class); } + /** + * @deprecated since Symfony 6.3, use "getSupportedTypes()" instead + */ public function hasCacheableSupportsMethod(): bool { + trigger_deprecation('symfony/serializer', '6.3', 'The "%s()" method is deprecated, use "getSupportedTypes()" instead.', __METHOD__); + return __CLASS__ === static::class; } } diff --git a/Normalizer/DataUriNormalizer.php b/Normalizer/DataUriNormalizer.php index b0cecd9e8..ff5ff258a 100644 --- a/Normalizer/DataUriNormalizer.php +++ b/Normalizer/DataUriNormalizer.php @@ -45,6 +45,15 @@ public function __construct(MimeTypeGuesserInterface $mimeTypeGuesser = null) $this->mimeTypeGuesser = $mimeTypeGuesser; } + public function getSupportedTypes(?string $format): array + { + return [ + \SplFileInfo::class => __CLASS__ === static::class, + \SplFileObject::class => __CLASS__ === static::class, + File::class => __CLASS__ === static::class, + ]; + } + public function normalize(mixed $object, string $format = null, array $context = []): string { if (!$object instanceof \SplFileInfo) { @@ -118,8 +127,13 @@ public function supportsDenormalization(mixed $data, string $type, string $forma return isset(self::SUPPORTED_TYPES[$type]); } + /** + * @deprecated since Symfony 6.3, use "getSupportedTypes()" instead + */ public function hasCacheableSupportsMethod(): bool { + trigger_deprecation('symfony/serializer', '6.3', 'The "%s()" method is deprecated, use "getSupportedTypes()" instead.', __METHOD__); + return __CLASS__ === static::class; } diff --git a/Normalizer/DateIntervalNormalizer.php b/Normalizer/DateIntervalNormalizer.php index bff7bcabb..839eeb65d 100644 --- a/Normalizer/DateIntervalNormalizer.php +++ b/Normalizer/DateIntervalNormalizer.php @@ -33,6 +33,13 @@ public function __construct(array $defaultContext = []) $this->defaultContext = array_merge($this->defaultContext, $defaultContext); } + public function getSupportedTypes(?string $format): array + { + return [ + \DateInterval::class => __CLASS__ === static::class, + ]; + } + /** * @throws InvalidArgumentException */ @@ -53,8 +60,13 @@ public function supportsNormalization(mixed $data, string $format = null /* , ar return $data instanceof \DateInterval; } + /** + * @deprecated since Symfony 6.3, use "getSupportedTypes()" instead + */ public function hasCacheableSupportsMethod(): bool { + trigger_deprecation('symfony/serializer', '6.3', 'The "%s()" method is deprecated, use "getSupportedTypes()" instead.', __METHOD__); + return __CLASS__ === static::class; } diff --git a/Normalizer/DateTimeNormalizer.php b/Normalizer/DateTimeNormalizer.php index 715a56d5b..841f4512d 100644 --- a/Normalizer/DateTimeNormalizer.php +++ b/Normalizer/DateTimeNormalizer.php @@ -47,6 +47,15 @@ public function setDefaultContext(array $defaultContext): void $this->defaultContext = array_merge($this->defaultContext, $defaultContext); } + public function getSupportedTypes(?string $format): array + { + return [ + \DateTimeInterface::class => __CLASS__ === static::class, + \DateTimeImmutable::class => __CLASS__ === static::class, + \DateTime::class => __CLASS__ === static::class, + ]; + } + /** * @throws InvalidArgumentException */ @@ -124,8 +133,13 @@ public function supportsDenormalization(mixed $data, string $type, string $forma return isset(self::SUPPORTED_TYPES[$type]); } + /** + * @deprecated since Symfony 6.3, use "getSupportedTypes()" instead + */ public function hasCacheableSupportsMethod(): bool { + trigger_deprecation('symfony/serializer', '6.3', 'The "%s()" method is deprecated, use "getSupportedTypes()" instead.', __METHOD__); + return __CLASS__ === static::class; } diff --git a/Normalizer/DateTimeZoneNormalizer.php b/Normalizer/DateTimeZoneNormalizer.php index 6309ce335..24de36121 100644 --- a/Normalizer/DateTimeZoneNormalizer.php +++ b/Normalizer/DateTimeZoneNormalizer.php @@ -22,6 +22,13 @@ */ class DateTimeZoneNormalizer implements NormalizerInterface, DenormalizerInterface, CacheableSupportsMethodInterface { + public function getSupportedTypes(?string $format): array + { + return [ + \DateTimeZone::class => __CLASS__ === static::class, + ]; + } + /** * @throws InvalidArgumentException */ @@ -66,8 +73,13 @@ public function supportsDenormalization(mixed $data, string $type, string $forma return \DateTimeZone::class === $type; } + /** + * @deprecated since Symfony 6.3, use "getSupportedTypes()" instead + */ public function hasCacheableSupportsMethod(): bool { + trigger_deprecation('symfony/serializer', '6.3', 'The "%s()" method is deprecated, use "getSupportedTypes()" instead.', __METHOD__); + return __CLASS__ === static::class; } } diff --git a/Normalizer/DenormalizerInterface.php b/Normalizer/DenormalizerInterface.php index 1786d6fff..97ae86be2 100644 --- a/Normalizer/DenormalizerInterface.php +++ b/Normalizer/DenormalizerInterface.php @@ -21,6 +21,8 @@ /** * @author Jordi Boggiano + * + * @method getSupportedTypes(?string $format): ?array */ interface DenormalizerInterface { @@ -49,12 +51,32 @@ public function denormalize(mixed $data, string $type, string $format = null, ar /** * Checks whether the given class is supported for denormalization by this normalizer. * - * @param mixed $data Data to denormalize from - * @param string $type The class to which the data should be denormalized - * @param string|null $format The format being deserialized from + * Since Symfony 6.3, this method will only be called if the type is + * included in the supported types returned by getSupportedTypes(). + * + * @see getSupportedTypes() + * + * @param mixed $data Data to denormalize from + * @param string $type The class to which the data should be denormalized + * @param string|null $format The format being deserialized from * @param array $context Options available to the denormalizer * * @return bool */ public function supportsDenormalization(mixed $data, string $type, string $format = null /* , array $context = [] */); + + /* + * Return the types supported for normalization by this denormalizer for + * this format associated to a boolean value indicating if the result of + * supports*() methods can be cached or if the result can not be cached + * because it depends on the context. + * Returning null means this denormalizer will be considered for + * every format/class. + * Return an empty array if no type is supported for this format. + * + * @param string $format The format being (de-)serialized from or into + * + * @return array|null + */ + /* public function getSupportedTypes(?string $format): ?array; */ } diff --git a/Normalizer/FormErrorNormalizer.php b/Normalizer/FormErrorNormalizer.php index a770e254c..77f17639a 100644 --- a/Normalizer/FormErrorNormalizer.php +++ b/Normalizer/FormErrorNormalizer.php @@ -38,6 +38,13 @@ public function normalize(mixed $object, string $format = null, array $context = return $data; } + public function getSupportedTypes(?string $format): array + { + return [ + FormInterface::class => false, + ]; + } + public function supportsNormalization(mixed $data, string $format = null, array $context = []): bool { return $data instanceof FormInterface && $data->isSubmitted() && !$data->isValid(); @@ -76,8 +83,13 @@ private function convertFormChildrenToArray(FormInterface $data): array return $children; } + /** + * @deprecated since Symfony 6.3, use "getSupportedTypes()" instead + */ public function hasCacheableSupportsMethod(): bool { + trigger_deprecation('symfony/serializer', '6.3', 'The "%s()" method is deprecated, use "getSupportedTypes()" instead.', __METHOD__); + return __CLASS__ === static::class; } } diff --git a/Normalizer/GetSetMethodNormalizer.php b/Normalizer/GetSetMethodNormalizer.php index e08dd5d9e..4395b43d2 100644 --- a/Normalizer/GetSetMethodNormalizer.php +++ b/Normalizer/GetSetMethodNormalizer.php @@ -36,6 +36,11 @@ class GetSetMethodNormalizer extends AbstractObjectNormalizer { private static $setterAccessibleCache = []; + public function getSupportedTypes(?string $format): ?array + { + return null; + } + /** * @param array $context */ @@ -52,8 +57,13 @@ public function supportsDenormalization(mixed $data, string $type, string $forma return parent::supportsDenormalization($data, $type, $format) && $this->supports($type); } + /** + * @deprecated since Symfony 6.3, use "getSupportedTypes()" instead + */ public function hasCacheableSupportsMethod(): bool { + trigger_deprecation('symfony/serializer', '6.3', 'The "%s()" method is deprecated, use "getSupportedTypes()" instead.', __METHOD__); + return __CLASS__ === static::class; } diff --git a/Normalizer/JsonSerializableNormalizer.php b/Normalizer/JsonSerializableNormalizer.php index b81385bc8..8b3985f2b 100644 --- a/Normalizer/JsonSerializableNormalizer.php +++ b/Normalizer/JsonSerializableNormalizer.php @@ -38,6 +38,13 @@ public function normalize(mixed $object, string $format = null, array $context = return $this->serializer->normalize($object->jsonSerialize(), $format, $context); } + public function getSupportedTypes(?string $format): array + { + return [ + \JsonSerializable::class => __CLASS__ === static::class, + ]; + } + /** * @param array $context */ @@ -59,8 +66,13 @@ public function denormalize(mixed $data, string $type, string $format = null, ar throw new LogicException(sprintf('Cannot denormalize with "%s".', \JsonSerializable::class)); } + /** + * @deprecated since Symfony 6.3, use "getSupportedTypes()" instead + */ public function hasCacheableSupportsMethod(): bool { + trigger_deprecation('symfony/serializer', '6.3', 'The "%s()" method is deprecated, use "getSupportedTypes()" instead.', __METHOD__); + return __CLASS__ === static::class; } } diff --git a/Normalizer/MimeMessageNormalizer.php b/Normalizer/MimeMessageNormalizer.php index a442fc54d..371e3bb36 100644 --- a/Normalizer/MimeMessageNormalizer.php +++ b/Normalizer/MimeMessageNormalizer.php @@ -42,6 +42,17 @@ public function __construct(PropertyNormalizer $normalizer) $this->headersProperty = new \ReflectionProperty(Headers::class, 'headers'); } + public function getSupportedTypes(?string $format): array + { + return [ + Message::class => true, + Headers::class => true, + HeaderInterface::class => true, + Address::class => true, + AbstractPart::class => true, + ]; + } + public function setSerializer(SerializerInterface $serializer): void { $this->serializer = $serializer; @@ -101,8 +112,13 @@ public function supportsDenormalization(mixed $data, string $type, string $forma return is_a($type, Message::class, true) || Headers::class === $type || AbstractPart::class === $type; } + /** + * @deprecated since Symfony 6.3, use "getSupportedTypes()" instead + */ public function hasCacheableSupportsMethod(): bool { + trigger_deprecation('symfony/serializer', '6.3', 'The "%s()" method is deprecated, use "getSupportedTypes()" instead.', __METHOD__); + return __CLASS__ === static::class; } } diff --git a/Normalizer/NormalizerInterface.php b/Normalizer/NormalizerInterface.php index cb43d78cc..eeffe89dd 100644 --- a/Normalizer/NormalizerInterface.php +++ b/Normalizer/NormalizerInterface.php @@ -18,6 +18,8 @@ /** * @author Jordi Boggiano + * + * @method getSupportedTypes(?string $format): ?array */ interface NormalizerInterface { @@ -41,11 +43,31 @@ public function normalize(mixed $object, string $format = null, array $context = /** * Checks whether the given class is supported for normalization by this normalizer. * + * Since Symfony 6.3, this method will only be called if the $data type is + * included in the supported types returned by getSupportedTypes(). + * + * @see getSupportedTypes() + * * @param mixed $data Data to normalize - * @param string|null $format The format being (de-)serialized from or into + * @param string|null $format The format being (de-)serialized from or into * @param array $context Context options for the normalizer * * @return bool */ public function supportsNormalization(mixed $data, string $format = null /* , array $context = [] */); + + /* + * Return the types supported for normalization by this normalizer for this + * format associated to a boolean value indicating if the result of + * supports*() methods can be cached or if the result can not be cached + * because it depends on the context. + * Returning null means this normalizer will be considered for + * every format/class. + * Return an empty array if no type is supported for this format. + * + * @param string $format The format being (de-)serialized from or into + * + * @return array|null + */ + /* public function getSupportedTypes(?string $format): ?array; */ } diff --git a/Normalizer/ObjectNormalizer.php b/Normalizer/ObjectNormalizer.php index 8018cb7a4..780f5fbc8 100644 --- a/Normalizer/ObjectNormalizer.php +++ b/Normalizer/ObjectNormalizer.php @@ -47,8 +47,18 @@ public function __construct(ClassMetadataFactoryInterface $classMetadataFactory $this->objectClassResolver = $objectClassResolver ?? fn ($class) => \is_object($class) ? $class::class : $class; } + public function getSupportedTypes(?string $format): ?array + { + return null; + } + + /** + * @deprecated since Symfony 6.3, use "getSupportedTypes()" instead + */ public function hasCacheableSupportsMethod(): bool { + trigger_deprecation('symfony/serializer', '6.3', 'The "%s()" method is deprecated, use "getSupportedTypes()" instead.', __METHOD__); + return __CLASS__ === static::class; } diff --git a/Normalizer/ProblemNormalizer.php b/Normalizer/ProblemNormalizer.php index 59b8427ab..941372489 100644 --- a/Normalizer/ProblemNormalizer.php +++ b/Normalizer/ProblemNormalizer.php @@ -40,6 +40,13 @@ public function __construct(bool $debug = false, array $defaultContext = []) $this->defaultContext = $defaultContext + $this->defaultContext; } + public function getSupportedTypes(?string $format): array + { + return [ + FlattenException::class => __CLASS__ === self::class, + ]; + } + public function normalize(mixed $object, string $format = null, array $context = []): array { if (!$object instanceof FlattenException) { @@ -71,8 +78,13 @@ public function supportsNormalization(mixed $data, string $format = null /* , ar return $data instanceof FlattenException; } + /** + * @deprecated since Symfony 6.3, use "getSupportedTypes()" instead + */ public function hasCacheableSupportsMethod(): bool { + trigger_deprecation('symfony/serializer', '6.3', 'The "%s()" method is deprecated, use "getSupportedTypes()" instead.', __METHOD__); + return true; } } diff --git a/Normalizer/PropertyNormalizer.php b/Normalizer/PropertyNormalizer.php index 3dd734055..76f068c9e 100644 --- a/Normalizer/PropertyNormalizer.php +++ b/Normalizer/PropertyNormalizer.php @@ -54,6 +54,11 @@ public function __construct(ClassMetadataFactoryInterface $classMetadataFactory } } + public function getSupportedTypes(?string $format): ?array + { + return null; + } + /** * @param array $context */ @@ -70,8 +75,13 @@ public function supportsDenormalization(mixed $data, string $type, string $forma return parent::supportsDenormalization($data, $type, $format) && $this->supports($type); } + /** + * @deprecated since Symfony 6.3, use "getSupportedTypes()" instead + */ public function hasCacheableSupportsMethod(): bool { + trigger_deprecation('symfony/serializer', '6.3', 'The "%s()" method is deprecated, use "getSupportedTypes()" instead.', __METHOD__); + return __CLASS__ === static::class; } diff --git a/Normalizer/UidNormalizer.php b/Normalizer/UidNormalizer.php index ea86f3754..e723ec3f2 100644 --- a/Normalizer/UidNormalizer.php +++ b/Normalizer/UidNormalizer.php @@ -41,6 +41,13 @@ public function __construct(array $defaultContext = []) $this->defaultContext = array_merge($this->defaultContext, $defaultContext); } + public function getSupportedTypes(?string $format): array + { + return [ + AbstractUid::class => true, + ]; + } + /** * @param AbstractUid $object */ @@ -92,8 +99,13 @@ public function supportsDenormalization(mixed $data, string $type, string $forma return is_subclass_of($type, AbstractUid::class, true); } + /** + * @deprecated since Symfony 6.3, use "getSupportedTypes()" instead + */ public function hasCacheableSupportsMethod(): bool { + trigger_deprecation('symfony/serializer', '6.3', 'The "%s()" method is deprecated, use "getSupportedTypes()" instead.', __METHOD__); + return __CLASS__ === static::class; } } diff --git a/Normalizer/UnwrappingDenormalizer.php b/Normalizer/UnwrappingDenormalizer.php index 2eff8b775..fb3baa4e3 100644 --- a/Normalizer/UnwrappingDenormalizer.php +++ b/Normalizer/UnwrappingDenormalizer.php @@ -32,6 +32,11 @@ public function __construct(PropertyAccessorInterface $propertyAccessor = null) $this->propertyAccessor = $propertyAccessor ?? PropertyAccess::createPropertyAccessor(); } + public function getSupportedTypes(?string $format): ?array + { + return null; + } + public function denormalize(mixed $data, string $class, string $format = null, array $context = []): mixed { $propertyPath = $context[self::UNWRAP_PATH]; @@ -53,8 +58,13 @@ public function supportsDenormalization(mixed $data, string $type, string $forma return \array_key_exists(self::UNWRAP_PATH, $context) && !isset($context['unwrapped']); } + /** + * @deprecated since Symfony 6.3, use "getSupportedTypes()" instead + */ public function hasCacheableSupportsMethod(): bool { + trigger_deprecation('symfony/serializer', '6.3', 'The "%s()" method is deprecated, use "getSupportedTypes()" instead.', __METHOD__); + return $this->serializer instanceof CacheableSupportsMethodInterface && $this->serializer->hasCacheableSupportsMethod(); } } diff --git a/Serializer.php b/Serializer.php index ab888592e..ab0da7b96 100644 --- a/Serializer.php +++ b/Serializer.php @@ -227,6 +227,11 @@ public function denormalize(mixed $data, string $type, string $format = null, ar return $normalizer->denormalize($data, $type, $format, $context); } + public function getSupportedTypes(?string $format): ?array + { + return null; + } + public function supportsNormalization(mixed $data, string $format = null, array $context = []): bool { return null !== $this->getNormalizer($data, $format, $context); @@ -256,11 +261,35 @@ private function getNormalizer(mixed $data, ?string $format, array $context): ?N continue; } - if (!$normalizer instanceof CacheableSupportsMethodInterface || !$normalizer->hasCacheableSupportsMethod()) { + if (!method_exists($normalizer, 'getSupportedTypes')) { + trigger_deprecation('symfony/serializer', '6.3', '"%s" should implement "NormalizerInterface::getSupportedTypes(?string $format): array".', $normalizer::class); + + if (!$normalizer instanceof CacheableSupportsMethodInterface || !$normalizer->hasCacheableSupportsMethod()) { + $this->normalizerCache[$format][$type][$k] = false; + } elseif ($normalizer->supportsNormalization($data, $format, $context)) { + $this->normalizerCache[$format][$type][$k] = true; + break; + } + + continue; + } + + if (null === $supportedTypes = $normalizer->getSupportedTypes($format)) { + $this->normalizerCache[$format][$type][$k] = false; + continue; + } + + foreach ($supportedTypes as $supportedType => $isCacheable) { + if ($type !== $supportedType && !is_subclass_of($type, $supportedType, true)) { + continue; + } + + if ($isCacheable && $normalizer->supportsNormalization($data, $format, $context)) { + $this->normalizerCache[$format][$type][$k] = true; + break 2; + } + $this->normalizerCache[$format][$type][$k] = false; - } elseif ($normalizer->supportsNormalization($data, $format, $context)) { - $this->normalizerCache[$format][$type][$k] = true; - break; } } } @@ -293,11 +322,35 @@ private function getDenormalizer(mixed $data, string $class, ?string $format, ar continue; } - if (!$normalizer instanceof CacheableSupportsMethodInterface || !$normalizer->hasCacheableSupportsMethod()) { + if (!method_exists($normalizer, 'getSupportedTypes')) { + trigger_deprecation('symfony/serializer', '6.3', '"%s" should implement "DenormalizerInterface::getSupportedTypes(?string $format): array".', $normalizer::class); + + if (!$normalizer instanceof CacheableSupportsMethodInterface || !$normalizer->hasCacheableSupportsMethod()) { + $this->denormalizerCache[$format][$class][$k] = false; + } elseif ($normalizer->supportsDenormalization(null, $class, $format, $context)) { + $this->denormalizerCache[$format][$class][$k] = true; + break; + } + + continue; + } + + if (null === $supportedTypes = $normalizer->getSupportedTypes($format)) { + $this->denormalizerCache[$format][$class][$k] = false; + continue; + } + + foreach ($supportedTypes as $supportedType => $isCacheable) { + if ($class !== $supportedType && !is_subclass_of($class, $supportedType, true)) { + continue; + } + + if ($isCacheable && $normalizer->supportsDenormalization(null, $class, $format, $context)) { + $this->denormalizerCache[$format][$class][$k] = true; + break; + } + $this->denormalizerCache[$format][$class][$k] = false; - } elseif ($normalizer->supportsDenormalization(null, $class, $format, $context)) { - $this->denormalizerCache[$format][$class][$k] = true; - break; } } } diff --git a/Tests/Debug/TraceableNormalizerTest.php b/Tests/Debug/TraceableNormalizerTest.php index 5f78ef81f..41e3441ed 100644 --- a/Tests/Debug/TraceableNormalizerTest.php +++ b/Tests/Debug/TraceableNormalizerTest.php @@ -15,14 +15,15 @@ use Symfony\Component\Serializer\DataCollector\SerializerDataCollector; use Symfony\Component\Serializer\Debug\TraceableNormalizer; use Symfony\Component\Serializer\Debug\TraceableSerializer; -use Symfony\Component\Serializer\Normalizer\DenormalizerInterface; -use Symfony\Component\Serializer\Normalizer\NormalizerInterface; +use Symfony\Component\Serializer\Tests\Fixtures\UpcomingDenormalizerInterface as DenormalizerInterface; +use Symfony\Component\Serializer\Tests\Fixtures\UpcomingNormalizerInterface as NormalizerInterface; class TraceableNormalizerTest extends TestCase { public function testForwardsToNormalizer() { $normalizer = $this->createMock(NormalizerInterface::class); + $normalizer->method('getSupportedTypes')->willReturn(['*' => false]); $normalizer ->expects($this->once()) ->method('normalize') @@ -30,6 +31,7 @@ public function testForwardsToNormalizer() ->willReturn('normalized'); $denormalizer = $this->createMock(DenormalizerInterface::class); + $denormalizer->method('getSupportedTypes')->willReturn(['*' => false]); $denormalizer ->expects($this->once()) ->method('denormalize') @@ -43,7 +45,9 @@ public function testForwardsToNormalizer() public function testCollectNormalizationData() { $normalizer = $this->createMock(NormalizerInterface::class); + $normalizer->method('getSupportedTypes')->willReturn(['*' => false]); $denormalizer = $this->createMock(DenormalizerInterface::class); + $denormalizer->method('getSupportedTypes')->willReturn(['*' => false]); $dataCollector = $this->createMock(SerializerDataCollector::class); $dataCollector @@ -62,7 +66,9 @@ public function testCollectNormalizationData() public function testNotCollectNormalizationDataIfNoDebugTraceId() { $normalizer = $this->createMock(NormalizerInterface::class); + $normalizer->method('getSupportedTypes')->willReturn(['*' => false]); $denormalizer = $this->createMock(DenormalizerInterface::class); + $denormalizer->method('getSupportedTypes')->willReturn(['*' => false]); $dataCollector = $this->createMock(SerializerDataCollector::class); $dataCollector->expects($this->never())->method('collectNormalization'); @@ -89,9 +95,11 @@ public function testCannotDenormalizeIfNotDenormalizer() public function testSupports() { $normalizer = $this->createMock(NormalizerInterface::class); + $normalizer->method('getSupportedTypes')->willReturn(['*' => false]); $normalizer->method('supportsNormalization')->willReturn(true); $denormalizer = $this->createMock(DenormalizerInterface::class); + $denormalizer->method('getSupportedTypes')->willReturn(['*' => false]); $denormalizer->method('supportsDenormalization')->willReturn(true); $traceableNormalizer = new TraceableNormalizer($normalizer, new SerializerDataCollector()); diff --git a/Tests/Debug/TraceableSerializerTest.php b/Tests/Debug/TraceableSerializerTest.php index 00a1ef58a..6083624dd 100644 --- a/Tests/Debug/TraceableSerializerTest.php +++ b/Tests/Debug/TraceableSerializerTest.php @@ -145,6 +145,11 @@ public function normalize(mixed $object, string $format = null, array $context = return 'normalized'; } + public function getSupportedTypes(?string $format): ?array + { + return null; + } + public function supportsNormalization(mixed $data, string $format = null, array $context = []): bool { return true; diff --git a/Tests/Encoder/XmlEncoderTest.php b/Tests/Encoder/XmlEncoderTest.php index 11e7bef3e..b98447b54 100644 --- a/Tests/Encoder/XmlEncoderTest.php +++ b/Tests/Encoder/XmlEncoderTest.php @@ -942,6 +942,11 @@ private function createMockDateTimeNormalizer(): MockObject&NormalizerInterface ->with(new \DateTime($this->exampleDateTimeString), 'xml', []) ->willReturn($this->exampleDateTimeString); + $mock + ->expects($this->once()) + ->method('getSupportedTypes') + ->willReturn([\DateTime::class => true]); + $mock ->expects($this->once()) ->method('supportsNormalization') diff --git a/Tests/Fixtures/AbstractNormalizerDummy.php b/Tests/Fixtures/AbstractNormalizerDummy.php index 82586062b..20ce0ffcb 100644 --- a/Tests/Fixtures/AbstractNormalizerDummy.php +++ b/Tests/Fixtures/AbstractNormalizerDummy.php @@ -20,6 +20,11 @@ */ class AbstractNormalizerDummy extends AbstractNormalizer { + public function getSupportedTypes(?string $format): ?array + { + return null; + } + public function getAllowedAttributes(string|object $classOrObject, array $context, bool $attributesAsString = false): array|bool { return parent::getAllowedAttributes($classOrObject, $context, $attributesAsString); diff --git a/Tests/Fixtures/EnvelopeNormalizer.php b/Tests/Fixtures/EnvelopeNormalizer.php index 1492d5d02..367afa8c8 100644 --- a/Tests/Fixtures/EnvelopeNormalizer.php +++ b/Tests/Fixtures/EnvelopeNormalizer.php @@ -31,6 +31,13 @@ public function normalize($envelope, string $format = null, array $context = []) ]; } + public function getSupportedTypes(?string $format): ?array + { + return [ + EnvelopeObject::class => true, + ]; + } + public function supportsNormalization($data, string $format = null, array $context = []): bool { return $data instanceof EnvelopeObject; diff --git a/Tests/Fixtures/EnvelopedMessageNormalizer.php b/Tests/Fixtures/EnvelopedMessageNormalizer.php index dfdec91b1..7dc87de51 100644 --- a/Tests/Fixtures/EnvelopedMessageNormalizer.php +++ b/Tests/Fixtures/EnvelopedMessageNormalizer.php @@ -25,6 +25,13 @@ public function normalize($message, string $format = null, array $context = []): ]; } + public function getSupportedTypes(?string $format): ?array + { + return [ + EnvelopedMessage::class => true, + ]; + } + public function supportsNormalization($data, string $format = null, array $context = []): bool { return $data instanceof EnvelopedMessage; diff --git a/Tests/Fixtures/UpcomingDenormalizerInterface.php b/Tests/Fixtures/UpcomingDenormalizerInterface.php new file mode 100644 index 000000000..2efc12bb6 --- /dev/null +++ b/Tests/Fixtures/UpcomingDenormalizerInterface.php @@ -0,0 +1,20 @@ + + * + * 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\DenormalizerInterface; + +// @deprecated remove in 7.0 in favor of direct use of the DenormalizerInterface +interface UpcomingDenormalizerInterface extends DenormalizerInterface +{ + public function getSupportedTypes(?string $format): array; +} diff --git a/Tests/Fixtures/UpcomingNormalizerInterface.php b/Tests/Fixtures/UpcomingNormalizerInterface.php new file mode 100644 index 000000000..59972bb5e --- /dev/null +++ b/Tests/Fixtures/UpcomingNormalizerInterface.php @@ -0,0 +1,20 @@ + + * + * 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\NormalizerInterface; + +// @deprecated remove in 7.0 in favor of direct use of the NormalizerInterface +interface UpcomingNormalizerInterface extends NormalizerInterface +{ + public function getSupportedTypes(?string $format): array; +} diff --git a/Tests/Normalizer/AbstractObjectNormalizerTest.php b/Tests/Normalizer/AbstractObjectNormalizerTest.php index 4cc668658..ece903d31 100644 --- a/Tests/Normalizer/AbstractObjectNormalizerTest.php +++ b/Tests/Normalizer/AbstractObjectNormalizerTest.php @@ -546,6 +546,11 @@ public function testDenormalizeUsesContextAttributeForPropertiesInConstructorWit class AbstractObjectNormalizerDummy extends AbstractObjectNormalizer { + public function getSupportedTypes(?string $format): ?array + { + return null; + } + protected function extractAttributes(object $object, string $format = null, array $context = []): array { return []; @@ -651,6 +656,11 @@ public function __construct() parent::__construct($classMetadataFactory, new MetadataAwareNameConverter($classMetadataFactory)); } + public function getSupportedTypes(?string $format): ?array + { + return null; + } + protected function extractAttributes(object $object, string $format = null, array $context = []): array { } @@ -752,6 +762,11 @@ public function denormalize($data, string $type, string $format = null, array $c return null; } + public function getSupportedTypes(?string $format): ?array + { + return null; + } + public function supportsDenormalization($data, string $type, string $format = null, array $context = []): bool { return true; @@ -760,6 +775,11 @@ public function supportsDenormalization($data, string $type, string $format = nu class AbstractObjectNormalizerCollectionDummy extends AbstractObjectNormalizer { + public function getSupportedTypes(?string $format): ?array + { + return null; + } + protected function extractAttributes(object $object, string $format = null, array $context = []): array { } @@ -814,6 +834,11 @@ public function denormalize($data, string $type, string $format = null, array $c return $data; } + public function getSupportedTypes(?string $format): ?array + { + return $this->serializer->getSupportedTypes($format); + } + public function supportsDenormalization($data, string $type, string $format = null, array $context = []): bool { return str_ends_with($type, '[]') diff --git a/Tests/Normalizer/ArrayDenormalizerTest.php b/Tests/Normalizer/ArrayDenormalizerTest.php index 391d3374f..8f7a75b11 100644 --- a/Tests/Normalizer/ArrayDenormalizerTest.php +++ b/Tests/Normalizer/ArrayDenormalizerTest.php @@ -14,23 +14,16 @@ use PHPUnit\Framework\MockObject\MockObject; use PHPUnit\Framework\TestCase; use Symfony\Component\Serializer\Normalizer\ArrayDenormalizer; -use Symfony\Component\Serializer\Normalizer\ContextAwareDenormalizerInterface; +use Symfony\Component\Serializer\Tests\Fixtures\UpcomingDenormalizerInterface as DenormalizerInterface; class ArrayDenormalizerTest extends TestCase { - /** - * @var ArrayDenormalizer - */ - private $denormalizer; - - /** - * @var MockObject&ContextAwareDenormalizerInterface - */ - private $serializer; + private ArrayDenormalizer $denormalizer; + private MockObject&DenormalizerInterface $serializer; protected function setUp(): void { - $this->serializer = $this->createMock(ContextAwareDenormalizerInterface::class); + $this->serializer = $this->createMock(DenormalizerInterface::class); $this->denormalizer = new ArrayDenormalizer(); $this->denormalizer->setDenormalizer($this->serializer); } diff --git a/Tests/Normalizer/TestDenormalizer.php b/Tests/Normalizer/TestDenormalizer.php index ee9f2da0e..547acbca9 100644 --- a/Tests/Normalizer/TestDenormalizer.php +++ b/Tests/Normalizer/TestDenormalizer.php @@ -24,6 +24,11 @@ public function denormalize($data, string $type, string $format = null, array $c { } + public function getSupportedTypes(?string $format): ?array + { + return null; + } + public function supportsDenormalization($data, string $type, string $format = null, array $context = []): bool { return true; diff --git a/Tests/Normalizer/TestNormalizer.php b/Tests/Normalizer/TestNormalizer.php index 5d941e7a5..005bf6c1d 100644 --- a/Tests/Normalizer/TestNormalizer.php +++ b/Tests/Normalizer/TestNormalizer.php @@ -25,6 +25,11 @@ public function normalize($object, string $format = null, array $context = []): return null; } + public function getSupportedTypes(?string $format): ?array + { + return null; + } + public function supportsNormalization($data, string $format = null, array $context = []): bool { return true; diff --git a/Tests/SerializerTest.php b/Tests/SerializerTest.php index fa06773fb..331eaa25e 100644 --- a/Tests/SerializerTest.php +++ b/Tests/SerializerTest.php @@ -42,10 +42,8 @@ use Symfony\Component\Serializer\Normalizer\DateTimeNormalizer; use Symfony\Component\Serializer\Normalizer\DateTimeZoneNormalizer; use Symfony\Component\Serializer\Normalizer\DenormalizerAwareInterface; -use Symfony\Component\Serializer\Normalizer\DenormalizerInterface; use Symfony\Component\Serializer\Normalizer\GetSetMethodNormalizer; use Symfony\Component\Serializer\Normalizer\NormalizerAwareInterface; -use Symfony\Component\Serializer\Normalizer\NormalizerInterface; use Symfony\Component\Serializer\Normalizer\ObjectNormalizer; use Symfony\Component\Serializer\Normalizer\PropertyNormalizer; use Symfony\Component\Serializer\Normalizer\UidNormalizer; @@ -66,22 +64,13 @@ use Symfony\Component\Serializer\Tests\Fixtures\Php80WithPromotedTypedConstructor; use Symfony\Component\Serializer\Tests\Fixtures\TraversableDummy; use Symfony\Component\Serializer\Tests\Fixtures\TrueBuiltInDummy; +use Symfony\Component\Serializer\Tests\Fixtures\UpcomingDenormalizerInterface as DenormalizerInterface; +use Symfony\Component\Serializer\Tests\Fixtures\UpcomingNormalizerInterface as NormalizerInterface; use Symfony\Component\Serializer\Tests\Normalizer\TestDenormalizer; use Symfony\Component\Serializer\Tests\Normalizer\TestNormalizer; class SerializerTest extends TestCase { - public function testInterface() - { - $serializer = new Serializer(); - - $this->assertInstanceOf(SerializerInterface::class, $serializer); - $this->assertInstanceOf(NormalizerInterface::class, $serializer); - $this->assertInstanceOf(DenormalizerInterface::class, $serializer); - $this->assertInstanceOf(EncoderInterface::class, $serializer); - $this->assertInstanceOf(DecoderInterface::class, $serializer); - } - public function testItThrowsExceptionOnInvalidNormalizer() { $this->expectException(InvalidArgumentException::class); @@ -153,11 +142,13 @@ public function testCustomNormalizerCanNormalizeCollectionsAndScalar() public function testNormalizeWithSupportOnData() { $normalizer1 = $this->createMock(NormalizerInterface::class); + $normalizer1->method('getSupportedTypes')->willReturn(['*' => false]); $normalizer1->method('supportsNormalization') ->willReturnCallback(fn ($data, $format) => isset($data->test)); $normalizer1->method('normalize')->willReturn('test1'); $normalizer2 = $this->createMock(NormalizerInterface::class); + $normalizer2->method('getSupportedTypes')->willReturn(['*' => false]); $normalizer2->method('supportsNormalization') ->willReturn(true); $normalizer2->method('normalize')->willReturn('test2'); @@ -174,11 +165,13 @@ public function testNormalizeWithSupportOnData() public function testDenormalizeWithSupportOnData() { $denormalizer1 = $this->createMock(DenormalizerInterface::class); + $denormalizer1->method('getSupportedTypes')->willReturn(['*' => false]); $denormalizer1->method('supportsDenormalization') ->willReturnCallback(fn ($data, $type, $format) => isset($data['test1'])); $denormalizer1->method('denormalize')->willReturn('test1'); $denormalizer2 = $this->createMock(DenormalizerInterface::class); + $denormalizer2->method('getSupportedTypes')->willReturn(['*' => false]); $denormalizer2->method('supportsDenormalization') ->willReturn(true); $denormalizer2->method('denormalize')->willReturn('test2'); @@ -370,8 +363,7 @@ public function testNormalizerAware() { $normalizerAware = $this->createMock(NormalizerAwareNormalizer::class); $normalizerAware->expects($this->once()) - ->method('setNormalizer') - ->with($this->isInstanceOf(NormalizerInterface::class)); + ->method('setNormalizer'); new Serializer([$normalizerAware]); } @@ -380,8 +372,7 @@ public function testDenormalizerAware() { $denormalizerAware = $this->createMock(DenormalizerAwareDenormalizer::class); $denormalizerAware->expects($this->once()) - ->method('setDenormalizer') - ->with($this->isInstanceOf(DenormalizerInterface::class)); + ->method('setDenormalizer'); new Serializer([$denormalizerAware]); } @@ -1235,6 +1226,76 @@ public static function provideCollectDenormalizationErrors() [new ClassMetadataFactory(new AnnotationLoader(new AnnotationReader()))], ]; } + + public function testSerializerUsesSupportedTypesMethod() + { + $neverCalledNormalizer = $this->createMock(DummyNormalizer::class); + $neverCalledNormalizer + // once for normalization, once for denormalization + ->expects($this->exactly(2)) + ->method('getSupportedTypes') + ->willReturn([ + Foo::class => true, + Bar::class => false, + ]); + + $supportedAndCachedNormalizer = $this->createMock(DummyNormalizer::class); + $supportedAndCachedNormalizer + // once for normalization, once for denormalization + ->expects($this->exactly(2)) + ->method('getSupportedTypes') + ->willReturn([ + Model::class => true, + ]); + + $serializer = new Serializer( + [ + $neverCalledNormalizer, + $supportedAndCachedNormalizer, + new ObjectNormalizer(), + ], + ['json' => new JsonEncoder()] + ); + + // Normalization process + $neverCalledNormalizer + ->expects($this->never()) + ->method('supportsNormalization'); + $neverCalledNormalizer + ->expects($this->never()) + ->method('normalize'); + + $supportedAndCachedNormalizer + ->expects($this->once()) + ->method('supportsNormalization') + ->willReturn(true); + $supportedAndCachedNormalizer + ->expects($this->exactly(2)) + ->method('normalize') + ->willReturn(['foo' => 'bar']); + + $serializer->normalize(new Model(), 'json'); + $serializer->normalize(new Model(), 'json'); + + // Denormalization pass + $neverCalledNormalizer + ->expects($this->never()) + ->method('supportsDenormalization'); + $neverCalledNormalizer + ->expects($this->never()) + ->method('denormalize'); + $supportedAndCachedNormalizer + ->expects($this->once()) + ->method('supportsDenormalization') + ->willReturn(true); + $supportedAndCachedNormalizer + ->expects($this->exactly(2)) + ->method('denormalize') + ->willReturn(new Model()); + + $serializer->denormalize('foo', Model::class, 'json'); + $serializer->denormalize('foo', Model::class, 'json'); + } } class Model @@ -1389,6 +1450,11 @@ public function getIterator(): \ArrayIterator } } +abstract class DummyNormalizer implements NormalizerInterface, DenormalizerInterface +{ + abstract public function getSupportedTypes(?string $format): array; +} + interface NormalizerAwareNormalizer extends NormalizerInterface, NormalizerAwareInterface { } From 47f7a8c64a722aafca0bfc4f3c5da877bc3beb8e Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Thu, 9 Mar 2023 11:03:40 +0100 Subject: [PATCH 083/297] [Serializer] Add wildcard support to getSupportedTypes() --- Debug/TraceableNormalizer.php | 7 ++- Debug/TraceableSerializer.php | 13 ++--- Normalizer/ArrayDenormalizer.php | 13 ++++- .../ConstraintViolationListNormalizer.php | 4 +- Normalizer/CustomNormalizer.php | 2 +- Normalizer/DataUriNormalizer.php | 8 +-- Normalizer/DateIntervalNormalizer.php | 2 +- Normalizer/DateTimeNormalizer.php | 8 +-- Normalizer/DateTimeZoneNormalizer.php | 2 +- Normalizer/DenormalizerInterface.php | 25 +++++----- Normalizer/GetSetMethodNormalizer.php | 4 +- Normalizer/JsonSerializableNormalizer.php | 2 +- Normalizer/MimeMessageNormalizer.php | 12 +++-- Normalizer/NormalizerInterface.php | 25 +++++----- Normalizer/ObjectNormalizer.php | 4 +- Normalizer/ProblemNormalizer.php | 2 +- Normalizer/PropertyNormalizer.php | 4 +- Normalizer/UnwrappingDenormalizer.php | 4 +- Serializer.php | 50 ++++++++++++------- Tests/Debug/TraceableSerializerTest.php | 4 +- Tests/Fixtures/AbstractNormalizerDummy.php | 4 +- Tests/Fixtures/EnvelopeNormalizer.php | 2 +- Tests/Fixtures/EnvelopedMessageNormalizer.php | 2 +- .../AbstractObjectNormalizerTest.php | 18 +++---- Tests/Normalizer/TestDenormalizer.php | 4 +- Tests/Normalizer/TestNormalizer.php | 4 +- 26 files changed, 131 insertions(+), 98 deletions(-) diff --git a/Debug/TraceableNormalizer.php b/Debug/TraceableNormalizer.php index 8a00056f1..d4cf6fcbd 100644 --- a/Debug/TraceableNormalizer.php +++ b/Debug/TraceableNormalizer.php @@ -33,13 +33,16 @@ public function __construct( private NormalizerInterface|DenormalizerInterface $normalizer, private SerializerDataCollector $dataCollector, ) { + if (!method_exists($normalizer, 'getSupportedTypes')) { + trigger_deprecation('symfony/serializer', '6.3', 'Not implementing the "NormalizerInterface::getSupportedTypes()" in "%s" is deprecated.', get_debug_type($normalizer)); + } } - public function getSupportedTypes(?string $format): ?array + public function getSupportedTypes(?string $format): array { // @deprecated remove condition in 7.0 if (!method_exists($this->normalizer, 'getSupportedTypes')) { - return null; + return ['*' => $this->normalizer instanceof CacheableSupportsMethodInterface && $this->normalizer->hasCacheableSupportsMethod()]; } return $this->normalizer->getSupportedTypes($format); diff --git a/Debug/TraceableSerializer.php b/Debug/TraceableSerializer.php index 7aa1cb01f..2a8e96c7a 100644 --- a/Debug/TraceableSerializer.php +++ b/Debug/TraceableSerializer.php @@ -14,6 +14,7 @@ use Symfony\Component\Serializer\DataCollector\SerializerDataCollector; use Symfony\Component\Serializer\Encoder\DecoderInterface; use Symfony\Component\Serializer\Encoder\EncoderInterface; +use Symfony\Component\Serializer\Normalizer\CacheableSupportsMethodInterface; use Symfony\Component\Serializer\Normalizer\DenormalizerInterface; use Symfony\Component\Serializer\Normalizer\NormalizerInterface; use Symfony\Component\Serializer\SerializerInterface; @@ -29,13 +30,13 @@ class TraceableSerializer implements SerializerInterface, NormalizerInterface, D { public const DEBUG_TRACE_ID = 'debug_trace_id'; - /** - * @param SerializerInterface&NormalizerInterface&DenormalizerInterface&EncoderInterface&DecoderInterface $serializer - */ public function __construct( - private SerializerInterface $serializer, + private SerializerInterface&NormalizerInterface&DenormalizerInterface&EncoderInterface&DecoderInterface $serializer, private SerializerDataCollector $dataCollector, ) { + if (!method_exists($serializer, 'getSupportedTypes')) { + trigger_deprecation('symfony/serializer', '6.3', 'Not implementing the "NormalizerInterface::getSupportedTypes()" in "%s" is deprecated.', get_debug_type($serializer)); + } } public function serialize(mixed $data, string $format, array $context = []): string @@ -128,11 +129,11 @@ public function decode(string $data, string $format, array $context = []): mixed return $result; } - public function getSupportedTypes(?string $format): ?array + public function getSupportedTypes(?string $format): array { // @deprecated remove condition in 7.0 if (!method_exists($this->serializer, 'getSupportedTypes')) { - return null; + return ['*' => $this->serializer instanceof CacheableSupportsMethodInterface && $this->serializer->hasCacheableSupportsMethod()]; } return $this->serializer->getSupportedTypes($format); diff --git a/Normalizer/ArrayDenormalizer.php b/Normalizer/ArrayDenormalizer.php index 71ee54fdc..8b3693508 100644 --- a/Normalizer/ArrayDenormalizer.php +++ b/Normalizer/ArrayDenormalizer.php @@ -27,11 +27,20 @@ class ArrayDenormalizer implements ContextAwareDenormalizerInterface, Denormaliz { use DenormalizerAwareTrait; - public function getSupportedTypes(?string $format): ?array + public function setDenormalizer(DenormalizerInterface $denormalizer): void + { + if (!method_exists($denormalizer, 'getSupportedTypes')) { + trigger_deprecation('symfony/serializer', '6.3', 'Not implementing the "DenormalizerInterface::getSupportedTypes()" in "%s" is deprecated.', get_debug_type($denormalizer)); + } + + $this->denormalizer = $denormalizer; + } + + public function getSupportedTypes(?string $format): array { // @deprecated remove condition in 7.0 if (!method_exists($this->denormalizer, 'getSupportedTypes')) { - return null; + return ['*' => $this->denormalizer instanceof CacheableSupportsMethodInterface && $this->denormalizer->hasCacheableSupportsMethod()]; } return $this->denormalizer->getSupportedTypes($format); diff --git a/Normalizer/ConstraintViolationListNormalizer.php b/Normalizer/ConstraintViolationListNormalizer.php index 7d0804c18..0e23d90b3 100644 --- a/Normalizer/ConstraintViolationListNormalizer.php +++ b/Normalizer/ConstraintViolationListNormalizer.php @@ -39,10 +39,10 @@ public function __construct(array $defaultContext = [], NameConverterInterface $ $this->nameConverter = $nameConverter; } - public function getSupportedTypes(?string $format): ?array + public function getSupportedTypes(?string $format): array { return [ - ConstraintViolationListInterface::class => __CLASS__ === static::class, + ConstraintViolationListInterface::class => __CLASS__ === static::class || $this->hasCacheableSupportsMethod(), ]; } diff --git a/Normalizer/CustomNormalizer.php b/Normalizer/CustomNormalizer.php index 97cc865b9..4c17c2552 100644 --- a/Normalizer/CustomNormalizer.php +++ b/Normalizer/CustomNormalizer.php @@ -25,7 +25,7 @@ class CustomNormalizer implements NormalizerInterface, DenormalizerInterface, Se public function getSupportedTypes(?string $format): array { return [ - NormalizableInterface::class => __CLASS__ === static::class, + NormalizableInterface::class => __CLASS__ === static::class || $this->hasCacheableSupportsMethod(), ]; } diff --git a/Normalizer/DataUriNormalizer.php b/Normalizer/DataUriNormalizer.php index ff5ff258a..9012f6f3a 100644 --- a/Normalizer/DataUriNormalizer.php +++ b/Normalizer/DataUriNormalizer.php @@ -47,10 +47,12 @@ public function __construct(MimeTypeGuesserInterface $mimeTypeGuesser = null) public function getSupportedTypes(?string $format): array { + $isCacheable = __CLASS__ === static::class || $this->hasCacheableSupportsMethod(); + return [ - \SplFileInfo::class => __CLASS__ === static::class, - \SplFileObject::class => __CLASS__ === static::class, - File::class => __CLASS__ === static::class, + \SplFileInfo::class => $isCacheable, + \SplFileObject::class => $isCacheable, + File::class => $isCacheable, ]; } diff --git a/Normalizer/DateIntervalNormalizer.php b/Normalizer/DateIntervalNormalizer.php index 839eeb65d..616d500e7 100644 --- a/Normalizer/DateIntervalNormalizer.php +++ b/Normalizer/DateIntervalNormalizer.php @@ -36,7 +36,7 @@ public function __construct(array $defaultContext = []) public function getSupportedTypes(?string $format): array { return [ - \DateInterval::class => __CLASS__ === static::class, + \DateInterval::class => __CLASS__ === static::class || $this->hasCacheableSupportsMethod(), ]; } diff --git a/Normalizer/DateTimeNormalizer.php b/Normalizer/DateTimeNormalizer.php index 841f4512d..2235b1ea5 100644 --- a/Normalizer/DateTimeNormalizer.php +++ b/Normalizer/DateTimeNormalizer.php @@ -49,10 +49,12 @@ public function setDefaultContext(array $defaultContext): void public function getSupportedTypes(?string $format): array { + $isCacheable = __CLASS__ === static::class || $this->hasCacheableSupportsMethod(); + return [ - \DateTimeInterface::class => __CLASS__ === static::class, - \DateTimeImmutable::class => __CLASS__ === static::class, - \DateTime::class => __CLASS__ === static::class, + \DateTimeInterface::class => $isCacheable, + \DateTimeImmutable::class => $isCacheable, + \DateTime::class => $isCacheable, ]; } diff --git a/Normalizer/DateTimeZoneNormalizer.php b/Normalizer/DateTimeZoneNormalizer.php index 24de36121..a7c26f1d4 100644 --- a/Normalizer/DateTimeZoneNormalizer.php +++ b/Normalizer/DateTimeZoneNormalizer.php @@ -25,7 +25,7 @@ class DateTimeZoneNormalizer implements NormalizerInterface, DenormalizerInterfa public function getSupportedTypes(?string $format): array { return [ - \DateTimeZone::class => __CLASS__ === static::class, + \DateTimeZone::class => __CLASS__ === static::class || $this->hasCacheableSupportsMethod(), ]; } diff --git a/Normalizer/DenormalizerInterface.php b/Normalizer/DenormalizerInterface.php index 97ae86be2..1d83b2da1 100644 --- a/Normalizer/DenormalizerInterface.php +++ b/Normalizer/DenormalizerInterface.php @@ -22,7 +22,7 @@ /** * @author Jordi Boggiano * - * @method getSupportedTypes(?string $format): ?array + * @method getSupportedTypes(?string $format): array */ interface DenormalizerInterface { @@ -65,18 +65,19 @@ public function denormalize(mixed $data, string $type, string $format = null, ar */ public function supportsDenormalization(mixed $data, string $type, string $format = null /* , array $context = [] */); - /* - * Return the types supported for normalization by this denormalizer for - * this format associated to a boolean value indicating if the result of - * supports*() methods can be cached or if the result can not be cached - * because it depends on the context. - * Returning null means this denormalizer will be considered for - * every format/class. - * Return an empty array if no type is supported for this format. + /** + * Returns the types potentially supported by this denormalizer. + * + * For each supported formats (if applicable), the supported types should be + * returned as keys, and each type should be mapped to a boolean indicating + * if the result of supportsDenormalization() can be cached or not + * (a result cannot be cached when it depends on the context or on the data.) * - * @param string $format The format being (de-)serialized from or into + * The special type '*' can be used to indicate that the denormalizer might + * support any types. A null value means that the denormalizer does not support + * the corresponding type. * - * @return array|null + * @return array */ - /* public function getSupportedTypes(?string $format): ?array; */ + /* public function getSupportedTypes(?string $format): array; */ } diff --git a/Normalizer/GetSetMethodNormalizer.php b/Normalizer/GetSetMethodNormalizer.php index 4395b43d2..2719c8b52 100644 --- a/Normalizer/GetSetMethodNormalizer.php +++ b/Normalizer/GetSetMethodNormalizer.php @@ -36,9 +36,9 @@ class GetSetMethodNormalizer extends AbstractObjectNormalizer { private static $setterAccessibleCache = []; - public function getSupportedTypes(?string $format): ?array + public function getSupportedTypes(?string $format): array { - return null; + return ['*' => __CLASS__ === static::class || $this->hasCacheableSupportsMethod()]; } /** diff --git a/Normalizer/JsonSerializableNormalizer.php b/Normalizer/JsonSerializableNormalizer.php index 8b3985f2b..841fca319 100644 --- a/Normalizer/JsonSerializableNormalizer.php +++ b/Normalizer/JsonSerializableNormalizer.php @@ -41,7 +41,7 @@ public function normalize(mixed $object, string $format = null, array $context = public function getSupportedTypes(?string $format): array { return [ - \JsonSerializable::class => __CLASS__ === static::class, + \JsonSerializable::class => __CLASS__ === static::class || $this->hasCacheableSupportsMethod(), ]; } diff --git a/Normalizer/MimeMessageNormalizer.php b/Normalizer/MimeMessageNormalizer.php index 371e3bb36..74a8b66b1 100644 --- a/Normalizer/MimeMessageNormalizer.php +++ b/Normalizer/MimeMessageNormalizer.php @@ -44,12 +44,14 @@ public function __construct(PropertyNormalizer $normalizer) public function getSupportedTypes(?string $format): array { + $isCacheable = __CLASS__ === static::class || $this->hasCacheableSupportsMethod(); + return [ - Message::class => true, - Headers::class => true, - HeaderInterface::class => true, - Address::class => true, - AbstractPart::class => true, + Message::class => $isCacheable, + Headers::class => $isCacheable, + HeaderInterface::class => $isCacheable, + Address::class => $isCacheable, + AbstractPart::class => $isCacheable, ]; } diff --git a/Normalizer/NormalizerInterface.php b/Normalizer/NormalizerInterface.php index eeffe89dd..d6d0707ff 100644 --- a/Normalizer/NormalizerInterface.php +++ b/Normalizer/NormalizerInterface.php @@ -19,7 +19,7 @@ /** * @author Jordi Boggiano * - * @method getSupportedTypes(?string $format): ?array + * @method getSupportedTypes(?string $format): array */ interface NormalizerInterface { @@ -56,18 +56,19 @@ public function normalize(mixed $object, string $format = null, array $context = */ public function supportsNormalization(mixed $data, string $format = null /* , array $context = [] */); - /* - * Return the types supported for normalization by this normalizer for this - * format associated to a boolean value indicating if the result of - * supports*() methods can be cached or if the result can not be cached - * because it depends on the context. - * Returning null means this normalizer will be considered for - * every format/class. - * Return an empty array if no type is supported for this format. + /** + * Returns the types potentially supported by this normalizer. + * + * For each supported formats (if applicable), the supported types should be + * returned as keys, and each type should be mapped to a boolean indicating + * if the result of supportsNormalization() can be cached or not + * (a result cannot be cached when it depends on the context or on the data.) * - * @param string $format The format being (de-)serialized from or into + * The special type '*' can be used to indicate that the normalizer might + * support any types. A null value means that the normalizer does not support + * the corresponding type. * - * @return array|null + * @return array */ - /* public function getSupportedTypes(?string $format): ?array; */ + /* public function getSupportedTypes(?string $format): array; */ } diff --git a/Normalizer/ObjectNormalizer.php b/Normalizer/ObjectNormalizer.php index 780f5fbc8..140e89c6a 100644 --- a/Normalizer/ObjectNormalizer.php +++ b/Normalizer/ObjectNormalizer.php @@ -47,9 +47,9 @@ public function __construct(ClassMetadataFactoryInterface $classMetadataFactory $this->objectClassResolver = $objectClassResolver ?? fn ($class) => \is_object($class) ? $class::class : $class; } - public function getSupportedTypes(?string $format): ?array + public function getSupportedTypes(?string $format): array { - return null; + return ['*' => __CLASS__ === static::class || $this->hasCacheableSupportsMethod()]; } /** diff --git a/Normalizer/ProblemNormalizer.php b/Normalizer/ProblemNormalizer.php index 941372489..dc4dadd88 100644 --- a/Normalizer/ProblemNormalizer.php +++ b/Normalizer/ProblemNormalizer.php @@ -43,7 +43,7 @@ public function __construct(bool $debug = false, array $defaultContext = []) public function getSupportedTypes(?string $format): array { return [ - FlattenException::class => __CLASS__ === self::class, + FlattenException::class => __CLASS__ === self::class || $this->hasCacheableSupportsMethod(), ]; } diff --git a/Normalizer/PropertyNormalizer.php b/Normalizer/PropertyNormalizer.php index 76f068c9e..645ba7429 100644 --- a/Normalizer/PropertyNormalizer.php +++ b/Normalizer/PropertyNormalizer.php @@ -54,9 +54,9 @@ public function __construct(ClassMetadataFactoryInterface $classMetadataFactory } } - public function getSupportedTypes(?string $format): ?array + public function getSupportedTypes(?string $format): array { - return null; + return ['*' => __CLASS__ === static::class || $this->hasCacheableSupportsMethod()]; } /** diff --git a/Normalizer/UnwrappingDenormalizer.php b/Normalizer/UnwrappingDenormalizer.php index fb3baa4e3..738ece32e 100644 --- a/Normalizer/UnwrappingDenormalizer.php +++ b/Normalizer/UnwrappingDenormalizer.php @@ -32,9 +32,9 @@ public function __construct(PropertyAccessorInterface $propertyAccessor = null) $this->propertyAccessor = $propertyAccessor ?? PropertyAccess::createPropertyAccessor(); } - public function getSupportedTypes(?string $format): ?array + public function getSupportedTypes(?string $format): array { - return null; + return ['*' => false]; } public function denormalize(mixed $data, string $class, string $format = null, array $context = []): mixed diff --git a/Serializer.php b/Serializer.php index ab0da7b96..5eb0c5b6b 100644 --- a/Serializer.php +++ b/Serializer.php @@ -227,9 +227,9 @@ public function denormalize(mixed $data, string $type, string $format = null, ar return $normalizer->denormalize($data, $type, $format, $context); } - public function getSupportedTypes(?string $format): ?array + public function getSupportedTypes(?string $format): array { - return null; + return ['*' => false]; } public function supportsNormalization(mixed $data, string $format = null, array $context = []): bool @@ -274,22 +274,28 @@ private function getNormalizer(mixed $data, ?string $format, array $context): ?N continue; } - if (null === $supportedTypes = $normalizer->getSupportedTypes($format)) { - $this->normalizerCache[$format][$type][$k] = false; - continue; - } + $supportedTypes = $normalizer->getSupportedTypes($format); foreach ($supportedTypes as $supportedType => $isCacheable) { - if ($type !== $supportedType && !is_subclass_of($type, $supportedType, true)) { + if ('*' === $supportedType || $type !== $supportedType && !is_subclass_of($type, $supportedType, true)) { continue; } - if ($isCacheable && $normalizer->supportsNormalization($data, $format, $context)) { - $this->normalizerCache[$format][$type][$k] = true; + if (null === $isCacheable) { + unset($supportedTypes['*']); + } elseif ($this->normalizerCache[$format][$type][$k] = $isCacheable && $normalizer->supportsNormalization($data, $format, $context)) { break 2; } - $this->normalizerCache[$format][$type][$k] = false; + break; + } + + if (null === $isCacheable = $supportedTypes['*'] ?? null) { + continue; + } + + if ($this->normalizerCache[$format][$type][$k] ??= $isCacheable && $normalizer->supportsNormalization($data, $format, $context)) { + break; } } } @@ -335,22 +341,28 @@ private function getDenormalizer(mixed $data, string $class, ?string $format, ar continue; } - if (null === $supportedTypes = $normalizer->getSupportedTypes($format)) { - $this->denormalizerCache[$format][$class][$k] = false; - continue; - } + $supportedTypes = $normalizer->getSupportedTypes($format); foreach ($supportedTypes as $supportedType => $isCacheable) { - if ($class !== $supportedType && !is_subclass_of($class, $supportedType, true)) { + if ('*' === $supportedType || $class !== $supportedType && !is_subclass_of($class, $supportedType, true)) { continue; } - if ($isCacheable && $normalizer->supportsDenormalization(null, $class, $format, $context)) { - $this->denormalizerCache[$format][$class][$k] = true; - break; + if (null === $isCacheable) { + unset($supportedTypes['*']); + } elseif ($this->denormalizerCache[$format][$class][$k] = $isCacheable && $normalizer->supportsDenormalization(null, $class, $format, $context)) { + break 2; } - $this->denormalizerCache[$format][$class][$k] = false; + break; + } + + if (null === $isCacheable = $supportedTypes['*'] ?? null) { + continue; + } + + if ($this->denormalizerCache[$format][$class][$k] ??= $isCacheable && $normalizer->supportsDenormalization(null, $class, $format, $context)) { + break; } } } diff --git a/Tests/Debug/TraceableSerializerTest.php b/Tests/Debug/TraceableSerializerTest.php index 6083624dd..9cc43d40a 100644 --- a/Tests/Debug/TraceableSerializerTest.php +++ b/Tests/Debug/TraceableSerializerTest.php @@ -145,9 +145,9 @@ public function normalize(mixed $object, string $format = null, array $context = return 'normalized'; } - public function getSupportedTypes(?string $format): ?array + public function getSupportedTypes(?string $format): array { - return null; + return ['*' => false]; } public function supportsNormalization(mixed $data, string $format = null, array $context = []): bool diff --git a/Tests/Fixtures/AbstractNormalizerDummy.php b/Tests/Fixtures/AbstractNormalizerDummy.php index 20ce0ffcb..9da1fc5f5 100644 --- a/Tests/Fixtures/AbstractNormalizerDummy.php +++ b/Tests/Fixtures/AbstractNormalizerDummy.php @@ -20,9 +20,9 @@ */ class AbstractNormalizerDummy extends AbstractNormalizer { - public function getSupportedTypes(?string $format): ?array + public function getSupportedTypes(?string $format): array { - return null; + return ['*' => false]; } public function getAllowedAttributes(string|object $classOrObject, array $context, bool $attributesAsString = false): array|bool diff --git a/Tests/Fixtures/EnvelopeNormalizer.php b/Tests/Fixtures/EnvelopeNormalizer.php index 367afa8c8..021c22a04 100644 --- a/Tests/Fixtures/EnvelopeNormalizer.php +++ b/Tests/Fixtures/EnvelopeNormalizer.php @@ -31,7 +31,7 @@ public function normalize($envelope, string $format = null, array $context = []) ]; } - public function getSupportedTypes(?string $format): ?array + public function getSupportedTypes(?string $format): array { return [ EnvelopeObject::class => true, diff --git a/Tests/Fixtures/EnvelopedMessageNormalizer.php b/Tests/Fixtures/EnvelopedMessageNormalizer.php index 7dc87de51..5d48f4569 100644 --- a/Tests/Fixtures/EnvelopedMessageNormalizer.php +++ b/Tests/Fixtures/EnvelopedMessageNormalizer.php @@ -25,7 +25,7 @@ public function normalize($message, string $format = null, array $context = []): ]; } - public function getSupportedTypes(?string $format): ?array + public function getSupportedTypes(?string $format): array { return [ EnvelopedMessage::class => true, diff --git a/Tests/Normalizer/AbstractObjectNormalizerTest.php b/Tests/Normalizer/AbstractObjectNormalizerTest.php index ece903d31..e1f466b22 100644 --- a/Tests/Normalizer/AbstractObjectNormalizerTest.php +++ b/Tests/Normalizer/AbstractObjectNormalizerTest.php @@ -546,9 +546,9 @@ public function testDenormalizeUsesContextAttributeForPropertiesInConstructorWit class AbstractObjectNormalizerDummy extends AbstractObjectNormalizer { - public function getSupportedTypes(?string $format): ?array + public function getSupportedTypes(?string $format): array { - return null; + return ['*' => false]; } protected function extractAttributes(object $object, string $format = null, array $context = []): array @@ -656,9 +656,9 @@ public function __construct() parent::__construct($classMetadataFactory, new MetadataAwareNameConverter($classMetadataFactory)); } - public function getSupportedTypes(?string $format): ?array + public function getSupportedTypes(?string $format): array { - return null; + return ['*' => false]; } protected function extractAttributes(object $object, string $format = null, array $context = []): array @@ -762,9 +762,9 @@ public function denormalize($data, string $type, string $format = null, array $c return null; } - public function getSupportedTypes(?string $format): ?array + public function getSupportedTypes(?string $format): array { - return null; + return ['*' => false]; } public function supportsDenormalization($data, string $type, string $format = null, array $context = []): bool @@ -775,9 +775,9 @@ public function supportsDenormalization($data, string $type, string $format = nu class AbstractObjectNormalizerCollectionDummy extends AbstractObjectNormalizer { - public function getSupportedTypes(?string $format): ?array + public function getSupportedTypes(?string $format): array { - return null; + return ['*' => false]; } protected function extractAttributes(object $object, string $format = null, array $context = []): array @@ -834,7 +834,7 @@ public function denormalize($data, string $type, string $format = null, array $c return $data; } - public function getSupportedTypes(?string $format): ?array + public function getSupportedTypes(?string $format): array { return $this->serializer->getSupportedTypes($format); } diff --git a/Tests/Normalizer/TestDenormalizer.php b/Tests/Normalizer/TestDenormalizer.php index 547acbca9..70518c797 100644 --- a/Tests/Normalizer/TestDenormalizer.php +++ b/Tests/Normalizer/TestDenormalizer.php @@ -24,9 +24,9 @@ public function denormalize($data, string $type, string $format = null, array $c { } - public function getSupportedTypes(?string $format): ?array + public function getSupportedTypes(?string $format): array { - return null; + return ['*' => false]; } public function supportsDenormalization($data, string $type, string $format = null, array $context = []): bool diff --git a/Tests/Normalizer/TestNormalizer.php b/Tests/Normalizer/TestNormalizer.php index 005bf6c1d..26e5917e7 100644 --- a/Tests/Normalizer/TestNormalizer.php +++ b/Tests/Normalizer/TestNormalizer.php @@ -25,9 +25,9 @@ public function normalize($object, string $format = null, array $context = []): return null; } - public function getSupportedTypes(?string $format): ?array + public function getSupportedTypes(?string $format): array { - return null; + return ['*' => false]; } public function supportsNormalization($data, string $format = null, array $context = []): bool From 23c02383b9323d35aecea3fccfb757c49fb24445 Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Sun, 19 Mar 2023 19:19:19 +0100 Subject: [PATCH 084/297] [Serializer] Allow filtering "object" when using "getSupportedTypes()" --- Normalizer/ArrayDenormalizer.php | 7 +----- Normalizer/DenormalizerInterface.php | 14 ++++------- Normalizer/GetSetMethodNormalizer.php | 2 +- Normalizer/NormalizerInterface.php | 14 ++++------- Normalizer/ObjectNormalizer.php | 2 +- Normalizer/PropertyNormalizer.php | 2 +- Serializer.php | 25 +++++++++++++------ Tests/Fixtures/Annotations/GroupDummy.php | 2 +- .../DummyObjectWithEnumConstructor.php | 11 ++++++-- Tests/Fixtures/Php74Full.php | 1 - Tests/SerializerTest.php | 3 --- 11 files changed, 42 insertions(+), 41 deletions(-) diff --git a/Normalizer/ArrayDenormalizer.php b/Normalizer/ArrayDenormalizer.php index 8b3693508..b37e9eace 100644 --- a/Normalizer/ArrayDenormalizer.php +++ b/Normalizer/ArrayDenormalizer.php @@ -38,12 +38,7 @@ public function setDenormalizer(DenormalizerInterface $denormalizer): void public function getSupportedTypes(?string $format): array { - // @deprecated remove condition in 7.0 - if (!method_exists($this->denormalizer, 'getSupportedTypes')) { - return ['*' => $this->denormalizer instanceof CacheableSupportsMethodInterface && $this->denormalizer->hasCacheableSupportsMethod()]; - } - - return $this->denormalizer->getSupportedTypes($format); + return ['object' => null, '*' => false]; } /** diff --git a/Normalizer/DenormalizerInterface.php b/Normalizer/DenormalizerInterface.php index 1d83b2da1..4edb70096 100644 --- a/Normalizer/DenormalizerInterface.php +++ b/Normalizer/DenormalizerInterface.php @@ -51,11 +51,6 @@ public function denormalize(mixed $data, string $type, string $format = null, ar /** * Checks whether the given class is supported for denormalization by this normalizer. * - * Since Symfony 6.3, this method will only be called if the type is - * included in the supported types returned by getSupportedTypes(). - * - * @see getSupportedTypes() - * * @param mixed $data Data to denormalize from * @param string $type The class to which the data should be denormalized * @param string|null $format The format being deserialized from @@ -72,12 +67,13 @@ public function supportsDenormalization(mixed $data, string $type, string $forma * returned as keys, and each type should be mapped to a boolean indicating * if the result of supportsDenormalization() can be cached or not * (a result cannot be cached when it depends on the context or on the data.) + * A null value means that the denormalizer does not support the corresponding + * type. * - * The special type '*' can be used to indicate that the denormalizer might - * support any types. A null value means that the denormalizer does not support - * the corresponding type. + * Use type "object" to match any classes or interfaces, + * and type "*" to match any types. * - * @return array + * @return array */ /* public function getSupportedTypes(?string $format): array; */ } diff --git a/Normalizer/GetSetMethodNormalizer.php b/Normalizer/GetSetMethodNormalizer.php index 2719c8b52..403bb2a45 100644 --- a/Normalizer/GetSetMethodNormalizer.php +++ b/Normalizer/GetSetMethodNormalizer.php @@ -38,7 +38,7 @@ class GetSetMethodNormalizer extends AbstractObjectNormalizer public function getSupportedTypes(?string $format): array { - return ['*' => __CLASS__ === static::class || $this->hasCacheableSupportsMethod()]; + return ['object' => __CLASS__ === static::class || $this->hasCacheableSupportsMethod()]; } /** diff --git a/Normalizer/NormalizerInterface.php b/Normalizer/NormalizerInterface.php index d6d0707ff..40779de31 100644 --- a/Normalizer/NormalizerInterface.php +++ b/Normalizer/NormalizerInterface.php @@ -43,11 +43,6 @@ public function normalize(mixed $object, string $format = null, array $context = /** * Checks whether the given class is supported for normalization by this normalizer. * - * Since Symfony 6.3, this method will only be called if the $data type is - * included in the supported types returned by getSupportedTypes(). - * - * @see getSupportedTypes() - * * @param mixed $data Data to normalize * @param string|null $format The format being (de-)serialized from or into * @param array $context Context options for the normalizer @@ -63,12 +58,13 @@ public function supportsNormalization(mixed $data, string $format = null /* , ar * returned as keys, and each type should be mapped to a boolean indicating * if the result of supportsNormalization() can be cached or not * (a result cannot be cached when it depends on the context or on the data.) + * A null value means that the normalizer does not support the corresponding + * type. * - * The special type '*' can be used to indicate that the normalizer might - * support any types. A null value means that the normalizer does not support - * the corresponding type. + * Use type "object" to match any classes or interfaces, + * and type "*" to match any types. * - * @return array + * @return array */ /* public function getSupportedTypes(?string $format): array; */ } diff --git a/Normalizer/ObjectNormalizer.php b/Normalizer/ObjectNormalizer.php index 140e89c6a..dce443464 100644 --- a/Normalizer/ObjectNormalizer.php +++ b/Normalizer/ObjectNormalizer.php @@ -49,7 +49,7 @@ public function __construct(ClassMetadataFactoryInterface $classMetadataFactory public function getSupportedTypes(?string $format): array { - return ['*' => __CLASS__ === static::class || $this->hasCacheableSupportsMethod()]; + return ['object' => __CLASS__ === static::class || $this->hasCacheableSupportsMethod()]; } /** diff --git a/Normalizer/PropertyNormalizer.php b/Normalizer/PropertyNormalizer.php index 645ba7429..56e5ebf74 100644 --- a/Normalizer/PropertyNormalizer.php +++ b/Normalizer/PropertyNormalizer.php @@ -56,7 +56,7 @@ public function __construct(ClassMetadataFactoryInterface $classMetadataFactory public function getSupportedTypes(?string $format): array { - return ['*' => __CLASS__ === static::class || $this->hasCacheableSupportsMethod()]; + return ['object' => __CLASS__ === static::class || $this->hasCacheableSupportsMethod()]; } /** diff --git a/Serializer.php b/Serializer.php index 5eb0c5b6b..ea219f403 100644 --- a/Serializer.php +++ b/Serializer.php @@ -251,7 +251,13 @@ public function supportsDenormalization(mixed $data, string $type, string $forma */ private function getNormalizer(mixed $data, ?string $format, array $context): ?NormalizerInterface { - $type = \is_object($data) ? $data::class : 'native-'.\gettype($data); + if (\is_object($data)) { + $type = $data::class; + $genericType = 'object'; + } else { + $type = 'native-'.\gettype($data); + $genericType = '*'; + } if (!isset($this->normalizerCache[$format][$type])) { $this->normalizerCache[$format][$type] = []; @@ -277,12 +283,14 @@ private function getNormalizer(mixed $data, ?string $format, array $context): ?N $supportedTypes = $normalizer->getSupportedTypes($format); foreach ($supportedTypes as $supportedType => $isCacheable) { - if ('*' === $supportedType || $type !== $supportedType && !is_subclass_of($type, $supportedType, true)) { + if (\in_array($supportedType, ['*', 'object'], true) + || $type !== $supportedType && ('object' !== $genericType || !is_subclass_of($type, $supportedType)) + ) { continue; } if (null === $isCacheable) { - unset($supportedTypes['*']); + unset($supportedTypes['*'], $supportedTypes['object']); } elseif ($this->normalizerCache[$format][$type][$k] = $isCacheable && $normalizer->supportsNormalization($data, $format, $context)) { break 2; } @@ -290,7 +298,7 @@ private function getNormalizer(mixed $data, ?string $format, array $context): ?N break; } - if (null === $isCacheable = $supportedTypes['*'] ?? null) { + if (null === $isCacheable = $supportedTypes[\array_key_exists($genericType, $supportedTypes) ? $genericType : '*'] ?? null) { continue; } @@ -322,6 +330,7 @@ private function getDenormalizer(mixed $data, string $class, ?string $format, ar { if (!isset($this->denormalizerCache[$format][$class])) { $this->denormalizerCache[$format][$class] = []; + $genericType = class_exists($class) || interface_exists($class, false) ? 'object' : '*'; foreach ($this->normalizers as $k => $normalizer) { if (!$normalizer instanceof DenormalizerInterface) { @@ -344,12 +353,14 @@ private function getDenormalizer(mixed $data, string $class, ?string $format, ar $supportedTypes = $normalizer->getSupportedTypes($format); foreach ($supportedTypes as $supportedType => $isCacheable) { - if ('*' === $supportedType || $class !== $supportedType && !is_subclass_of($class, $supportedType, true)) { + if (\in_array($supportedType, ['*', 'object'], true) + || $class !== $supportedType && ('object' !== $genericType || !is_subclass_of($class, $supportedType)) + ) { continue; } if (null === $isCacheable) { - unset($supportedTypes['*']); + unset($supportedTypes['*'], $supportedTypes['object']); } elseif ($this->denormalizerCache[$format][$class][$k] = $isCacheable && $normalizer->supportsDenormalization(null, $class, $format, $context)) { break 2; } @@ -357,7 +368,7 @@ private function getDenormalizer(mixed $data, string $class, ?string $format, ar break; } - if (null === $isCacheable = $supportedTypes['*'] ?? null) { + if (null === $isCacheable = $supportedTypes[\array_key_exists($genericType, $supportedTypes) ? $genericType : '*'] ?? null) { continue; } diff --git a/Tests/Fixtures/Annotations/GroupDummy.php b/Tests/Fixtures/Annotations/GroupDummy.php index 1d502c60c..36a63e6f8 100644 --- a/Tests/Fixtures/Annotations/GroupDummy.php +++ b/Tests/Fixtures/Annotations/GroupDummy.php @@ -12,8 +12,8 @@ namespace Symfony\Component\Serializer\Tests\Fixtures\Annotations; use Symfony\Component\Serializer\Annotation\Groups; -use Symfony\Component\Serializer\Tests\Fixtures\GroupDummyInterface; use Symfony\Component\Serializer\Tests\Fixtures\ChildOfGroupsAnnotationDummy; +use Symfony\Component\Serializer\Tests\Fixtures\GroupDummyInterface; /** * @author Kévin Dunglas diff --git a/Tests/Fixtures/DummyObjectWithEnumConstructor.php b/Tests/Fixtures/DummyObjectWithEnumConstructor.php index be5ea3cff..022b57cd9 100644 --- a/Tests/Fixtures/DummyObjectWithEnumConstructor.php +++ b/Tests/Fixtures/DummyObjectWithEnumConstructor.php @@ -1,8 +1,15 @@ + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ -use Symfony\Component\Serializer\Tests\Fixtures\StringBackedEnumDummy; +namespace Symfony\Component\Serializer\Tests\Fixtures; class DummyObjectWithEnumConstructor { diff --git a/Tests/Fixtures/Php74Full.php b/Tests/Fixtures/Php74Full.php index 5aea0fa4a..0da6be20f 100644 --- a/Tests/Fixtures/Php74Full.php +++ b/Tests/Fixtures/Php74Full.php @@ -35,7 +35,6 @@ final class Php74Full public $anotherCollection; } - final class Php74FullWithConstructor { public function __construct($constructorArgument) diff --git a/Tests/SerializerTest.php b/Tests/SerializerTest.php index 331eaa25e..8de4eefb7 100644 --- a/Tests/SerializerTest.php +++ b/Tests/SerializerTest.php @@ -17,8 +17,6 @@ use Symfony\Component\PropertyInfo\Extractor\PhpDocExtractor; use Symfony\Component\PropertyInfo\Extractor\ReflectionExtractor; use Symfony\Component\PropertyInfo\PropertyInfoExtractor; -use Symfony\Component\Serializer\Encoder\DecoderInterface; -use Symfony\Component\Serializer\Encoder\EncoderInterface; use Symfony\Component\Serializer\Encoder\JsonEncoder; use Symfony\Component\Serializer\Exception\ExtraAttributesException; use Symfony\Component\Serializer\Exception\InvalidArgumentException; @@ -49,7 +47,6 @@ use Symfony\Component\Serializer\Normalizer\UidNormalizer; use Symfony\Component\Serializer\Normalizer\UnwrappingDenormalizer; use Symfony\Component\Serializer\Serializer; -use Symfony\Component\Serializer\SerializerInterface; use Symfony\Component\Serializer\Tests\Fixtures\Annotations\AbstractDummy; use Symfony\Component\Serializer\Tests\Fixtures\Annotations\AbstractDummyFirstChild; use Symfony\Component\Serializer\Tests\Fixtures\Annotations\AbstractDummySecondChild; From bfa57470309adbe00811ca1e1803f4f4cc33b3b7 Mon Sep 17 00:00:00 2001 From: Mathias Arlaud Date: Tue, 21 Mar 2023 18:29:25 +0100 Subject: [PATCH 085/297] [Serializer] Add withSaveOptions to XmlEncoderContextBuilder --- Context/Encoder/XmlEncoderContextBuilder.php | 12 ++++++++++++ .../Context/Encoder/XmlEncoderContextBuilderTest.php | 3 +++ 2 files changed, 15 insertions(+) diff --git a/Context/Encoder/XmlEncoderContextBuilder.php b/Context/Encoder/XmlEncoderContextBuilder.php index 3f8e92f4e..78617a2bb 100644 --- a/Context/Encoder/XmlEncoderContextBuilder.php +++ b/Context/Encoder/XmlEncoderContextBuilder.php @@ -89,6 +89,18 @@ public function withLoadOptions(?int $loadOptions): static return $this->with(XmlEncoder::LOAD_OPTIONS, $loadOptions); } + /** + * Configures the DOMDocument::saveXml options bitmask. + * + * @see https://www.php.net/manual/en/libxml.constants.php + * + * @param positive-int|null $saveOptions + */ + public function withSaveOptions(?int $saveOptions): static + { + return $this->with(XmlEncoder::SAVE_OPTIONS, $saveOptions); + } + /** * Configures whether to keep empty nodes. */ diff --git a/Tests/Context/Encoder/XmlEncoderContextBuilderTest.php b/Tests/Context/Encoder/XmlEncoderContextBuilderTest.php index c730695d8..1701733a8 100644 --- a/Tests/Context/Encoder/XmlEncoderContextBuilderTest.php +++ b/Tests/Context/Encoder/XmlEncoderContextBuilderTest.php @@ -41,6 +41,7 @@ public function testWithers(array $values) ->withEncoding($values[XmlEncoder::ENCODING]) ->withFormatOutput($values[XmlEncoder::FORMAT_OUTPUT]) ->withLoadOptions($values[XmlEncoder::LOAD_OPTIONS]) + ->withSaveOptions($values[XmlEncoder::SAVE_OPTIONS]) ->withRemoveEmptyTags($values[XmlEncoder::REMOVE_EMPTY_TAGS]) ->withRootNodeName($values[XmlEncoder::ROOT_NODE_NAME]) ->withStandalone($values[XmlEncoder::STANDALONE]) @@ -63,6 +64,7 @@ public static function withersDataProvider(): iterable XmlEncoder::ENCODING => 'UTF-8', XmlEncoder::FORMAT_OUTPUT => false, XmlEncoder::LOAD_OPTIONS => \LIBXML_COMPACT, + XmlEncoder::SAVE_OPTIONS => \LIBXML_NOERROR, XmlEncoder::REMOVE_EMPTY_TAGS => true, XmlEncoder::ROOT_NODE_NAME => 'root', XmlEncoder::STANDALONE => false, @@ -77,6 +79,7 @@ public static function withersDataProvider(): iterable XmlEncoder::ENCODING => null, XmlEncoder::FORMAT_OUTPUT => null, XmlEncoder::LOAD_OPTIONS => null, + XmlEncoder::SAVE_OPTIONS => null, XmlEncoder::REMOVE_EMPTY_TAGS => null, XmlEncoder::ROOT_NODE_NAME => null, XmlEncoder::STANDALONE => null, From 9f6ef544e20bcdd20ac8d7ba2235a62f48d5e33b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tobias=20B=C3=B6nner?= Date: Thu, 23 Feb 2023 10:01:28 +0100 Subject: [PATCH 086/297] [Serializer] Fix serializedpath for non scalar types --- Normalizer/AbstractObjectNormalizer.php | 11 ++++---- .../AbstractObjectNormalizerTest.php | 25 +++++++++++++++++++ 2 files changed, 31 insertions(+), 5 deletions(-) diff --git a/Normalizer/AbstractObjectNormalizer.php b/Normalizer/AbstractObjectNormalizer.php index 7c4c5fb41..1feff46ad 100644 --- a/Normalizer/AbstractObjectNormalizer.php +++ b/Normalizer/AbstractObjectNormalizer.php @@ -198,14 +198,15 @@ public function normalize(mixed $object, string $format = null, array $context = $attributeValue = $this->applyCallbacks($attributeValue, $object, $attribute, $format, $attributeContext); - if (null !== $attributeValue && !\is_scalar($attributeValue)) { - $stack[$attribute] = $attributeValue; - } - - $data = $this->updateData($data, $attribute, $attributeValue, $class, $format, $attributeContext, $attributesMetadata, $classMetadata); + $stack[$attribute] = $attributeValue; } foreach ($stack as $attribute => $attributeValue) { + if (null === $attributeValue || \is_scalar($attributeValue)) { + $data = $this->updateData($data, $attribute, $attributeValue, $class, $format, $context, $attributesMetadata, $classMetadata); + continue; + } + if (!$this->serializer instanceof NormalizerInterface) { throw new LogicException(sprintf('Cannot normalize attribute "%s" because the injected serializer is not a normalizer.', $attribute)); } diff --git a/Tests/Normalizer/AbstractObjectNormalizerTest.php b/Tests/Normalizer/AbstractObjectNormalizerTest.php index 4cc668658..9a319f7ef 100644 --- a/Tests/Normalizer/AbstractObjectNormalizerTest.php +++ b/Tests/Normalizer/AbstractObjectNormalizerTest.php @@ -542,6 +542,21 @@ public function testDenormalizeUsesContextAttributeForPropertiesInConstructorWit $this->assertSame($obj->propertyWithSerializedName->format('Y-m-d'), $obj->propertyWithoutSerializedName->format('Y-m-d')); } + + public function testNormalizeUsesContextAttributeForPropertiesInConstructorWithSerializedPath() + { + $classMetadataFactory = new ClassMetadataFactory(new AnnotationLoader(new AnnotationReader())); + + $extractor = new PropertyInfoExtractor([], [new PhpDocExtractor(), new ReflectionExtractor()]); + $normalizer = new ObjectNormalizer($classMetadataFactory, new MetadataAwareNameConverter($classMetadataFactory), null, $extractor); + $serializer = new Serializer([new DateTimeNormalizer([DateTimeNormalizer::FORMAT_KEY => 'd-m-Y']), $normalizer]); + + $obj = new ObjectDummyWithContextAttributeAndSerializedPath(new \DateTimeImmutable('22-02-2023')); + + $data = $serializer->normalize($obj); + + $this->assertSame(['property' => ['with_path' => '22-02-2023']], $data); + } } class AbstractObjectNormalizerDummy extends AbstractObjectNormalizer @@ -643,6 +658,16 @@ class DuplicateKeyNestedDummy public $notquux; } +class ObjectDummyWithContextAttributeAndSerializedPath +{ + public function __construct( + #[Context([DateTimeNormalizer::FORMAT_KEY => 'm-d-Y'])] + #[SerializedPath('[property][with_path]')] + public \DateTimeImmutable $propertyWithPath, + ) { + } +} + class AbstractObjectNormalizerWithMetadata extends AbstractObjectNormalizer { public function __construct() From 1338831a9ed18f3591db6bcf4e1df908dd3fd037 Mon Sep 17 00:00:00 2001 From: Yassine Guedidi Date: Sun, 2 Apr 2023 04:08:55 +0200 Subject: [PATCH 087/297] Apply operator_linebreak PHP-CS-Fixer rule --- Encoder/XmlEncoder.php | 6 +++--- Normalizer/AbstractNormalizer.php | 6 +++--- Normalizer/AbstractObjectNormalizer.php | 6 +++--- Normalizer/ConstraintViolationListNormalizer.php | 8 ++++---- Normalizer/ObjectNormalizer.php | 8 ++++---- 5 files changed, 17 insertions(+), 17 deletions(-) diff --git a/Encoder/XmlEncoder.php b/Encoder/XmlEncoder.php index 2207012ca..965683c89 100644 --- a/Encoder/XmlEncoder.php +++ b/Encoder/XmlEncoder.php @@ -227,9 +227,9 @@ final protected function appendComment(\DOMNode $node, string $data): bool */ final protected function isElementNameValid(string $name): bool { - return $name && - !str_contains($name, ' ') && - preg_match('#^[\pL_][\pL0-9._:-]*$#ui', $name); + return $name + && !str_contains($name, ' ') + && preg_match('#^[\pL_][\pL0-9._:-]*$#ui', $name); } /** diff --git a/Normalizer/AbstractNormalizer.php b/Normalizer/AbstractNormalizer.php index 95f65df45..58deea9f0 100644 --- a/Normalizer/AbstractNormalizer.php +++ b/Normalizer/AbstractNormalizer.php @@ -236,9 +236,9 @@ protected function getAllowedAttributes(string|object $classOrObject, array $con // If you update this check, update accordingly the one in Symfony\Component\PropertyInfo\Extractor\SerializerExtractor::getProperties() if ( - !$ignore && - ([] === $groups || array_intersect(array_merge($attributeMetadata->getGroups(), ['*']), $groups)) && - $this->isAllowedAttribute($classOrObject, $name = $attributeMetadata->getName(), null, $context) + !$ignore + && ([] === $groups || array_intersect(array_merge($attributeMetadata->getGroups(), ['*']), $groups)) + && $this->isAllowedAttribute($classOrObject, $name = $attributeMetadata->getName(), null, $context) ) { $allowedAttributes[] = $attributesAsString ? $name : $attributeMetadata; } diff --git a/Normalizer/AbstractObjectNormalizer.php b/Normalizer/AbstractObjectNormalizer.php index 647628018..20f15becb 100644 --- a/Normalizer/AbstractObjectNormalizer.php +++ b/Normalizer/AbstractObjectNormalizer.php @@ -678,9 +678,9 @@ private function isMaxDepthReached(array $attributesMetadata, string $class, str { $enableMaxDepth = $context[self::ENABLE_MAX_DEPTH] ?? $this->defaultContext[self::ENABLE_MAX_DEPTH] ?? false; if ( - !$enableMaxDepth || - !isset($attributesMetadata[$attribute]) || - null === $maxDepth = $attributesMetadata[$attribute]->getMaxDepth() + !$enableMaxDepth + || !isset($attributesMetadata[$attribute]) + || null === $maxDepth = $attributesMetadata[$attribute]->getMaxDepth() ) { return false; } diff --git a/Normalizer/ConstraintViolationListNormalizer.php b/Normalizer/ConstraintViolationListNormalizer.php index 0e23d90b3..9b39b727f 100644 --- a/Normalizer/ConstraintViolationListNormalizer.php +++ b/Normalizer/ConstraintViolationListNormalizer.php @@ -76,11 +76,11 @@ public function normalize(mixed $object, string $format = null, array $context = $constraint = $violation->getConstraint(); if ( - [] !== $payloadFieldsToSerialize && - $constraint && - $constraint->payload && + [] !== $payloadFieldsToSerialize + && $constraint + && $constraint->payload // If some or all payload fields are whitelisted, add them - $payloadFields = null === $payloadFieldsToSerialize || true === $payloadFieldsToSerialize ? $constraint->payload : array_intersect_key($constraint->payload, $payloadFieldsToSerialize) + && $payloadFields = null === $payloadFieldsToSerialize || true === $payloadFieldsToSerialize ? $constraint->payload : array_intersect_key($constraint->payload, $payloadFieldsToSerialize) ) { $violationEntry['payload'] = $payloadFields; } diff --git a/Normalizer/ObjectNormalizer.php b/Normalizer/ObjectNormalizer.php index dce443464..dc5067dfb 100644 --- a/Normalizer/ObjectNormalizer.php +++ b/Normalizer/ObjectNormalizer.php @@ -77,10 +77,10 @@ protected function extractAttributes(object $object, string $format = null, arra foreach ($reflClass->getMethods(\ReflectionMethod::IS_PUBLIC) as $reflMethod) { if ( - 0 !== $reflMethod->getNumberOfRequiredParameters() || - $reflMethod->isStatic() || - $reflMethod->isConstructor() || - $reflMethod->isDestructor() + 0 !== $reflMethod->getNumberOfRequiredParameters() + || $reflMethod->isStatic() + || $reflMethod->isConstructor() + || $reflMethod->isDestructor() ) { continue; } From 93416e4e11a8a7dc4ae8fe65ab996c5e3a7038b9 Mon Sep 17 00:00:00 2001 From: Tugdual Saunier Date: Wed, 5 Apr 2023 13:04:49 -0400 Subject: [PATCH 088/297] [Serializer] Cleanup `static` uses when classes are final --- Normalizer/FormErrorNormalizer.php | 2 +- Normalizer/MimeMessageNormalizer.php | 2 +- Normalizer/UidNormalizer.php | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Normalizer/FormErrorNormalizer.php b/Normalizer/FormErrorNormalizer.php index 77f17639a..57d9bd4bb 100644 --- a/Normalizer/FormErrorNormalizer.php +++ b/Normalizer/FormErrorNormalizer.php @@ -90,6 +90,6 @@ public function hasCacheableSupportsMethod(): bool { trigger_deprecation('symfony/serializer', '6.3', 'The "%s()" method is deprecated, use "getSupportedTypes()" instead.', __METHOD__); - return __CLASS__ === static::class; + return true; } } diff --git a/Normalizer/MimeMessageNormalizer.php b/Normalizer/MimeMessageNormalizer.php index 74a8b66b1..1decc0137 100644 --- a/Normalizer/MimeMessageNormalizer.php +++ b/Normalizer/MimeMessageNormalizer.php @@ -121,6 +121,6 @@ public function hasCacheableSupportsMethod(): bool { trigger_deprecation('symfony/serializer', '6.3', 'The "%s()" method is deprecated, use "getSupportedTypes()" instead.', __METHOD__); - return __CLASS__ === static::class; + return true; } } diff --git a/Normalizer/UidNormalizer.php b/Normalizer/UidNormalizer.php index e723ec3f2..40559ec0b 100644 --- a/Normalizer/UidNormalizer.php +++ b/Normalizer/UidNormalizer.php @@ -106,6 +106,6 @@ public function hasCacheableSupportsMethod(): bool { trigger_deprecation('symfony/serializer', '6.3', 'The "%s()" method is deprecated, use "getSupportedTypes()" instead.', __METHOD__); - return __CLASS__ === static::class; + return true; } } From 4ef6322528bdc18e7a37f24e424d801779554b7c Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Wed, 5 Apr 2023 14:28:43 +0200 Subject: [PATCH 089/297] [Serializer] Make `ProblemNormalizer` give details about `ValidationFailedException` and `PartialDenormalizationException` --- CHANGELOG.md | 1 + Exception/PartialDenormalizationException.php | 20 ++++-- .../ConstraintViolationListNormalizer.php | 1 + Normalizer/MimeMessageNormalizer.php | 12 ++-- Normalizer/ProblemNormalizer.php | 64 ++++++++++++++----- Normalizer/UnwrappingDenormalizer.php | 9 ++- .../ConstraintViolationListNormalizerTest.php | 9 ++- Tests/Normalizer/ProblemNormalizerTest.php | 60 +++++++++++++++++ 8 files changed, 146 insertions(+), 30 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 99f68f71f..6b897c1ea 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,7 @@ CHANGELOG * Add `XmlEncoder::SAVE_OPTIONS` context option * Add `BackedEnumNormalizer::ALLOW_INVALID_VALUES` context option * Add method `getSupportedTypes(?string $format)` to `NormalizerInterface` and `DenormalizerInterface` + * Make `ProblemNormalizer` give details about `ValidationFailedException` and `PartialDenormalizationException` * Deprecate `MissingConstructorArgumentsException` in favor of `MissingConstructorArgumentException` * Deprecate `CacheableSupportsMethodInterface` in favor of the new `getSupportedTypes(?string $format)` methods diff --git a/Exception/PartialDenormalizationException.php b/Exception/PartialDenormalizationException.php index fdb838be7..b684fddb2 100644 --- a/Exception/PartialDenormalizationException.php +++ b/Exception/PartialDenormalizationException.php @@ -16,20 +16,26 @@ */ class PartialDenormalizationException extends UnexpectedValueException { - private $data; - private $errors; - - public function __construct($data, array $errors) - { - $this->data = $data; - $this->errors = $errors; + /** + * @param NotNormalizableValueException[] $errors + */ + public function __construct( + private mixed $data, + private array $errors, + ) { } + /** + * @return mixed + */ public function getData() { return $this->data; } + /** + * @return NotNormalizableValueException[] + */ public function getErrors(): array { return $this->errors; diff --git a/Normalizer/ConstraintViolationListNormalizer.php b/Normalizer/ConstraintViolationListNormalizer.php index 9b39b727f..ffdbfebf1 100644 --- a/Normalizer/ConstraintViolationListNormalizer.php +++ b/Normalizer/ConstraintViolationListNormalizer.php @@ -68,6 +68,7 @@ public function normalize(mixed $object, string $format = null, array $context = $violationEntry = [ 'propertyPath' => $propertyPath, 'title' => $violation->getMessage(), + 'template' => $violation->getMessageTemplate(), 'parameters' => $violation->getParameters(), ]; if (null !== $code = $violation->getCode()) { diff --git a/Normalizer/MimeMessageNormalizer.php b/Normalizer/MimeMessageNormalizer.php index 74a8b66b1..ab2b7e3e1 100644 --- a/Normalizer/MimeMessageNormalizer.php +++ b/Normalizer/MimeMessageNormalizer.php @@ -17,6 +17,7 @@ use Symfony\Component\Mime\Header\UnstructuredHeader; use Symfony\Component\Mime\Message; use Symfony\Component\Mime\Part\AbstractPart; +use Symfony\Component\Serializer\Exception\LogicException; use Symfony\Component\Serializer\SerializerAwareInterface; use Symfony\Component\Serializer\SerializerInterface; @@ -30,10 +31,10 @@ */ final class MimeMessageNormalizer implements NormalizerInterface, DenormalizerInterface, SerializerAwareInterface, CacheableSupportsMethodInterface { - private $serializer; - private $normalizer; - private $headerClassMap; - private $headersProperty; + private NormalizerInterface&DenormalizerInterface $serializer; + private PropertyNormalizer $normalizer; + private array $headerClassMap; + private \ReflectionProperty $headersProperty; public function __construct(PropertyNormalizer $normalizer) { @@ -57,6 +58,9 @@ public function getSupportedTypes(?string $format): array public function setSerializer(SerializerInterface $serializer): void { + if (!$serializer instanceof NormalizerInterface || !$serializer instanceof DenormalizerInterface) { + throw new LogicException(sprintf('The passed serializer should implement both NormalizerInterface and DenormalizerInterface, "%s" given.', get_debug_type($serializer))); + } $this->serializer = $serializer; $this->normalizer->setSerializer($serializer); } diff --git a/Normalizer/ProblemNormalizer.php b/Normalizer/ProblemNormalizer.php index dc4dadd88..fd23f3d6d 100644 --- a/Normalizer/ProblemNormalizer.php +++ b/Normalizer/ProblemNormalizer.php @@ -12,7 +12,13 @@ namespace Symfony\Component\Serializer\Normalizer; use Symfony\Component\ErrorHandler\Exception\FlattenException; +use Symfony\Component\HttpKernel\Exception\HttpExceptionInterface; use Symfony\Component\Serializer\Exception\InvalidArgumentException; +use Symfony\Component\Serializer\Exception\PartialDenormalizationException; +use Symfony\Component\Serializer\SerializerAwareInterface; +use Symfony\Component\Serializer\SerializerAwareTrait; +use Symfony\Component\Validator\Exception\ValidationFailedException; +use Symfony\Contracts\Translation\TranslatorInterface; /** * Normalizes errors according to the API Problem spec (RFC 7807). @@ -22,22 +28,19 @@ * @author Kévin Dunglas * @author Yonel Ceruto */ -class ProblemNormalizer implements NormalizerInterface, CacheableSupportsMethodInterface +class ProblemNormalizer implements NormalizerInterface, SerializerAwareInterface, CacheableSupportsMethodInterface { + use SerializerAwareTrait; + public const TITLE = 'title'; public const TYPE = 'type'; public const STATUS = 'status'; - private $debug; - private $defaultContext = [ - self::TYPE => 'https://tools.ietf.org/html/rfc2616#section-10', - self::TITLE => 'An error occurred', - ]; - - public function __construct(bool $debug = false, array $defaultContext = []) - { - $this->debug = $debug; - $this->defaultContext = $defaultContext + $this->defaultContext; + public function __construct( + private bool $debug = false, + private array $defaultContext = [], + private ?TranslatorInterface $translator = null, + ) { } public function getSupportedTypes(?string $format): array @@ -53,15 +56,46 @@ public function normalize(mixed $object, string $format = null, array $context = throw new InvalidArgumentException(sprintf('The object must implement "%s".', FlattenException::class)); } + $data = []; $context += $this->defaultContext; $debug = $this->debug && ($context['debug'] ?? true); + $exception = $context['exception'] ?? null; + if ($exception instanceof HttpExceptionInterface) { + $exception = $exception->getPrevious(); + + if ($exception instanceof PartialDenormalizationException) { + $trans = $this->translator ? $this->translator->trans(...) : fn ($m, $p) => strtr($m, $p); + $template = 'This value should be of type {{ type }}.'; + $data = [ + self::TYPE => 'https://symfony.com/errors/validation', + 'violations' => array_map( + fn ($e) => [ + 'propertyPath' => $e->getPath(), + 'title' => $trans($template, [ + '{{ type }}' => implode('|', $e->getExpectedTypes() ?? ['?']), + ], 'validators'), + 'template' => $template, + 'parameter' => [ + '{{ type }}' => implode('|', $e->getExpectedTypes() ?? ['?']), + ], + ] + ($debug || $e->canUseMessageForUser() ? ['hint' => $e->getMessage()] : []), + $exception->getErrors() + ), + ]; + } elseif ($exception instanceof ValidationFailedException + && $this->serializer instanceof NormalizerInterface + && $this->serializer->supportsNormalization($exception->getViolations(), $format, $context) + ) { + $data = $this->serializer->normalize($exception->getViolations(), $format, $context); + } + } $data = [ - self::TYPE => $context['type'], - self::TITLE => $context['title'], - self::STATUS => $context['status'] ?? $object->getStatusCode(), + self::TYPE => $data[self::TYPE] ?? $context[self::TYPE] ?? 'https://tools.ietf.org/html/rfc2616#section-10', + self::TITLE => $data[self::TITLE] ?? $context[self::TITLE] ?? 'An error occurred', + self::STATUS => $context[self::STATUS] ?? $object->getStatusCode(), 'detail' => $debug ? $object->getMessage() : $object->getStatusText(), - ]; + ] + $data; if ($debug) { $data['class'] = $object->getClass(); $data['trace'] = $object->getTrace(); diff --git a/Normalizer/UnwrappingDenormalizer.php b/Normalizer/UnwrappingDenormalizer.php index 738ece32e..31cc37be9 100644 --- a/Normalizer/UnwrappingDenormalizer.php +++ b/Normalizer/UnwrappingDenormalizer.php @@ -13,6 +13,7 @@ use Symfony\Component\PropertyAccess\PropertyAccess; use Symfony\Component\PropertyAccess\PropertyAccessorInterface; +use Symfony\Component\Serializer\Exception\LogicException; use Symfony\Component\Serializer\SerializerAwareInterface; use Symfony\Component\Serializer\SerializerAwareTrait; @@ -37,7 +38,7 @@ public function getSupportedTypes(?string $format): array return ['*' => false]; } - public function denormalize(mixed $data, string $class, string $format = null, array $context = []): mixed + public function denormalize(mixed $data, string $type, string $format = null, array $context = []): mixed { $propertyPath = $context[self::UNWRAP_PATH]; $context['unwrapped'] = true; @@ -50,7 +51,11 @@ public function denormalize(mixed $data, string $class, string $format = null, a $data = $this->propertyAccessor->getValue($data, $propertyPath); } - return $this->serializer->denormalize($data, $class, $format, $context); + if (!$this->serializer instanceof DenormalizerInterface) { + throw new LogicException('Cannot unwrap path because the injected serializer is not a denormalizer.'); + } + + return $this->serializer->denormalize($data, $type, $format, $context); } public function supportsDenormalization(mixed $data, string $type, string $format = null, array $context = []): bool diff --git a/Tests/Normalizer/ConstraintViolationListNormalizerTest.php b/Tests/Normalizer/ConstraintViolationListNormalizerTest.php index 4dd177948..8e8df388d 100644 --- a/Tests/Normalizer/ConstraintViolationListNormalizerTest.php +++ b/Tests/Normalizer/ConstraintViolationListNormalizerTest.php @@ -53,6 +53,7 @@ public function testNormalize() [ 'propertyPath' => 'd', 'title' => 'a', + 'template' => 'b', 'type' => 'urn:uuid:f', 'parameters' => [ 'value' => 'foo', @@ -61,6 +62,7 @@ public function testNormalize() [ 'propertyPath' => '4', 'title' => '1', + 'template' => '2', 'type' => 'urn:uuid:6', 'parameters' => [], ], @@ -75,9 +77,9 @@ public function testNormalizeWithNameConverter() $normalizer = new ConstraintViolationListNormalizer([], new CamelCaseToSnakeCaseNameConverter()); $list = new ConstraintViolationList([ - new ConstraintViolation('too short', 'a', [], 'c', 'shortDescription', ''), + new ConstraintViolation('too short', 'a', [], '3', 'shortDescription', ''), new ConstraintViolation('too long', 'b', [], '3', 'product.shortDescription', 'Lorem ipsum dolor sit amet'), - new ConstraintViolation('error', 'b', [], '3', '', ''), + new ConstraintViolation('error', 'c', [], '3', '', ''), ]); $expected = [ @@ -90,16 +92,19 @@ public function testNormalizeWithNameConverter() [ 'propertyPath' => 'short_description', 'title' => 'too short', + 'template' => 'a', 'parameters' => [], ], [ 'propertyPath' => 'product.short_description', 'title' => 'too long', + 'template' => 'b', 'parameters' => [], ], [ 'propertyPath' => '', 'title' => 'error', + 'template' => 'c', 'parameters' => [], ], ], diff --git a/Tests/Normalizer/ProblemNormalizerTest.php b/Tests/Normalizer/ProblemNormalizerTest.php index 4a754ac5b..8cc506c6e 100644 --- a/Tests/Normalizer/ProblemNormalizerTest.php +++ b/Tests/Normalizer/ProblemNormalizerTest.php @@ -13,7 +13,15 @@ use PHPUnit\Framework\TestCase; use Symfony\Component\ErrorHandler\Exception\FlattenException; +use Symfony\Component\HttpKernel\Exception\HttpException; +use Symfony\Component\Serializer\Exception\NotNormalizableValueException; +use Symfony\Component\Serializer\Exception\PartialDenormalizationException; +use Symfony\Component\Serializer\Normalizer\ConstraintViolationListNormalizer; use Symfony\Component\Serializer\Normalizer\ProblemNormalizer; +use Symfony\Component\Serializer\Serializer; +use Symfony\Component\Validator\ConstraintViolation; +use Symfony\Component\Validator\ConstraintViolationList; +use Symfony\Component\Validator\Exception\ValidationFailedException; class ProblemNormalizerTest extends TestCase { @@ -45,4 +53,56 @@ public function testNormalize() $this->assertSame($expected, $this->normalizer->normalize(FlattenException::createFromThrowable(new \RuntimeException('Error')))); } + + public function testNormalizePartialDenormalizationException() + { + $this->normalizer->setSerializer(new Serializer()); + + $expected = [ + 'type' => 'https://symfony.com/errors/validation', + 'title' => 'An error occurred', + 'status' => 422, + 'detail' => 'Unprocessable Content', + 'violations' => [ + [ + 'propertyPath' => 'foo', + 'title' => 'This value should be of type int.', + 'template' => 'This value should be of type {{ type }}.', + 'parameters' => [ + '{{ type }}' => 'int', + ], + 'hint' => 'Invalid value', + ], + ], + ]; + + $exception = NotNormalizableValueException::createForUnexpectedDataType('Invalid value', null, ['int'], 'foo', true); + $exception = new PartialDenormalizationException('Validation Failed', [$exception]); + $exception = new HttpException(422, 'Validation Failed', $exception); + $this->assertSame($expected, $this->normalizer->normalize(FlattenException::createFromThrowable($exception), null, ['exception' => $exception])); + } + + public function testNormalizeValidationFailedException() + { + $this->normalizer->setSerializer(new Serializer([new ConstraintViolationListNormalizer()])); + + $expected = [ + 'type' => 'https://symfony.com/errors/validation', + 'title' => 'Validation Failed', + 'status' => 422, + 'detail' => 'Unprocessable Content', + 'violations' => [ + [ + 'propertyPath' => '', + 'title' => 'Invalid value', + 'template' => '', + 'parameters' => [], + ], + ], + ]; + + $exception = new ValidationFailedException('Validation Failed', new ConstraintViolationList([new ConstraintViolation('Invalid value', '', [], '', null, null)])); + $exception = new HttpException(422, 'Validation Failed', $exception); + $this->assertSame($expected, $this->normalizer->normalize(FlattenException::createFromThrowable($exception), null, ['exception' => $exception])); + } } From 7682f3d4f7986574bcc36735c60e078f5dc954b3 Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Fri, 7 Apr 2023 13:46:48 +0200 Subject: [PATCH 090/297] [Serializer] Fix tests --- Normalizer/ProblemNormalizer.php | 6 ++++-- Tests/Normalizer/ProblemNormalizerTest.php | 6 +++--- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/Normalizer/ProblemNormalizer.php b/Normalizer/ProblemNormalizer.php index fd23f3d6d..4161d0b1c 100644 --- a/Normalizer/ProblemNormalizer.php +++ b/Normalizer/ProblemNormalizer.php @@ -68,6 +68,7 @@ public function normalize(mixed $object, string $format = null, array $context = $template = 'This value should be of type {{ type }}.'; $data = [ self::TYPE => 'https://symfony.com/errors/validation', + self::TITLE => 'Validation Failed', 'violations' => array_map( fn ($e) => [ 'propertyPath' => $e->getPath(), @@ -75,13 +76,14 @@ public function normalize(mixed $object, string $format = null, array $context = '{{ type }}' => implode('|', $e->getExpectedTypes() ?? ['?']), ], 'validators'), 'template' => $template, - 'parameter' => [ + 'parameters' => [ '{{ type }}' => implode('|', $e->getExpectedTypes() ?? ['?']), ], ] + ($debug || $e->canUseMessageForUser() ? ['hint' => $e->getMessage()] : []), $exception->getErrors() ), ]; + $data['detail'] = implode("\n", array_map(fn ($e) => $e['propertyPath'].': '.$e['title'], $data['violations'])); } elseif ($exception instanceof ValidationFailedException && $this->serializer instanceof NormalizerInterface && $this->serializer->supportsNormalization($exception->getViolations(), $format, $context) @@ -94,7 +96,7 @@ public function normalize(mixed $object, string $format = null, array $context = self::TYPE => $data[self::TYPE] ?? $context[self::TYPE] ?? 'https://tools.ietf.org/html/rfc2616#section-10', self::TITLE => $data[self::TITLE] ?? $context[self::TITLE] ?? 'An error occurred', self::STATUS => $context[self::STATUS] ?? $object->getStatusCode(), - 'detail' => $debug ? $object->getMessage() : $object->getStatusText(), + 'detail' => $data['detail'] ?? ($debug ? $object->getMessage() : $object->getStatusText()), ] + $data; if ($debug) { $data['class'] = $object->getClass(); diff --git a/Tests/Normalizer/ProblemNormalizerTest.php b/Tests/Normalizer/ProblemNormalizerTest.php index 8cc506c6e..0937e66d8 100644 --- a/Tests/Normalizer/ProblemNormalizerTest.php +++ b/Tests/Normalizer/ProblemNormalizerTest.php @@ -60,9 +60,9 @@ public function testNormalizePartialDenormalizationException() $expected = [ 'type' => 'https://symfony.com/errors/validation', - 'title' => 'An error occurred', + 'title' => 'Validation Failed', 'status' => 422, - 'detail' => 'Unprocessable Content', + 'detail' => 'foo: This value should be of type int.', 'violations' => [ [ 'propertyPath' => 'foo', @@ -90,7 +90,7 @@ public function testNormalizeValidationFailedException() 'type' => 'https://symfony.com/errors/validation', 'title' => 'Validation Failed', 'status' => 422, - 'detail' => 'Unprocessable Content', + 'detail' => 'Invalid value', 'violations' => [ [ 'propertyPath' => '', From b314f451279d9a104cd8f8022a5b358aec6620e4 Mon Sep 17 00:00:00 2001 From: Tugdual Saunier Date: Wed, 5 Apr 2023 14:55:21 -0400 Subject: [PATCH 091/297] [Serializer] Mark ObjectNormalizer as final for 7.0 --- CHANGELOG.md | 1 + Normalizer/ObjectNormalizer.php | 2 ++ 2 files changed, 3 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6b897c1ea..ba277a387 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ CHANGELOG * Make `ProblemNormalizer` give details about `ValidationFailedException` and `PartialDenormalizationException` * Deprecate `MissingConstructorArgumentsException` in favor of `MissingConstructorArgumentException` * Deprecate `CacheableSupportsMethodInterface` in favor of the new `getSupportedTypes(?string $format)` methods + * The following Normalizer classes will become final in 7.0: `ObjectNormalizer` 6.2 --- diff --git a/Normalizer/ObjectNormalizer.php b/Normalizer/ObjectNormalizer.php index dc5067dfb..f4e50841e 100644 --- a/Normalizer/ObjectNormalizer.php +++ b/Normalizer/ObjectNormalizer.php @@ -25,6 +25,8 @@ * Converts between objects and arrays using the PropertyAccess component. * * @author Kévin Dunglas + * + * @final since Symfony 6.3 */ class ObjectNormalizer extends AbstractObjectNormalizer { From f5971d96e79df2db951ae9e42ddbc6f1dcfaa232 Mon Sep 17 00:00:00 2001 From: Tugdual Saunier Date: Wed, 5 Apr 2023 12:48:46 -0400 Subject: [PATCH 092/297] [Serializer] Marking some Normalizer classes as final | Q | A | ------------- | --- | Branch? | 6.3 for features | Bug fix? | no | New feature? | no | Deprecations? | yes | Tickets | https://github.com/symfony/symfony/pull/49291#pullrequestreview-1306165711 | License | MIT | Doc PR | n/a As mentioned in https://github.com/symfony/symfony/pull/49291#pullrequestreview-1306165711. --- CHANGELOG.md | 12 +++++++++++- Normalizer/ConstraintViolationListNormalizer.php | 2 ++ Normalizer/CustomNormalizer.php | 2 ++ Normalizer/DataUriNormalizer.php | 2 ++ Normalizer/DateIntervalNormalizer.php | 2 ++ Normalizer/DateTimeNormalizer.php | 2 ++ Normalizer/DateTimeZoneNormalizer.php | 2 ++ Normalizer/GetSetMethodNormalizer.php | 2 ++ Normalizer/JsonSerializableNormalizer.php | 2 ++ Normalizer/PropertyNormalizer.php | 2 ++ 10 files changed, 29 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ba277a387..622d4152e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,7 +10,17 @@ CHANGELOG * Make `ProblemNormalizer` give details about `ValidationFailedException` and `PartialDenormalizationException` * Deprecate `MissingConstructorArgumentsException` in favor of `MissingConstructorArgumentException` * Deprecate `CacheableSupportsMethodInterface` in favor of the new `getSupportedTypes(?string $format)` methods - * The following Normalizer classes will become final in 7.0: `ObjectNormalizer` + * The following Normalizer classes will become final in 7.0: + * `ConstraintViolationListNormalizer` + * `CustomNormalizer` + * `DataUriNormalizer` + * `DateIntervalNormalizer` + * `DateTimeNormalizer` + * `DateTimeZoneNormalizer` + * `GetSetMethodNormalizer` + * `JsonSerializableNormalizer` + * `ObjectNormalizer` + * `PropertyNormalizer` 6.2 --- diff --git a/Normalizer/ConstraintViolationListNormalizer.php b/Normalizer/ConstraintViolationListNormalizer.php index ffdbfebf1..8816055a3 100644 --- a/Normalizer/ConstraintViolationListNormalizer.php +++ b/Normalizer/ConstraintViolationListNormalizer.php @@ -21,6 +21,8 @@ * * @author Grégoire Pineau * @author Kévin Dunglas + * + * @final since Symfony 6.3 */ class ConstraintViolationListNormalizer implements NormalizerInterface, CacheableSupportsMethodInterface { diff --git a/Normalizer/CustomNormalizer.php b/Normalizer/CustomNormalizer.php index 4c17c2552..dd64c9dca 100644 --- a/Normalizer/CustomNormalizer.php +++ b/Normalizer/CustomNormalizer.php @@ -16,6 +16,8 @@ /** * @author Jordi Boggiano + * + * @final since Symfony 6.3 */ class CustomNormalizer implements NormalizerInterface, DenormalizerInterface, SerializerAwareInterface, CacheableSupportsMethodInterface { diff --git a/Normalizer/DataUriNormalizer.php b/Normalizer/DataUriNormalizer.php index 9012f6f3a..ff07c1f30 100644 --- a/Normalizer/DataUriNormalizer.php +++ b/Normalizer/DataUriNormalizer.php @@ -22,6 +22,8 @@ * Denormalizes a data URI to a {@see \SplFileObject} object. * * @author Kévin Dunglas + * + * @final since Symfony 6.3 */ class DataUriNormalizer implements NormalizerInterface, DenormalizerInterface, CacheableSupportsMethodInterface { diff --git a/Normalizer/DateIntervalNormalizer.php b/Normalizer/DateIntervalNormalizer.php index 616d500e7..c9bc51772 100644 --- a/Normalizer/DateIntervalNormalizer.php +++ b/Normalizer/DateIntervalNormalizer.php @@ -19,6 +19,8 @@ * Denormalizes an interval string to an instance of {@see \DateInterval}. * * @author Jérôme Parmentier + * + * @final since Symfony 6.3 */ class DateIntervalNormalizer implements NormalizerInterface, DenormalizerInterface, CacheableSupportsMethodInterface { diff --git a/Normalizer/DateTimeNormalizer.php b/Normalizer/DateTimeNormalizer.php index 2235b1ea5..43faf9ad1 100644 --- a/Normalizer/DateTimeNormalizer.php +++ b/Normalizer/DateTimeNormalizer.php @@ -20,6 +20,8 @@ * Denormalizes a date string to an instance of {@see \DateTime} or {@see \DateTimeImmutable}. * * @author Kévin Dunglas + * + * @final since Symfony 6.3 */ class DateTimeNormalizer implements NormalizerInterface, DenormalizerInterface, CacheableSupportsMethodInterface { diff --git a/Normalizer/DateTimeZoneNormalizer.php b/Normalizer/DateTimeZoneNormalizer.php index a7c26f1d4..472f64fc8 100644 --- a/Normalizer/DateTimeZoneNormalizer.php +++ b/Normalizer/DateTimeZoneNormalizer.php @@ -19,6 +19,8 @@ * Normalizes a {@see \DateTimeZone} object to a timezone string. * * @author Jérôme Desjardins + * + * @final since Symfony 6.3 */ class DateTimeZoneNormalizer implements NormalizerInterface, DenormalizerInterface, CacheableSupportsMethodInterface { diff --git a/Normalizer/GetSetMethodNormalizer.php b/Normalizer/GetSetMethodNormalizer.php index d92394834..063d34ea5 100644 --- a/Normalizer/GetSetMethodNormalizer.php +++ b/Normalizer/GetSetMethodNormalizer.php @@ -33,6 +33,8 @@ * * @author Nils Adermann * @author Kévin Dunglas + * + * @final since Symfony 6.3 */ class GetSetMethodNormalizer extends AbstractObjectNormalizer { diff --git a/Normalizer/JsonSerializableNormalizer.php b/Normalizer/JsonSerializableNormalizer.php index 841fca319..1c8bbfe4a 100644 --- a/Normalizer/JsonSerializableNormalizer.php +++ b/Normalizer/JsonSerializableNormalizer.php @@ -18,6 +18,8 @@ * A normalizer that uses an objects own JsonSerializable implementation. * * @author Fred Cox + * + * @final since Symfony 6.3 */ class JsonSerializableNormalizer extends AbstractNormalizer { diff --git a/Normalizer/PropertyNormalizer.php b/Normalizer/PropertyNormalizer.php index 56e5ebf74..ec12db9bb 100644 --- a/Normalizer/PropertyNormalizer.php +++ b/Normalizer/PropertyNormalizer.php @@ -33,6 +33,8 @@ * * @author Matthieu Napoli * @author Kévin Dunglas + * + * @final since Symfony 6.3 */ class PropertyNormalizer extends AbstractObjectNormalizer { From 8ee26cc89f515e7cb67feb74a44be9a7828cd405 Mon Sep 17 00:00:00 2001 From: Christian Flothmann Date: Tue, 11 Apr 2023 11:40:12 +0200 Subject: [PATCH 093/297] fix deprecations caused by ObjectNormalizer marked as final --- .../FormatAndContextAwareNormalizer.php | 22 ++++++++++++++++ .../Fixtures/StaticConstructorNormalizer.php | 24 ++++++++++++++++-- Tests/Normalizer/ObjectNormalizerTest.php | 25 ++++++------------- 3 files changed, 51 insertions(+), 20 deletions(-) create mode 100644 Tests/Fixtures/FormatAndContextAwareNormalizer.php diff --git a/Tests/Fixtures/FormatAndContextAwareNormalizer.php b/Tests/Fixtures/FormatAndContextAwareNormalizer.php new file mode 100644 index 000000000..404228845 --- /dev/null +++ b/Tests/Fixtures/FormatAndContextAwareNormalizer.php @@ -0,0 +1,22 @@ + + * + * 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\ObjectNormalizer; + +class FormatAndContextAwareNormalizer extends ObjectNormalizer +{ + protected function isAllowedAttribute($classOrObject, string $attribute, string $format = null, array $context = []): bool + { + return \in_array($attribute, ['foo', 'bar']) && 'foo_and_bar_included' === $format; + } +} diff --git a/Tests/Fixtures/StaticConstructorNormalizer.php b/Tests/Fixtures/StaticConstructorNormalizer.php index 44a367e92..67ae03144 100644 --- a/Tests/Fixtures/StaticConstructorNormalizer.php +++ b/Tests/Fixtures/StaticConstructorNormalizer.php @@ -11,13 +11,33 @@ namespace Symfony\Component\Serializer\Tests\Fixtures; -use Symfony\Component\Serializer\Normalizer\ObjectNormalizer; +use Symfony\Component\Serializer\Normalizer\AbstractObjectNormalizer; /** * @author Guilhem N. */ -class StaticConstructorNormalizer extends ObjectNormalizer +class StaticConstructorNormalizer extends AbstractObjectNormalizer { + public function getSupportedTypes(?string $format): array + { + return [StaticConstructorDummy::class]; + } + + protected function extractAttributes(object $object, string $format = null, array $context = []) + { + return get_object_vars($object); + } + + protected function getAttributeValue(object $object, string $attribute, string $format = null, array $context = []) + { + return $object->$attribute; + } + + protected function setAttributeValue(object $object, string $attribute, mixed $value, string $format = null, array $context = []) + { + $object->$attribute = $value; + } + protected function getConstructor(array &$data, string $class, array &$context, \ReflectionClass $reflectionClass, array|bool $allowedAttributes): ?\ReflectionMethod { if (is_a($class, StaticConstructorDummy::class, true)) { diff --git a/Tests/Normalizer/ObjectNormalizerTest.php b/Tests/Normalizer/ObjectNormalizerTest.php index 47747c6db..5eba0707c 100644 --- a/Tests/Normalizer/ObjectNormalizerTest.php +++ b/Tests/Normalizer/ObjectNormalizerTest.php @@ -27,6 +27,7 @@ use Symfony\Component\Serializer\NameConverter\AdvancedNameConverterInterface; use Symfony\Component\Serializer\NameConverter\CamelCaseToSnakeCaseNameConverter; use Symfony\Component\Serializer\NameConverter\MetadataAwareNameConverter; +use Symfony\Component\Serializer\Normalizer\AbstractNormalizer; use Symfony\Component\Serializer\Normalizer\ArrayDenormalizer; use Symfony\Component\Serializer\Normalizer\DateTimeNormalizer; use Symfony\Component\Serializer\Normalizer\NormalizerInterface; @@ -36,6 +37,7 @@ use Symfony\Component\Serializer\Tests\Fixtures\Annotations\GroupDummy; use Symfony\Component\Serializer\Tests\Fixtures\CircularReferenceDummy; use Symfony\Component\Serializer\Tests\Fixtures\DummyPrivatePropertyWithoutGetter; +use Symfony\Component\Serializer\Tests\Fixtures\FormatAndContextAwareNormalizer; use Symfony\Component\Serializer\Tests\Fixtures\OtherSerializedNameDummy; use Symfony\Component\Serializer\Tests\Fixtures\Php74Dummy; use Symfony\Component\Serializer\Tests\Fixtures\Php74DummyPrivate; @@ -742,6 +744,9 @@ public function testDoesntHaveIssuesWithUnionConstTypes() })::class)->foo); } + /** + * @group legacy + */ public function testExtractAttributesRespectsFormat() { $normalizer = new FormatAndContextAwareNormalizer(); @@ -755,13 +760,13 @@ public function testExtractAttributesRespectsFormat() public function testExtractAttributesRespectsContext() { - $normalizer = new FormatAndContextAwareNormalizer(); + $normalizer = new ObjectNormalizer(); $data = new ObjectDummy(); $data->setFoo('bar'); $data->bar = 'foo'; - $this->assertSame(['foo' => 'bar', 'bar' => 'foo'], $normalizer->normalize($data, null, ['include_foo_and_bar' => true])); + $this->assertSame(['foo' => 'bar', 'bar' => 'foo'], $normalizer->normalize($data, null, [AbstractNormalizer::ATTRIBUTES => ['foo', 'bar']])); } public function testDenormalizeFalsePseudoType() @@ -1040,22 +1045,6 @@ public function __get($name) } } -class FormatAndContextAwareNormalizer extends ObjectNormalizer -{ - protected function isAllowedAttribute($classOrObject, string $attribute, string $format = null, array $context = []): bool - { - if (\in_array($attribute, ['foo', 'bar']) && 'foo_and_bar_included' === $format) { - return true; - } - - if (\in_array($attribute, ['foo', 'bar']) && isset($context['include_foo_and_bar']) && true === $context['include_foo_and_bar']) { - return true; - } - - return false; - } -} - class DummyWithConstructorObject { private $id; From cfe19ea11c2aacfa7a62d3169785623c94e17ec8 Mon Sep 17 00:00:00 2001 From: Matthias Pigulla Date: Wed, 12 Apr 2023 17:26:31 +0200 Subject: [PATCH 094/297] Update return type for NormalizableInterface::normalize to match NormalizerInterface::normalize --- Normalizer/NormalizableInterface.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Normalizer/NormalizableInterface.php b/Normalizer/NormalizableInterface.php index d449a4306..0bad9d183 100644 --- a/Normalizer/NormalizableInterface.php +++ b/Normalizer/NormalizableInterface.php @@ -33,5 +33,5 @@ interface NormalizableInterface * based on different output formats * @param array $context Options for normalizing this object */ - public function normalize(NormalizerInterface $normalizer, string $format = null, array $context = []): array|string|int|float|bool; + public function normalize(NormalizerInterface $normalizer, string $format = null, array $context = []): array|string|int|float|bool|\ArrayObject|null; } From c4b77e6f7d3ab730b0d4946e8b9bd92f76008b28 Mon Sep 17 00:00:00 2001 From: Alex Kalineskou Date: Fri, 14 Apr 2023 03:52:15 +0300 Subject: [PATCH 095/297] Fix serializer normalize attribute context --- Normalizer/AbstractObjectNormalizer.php | 9 +++--- .../AbstractObjectNormalizerTest.php | 29 +++++++++++++++++-- 2 files changed, 31 insertions(+), 7 deletions(-) diff --git a/Normalizer/AbstractObjectNormalizer.php b/Normalizer/AbstractObjectNormalizer.php index 1feff46ad..afd3acabc 100644 --- a/Normalizer/AbstractObjectNormalizer.php +++ b/Normalizer/AbstractObjectNormalizer.php @@ -196,14 +196,14 @@ public function normalize(mixed $object, string $format = null, array $context = $attributeValue = $maxDepthHandler($attributeValue, $object, $attribute, $format, $attributeContext); } - $attributeValue = $this->applyCallbacks($attributeValue, $object, $attribute, $format, $attributeContext); - - $stack[$attribute] = $attributeValue; + $stack[$attribute] = $this->applyCallbacks($attributeValue, $object, $attribute, $format, $attributeContext); } foreach ($stack as $attribute => $attributeValue) { + $attributeContext = $this->getAttributeNormalizationContext($object, $attribute, $context); + if (null === $attributeValue || \is_scalar($attributeValue)) { - $data = $this->updateData($data, $attribute, $attributeValue, $class, $format, $context, $attributesMetadata, $classMetadata); + $data = $this->updateData($data, $attribute, $attributeValue, $class, $format, $attributeContext, $attributesMetadata, $classMetadata); continue; } @@ -211,7 +211,6 @@ public function normalize(mixed $object, string $format = null, array $context = throw new LogicException(sprintf('Cannot normalize attribute "%s" because the injected serializer is not a normalizer.', $attribute)); } - $attributeContext = $this->getAttributeNormalizationContext($object, $attribute, $context); $childContext = $this->createChildContext($attributeContext, $attribute, $format); $data = $this->updateData($data, $attribute, $this->serializer->normalize($attributeValue, $format, $childContext), $class, $format, $attributeContext, $attributesMetadata, $classMetadata); diff --git a/Tests/Normalizer/AbstractObjectNormalizerTest.php b/Tests/Normalizer/AbstractObjectNormalizerTest.php index 9a319f7ef..0c342b64d 100644 --- a/Tests/Normalizer/AbstractObjectNormalizerTest.php +++ b/Tests/Normalizer/AbstractObjectNormalizerTest.php @@ -17,6 +17,7 @@ use Symfony\Component\PropertyInfo\Extractor\ReflectionExtractor; use Symfony\Component\PropertyInfo\PropertyInfoExtractor; use Symfony\Component\PropertyInfo\Type; +use Symfony\Component\Serializer\Annotation\Context; use Symfony\Component\Serializer\Annotation\SerializedName; use Symfony\Component\Serializer\Annotation\SerializedPath; use Symfony\Component\Serializer\Exception\ExtraAttributesException; @@ -549,13 +550,28 @@ public function testNormalizeUsesContextAttributeForPropertiesInConstructorWithS $extractor = new PropertyInfoExtractor([], [new PhpDocExtractor(), new ReflectionExtractor()]); $normalizer = new ObjectNormalizer($classMetadataFactory, new MetadataAwareNameConverter($classMetadataFactory), null, $extractor); - $serializer = new Serializer([new DateTimeNormalizer([DateTimeNormalizer::FORMAT_KEY => 'd-m-Y']), $normalizer]); + $serializer = new Serializer([new DateTimeNormalizer(), $normalizer]); $obj = new ObjectDummyWithContextAttributeAndSerializedPath(new \DateTimeImmutable('22-02-2023')); $data = $serializer->normalize($obj); - $this->assertSame(['property' => ['with_path' => '22-02-2023']], $data); + $this->assertSame(['property' => ['with_path' => '02-22-2023']], $data); + } + + public function testNormalizeUsesContextAttributeForProperties() + { + $classMetadataFactory = new ClassMetadataFactory(new AnnotationLoader(new AnnotationReader())); + + $extractor = new PropertyInfoExtractor([], [new PhpDocExtractor(), new ReflectionExtractor()]); + $normalizer = new ObjectNormalizer($classMetadataFactory, new MetadataAwareNameConverter($classMetadataFactory), null, $extractor); + $serializer = new Serializer([$normalizer]); + + $obj = new ObjectDummyWithContextAttributeSkipNullValues(); + + $data = $serializer->normalize($obj); + + $this->assertSame(['propertyWithoutNullSkipNullValues' => 'foo'], $data); } } @@ -668,6 +684,15 @@ public function __construct( } } +class ObjectDummyWithContextAttributeSkipNullValues +{ + #[Context([AbstractObjectNormalizer::SKIP_NULL_VALUES => true])] + public ?string $propertyWithoutNullSkipNullValues = 'foo'; + + #[Context([AbstractObjectNormalizer::SKIP_NULL_VALUES => true])] + public ?string $propertyWithNullSkipNullValues = null; +} + class AbstractObjectNormalizerWithMetadata extends AbstractObjectNormalizer { public function __construct() From 980037eab36e1914d5d400dc61384da3fbf4ed2b Mon Sep 17 00:00:00 2001 From: "alexandre.lassauge" Date: Thu, 13 Apr 2023 13:52:27 +0200 Subject: [PATCH 096/297] [Serializer] Add missing return types --- Mapping/AttributeMetadata.php | 25 +++++------------------ Mapping/AttributeMetadataInterface.php | 10 ++++----- Mapping/ClassMetadata.php | 15 +++----------- Mapping/ClassMetadataInterface.php | 6 +++--- Normalizer/DenormalizerAwareInterface.php | 2 ++ Normalizer/NormalizerAwareInterface.php | 2 ++ SerializerAwareInterface.php | 2 ++ 7 files changed, 22 insertions(+), 40 deletions(-) diff --git a/Mapping/AttributeMetadata.php b/Mapping/AttributeMetadata.php index aeee5ac68..cb77b53d2 100644 --- a/Mapping/AttributeMetadata.php +++ b/Mapping/AttributeMetadata.php @@ -94,10 +94,7 @@ public function getName(): string return $this->name; } - /** - * @return void - */ - public function addGroup(string $group) + public function addGroup(string $group): void { if (!\in_array($group, $this->groups)) { $this->groups[] = $group; @@ -109,10 +106,7 @@ public function getGroups(): array return $this->groups; } - /** - * @return void - */ - public function setMaxDepth(?int $maxDepth) + public function setMaxDepth(?int $maxDepth): void { $this->maxDepth = $maxDepth; } @@ -122,10 +116,7 @@ public function getMaxDepth(): ?int return $this->maxDepth; } - /** - * @return void - */ - public function setSerializedName(string $serializedName = null) + public function setSerializedName(string $serializedName = null): void { if (1 > \func_num_args()) { trigger_deprecation('symfony/serializer', '6.2', 'Calling "%s()" without any arguments is deprecated, pass null explicitly instead.', __METHOD__); @@ -149,10 +140,7 @@ public function getSerializedPath(): ?PropertyPath return $this->serializedPath; } - /** - * @return void - */ - public function setIgnore(bool $ignore) + public function setIgnore(bool $ignore): void { $this->ignore = $ignore; } @@ -214,10 +202,7 @@ public function setDenormalizationContextForGroups(array $context, array $groups } } - /** - * @return void - */ - public function merge(AttributeMetadataInterface $attributeMetadata) + public function merge(AttributeMetadataInterface $attributeMetadata): void { foreach ($attributeMetadata->getGroups() as $group) { $this->addGroup($group); diff --git a/Mapping/AttributeMetadataInterface.php b/Mapping/AttributeMetadataInterface.php index 67ca8d3c0..9d430602c 100644 --- a/Mapping/AttributeMetadataInterface.php +++ b/Mapping/AttributeMetadataInterface.php @@ -32,7 +32,7 @@ public function getName(): string; /** * Adds this attribute to the given group. */ - public function addGroup(string $group); + public function addGroup(string $group): void; /** * Gets groups of this attribute. @@ -44,7 +44,7 @@ public function getGroups(): array; /** * Sets the serialization max depth for this attribute. */ - public function setMaxDepth(?int $maxDepth); + public function setMaxDepth(?int $maxDepth): void; /** * Gets the serialization max depth for this attribute. @@ -54,7 +54,7 @@ public function getMaxDepth(): ?int; /** * Sets the serialization name for this attribute. */ - public function setSerializedName(?string $serializedName); + public function setSerializedName(?string $serializedName): void; /** * Gets the serialization name for this attribute. @@ -68,7 +68,7 @@ public function getSerializedPath(): ?PropertyPath; /** * Sets if this attribute must be ignored or not. */ - public function setIgnore(bool $ignore); + public function setIgnore(bool $ignore): void; /** * Gets if this attribute is ignored or not. @@ -78,7 +78,7 @@ public function isIgnored(): bool; /** * Merges an {@see AttributeMetadataInterface} with in the current one. */ - public function merge(self $attributeMetadata); + public function merge(self $attributeMetadata): void; /** * Gets all the normalization contexts per group ("*" being the base context applied to all groups). diff --git a/Mapping/ClassMetadata.php b/Mapping/ClassMetadata.php index fc6336ebd..4a70ee085 100644 --- a/Mapping/ClassMetadata.php +++ b/Mapping/ClassMetadata.php @@ -60,10 +60,7 @@ public function getName(): string return $this->name; } - /** - * @return void - */ - public function addAttributeMetadata(AttributeMetadataInterface $attributeMetadata) + public function addAttributeMetadata(AttributeMetadataInterface $attributeMetadata): void { $this->attributesMetadata[$attributeMetadata->getName()] = $attributeMetadata; } @@ -73,10 +70,7 @@ public function getAttributesMetadata(): array return $this->attributesMetadata; } - /** - * @return void - */ - public function merge(ClassMetadataInterface $classMetadata) + public function merge(ClassMetadataInterface $classMetadata): void { foreach ($classMetadata->getAttributesMetadata() as $attributeMetadata) { if (isset($this->attributesMetadata[$attributeMetadata->getName()])) { @@ -101,10 +95,7 @@ public function getClassDiscriminatorMapping(): ?ClassDiscriminatorMapping return $this->classDiscriminatorMapping; } - /** - * @return void - */ - public function setClassDiscriminatorMapping(ClassDiscriminatorMapping $mapping = null) + public function setClassDiscriminatorMapping(ClassDiscriminatorMapping $mapping = null): void { if (1 > \func_num_args()) { trigger_deprecation('symfony/serializer', '6.2', 'Calling "%s()" without any arguments is deprecated, pass null explicitly instead.', __METHOD__); diff --git a/Mapping/ClassMetadataInterface.php b/Mapping/ClassMetadataInterface.php index 14ab4bb41..b19cb1a46 100644 --- a/Mapping/ClassMetadataInterface.php +++ b/Mapping/ClassMetadataInterface.php @@ -32,7 +32,7 @@ public function getName(): string; /** * Adds an {@link AttributeMetadataInterface}. */ - public function addAttributeMetadata(AttributeMetadataInterface $attributeMetadata); + public function addAttributeMetadata(AttributeMetadataInterface $attributeMetadata): void; /** * Gets the list of {@link AttributeMetadataInterface}. @@ -44,7 +44,7 @@ public function getAttributesMetadata(): array; /** * Merges a {@link ClassMetadataInterface} in the current one. */ - public function merge(self $classMetadata); + public function merge(self $classMetadata): void; /** * Returns a {@link \ReflectionClass} instance for this class. @@ -53,5 +53,5 @@ public function getReflectionClass(): \ReflectionClass; public function getClassDiscriminatorMapping(): ?ClassDiscriminatorMapping; - public function setClassDiscriminatorMapping(?ClassDiscriminatorMapping $mapping); + public function setClassDiscriminatorMapping(?ClassDiscriminatorMapping $mapping): void; } diff --git a/Normalizer/DenormalizerAwareInterface.php b/Normalizer/DenormalizerAwareInterface.php index f88df1bec..48e8c3fb5 100644 --- a/Normalizer/DenormalizerAwareInterface.php +++ b/Normalizer/DenormalizerAwareInterface.php @@ -18,6 +18,8 @@ interface DenormalizerAwareInterface { /** * Sets the owning Denormalizer object. + * + * @return void */ public function setDenormalizer(DenormalizerInterface $denormalizer); } diff --git a/Normalizer/NormalizerAwareInterface.php b/Normalizer/NormalizerAwareInterface.php index 08865a3f1..5f3deaa01 100644 --- a/Normalizer/NormalizerAwareInterface.php +++ b/Normalizer/NormalizerAwareInterface.php @@ -18,6 +18,8 @@ interface NormalizerAwareInterface { /** * Sets the owning Normalizer object. + * + * @return void */ public function setNormalizer(NormalizerInterface $normalizer); } diff --git a/SerializerAwareInterface.php b/SerializerAwareInterface.php index 4811fc966..4919436d9 100644 --- a/SerializerAwareInterface.php +++ b/SerializerAwareInterface.php @@ -18,6 +18,8 @@ interface SerializerAwareInterface { /** * Sets the owning Serializer object. + * + * @return void */ public function setSerializer(SerializerInterface $serializer); } From 86aff5a0ded39c8fdd9e44808e242bdb0b78b6c4 Mon Sep 17 00:00:00 2001 From: Konstantin Myakshin Date: Sat, 28 Jan 2023 11:37:55 +0200 Subject: [PATCH 097/297] [HttpKernel] Create Attributes `#[MapRequestPayload]` and `#[MapQueryString]` to map Request input to typed objects --- CHANGELOG.md | 1 + Exception/UnsupportedFormatException.php | 19 +++++++++++++++++++ Serializer.php | 6 +++--- 3 files changed, 23 insertions(+), 3 deletions(-) create mode 100644 Exception/UnsupportedFormatException.php diff --git a/CHANGELOG.md b/CHANGELOG.md index 622d4152e..7c2dd3114 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,7 @@ CHANGELOG * Add `XmlEncoder::SAVE_OPTIONS` context option * Add `BackedEnumNormalizer::ALLOW_INVALID_VALUES` context option + * Add `UnsupportedFormatException` which is thrown when there is no decoder for a given format * Add method `getSupportedTypes(?string $format)` to `NormalizerInterface` and `DenormalizerInterface` * Make `ProblemNormalizer` give details about `ValidationFailedException` and `PartialDenormalizationException` * Deprecate `MissingConstructorArgumentsException` in favor of `MissingConstructorArgumentException` diff --git a/Exception/UnsupportedFormatException.php b/Exception/UnsupportedFormatException.php new file mode 100644 index 000000000..9b87bcc5b --- /dev/null +++ b/Exception/UnsupportedFormatException.php @@ -0,0 +1,19 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Serializer\Exception; + +/** + * @author Konstantin Myakshin + */ +class UnsupportedFormatException extends NotEncodableValueException +{ +} diff --git a/Serializer.php b/Serializer.php index ea219f403..e79ca7ba7 100644 --- a/Serializer.php +++ b/Serializer.php @@ -19,9 +19,9 @@ use Symfony\Component\Serializer\Encoder\EncoderInterface; use Symfony\Component\Serializer\Exception\InvalidArgumentException; use Symfony\Component\Serializer\Exception\LogicException; -use Symfony\Component\Serializer\Exception\NotEncodableValueException; use Symfony\Component\Serializer\Exception\NotNormalizableValueException; use Symfony\Component\Serializer\Exception\PartialDenormalizationException; +use Symfony\Component\Serializer\Exception\UnsupportedFormatException; use Symfony\Component\Serializer\Normalizer\AbstractObjectNormalizer; use Symfony\Component\Serializer\Normalizer\CacheableSupportsMethodInterface; use Symfony\Component\Serializer\Normalizer\ContextAwareDenormalizerInterface; @@ -124,7 +124,7 @@ public function __construct(array $normalizers = [], array $encoders = []) final public function serialize(mixed $data, string $format, array $context = []): string { if (!$this->supportsEncoding($format, $context)) { - throw new NotEncodableValueException(sprintf('Serialization for the format "%s" is not supported.', $format)); + throw new UnsupportedFormatException(sprintf('Serialization for the format "%s" is not supported.', $format)); } if ($this->encoder->needsNormalization($format, $context)) { @@ -137,7 +137,7 @@ final public function serialize(mixed $data, string $format, array $context = [] final public function deserialize(mixed $data, string $type, string $format, array $context = []): mixed { if (!$this->supportsDecoding($format, $context)) { - throw new NotEncodableValueException(sprintf('Deserialization for the format "%s" is not supported.', $format)); + throw new UnsupportedFormatException(sprintf('Deserialization for the format "%s" is not supported.', $format)); } $data = $this->decode($data, $format, $context); From cb2db160870f2c46f9c063aaeb292644dcfd87da Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lo=C3=AFc=20Fr=C3=A9mont?= Date: Fri, 9 Dec 2022 12:07:40 +0100 Subject: [PATCH 098/297] [Serializer] Add Debug command --- Command/DebugCommand.php | 113 +++++++++++++++++++++++++++++ Tests/Command/DebugCommandTest.php | 92 +++++++++++++++++++++++ Tests/Dummy/DummyClassOne.php | 33 +++++++++ composer.json | 1 + 4 files changed, 239 insertions(+) create mode 100644 Command/DebugCommand.php create mode 100644 Tests/Command/DebugCommandTest.php create mode 100644 Tests/Dummy/DummyClassOne.php diff --git a/Command/DebugCommand.php b/Command/DebugCommand.php new file mode 100644 index 000000000..13873dd1f --- /dev/null +++ b/Command/DebugCommand.php @@ -0,0 +1,113 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Serializer\Command; + +use Symfony\Component\Console\Attribute\AsCommand; +use Symfony\Component\Console\Command\Command; +use Symfony\Component\Console\Helper\Dumper; +use Symfony\Component\Console\Helper\Table; +use Symfony\Component\Console\Input\InputArgument; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Output\OutputInterface; +use Symfony\Component\Console\Style\SymfonyStyle; +use Symfony\Component\Serializer\Mapping\ClassMetadataInterface; +use Symfony\Component\Serializer\Mapping\Factory\ClassMetadataFactoryInterface; + +/** + * A console command to debug Serializer information. + * + * @author Loïc Frémont + */ +#[AsCommand(name: 'debug:serializer', description: 'Display serialization information for classes')] +class DebugCommand extends Command +{ + public function __construct(private readonly ClassMetadataFactoryInterface $serializer) + { + parent::__construct(); + } + + protected function configure(): void + { + $this + ->addArgument('class', InputArgument::REQUIRED, 'A fully qualified class name') + ->setHelp("The %command.name% 'App\Entity\Dummy' command dumps the serializer groups for the dummy class.") + ; + } + + protected function execute(InputInterface $input, OutputInterface $output): int + { + $class = $input->getArgument('class'); + + if (!class_exists($class)) { + $io = new SymfonyStyle($input, $output); + $io->error(sprintf('Class "%s" was not found.', $class)); + + return Command::FAILURE; + } + + $this->dumpSerializerDataForClass($input, $output, $class); + + return Command::SUCCESS; + } + + private function dumpSerializerDataForClass(InputInterface $input, OutputInterface $output, string $class): void + { + $io = new SymfonyStyle($input, $output); + $title = sprintf('%s', $class); + $rows = []; + $dump = new Dumper($output); + + $classMetadata = $this->serializer->getMetadataFor($class); + + foreach ($this->getAttributesData($classMetadata) as $propertyName => $data) { + $rows[] = [ + $propertyName, + $dump($data), + ]; + } + + if (!$rows) { + $io->section($title); + $io->text('No Serializer data were found for this class.'); + + return; + } + + $io->section($title); + + $table = new Table($output); + $table->setHeaders(['Property', 'Options']); + $table->setRows($rows); + $table->render(); + } + + /** + * @return array> + */ + private function getAttributesData(ClassMetadataInterface $classMetadata): array + { + $data = []; + + foreach ($classMetadata->getAttributesMetadata() as $attributeMetadata) { + $data[$attributeMetadata->getName()] = [ + 'groups' => $attributeMetadata->getGroups(), + 'maxDepth' => $attributeMetadata->getMaxDepth(), + 'serializedName' => $attributeMetadata->getSerializedName(), + 'ignore' => $attributeMetadata->isIgnored(), + 'normalizationContexts' => $attributeMetadata->getNormalizationContexts(), + 'denormalizationContexts' => $attributeMetadata->getDenormalizationContexts(), + ]; + } + + return $data; + } +} diff --git a/Tests/Command/DebugCommandTest.php b/Tests/Command/DebugCommandTest.php new file mode 100644 index 000000000..a92e73f33 --- /dev/null +++ b/Tests/Command/DebugCommandTest.php @@ -0,0 +1,92 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Serializer\Tests\Command; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\Console\Tester\CommandTester; +use Symfony\Component\Serializer\Command\DebugCommand; +use Symfony\Component\Serializer\Mapping\Factory\ClassMetadataFactory; +use Symfony\Component\Serializer\Mapping\Factory\ClassMetadataFactoryInterface; +use Symfony\Component\Serializer\Mapping\Loader\AnnotationLoader; +use Symfony\Component\Serializer\Tests\Dummy\DummyClassOne; + +/** + * @author Loïc Frémont + */ +class DebugCommandTest extends TestCase +{ + public function testOutputWithClassArgument() + { + $command = new DebugCommand(new ClassMetadataFactory(new AnnotationLoader())); + + $tester = new CommandTester($command); + $tester->execute(['class' => DummyClassOne::class], ['decorated' => false]); + + $this->assertSame(<< [ | + | | "book:read", | + | | "book:write" | + | | ], | + | | "maxDepth" => 1, | + | | "serializedName" => "identifier", | + | | "ignore" => true, | + | | "normalizationContexts" => [ | + | | "*" => [ | + | | "groups" => [ | + | | "book:read" | + | | ] | + | | ] | + | | ], | + | | "denormalizationContexts" => [ | + | | "*" => [ | + | | "groups" => [ | + | | "book:write" | + | | ] | + | | ] | + | | ] | + | | ] | + | name | [ | + | | "groups" => [], | + | | "maxDepth" => null, | + | | "serializedName" => null, | + | | "ignore" => false, | + | | "normalizationContexts" => [], | + | | "denormalizationContexts" => [] | + | | ] | + +----------+-------------------------------------+ + + TXT, + $tester->getDisplay(true), + ); + } + + public function testOutputWithInvalidClassArgument() + { + $serializer = $this->createMock(ClassMetadataFactoryInterface::class); + + $command = new DebugCommand($serializer); + + $tester = new CommandTester($command); + $tester->execute(['class' => 'App\\NotFoundResource'], ['decorated' => false]); + + $this->assertStringContainsString('[ERROR] Class "App\NotFoundResource" was not found.', $tester->getDisplay(true) + ); + } +} diff --git a/Tests/Dummy/DummyClassOne.php b/Tests/Dummy/DummyClassOne.php new file mode 100644 index 000000000..67e80d2c6 --- /dev/null +++ b/Tests/Dummy/DummyClassOne.php @@ -0,0 +1,33 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Serializer\Tests\Dummy; + +use Symfony\Component\Serializer\Annotation\Context; +use Symfony\Component\Serializer\Annotation\Groups; +use Symfony\Component\Serializer\Annotation\Ignore; +use Symfony\Component\Serializer\Annotation\MaxDepth; +use Symfony\Component\Serializer\Annotation\SerializedName; + +class DummyClassOne +{ + #[MaxDepth(1)] + #[Groups(['book:read', 'book:write'])] + #[SerializedName('identifier')] + #[Ignore] + #[Context( + normalizationContext: ['groups' => ['book:read']], + denormalizationContext: ['groups' => ['book:write']], + )] + public string $code; + + public string $name; +} diff --git a/composer.json b/composer.json index 8b17ca6ea..c78f0246c 100644 --- a/composer.json +++ b/composer.json @@ -24,6 +24,7 @@ "phpdocumentor/reflection-docblock": "^3.2|^4.0|^5.0", "symfony/cache": "^5.4|^6.0", "symfony/config": "^5.4|^6.0", + "symfony/console": "^5.4|^6.0", "symfony/dependency-injection": "^5.4|^6.0", "symfony/error-handler": "^5.4|^6.0", "symfony/filesystem": "^5.4|^6.0", From 0732edf0ad28dd3faacde4f1200ab9d7a4d5f40d Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Tue, 18 Apr 2023 15:57:49 +0200 Subject: [PATCH 099/297] Fix tests --- Tests/SerializerTest.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Tests/SerializerTest.php b/Tests/SerializerTest.php index b4341bd41..908f4b352 100644 --- a/Tests/SerializerTest.php +++ b/Tests/SerializerTest.php @@ -1025,7 +1025,7 @@ public function testCollectDenormalizationErrors(?ClassMetadataFactory $classMet 'expectedTypes' => [ 'float', ], - 'path' => 'php74FullWithTypedConstructor', + 'path' => 'php74FullWithTypedConstructor.something', 'useMessageForUser' => false, 'message' => 'The type of the "something" attribute for class "Symfony\Component\Serializer\Tests\Fixtures\Php74FullWithTypedConstructor" must be one of "float" ("string" given).', ], From 4584847dd06a72f7dfe69ab7b475b35f21b68410 Mon Sep 17 00:00:00 2001 From: "Alexander M. Turek" Date: Fri, 11 Mar 2022 23:40:38 +0100 Subject: [PATCH 100/297] [Serializer] Add types to private and internal properties --- Annotation/Context.php | 6 ++-- Annotation/DiscriminatorMap.php | 4 +-- Annotation/Groups.php | 2 +- Annotation/MaxDepth.php | 2 +- Annotation/SerializedName.php | 2 +- .../CompiledClassMetadataCacheWarmer.php | 20 ++++--------- Encoder/ChainDecoder.php | 13 ++++++--- Encoder/ChainEncoder.php | 13 ++++++--- Encoder/CsvEncoder.php | 2 +- Encoder/JsonDecode.php | 2 +- Encoder/JsonEncode.php | 2 +- Encoder/XmlEncoder.php | 2 +- Encoder/YamlEncoder.php | 6 ++-- Exception/ExtraAttributesException.php | 10 +++---- Exception/NotNormalizableValueException.php | 17 ++++++----- Extractor/ObjectPropertyListExtractor.php | 8 ++--- Mapping/AttributeMetadata.php | 20 +++++-------- .../ClassDiscriminatorFromClassMetadata.php | 12 +++----- Mapping/ClassDiscriminatorMapping.php | 15 +++++----- Mapping/ClassMetadata.php | 13 +++------ Mapping/ClassMetadataInterface.php | 2 +- Mapping/Factory/CacheClassMetadataFactory.php | 19 ++++-------- Mapping/Factory/ClassMetadataFactory.php | 12 ++++---- .../Factory/CompiledClassMetadataFactory.php | 13 ++++----- Mapping/Loader/AnnotationLoader.php | 8 ++--- Mapping/Loader/LoaderChain.php | 6 +--- Mapping/Loader/XmlFileLoader.php | 2 +- Mapping/Loader/YamlFileLoader.php | 6 ++-- .../CamelCaseToSnakeCaseNameConverter.php | 11 +++---- NameConverter/MetadataAwareNameConverter.php | 29 +++++++++++-------- Normalizer/AbstractObjectNormalizer.php | 28 +++++++++--------- .../ConstraintViolationListNormalizer.php | 11 +++---- Normalizer/DataUriNormalizer.php | 11 ++----- Normalizer/DateIntervalNormalizer.php | 2 +- Normalizer/DateTimeNormalizer.php | 2 +- Normalizer/MimeMessageNormalizer.php | 4 +-- Normalizer/ObjectNormalizer.php | 7 +++-- Normalizer/UidNormalizer.php | 2 +- Normalizer/UnwrappingDenormalizer.php | 2 +- Serializer.php | 23 ++++++++++----- 40 files changed, 169 insertions(+), 202 deletions(-) diff --git a/Annotation/Context.php b/Annotation/Context.php index 908825047..e3878c7be 100644 --- a/Annotation/Context.php +++ b/Annotation/Context.php @@ -33,9 +33,9 @@ class Context * @throws InvalidArgumentException */ public function __construct( - private array $context = [], - private array $normalizationContext = [], - private array $denormalizationContext = [], + private readonly array $context = [], + private readonly array $normalizationContext = [], + private readonly array $denormalizationContext = [], string|array $groups = [], ) { if (!$context && !$normalizationContext && !$denormalizationContext) { diff --git a/Annotation/DiscriminatorMap.php b/Annotation/DiscriminatorMap.php index 2606a1e4f..8e9ebb830 100644 --- a/Annotation/DiscriminatorMap.php +++ b/Annotation/DiscriminatorMap.php @@ -26,8 +26,8 @@ class DiscriminatorMap { public function __construct( - private string $typeProperty, - private array $mapping, + private readonly string $typeProperty, + private readonly array $mapping, ) { if (empty($typeProperty)) { throw new InvalidArgumentException(sprintf('Parameter "typeProperty" of annotation "%s" cannot be empty.', static::class)); diff --git a/Annotation/Groups.php b/Annotation/Groups.php index 2069d678c..5b16f567c 100644 --- a/Annotation/Groups.php +++ b/Annotation/Groups.php @@ -28,7 +28,7 @@ class Groups /** * @var string[] */ - private array $groups; + private readonly array $groups; /** * @param string|string[] $groups diff --git a/Annotation/MaxDepth.php b/Annotation/MaxDepth.php index 43785b292..36afff6a7 100644 --- a/Annotation/MaxDepth.php +++ b/Annotation/MaxDepth.php @@ -25,7 +25,7 @@ #[\Attribute(\Attribute::TARGET_METHOD | \Attribute::TARGET_PROPERTY)] class MaxDepth { - public function __construct(private int $maxDepth) + public function __construct(private readonly int $maxDepth) { if ($maxDepth <= 0) { throw new InvalidArgumentException(sprintf('Parameter of annotation "%s" must be a positive integer.', static::class)); diff --git a/Annotation/SerializedName.php b/Annotation/SerializedName.php index b6c6027e8..74df79cd2 100644 --- a/Annotation/SerializedName.php +++ b/Annotation/SerializedName.php @@ -25,7 +25,7 @@ #[\Attribute(\Attribute::TARGET_METHOD | \Attribute::TARGET_PROPERTY)] final class SerializedName { - public function __construct(private string $serializedName) + public function __construct(private readonly string $serializedName) { if ('' === $serializedName) { throw new InvalidArgumentException(sprintf('Parameter of annotation "%s" must be a non-empty string.', self::class)); diff --git a/CacheWarmer/CompiledClassMetadataCacheWarmer.php b/CacheWarmer/CompiledClassMetadataCacheWarmer.php index 565320af9..a43e9b408 100644 --- a/CacheWarmer/CompiledClassMetadataCacheWarmer.php +++ b/CacheWarmer/CompiledClassMetadataCacheWarmer.php @@ -21,20 +21,12 @@ */ final class CompiledClassMetadataCacheWarmer implements CacheWarmerInterface { - private $classesToCompile; - - private $classMetadataFactory; - - private $classMetadataFactoryCompiler; - - private $filesystem; - - public function __construct(array $classesToCompile, ClassMetadataFactoryInterface $classMetadataFactory, ClassMetadataFactoryCompiler $classMetadataFactoryCompiler, Filesystem $filesystem) - { - $this->classesToCompile = $classesToCompile; - $this->classMetadataFactory = $classMetadataFactory; - $this->classMetadataFactoryCompiler = $classMetadataFactoryCompiler; - $this->filesystem = $filesystem; + public function __construct( + private readonly array $classesToCompile, + private readonly ClassMetadataFactoryInterface $classMetadataFactory, + private readonly ClassMetadataFactoryCompiler $classMetadataFactoryCompiler, + private readonly Filesystem $filesystem, + ) { } public function warmUp(string $cacheDir): array diff --git a/Encoder/ChainDecoder.php b/Encoder/ChainDecoder.php index 469affdaa..f1b0cd2e2 100644 --- a/Encoder/ChainDecoder.php +++ b/Encoder/ChainDecoder.php @@ -24,12 +24,17 @@ */ class ChainDecoder implements ContextAwareDecoderInterface { - private array $decoders = []; + /** + * @var array + */ private array $decoderByFormat = []; - public function __construct(array $decoders = []) - { - $this->decoders = $decoders; + /** + * @param array $decoders + */ + public function __construct( + private readonly array $decoders = [] + ) { } final public function decode(string $data, string $format, array $context = []): mixed diff --git a/Encoder/ChainEncoder.php b/Encoder/ChainEncoder.php index 14aea5157..731cfc601 100644 --- a/Encoder/ChainEncoder.php +++ b/Encoder/ChainEncoder.php @@ -25,12 +25,17 @@ */ class ChainEncoder implements ContextAwareEncoderInterface { - private array $encoders = []; + /** + * @var array + */ private array $encoderByFormat = []; - public function __construct(array $encoders = []) - { - $this->encoders = $encoders; + /** + * @param array $encoders + */ + public function __construct( + private readonly array $encoders = [] + ) { } final public function encode(mixed $data, string $format, array $context = []): string diff --git a/Encoder/CsvEncoder.php b/Encoder/CsvEncoder.php index d8cc6e3f3..dc952b2e0 100644 --- a/Encoder/CsvEncoder.php +++ b/Encoder/CsvEncoder.php @@ -38,7 +38,7 @@ class CsvEncoder implements EncoderInterface, DecoderInterface private const FORMULAS_START_CHARACTERS = ['=', '-', '+', '@', "\t", "\r"]; - private $defaultContext = [ + private array $defaultContext = [ self::DELIMITER_KEY => ',', self::ENCLOSURE_KEY => '"', self::ESCAPE_CHAR_KEY => '', diff --git a/Encoder/JsonDecode.php b/Encoder/JsonDecode.php index 059ed7927..f17267fc9 100644 --- a/Encoder/JsonDecode.php +++ b/Encoder/JsonDecode.php @@ -34,7 +34,7 @@ class JsonDecode implements DecoderInterface */ public const RECURSION_DEPTH = 'json_decode_recursion_depth'; - private $defaultContext = [ + private array $defaultContext = [ self::ASSOCIATIVE => false, self::OPTIONS => 0, self::RECURSION_DEPTH => 512, diff --git a/Encoder/JsonEncode.php b/Encoder/JsonEncode.php index 2f7c81575..3e0ddced3 100644 --- a/Encoder/JsonEncode.php +++ b/Encoder/JsonEncode.php @@ -25,7 +25,7 @@ class JsonEncode implements EncoderInterface */ public const OPTIONS = 'json_encode_options'; - private $defaultContext = [ + private array $defaultContext = [ self::OPTIONS => \JSON_PRESERVE_ZERO_FRACTION, ]; diff --git a/Encoder/XmlEncoder.php b/Encoder/XmlEncoder.php index 965683c89..4b6f68921 100644 --- a/Encoder/XmlEncoder.php +++ b/Encoder/XmlEncoder.php @@ -59,7 +59,7 @@ class XmlEncoder implements EncoderInterface, DecoderInterface, NormalizationAwa public const TYPE_CAST_ATTRIBUTES = 'xml_type_cast_attributes'; public const VERSION = 'xml_version'; - private $defaultContext = [ + private array $defaultContext = [ self::AS_COLLECTION => false, self::DECODER_IGNORED_NODE_TYPES => [\XML_PI_NODE, \XML_COMMENT_NODE], self::ENCODER_IGNORED_NODE_TYPES => [], diff --git a/Encoder/YamlEncoder.php b/Encoder/YamlEncoder.php index 8d559a57b..031a3105e 100644 --- a/Encoder/YamlEncoder.php +++ b/Encoder/YamlEncoder.php @@ -42,9 +42,9 @@ class YamlEncoder implements EncoderInterface, DecoderInterface public const YAML_INDENT = 'yaml_indent'; public const YAML_FLAGS = 'yaml_flags'; - private $dumper; - private $parser; - private $defaultContext = [ + private readonly Dumper $dumper; + private readonly Parser $parser; + private array $defaultContext = [ self::YAML_INLINE => 0, self::YAML_INDENT => 0, self::YAML_FLAGS => 0, diff --git a/Exception/ExtraAttributesException.php b/Exception/ExtraAttributesException.php index 22fcc87b8..d681de419 100644 --- a/Exception/ExtraAttributesException.php +++ b/Exception/ExtraAttributesException.php @@ -18,14 +18,12 @@ */ class ExtraAttributesException extends RuntimeException { - private $extraAttributes; - - public function __construct(array $extraAttributes, \Throwable $previous = null) - { + public function __construct( + private readonly array $extraAttributes, + \Throwable $previous = null, + ) { $msg = sprintf('Extra attributes are not allowed ("%s" %s unknown).', implode('", "', $extraAttributes), \count($extraAttributes) > 1 ? 'are' : 'is'); - $this->extraAttributes = $extraAttributes; - parent::__construct($msg, 0, $previous); } diff --git a/Exception/NotNormalizableValueException.php b/Exception/NotNormalizableValueException.php index e601e5043..148c60ada 100644 --- a/Exception/NotNormalizableValueException.php +++ b/Exception/NotNormalizableValueException.php @@ -16,17 +16,18 @@ */ class NotNormalizableValueException extends UnexpectedValueException { - private $currentType; - private $expectedTypes; - private $path; - private $useMessageForUser = false; + private ?string $currentType = null; + private ?array $expectedTypes = null; + private ?string $path = null; + private bool $useMessageForUser = false; /** - * @param bool $useMessageForUser If the message passed to this exception is something that can be shown - * safely to your user. In other words, avoid catching other exceptions and - * passing their message directly to this class. + * @param string[] $expectedTypes + * @param bool $useMessageForUser If the message passed to this exception is something that can be shown + * safely to your user. In other words, avoid catching other exceptions and + * passing their message directly to this class. */ - public static function createForUnexpectedDataType(string $message, $data, array $expectedTypes, string $path = null, bool $useMessageForUser = false, int $code = 0, \Throwable $previous = null): self + public static function createForUnexpectedDataType(string $message, mixed $data, array $expectedTypes, string $path = null, bool $useMessageForUser = false, int $code = 0, \Throwable $previous = null): self { $self = new self($message, $code, $previous); diff --git a/Extractor/ObjectPropertyListExtractor.php b/Extractor/ObjectPropertyListExtractor.php index 9b7344627..c237e13a7 100644 --- a/Extractor/ObjectPropertyListExtractor.php +++ b/Extractor/ObjectPropertyListExtractor.php @@ -18,18 +18,18 @@ */ final class ObjectPropertyListExtractor implements ObjectPropertyListExtractorInterface { - private $propertyListExtractor; - private $objectClassResolver; + private PropertyListExtractorInterface $propertyListExtractor; + private \Closure $objectClassResolver; public function __construct(PropertyListExtractorInterface $propertyListExtractor, callable $objectClassResolver = null) { $this->propertyListExtractor = $propertyListExtractor; - $this->objectClassResolver = $objectClassResolver; + $this->objectClassResolver = ($objectClassResolver ?? 'get_class')(...); } public function getProperties(object $object, array $context = []): ?array { - $class = $this->objectClassResolver ? ($this->objectClassResolver)($object) : $object::class; + $class = ($this->objectClassResolver)($object); return $this->propertyListExtractor->getProperties($class, $context); } diff --git a/Mapping/AttributeMetadata.php b/Mapping/AttributeMetadata.php index cb77b53d2..c77d07e5e 100644 --- a/Mapping/AttributeMetadata.php +++ b/Mapping/AttributeMetadata.php @@ -23,32 +23,28 @@ class AttributeMetadata implements AttributeMetadataInterface * class' serialized representation. Do not access it. Use * {@link getName()} instead. */ - public $name; + public string $name; /** * @internal This property is public in order to reduce the size of the * class' serialized representation. Do not access it. Use * {@link getGroups()} instead. */ - public $groups = []; + public array $groups = []; /** - * @var int|null - * * @internal This property is public in order to reduce the size of the * class' serialized representation. Do not access it. Use * {@link getMaxDepth()} instead. */ - public $maxDepth; + public ?int $maxDepth = null; /** - * @var string|null - * * @internal This property is public in order to reduce the size of the * class' serialized representation. Do not access it. Use * {@link getSerializedName()} instead. */ - public $serializedName; + public ?string $serializedName = null; /** * @internal This property is public in order to reduce the size of the @@ -58,13 +54,11 @@ class AttributeMetadata implements AttributeMetadataInterface public ?PropertyPath $serializedPath = null; /** - * @var bool - * * @internal This property is public in order to reduce the size of the * class' serialized representation. Do not access it. Use * {@link isIgnored()} instead. */ - public $ignore = false; + public bool $ignore = false; /** * @var array[] Normalization contexts per group name ("*" applies to all groups) @@ -73,7 +67,7 @@ class AttributeMetadata implements AttributeMetadataInterface * class' serialized representation. Do not access it. Use * {@link getNormalizationContexts()} instead. */ - public $normalizationContexts = []; + public array $normalizationContexts = []; /** * @var array[] Denormalization contexts per group name ("*" applies to all groups) @@ -82,7 +76,7 @@ class AttributeMetadata implements AttributeMetadataInterface * class' serialized representation. Do not access it. Use * {@link getDenormalizationContexts()} instead. */ - public $denormalizationContexts = []; + public array $denormalizationContexts = []; public function __construct(string $name) { diff --git a/Mapping/ClassDiscriminatorFromClassMetadata.php b/Mapping/ClassDiscriminatorFromClassMetadata.php index 708d58e03..d6f4bbe67 100644 --- a/Mapping/ClassDiscriminatorFromClassMetadata.php +++ b/Mapping/ClassDiscriminatorFromClassMetadata.php @@ -18,15 +18,11 @@ */ class ClassDiscriminatorFromClassMetadata implements ClassDiscriminatorResolverInterface { - /** - * @var ClassMetadataFactoryInterface - */ - private $classMetadataFactory; - private $mappingForMappedObjectCache = []; + private array $mappingForMappedObjectCache = []; - public function __construct(ClassMetadataFactoryInterface $classMetadataFactory) - { - $this->classMetadataFactory = $classMetadataFactory; + public function __construct( + private readonly ClassMetadataFactoryInterface $classMetadataFactory, + ) { } public function getMappingForClass(string $class): ?ClassDiscriminatorMapping diff --git a/Mapping/ClassDiscriminatorMapping.php b/Mapping/ClassDiscriminatorMapping.php index c21d777ec..260575a41 100644 --- a/Mapping/ClassDiscriminatorMapping.php +++ b/Mapping/ClassDiscriminatorMapping.php @@ -16,14 +16,13 @@ */ class ClassDiscriminatorMapping { - private $typeProperty; - private $typesMapping; - - public function __construct(string $typeProperty, array $typesMapping = []) - { - $this->typeProperty = $typeProperty; - $this->typesMapping = $typesMapping; - + /** + * @param array $typesMapping + */ + public function __construct( + private readonly string $typeProperty, + private array $typesMapping = [], + ) { uasort($this->typesMapping, static function (string $a, string $b): int { if (is_a($a, $b, true)) { return -1; diff --git a/Mapping/ClassMetadata.php b/Mapping/ClassMetadata.php index 4a70ee085..bcec5bf96 100644 --- a/Mapping/ClassMetadata.php +++ b/Mapping/ClassMetadata.php @@ -21,7 +21,7 @@ class ClassMetadata implements ClassMetadataInterface * class' serialized representation. Do not access it. Use * {@link getName()} instead. */ - public $name; + public string $name; /** * @var AttributeMetadataInterface[] @@ -30,21 +30,16 @@ class ClassMetadata implements ClassMetadataInterface * class' serialized representation. Do not access it. Use * {@link getAttributesMetadata()} instead. */ - public $attributesMetadata = []; + public array $attributesMetadata = []; - /** - * @var \ReflectionClass - */ - private $reflClass; + private ?\ReflectionClass $reflClass = null; /** - * @var ClassDiscriminatorMapping|null - * * @internal This property is public in order to reduce the size of the * class' serialized representation. Do not access it. Use * {@link getClassDiscriminatorMapping()} instead. */ - public $classDiscriminatorMapping; + public ?ClassDiscriminatorMapping $classDiscriminatorMapping; /** * Constructs a metadata for the given class. diff --git a/Mapping/ClassMetadataInterface.php b/Mapping/ClassMetadataInterface.php index b19cb1a46..3f380d2ff 100644 --- a/Mapping/ClassMetadataInterface.php +++ b/Mapping/ClassMetadataInterface.php @@ -37,7 +37,7 @@ public function addAttributeMetadata(AttributeMetadataInterface $attributeMetada /** * Gets the list of {@link AttributeMetadataInterface}. * - * @return AttributeMetadataInterface[] + * @return array */ public function getAttributesMetadata(): array; diff --git a/Mapping/Factory/CacheClassMetadataFactory.php b/Mapping/Factory/CacheClassMetadataFactory.php index c3adbeec1..61c2fb606 100644 --- a/Mapping/Factory/CacheClassMetadataFactory.php +++ b/Mapping/Factory/CacheClassMetadataFactory.php @@ -24,21 +24,14 @@ class CacheClassMetadataFactory implements ClassMetadataFactoryInterface use ClassResolverTrait; /** - * @var ClassMetadataFactoryInterface + * @var array */ - private $decorated; + private array $loadedClasses = []; - /** - * @var CacheItemPoolInterface - */ - private $cacheItemPool; - - private $loadedClasses = []; - - public function __construct(ClassMetadataFactoryInterface $decorated, CacheItemPoolInterface $cacheItemPool) - { - $this->decorated = $decorated; - $this->cacheItemPool = $cacheItemPool; + public function __construct( + private readonly ClassMetadataFactoryInterface $decorated, + private readonly CacheItemPoolInterface $cacheItemPool, + ) { } public function getMetadataFor(string|object $value): ClassMetadataInterface diff --git a/Mapping/Factory/ClassMetadataFactory.php b/Mapping/Factory/ClassMetadataFactory.php index 9b2cf1cb7..fc5b2e71e 100644 --- a/Mapping/Factory/ClassMetadataFactory.php +++ b/Mapping/Factory/ClassMetadataFactory.php @@ -24,16 +24,14 @@ class ClassMetadataFactory implements ClassMetadataFactoryInterface { use ClassResolverTrait; - private $loader; - /** - * @var array + * @var array */ - private $loadedClasses; + private array $loadedClasses; - public function __construct(LoaderInterface $loader) - { - $this->loader = $loader; + public function __construct( + private readonly LoaderInterface $loader, + ) { } public function getMetadataFor(string|object $value): ClassMetadataInterface diff --git a/Mapping/Factory/CompiledClassMetadataFactory.php b/Mapping/Factory/CompiledClassMetadataFactory.php index 670a93587..f4d41c1e6 100644 --- a/Mapping/Factory/CompiledClassMetadataFactory.php +++ b/Mapping/Factory/CompiledClassMetadataFactory.php @@ -21,14 +21,14 @@ */ final class CompiledClassMetadataFactory implements ClassMetadataFactoryInterface { - private $compiledClassMetadata = []; + private array $compiledClassMetadata = []; - private $loadedClasses = []; + private array $loadedClasses = []; - private $classMetadataFactory; - - public function __construct(string $compiledClassMetadataFile, ClassMetadataFactoryInterface $classMetadataFactory) - { + public function __construct( + string $compiledClassMetadataFile, + private readonly ClassMetadataFactoryInterface $classMetadataFactory, + ) { if (!file_exists($compiledClassMetadataFile)) { throw new \RuntimeException("File \"{$compiledClassMetadataFile}\" could not be found."); } @@ -39,7 +39,6 @@ public function __construct(string $compiledClassMetadataFile, ClassMetadataFact } $this->compiledClassMetadata = $compiledClassMetadata; - $this->classMetadataFactory = $classMetadataFactory; } public function getMetadataFor(string|object $value): ClassMetadataInterface diff --git a/Mapping/Loader/AnnotationLoader.php b/Mapping/Loader/AnnotationLoader.php index cfcee8bd5..6d03511cf 100644 --- a/Mapping/Loader/AnnotationLoader.php +++ b/Mapping/Loader/AnnotationLoader.php @@ -43,11 +43,9 @@ class AnnotationLoader implements LoaderInterface Context::class, ]; - private $reader; - - public function __construct(Reader $reader = null) - { - $this->reader = $reader; + public function __construct( + private readonly ?Reader $reader = null, + ) { } public function loadClassMetadata(ClassMetadataInterface $classMetadata): bool diff --git a/Mapping/Loader/LoaderChain.php b/Mapping/Loader/LoaderChain.php index c25f5fa96..e93a0a7b0 100644 --- a/Mapping/Loader/LoaderChain.php +++ b/Mapping/Loader/LoaderChain.php @@ -27,8 +27,6 @@ */ class LoaderChain implements LoaderInterface { - private $loaders; - /** * Accepts a list of LoaderInterface instances. * @@ -36,15 +34,13 @@ class LoaderChain implements LoaderInterface * * @throws MappingException If any of the loaders does not implement LoaderInterface */ - public function __construct(array $loaders) + public function __construct(private readonly array $loaders) { foreach ($loaders as $loader) { if (!$loader instanceof LoaderInterface) { throw new MappingException(sprintf('Class "%s" is expected to implement LoaderInterface.', get_debug_type($loader))); } } - - $this->loaders = $loaders; } public function loadClassMetadata(ClassMetadataInterface $metadata): bool diff --git a/Mapping/Loader/XmlFileLoader.php b/Mapping/Loader/XmlFileLoader.php index ebaa4655d..b36fb871d 100644 --- a/Mapping/Loader/XmlFileLoader.php +++ b/Mapping/Loader/XmlFileLoader.php @@ -31,7 +31,7 @@ class XmlFileLoader extends FileLoader * * @var \SimpleXMLElement[]|null */ - private $classes; + private ?array $classes = null; public function loadClassMetadata(ClassMetadataInterface $classMetadata): bool { diff --git a/Mapping/Loader/YamlFileLoader.php b/Mapping/Loader/YamlFileLoader.php index fd73864fd..d47ad7430 100644 --- a/Mapping/Loader/YamlFileLoader.php +++ b/Mapping/Loader/YamlFileLoader.php @@ -27,14 +27,12 @@ */ class YamlFileLoader extends FileLoader { - private $yamlParser; + private ?Parser $yamlParser = null; /** * An array of YAML class descriptions. - * - * @var array */ - private $classes; + private ?array $classes = null; public function loadClassMetadata(ClassMetadataInterface $classMetadata): bool { diff --git a/NameConverter/CamelCaseToSnakeCaseNameConverter.php b/NameConverter/CamelCaseToSnakeCaseNameConverter.php index de34f5b57..ab6f99e13 100644 --- a/NameConverter/CamelCaseToSnakeCaseNameConverter.php +++ b/NameConverter/CamelCaseToSnakeCaseNameConverter.php @@ -18,17 +18,14 @@ */ class CamelCaseToSnakeCaseNameConverter implements NameConverterInterface { - private $attributes; - private $lowerCamelCase; - /** * @param array|null $attributes The list of attributes to rename or null for all attributes * @param bool $lowerCamelCase Use lowerCamelCase style */ - public function __construct(array $attributes = null, bool $lowerCamelCase = true) - { - $this->attributes = $attributes; - $this->lowerCamelCase = $lowerCamelCase; + public function __construct( + private ?array $attributes = null, + private bool $lowerCamelCase = true, + ) { } public function normalize(string $propertyName): string diff --git a/NameConverter/MetadataAwareNameConverter.php b/NameConverter/MetadataAwareNameConverter.php index 881d334bd..0c97e227c 100644 --- a/NameConverter/MetadataAwareNameConverter.php +++ b/NameConverter/MetadataAwareNameConverter.php @@ -20,23 +20,25 @@ */ final class MetadataAwareNameConverter implements AdvancedNameConverterInterface { - private $metadataFactory; - /** - * @var NameConverterInterface|AdvancedNameConverterInterface|null + * @var array> */ - private $fallbackNameConverter; - - private static $normalizeCache = []; + private static array $normalizeCache = []; - private static $denormalizeCache = []; + /** + * @var array> + */ + private static array $denormalizeCache = []; - private static $attributesMetadataCache = []; + /** + * @var array> + */ + private static array $attributesMetadataCache = []; - public function __construct(ClassMetadataFactoryInterface $metadataFactory, NameConverterInterface $fallbackNameConverter = null) - { - $this->metadataFactory = $metadataFactory; - $this->fallbackNameConverter = $fallbackNameConverter; + public function __construct( + private readonly ClassMetadataFactoryInterface $metadataFactory, + private readonly ?NameConverterInterface $fallbackNameConverter = null, + ) { } public function normalize(string $propertyName, string $class = null, string $format = null, array $context = []): string @@ -104,6 +106,9 @@ private function denormalizeFallback(string $propertyName, string $class = null, return $this->fallbackNameConverter ? $this->fallbackNameConverter->denormalize($propertyName, $class, $format, $context) : $propertyName; } + /** + * @return array + */ private function getCacheValueForAttributesMetadata(string $class, array $context): array { if (!$this->metadataFactory->hasMetadataFor($class)) { diff --git a/Normalizer/AbstractObjectNormalizer.php b/Normalizer/AbstractObjectNormalizer.php index 6f87c7d0a..88e3c3ea1 100644 --- a/Normalizer/AbstractObjectNormalizer.php +++ b/Normalizer/AbstractObjectNormalizer.php @@ -105,19 +105,23 @@ abstract class AbstractObjectNormalizer extends AbstractNormalizer */ public const PRESERVE_EMPTY_OBJECTS = 'preserve_empty_objects'; - private $propertyTypeExtractor; - private $typesCache = []; - private $attributesCache = []; - - private $objectClassResolver; + private array $typesCache = []; + private array $attributesCache = []; + private readonly \Closure $objectClassResolver; /** * @var ClassDiscriminatorResolverInterface|null */ protected $classDiscriminatorResolver; - public function __construct(ClassMetadataFactoryInterface $classMetadataFactory = null, NameConverterInterface $nameConverter = null, PropertyTypeExtractorInterface $propertyTypeExtractor = null, ClassDiscriminatorResolverInterface $classDiscriminatorResolver = null, callable $objectClassResolver = null, array $defaultContext = []) - { + public function __construct( + ClassMetadataFactoryInterface $classMetadataFactory = null, + NameConverterInterface $nameConverter = null, + private ?PropertyTypeExtractorInterface $propertyTypeExtractor = null, + ClassDiscriminatorResolverInterface $classDiscriminatorResolver = null, + callable $objectClassResolver = null, + array $defaultContext = [], + ) { parent::__construct($classMetadataFactory, $nameConverter, $defaultContext); if (isset($this->defaultContext[self::MAX_DEPTH_HANDLER]) && !\is_callable($this->defaultContext[self::MAX_DEPTH_HANDLER])) { @@ -126,13 +130,11 @@ public function __construct(ClassMetadataFactoryInterface $classMetadataFactory $this->defaultContext[self::EXCLUDE_FROM_CACHE_KEY] = array_merge($this->defaultContext[self::EXCLUDE_FROM_CACHE_KEY] ?? [], [self::CIRCULAR_REFERENCE_LIMIT_COUNTERS]); - $this->propertyTypeExtractor = $propertyTypeExtractor; - if (null === $classDiscriminatorResolver && null !== $classMetadataFactory) { $classDiscriminatorResolver = new ClassDiscriminatorFromClassMetadata($classMetadataFactory); } $this->classDiscriminatorResolver = $classDiscriminatorResolver; - $this->objectClassResolver = $objectClassResolver; + $this->objectClassResolver = ($objectClassResolver ?? 'get_class')(...); } /** @@ -158,7 +160,7 @@ public function normalize(mixed $object, string $format = null, array $context = $data = []; $stack = []; $attributes = $this->getAttributes($object, $format, $context); - $class = $this->objectClassResolver ? ($this->objectClassResolver)($object) : $object::class; + $class = ($this->objectClassResolver)($object); $classMetadata = $this->classMetadataFactory?->getMetadataFor($class); $attributesMetadata = $this->classMetadataFactory?->getMetadataFor($class)->getAttributesMetadata(); if (isset($context[self::MAX_DEPTH_HANDLER])) { @@ -251,7 +253,7 @@ protected function instantiateObject(array &$data, string $class, array &$contex */ protected function getAttributes(object $object, ?string $format, array $context): array { - $class = $this->objectClassResolver ? ($this->objectClassResolver)($object) : $object::class; + $class = ($this->objectClassResolver)($object); $key = $class.'-'.$context['cache_key']; if (isset($this->attributesCache[$key])) { @@ -321,7 +323,7 @@ public function denormalize(mixed $data, string $type, string $format = null, ar $reflectionClass = new \ReflectionClass($type); $object = $this->instantiateObject($normalizedData, $type, $context, $reflectionClass, $allowedAttributes, $format); - $resolvedClass = $this->objectClassResolver ? ($this->objectClassResolver)($object) : $object::class; + $resolvedClass = ($this->objectClassResolver)($object); $nestedAttributes = $this->getNestedAttributes($resolvedClass); $nestedData = []; diff --git a/Normalizer/ConstraintViolationListNormalizer.php b/Normalizer/ConstraintViolationListNormalizer.php index 8816055a3..1fdf8420d 100644 --- a/Normalizer/ConstraintViolationListNormalizer.php +++ b/Normalizer/ConstraintViolationListNormalizer.php @@ -32,13 +32,10 @@ class ConstraintViolationListNormalizer implements NormalizerInterface, Cacheabl public const TYPE = 'type'; public const PAYLOAD_FIELDS = 'payload_fields'; - private $defaultContext; - private $nameConverter; - - public function __construct(array $defaultContext = [], NameConverterInterface $nameConverter = null) - { - $this->defaultContext = $defaultContext; - $this->nameConverter = $nameConverter; + public function __construct( + private readonly array $defaultContext = [], + private readonly ?NameConverterInterface $nameConverter = null, + ) { } public function getSupportedTypes(?string $format): array diff --git a/Normalizer/DataUriNormalizer.php b/Normalizer/DataUriNormalizer.php index ff07c1f30..1bcf81f9b 100644 --- a/Normalizer/DataUriNormalizer.php +++ b/Normalizer/DataUriNormalizer.php @@ -33,10 +33,7 @@ class DataUriNormalizer implements NormalizerInterface, DenormalizerInterface, C File::class => true, ]; - /** - * @var MimeTypeGuesserInterface|null - */ - private $mimeTypeGuesser; + private readonly ?MimeTypeGuesserInterface $mimeTypeGuesser; public function __construct(MimeTypeGuesserInterface $mimeTypeGuesser = null) { @@ -150,11 +147,7 @@ private function getMimeType(\SplFileInfo $object): string return $object->getMimeType(); } - if ($this->mimeTypeGuesser && $mimeType = $this->mimeTypeGuesser->guessMimeType($object->getPathname())) { - return $mimeType; - } - - return 'application/octet-stream'; + return $this->mimeTypeGuesser?->guessMimeType($object->getPathname()) ?: 'application/octet-stream'; } /** diff --git a/Normalizer/DateIntervalNormalizer.php b/Normalizer/DateIntervalNormalizer.php index c9bc51772..3cf5b887f 100644 --- a/Normalizer/DateIntervalNormalizer.php +++ b/Normalizer/DateIntervalNormalizer.php @@ -26,7 +26,7 @@ class DateIntervalNormalizer implements NormalizerInterface, DenormalizerInterfa { public const FORMAT_KEY = 'dateinterval_format'; - private $defaultContext = [ + private array $defaultContext = [ self::FORMAT_KEY => '%rP%yY%mM%dDT%hH%iM%sS', ]; diff --git a/Normalizer/DateTimeNormalizer.php b/Normalizer/DateTimeNormalizer.php index b8d9ea235..aa7988d37 100644 --- a/Normalizer/DateTimeNormalizer.php +++ b/Normalizer/DateTimeNormalizer.php @@ -28,7 +28,7 @@ class DateTimeNormalizer implements NormalizerInterface, DenormalizerInterface, public const FORMAT_KEY = 'datetime_format'; public const TIMEZONE_KEY = 'datetime_timezone'; - private $defaultContext = [ + private array $defaultContext = [ self::FORMAT_KEY => \DateTime::RFC3339, self::TIMEZONE_KEY => null, ]; diff --git a/Normalizer/MimeMessageNormalizer.php b/Normalizer/MimeMessageNormalizer.php index 272495e68..4bf6f42c2 100644 --- a/Normalizer/MimeMessageNormalizer.php +++ b/Normalizer/MimeMessageNormalizer.php @@ -32,13 +32,11 @@ final class MimeMessageNormalizer implements NormalizerInterface, DenormalizerInterface, SerializerAwareInterface, CacheableSupportsMethodInterface { private NormalizerInterface&DenormalizerInterface $serializer; - private PropertyNormalizer $normalizer; private array $headerClassMap; private \ReflectionProperty $headersProperty; - public function __construct(PropertyNormalizer $normalizer) + public function __construct(private readonly PropertyNormalizer $normalizer) { - $this->normalizer = $normalizer; $this->headerClassMap = (new \ReflectionClassConstant(Headers::class, 'HEADER_CLASS_MAP'))->getValue(); $this->headersProperty = new \ReflectionProperty(Headers::class, 'headers'); } diff --git a/Normalizer/ObjectNormalizer.php b/Normalizer/ObjectNormalizer.php index f4e50841e..357c36426 100644 --- a/Normalizer/ObjectNormalizer.php +++ b/Normalizer/ObjectNormalizer.php @@ -32,9 +32,10 @@ class ObjectNormalizer extends AbstractObjectNormalizer { protected $propertyAccessor; - private $discriminatorCache = []; + /** @var array */ + private array $discriminatorCache = []; - private $objectClassResolver; + 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 = []) { @@ -46,7 +47,7 @@ public function __construct(ClassMetadataFactoryInterface $classMetadataFactory $this->propertyAccessor = $propertyAccessor ?: PropertyAccess::createPropertyAccessor(); - $this->objectClassResolver = $objectClassResolver ?? fn ($class) => \is_object($class) ? $class::class : $class; + $this->objectClassResolver = ($objectClassResolver ?? static fn ($class) => \is_object($class) ? $class::class : $class)(...); } public function getSupportedTypes(?string $format): array diff --git a/Normalizer/UidNormalizer.php b/Normalizer/UidNormalizer.php index 40559ec0b..0dfeae7b8 100644 --- a/Normalizer/UidNormalizer.php +++ b/Normalizer/UidNormalizer.php @@ -32,7 +32,7 @@ final class UidNormalizer implements NormalizerInterface, DenormalizerInterface, self::NORMALIZATION_FORMAT_RFC4122, ]; - private $defaultContext = [ + private array $defaultContext = [ self::NORMALIZATION_FORMAT_KEY => self::NORMALIZATION_FORMAT_CANONICAL, ]; diff --git a/Normalizer/UnwrappingDenormalizer.php b/Normalizer/UnwrappingDenormalizer.php index 31cc37be9..3708b1a08 100644 --- a/Normalizer/UnwrappingDenormalizer.php +++ b/Normalizer/UnwrappingDenormalizer.php @@ -26,7 +26,7 @@ final class UnwrappingDenormalizer implements DenormalizerInterface, SerializerA public const UNWRAP_PATH = 'unwrap_path'; - private $propertyAccessor; + private readonly PropertyAccessorInterface $propertyAccessor; public function __construct(PropertyAccessorInterface $propertyAccessor = null) { diff --git a/Serializer.php b/Serializer.php index e79ca7ba7..c83da01fb 100644 --- a/Serializer.php +++ b/Serializer.php @@ -62,25 +62,33 @@ class Serializer implements SerializerInterface, ContextAwareNormalizerInterface ]; /** - * @var Encoder\ChainEncoder + * @var ChainEncoder */ protected $encoder; /** - * @var Encoder\ChainDecoder + * @var ChainDecoder */ protected $decoder; - private $normalizers = []; - private $denormalizerCache = []; - private $normalizerCache = []; + /** + * @var array>> + */ + private array $denormalizerCache = []; + + /** + * @var array>> + */ + private array $normalizerCache = []; /** * @param array $normalizers * @param array $encoders */ - public function __construct(array $normalizers = [], array $encoders = []) - { + public function __construct( + private array $normalizers = [], + array $encoders = [], + ) { foreach ($normalizers as $normalizer) { if ($normalizer instanceof SerializerAwareInterface) { $normalizer->setSerializer($this); @@ -98,7 +106,6 @@ public function __construct(array $normalizers = [], array $encoders = []) throw new InvalidArgumentException(sprintf('The class "%s" neither implements "%s" nor "%s".', get_debug_type($normalizer), NormalizerInterface::class, DenormalizerInterface::class)); } } - $this->normalizers = $normalizers; $decoders = []; $realEncoders = []; From 9a337f9224d7bb8f06331d2740a4af340857eb93 Mon Sep 17 00:00:00 2001 From: Artyum Petrov <17199757+artyuum@users.noreply.github.com> Date: Thu, 20 Apr 2023 19:24:19 +0400 Subject: [PATCH 101/297] Add "composer require..." in all exception messages when needed --- composer.json | 9 --------- 1 file changed, 9 deletions(-) diff --git a/composer.json b/composer.json index c78f0246c..4c59800bd 100644 --- a/composer.json +++ b/composer.json @@ -50,15 +50,6 @@ "symfony/uid": "<5.4", "symfony/yaml": "<5.4" }, - "suggest": { - "psr/cache-implementation": "For using the metadata cache.", - "symfony/property-info": "To deserialize relations.", - "symfony/yaml": "For using the default YAML mapping loader.", - "symfony/config": "For using the XML mapping loader.", - "symfony/property-access": "For using the ObjectNormalizer.", - "symfony/mime": "For using a MIME type guesser within the DataUriNormalizer.", - "symfony/var-exporter": "For using the metadata compiler." - }, "autoload": { "psr-4": { "Symfony\\Component\\Serializer\\": "" }, "exclude-from-classmap": [ From 55ded112f53fb5ecc1af1e0fc840987c6d7cc866 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?THERAGE=20K=C3=A9vin?= Date: Mon, 27 Mar 2023 14:28:52 +0200 Subject: [PATCH 102/297] [Serializer] Fix MissingConstructorArgumentsException returning missing argument one by one --- CHANGELOG.md | 1 - .../MissingConstructorArgumentException.php | 41 ------------------- .../MissingConstructorArgumentsException.php | 34 +++++++-------- Normalizer/AbstractNormalizer.php | 35 +++++++++------- Normalizer/AbstractObjectNormalizer.php | 14 +++---- .../ConstructorArgumentsTestTrait.php | 11 +++-- 6 files changed, 49 insertions(+), 87 deletions(-) delete mode 100644 Exception/MissingConstructorArgumentException.php diff --git a/CHANGELOG.md b/CHANGELOG.md index 7c2dd3114..692226968 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,7 +9,6 @@ CHANGELOG * Add `UnsupportedFormatException` which is thrown when there is no decoder for a given format * Add method `getSupportedTypes(?string $format)` to `NormalizerInterface` and `DenormalizerInterface` * Make `ProblemNormalizer` give details about `ValidationFailedException` and `PartialDenormalizationException` - * Deprecate `MissingConstructorArgumentsException` in favor of `MissingConstructorArgumentException` * Deprecate `CacheableSupportsMethodInterface` in favor of the new `getSupportedTypes(?string $format)` methods * The following Normalizer classes will become final in 7.0: * `ConstraintViolationListNormalizer` diff --git a/Exception/MissingConstructorArgumentException.php b/Exception/MissingConstructorArgumentException.php deleted file mode 100644 index 3fdfaf605..000000000 --- a/Exception/MissingConstructorArgumentException.php +++ /dev/null @@ -1,41 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Component\Serializer\Exception; - -class MissingConstructorArgumentException extends MissingConstructorArgumentsException -{ - private string $class; - private string $missingArgument; - - /** - * @param class-string $class - */ - public function __construct(string $class, string $missingArgument, int $code = 0, \Throwable $previous = null) - { - $this->class = $class; - $this->missingArgument = $missingArgument; - - $message = sprintf('Cannot create an instance of "%s" from serialized data because its constructor requires parameter "%s" to be present.', $class, $missingArgument); - - parent::__construct($message, $code, $previous, [$missingArgument]); - } - - public function getClass(): string - { - return $this->class; - } - - public function getMissingArgument(): string - { - return $this->missingArgument; - } -} diff --git a/Exception/MissingConstructorArgumentsException.php b/Exception/MissingConstructorArgumentsException.php index a5a71d00c..ee8fcb5dc 100644 --- a/Exception/MissingConstructorArgumentsException.php +++ b/Exception/MissingConstructorArgumentsException.php @@ -12,37 +12,37 @@ namespace Symfony\Component\Serializer\Exception; /** - * @deprecated since Symfony 6.3, use {@see MissingConstructorArgumentException} instead - * * @author Maxime VEBER */ class MissingConstructorArgumentsException extends RuntimeException { /** - * @var string[] + * @param string[] $missingArguments + * @param class-string|null $class */ - private $missingArguments; - - public function __construct(string $message, int $code = 0, \Throwable $previous = null, array $missingArguments = []) - { - if (!$this instanceof MissingConstructorArgumentException) { - trigger_deprecation('symfony/serializer', '6.3', 'The "%s" class is deprecated, use "%s" instead.', __CLASS__, MissingConstructorArgumentException::class); - } - - $this->missingArguments = $missingArguments; - + public function __construct( + string $message, + int $code = 0, + \Throwable $previous = null, + private array $missingArguments = [], + private ?string $class = null, + ) { parent::__construct($message, $code, $previous); } /** - * @deprecated since Symfony 6.3, use {@see MissingConstructorArgumentException::getMissingArgument()} instead - * * @return string[] */ public function getMissingConstructorArguments(): array { - trigger_deprecation('symfony/serializer', '6.3', 'The "%s()" method is deprecated, use "%s::getMissingArgument()" instead.', __METHOD__, MissingConstructorArgumentException::class); - return $this->missingArguments; } + + /** + * @return class-string|null + */ + public function getClass(): ?string + { + return $this->class; + } } diff --git a/Normalizer/AbstractNormalizer.php b/Normalizer/AbstractNormalizer.php index 856aefbe9..61b14a1a8 100644 --- a/Normalizer/AbstractNormalizer.php +++ b/Normalizer/AbstractNormalizer.php @@ -14,7 +14,7 @@ use Symfony\Component\Serializer\Exception\CircularReferenceException; use Symfony\Component\Serializer\Exception\InvalidArgumentException; use Symfony\Component\Serializer\Exception\LogicException; -use Symfony\Component\Serializer\Exception\MissingConstructorArgumentException; +use Symfony\Component\Serializer\Exception\MissingConstructorArgumentsException; use Symfony\Component\Serializer\Exception\NotNormalizableValueException; use Symfony\Component\Serializer\Exception\RuntimeException; use Symfony\Component\Serializer\Mapping\AttributeMetadataInterface; @@ -313,7 +313,7 @@ protected function getConstructor(array &$data, string $class, array &$context, * @return object * * @throws RuntimeException - * @throws MissingConstructorArgumentException + * @throws MissingConstructorArgumentsException */ protected function instantiateObject(array &$data, string $class, array &$context, \ReflectionClass $reflectionClass, array|bool $allowedAttributes, string $format = null) { @@ -332,7 +332,7 @@ protected function instantiateObject(array &$data, string $class, array &$contex } $constructorParameters = $constructor->getParameters(); - + $missingConstructorArguments = []; $params = []; foreach ($constructorParameters as $constructorParameter) { $paramName = $constructorParameter->name; @@ -386,7 +386,8 @@ protected function instantiateObject(array &$data, string $class, array &$contex $params[] = null; } else { if (!isset($context['not_normalizable_value_exceptions'])) { - throw new MissingConstructorArgumentException($class, $constructorParameter->name); + $missingConstructorArguments[] = $constructorParameter->name; + continue; } $exception = NotNormalizableValueException::createForUnexpectedDataType( @@ -402,19 +403,23 @@ protected function instantiateObject(array &$data, string $class, array &$contex } } - if ($constructor->isConstructor()) { - try { - return $reflectionClass->newInstanceArgs($params); - } catch (\TypeError $e) { - if (!isset($context['not_normalizable_value_exceptions'])) { - throw $e; - } + if ($missingConstructorArguments) { + throw new MissingConstructorArgumentsException(sprintf('Cannot create an instance of "%s" from serialized data because its constructor requires the following parameters to be present : "$%s".', $class, implode('", "$', $missingConstructorArguments)), 0, null, $missingConstructorArguments, $class); + } - return $reflectionClass->newInstanceWithoutConstructor(); - } - } else { + if (!$constructor->isConstructor()) { return $constructor->invokeArgs(null, $params); } + + try { + return $reflectionClass->newInstanceArgs($params); + } catch (\TypeError $e) { + if (!isset($context['not_normalizable_value_exceptions'])) { + throw $e; + } + + return $reflectionClass->newInstanceWithoutConstructor(); + } } return new $class(); @@ -438,7 +443,7 @@ protected function denormalizeParameter(\ReflectionClass $class, \ReflectionPara } } catch (\ReflectionException $e) { throw new RuntimeException(sprintf('Could not determine the class of the parameter "%s".', $parameterName), 0, $e); - } catch (MissingConstructorArgumentException $e) { + } catch (MissingConstructorArgumentsException $e) { if (!$parameter->getType()->allowsNull()) { throw $e; } diff --git a/Normalizer/AbstractObjectNormalizer.php b/Normalizer/AbstractObjectNormalizer.php index 88e3c3ea1..16b2bb70a 100644 --- a/Normalizer/AbstractObjectNormalizer.php +++ b/Normalizer/AbstractObjectNormalizer.php @@ -22,7 +22,7 @@ use Symfony\Component\Serializer\Encoder\XmlEncoder; use Symfony\Component\Serializer\Exception\ExtraAttributesException; use Symfony\Component\Serializer\Exception\LogicException; -use Symfony\Component\Serializer\Exception\MissingConstructorArgumentException; +use Symfony\Component\Serializer\Exception\MissingConstructorArgumentsException; use Symfony\Component\Serializer\Exception\NotNormalizableValueException; use Symfony\Component\Serializer\Mapping\AttributeMetadataInterface; use Symfony\Component\Serializer\Mapping\ClassDiscriminatorFromClassMetadata; @@ -419,7 +419,7 @@ abstract protected function setAttributeValue(object $object, string $attribute, * * @throws NotNormalizableValueException * @throws ExtraAttributesException - * @throws MissingConstructorArgumentException + * @throws MissingConstructorArgumentsException * @throws LogicException */ private function validateAndDenormalize(array $types, string $currentClass, string $attribute, mixed $data, ?string $format, array $context): mixed @@ -427,7 +427,7 @@ private function validateAndDenormalize(array $types, string $currentClass, stri $expectedTypes = []; $isUnionType = \count($types) > 1; $extraAttributesException = null; - $missingConstructorArgumentException = null; + $missingConstructorArgumentsException = null; foreach ($types as $type) { if (null === $data && $type->isNullable()) { return null; @@ -567,12 +567,12 @@ private function validateAndDenormalize(array $types, string $currentClass, stri } $extraAttributesException ??= $e; - } catch (MissingConstructorArgumentException $e) { + } catch (MissingConstructorArgumentsException $e) { if (!$isUnionType) { throw $e; } - $missingConstructorArgumentException ??= $e; + $missingConstructorArgumentsException ??= $e; } } @@ -580,8 +580,8 @@ private function validateAndDenormalize(array $types, string $currentClass, stri throw $extraAttributesException; } - if ($missingConstructorArgumentException) { - throw $missingConstructorArgumentException; + if ($missingConstructorArgumentsException) { + throw $missingConstructorArgumentsException; } if ($context[self::DISABLE_TYPE_ENFORCEMENT] ?? $this->defaultContext[self::DISABLE_TYPE_ENFORCEMENT] ?? false) { diff --git a/Tests/Normalizer/Features/ConstructorArgumentsTestTrait.php b/Tests/Normalizer/Features/ConstructorArgumentsTestTrait.php index 9489136be..928ded512 100644 --- a/Tests/Normalizer/Features/ConstructorArgumentsTestTrait.php +++ b/Tests/Normalizer/Features/ConstructorArgumentsTestTrait.php @@ -11,7 +11,7 @@ namespace Symfony\Component\Serializer\Tests\Normalizer\Features; -use Symfony\Component\Serializer\Exception\MissingConstructorArgumentException; +use Symfony\Component\Serializer\Exception\MissingConstructorArgumentsException; use Symfony\Component\Serializer\Normalizer\DenormalizerInterface; use Symfony\Component\Serializer\Tests\Fixtures\NotSerializedConstructorArgumentDummy; @@ -62,14 +62,13 @@ public function testConstructorWithMissingData() ]; $normalizer = $this->getDenormalizerForConstructArguments(); - try { $normalizer->denormalize($data, ConstructorArgumentsObject::class); - self::fail(sprintf('Failed asserting that exception of type "%s" is thrown.', MissingConstructorArgumentException::class)); - } catch (MissingConstructorArgumentException $e) { - self::assertSame(sprintf('Cannot create an instance of "%s" from serialized data because its constructor requires parameter "bar" to be present.', ConstructorArgumentsObject::class), $e->getMessage()); + self::fail(sprintf('Failed asserting that exception of type "%s" is thrown.', MissingConstructorArgumentsException::class)); + } catch (MissingConstructorArgumentsException $e) { + self::assertSame(sprintf('Cannot create an instance of "%s" from serialized data because its constructor requires the following parameters to be present : "$bar", "$baz".', ConstructorArgumentsObject::class), $e->getMessage()); self::assertSame(ConstructorArgumentsObject::class, $e->getClass()); - self::assertSame('bar', $e->getMissingArgument()); + self::assertSame(['bar', 'baz'], $e->getMissingConstructorArguments()); } } } From 0d5ae03e8d26592e0cf6de97ca1bb5af343fcca0 Mon Sep 17 00:00:00 2001 From: Wouter de Jong Date: Sat, 18 Feb 2023 13:05:04 +0100 Subject: [PATCH 103/297] Add CI check ensuring interfaces have return types --- Normalizer/DenormalizableInterface.php | 2 ++ Tests/Fixtures/Dummy.php | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/Normalizer/DenormalizableInterface.php b/Normalizer/DenormalizableInterface.php index 73cff7377..503f3cb51 100644 --- a/Normalizer/DenormalizableInterface.php +++ b/Normalizer/DenormalizableInterface.php @@ -33,6 +33,8 @@ interface DenormalizableInterface * @param string|null $format The format is optionally given to be able to denormalize * differently based on different input formats * @param array $context Options for denormalizing + * + * @return void */ public function denormalize(DenormalizerInterface $denormalizer, array|string|int|float|bool $data, string $format = null, array $context = []); } diff --git a/Tests/Fixtures/Dummy.php b/Tests/Fixtures/Dummy.php index 76f042e0b..74d94f78f 100644 --- a/Tests/Fixtures/Dummy.php +++ b/Tests/Fixtures/Dummy.php @@ -33,7 +33,7 @@ public function normalize(NormalizerInterface $normalizer, string $format = null ]; } - public function denormalize(DenormalizerInterface $denormalizer, array|string|int|float|bool $data, string $format = null, array $context = []) + public function denormalize(DenormalizerInterface $denormalizer, array|string|int|float|bool $data, string $format = null, array $context = []): void { $this->foo = $data['foo']; $this->bar = $data['bar']; From 8f4afa580455433e0948a17e9290713f8075894b Mon Sep 17 00:00:00 2001 From: Wouter de Jong Date: Sat, 22 Apr 2023 15:51:15 +0200 Subject: [PATCH 104/297] [Serializer] Fix tests --- Tests/Fixtures/DenormalizableDummy.php | 2 +- Tests/Fixtures/NormalizableTraversableDummy.php | 6 +----- Tests/Fixtures/ScalarDummy.php | 2 +- 3 files changed, 3 insertions(+), 7 deletions(-) diff --git a/Tests/Fixtures/DenormalizableDummy.php b/Tests/Fixtures/DenormalizableDummy.php index bbf54bd5f..a768a1998 100644 --- a/Tests/Fixtures/DenormalizableDummy.php +++ b/Tests/Fixtures/DenormalizableDummy.php @@ -16,7 +16,7 @@ class DenormalizableDummy implements DenormalizableInterface { - public function denormalize(DenormalizerInterface $denormalizer, array|string|int|float|bool $data, string $format = null, array $context = []) + public function denormalize(DenormalizerInterface $denormalizer, array|string|int|float|bool $data, string $format = null, array $context = []): void { } } diff --git a/Tests/Fixtures/NormalizableTraversableDummy.php b/Tests/Fixtures/NormalizableTraversableDummy.php index 5ee205fd1..d6b38b89a 100644 --- a/Tests/Fixtures/NormalizableTraversableDummy.php +++ b/Tests/Fixtures/NormalizableTraversableDummy.php @@ -26,11 +26,7 @@ public function normalize(NormalizerInterface $normalizer, string $format = null ]; } - public function denormalize(DenormalizerInterface $denormalizer, array|string|int|float|bool $data, string $format = null, array $context = []) + public function denormalize(DenormalizerInterface $denormalizer, array|string|int|float|bool $data, string $format = null, array $context = []): void { - return [ - 'foo' => 'denormalizedFoo', - 'bar' => 'denormalizedBar', - ]; } } diff --git a/Tests/Fixtures/ScalarDummy.php b/Tests/Fixtures/ScalarDummy.php index 31ca36ab7..949407d11 100644 --- a/Tests/Fixtures/ScalarDummy.php +++ b/Tests/Fixtures/ScalarDummy.php @@ -26,7 +26,7 @@ public function normalize(NormalizerInterface $normalizer, string $format = null return 'xml' === $format ? $this->xmlFoo : $this->foo; } - public function denormalize(DenormalizerInterface $denormalizer, array|string|int|float|bool $data, string $format = null, array $context = []) + public function denormalize(DenormalizerInterface $denormalizer, array|string|int|float|bool $data, string $format = null, array $context = []): void { if ('xml' === $format) { $this->xmlFoo = $data; From 164177b3e1177c3125a859ce22ebed2290eba3ae Mon Sep 17 00:00:00 2001 From: Wouter de Jong Date: Fri, 28 Apr 2023 14:21:20 +0200 Subject: [PATCH 105/297] Add missing return types --- Normalizer/AbstractObjectNormalizer.php | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/Normalizer/AbstractObjectNormalizer.php b/Normalizer/AbstractObjectNormalizer.php index 16b2bb70a..ed42d56f4 100644 --- a/Normalizer/AbstractObjectNormalizer.php +++ b/Normalizer/AbstractObjectNormalizer.php @@ -139,12 +139,17 @@ public function __construct( /** * @param array $context + * + * @return bool */ public function supportsNormalization(mixed $data, string $format = null /* , array $context = [] */) { return \is_object($data) && !$data instanceof \Traversable; } + /** + * @return array|string|int|float|bool|\ArrayObject|null + */ public function normalize(mixed $object, string $format = null, array $context = []) { if (!isset($context['cache_key'])) { @@ -226,6 +231,9 @@ public function normalize(mixed $object, string $format = null, array $context = return $data; } + /** + * @return object + */ protected function instantiateObject(array &$data, string $class, array &$context, \ReflectionClass $reflectionClass, array|bool $allowedAttributes, string $format = null) { if ($this->classDiscriminatorResolver && $mapping = $this->classDiscriminatorResolver->getMappingForClass($class)) { @@ -299,12 +307,17 @@ abstract protected function getAttributeValue(object $object, string $attribute, /** * @param array $context + * + * @return bool */ public function supportsDenormalization(mixed $data, string $type, string $format = null /* , array $context = [] */) { return class_exists($type) || (interface_exists($type, false) && null !== $this->classDiscriminatorResolver?->getMappingForClass($type)); } + /** + * @return mixed + */ public function denormalize(mixed $data, string $type, string $format = null, array $context = []) { if (!isset($context['cache_key'])) { @@ -408,7 +421,7 @@ public function denormalize(mixed $data, string $type, string $format = null, ar } /** - * Sets attribute value. + * @return void */ abstract protected function setAttributeValue(object $object, string $attribute, mixed $value, string $format = null, array $context = []); From 07b4e8088f6a28a49d71b4f4a5645335c4147ac5 Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Wed, 26 Apr 2023 12:12:51 +0200 Subject: [PATCH 106/297] Fix registering traceable voters, argument resolvers and normalizers --- DependencyInjection/SerializerPass.php | 47 ++++++++----------- .../SerializerPassTest.php | 10 ++-- 2 files changed, 24 insertions(+), 33 deletions(-) diff --git a/DependencyInjection/SerializerPass.php b/DependencyInjection/SerializerPass.php index 71376d496..d0b0deb48 100644 --- a/DependencyInjection/SerializerPass.php +++ b/DependencyInjection/SerializerPass.php @@ -40,45 +40,38 @@ public function process(ContainerBuilder $container) return; } - if (!$normalizers = $container->findTaggedServiceIds('serializer.normalizer')) { + if (!$normalizers = $this->findAndSortTaggedServices('serializer.normalizer', $container)) { throw new RuntimeException('You must tag at least one service as "serializer.normalizer" to use the "serializer" service.'); } - if ($container->getParameter('kernel.debug') && $container->hasDefinition('serializer.data_collector')) { - foreach (array_keys($normalizers) as $normalizer) { - $container->register('debug.'.$normalizer, TraceableNormalizer::class) - ->setDecoratedService($normalizer) - ->setArguments([new Reference('debug.'.$normalizer.'.inner'), new Reference('serializer.data_collector')]); - } + if (!$encoders = $this->findAndSortTaggedServices('serializer.encoder', $container)) { + throw new RuntimeException('You must tag at least one service as "serializer.encoder" to use the "serializer" service.'); } - $serializerDefinition = $container->getDefinition('serializer'); - $serializerDefinition->replaceArgument(0, $this->findAndSortTaggedServices('serializer.normalizer', $container)); + if ($container->hasParameter('serializer.default_context')) { + $defaultContext = $container->getParameter('serializer.default_context'); + foreach (array_merge($normalizers, $encoders) as $service) { + $definition = $container->getDefinition($service); + $definition->setBindings(['array $defaultContext' => new BoundArgument($defaultContext, false)] + $definition->getBindings()); + } - if (!$encoders = $container->findTaggedServiceIds('serializer.encoder')) { - throw new RuntimeException('You must tag at least one service as "serializer.encoder" to use the "serializer" service.'); + $container->getParameterBag()->remove('serializer.default_context'); } if ($container->getParameter('kernel.debug') && $container->hasDefinition('serializer.data_collector')) { - foreach (array_keys($encoders) as $encoder) { - $container->register('debug.'.$encoder, TraceableEncoder::class) - ->setDecoratedService($encoder) - ->setArguments([new Reference('debug.'.$encoder.'.inner'), new Reference('serializer.data_collector')]); + foreach ($normalizers as $i => $normalizer) { + $normalizers[$i] = $container->register('.debug.serializer.normalizer.'.$normalizer, TraceableNormalizer::class) + ->setArguments([$normalizer, new Reference('serializer.data_collector')]); } - } - $serializerDefinition->replaceArgument(1, $this->findAndSortTaggedServices('serializer.encoder', $container)); - - if (!$container->hasParameter('serializer.default_context')) { - return; - } - - $defaultContext = $container->getParameter('serializer.default_context'); - foreach (array_keys(array_merge($container->findTaggedServiceIds('serializer.normalizer'), $container->findTaggedServiceIds('serializer.encoder'))) as $service) { - $definition = $container->getDefinition($service); - $definition->setBindings(['array $defaultContext' => new BoundArgument($defaultContext, false)] + $definition->getBindings()); + foreach ($encoders as $i => $encoder) { + $encoders[$i] = $container->register('.debug.serializer.encoder.'.$encoder, TraceableEncoder::class) + ->setArguments([$encoder, new Reference('serializer.data_collector')]); + } } - $container->getParameterBag()->remove('serializer.default_context'); + $serializerDefinition = $container->getDefinition('serializer'); + $serializerDefinition->replaceArgument(0, $normalizers); + $serializerDefinition->replaceArgument(1, $encoders); } } diff --git a/Tests/DependencyInjection/SerializerPassTest.php b/Tests/DependencyInjection/SerializerPassTest.php index abb1ade96..eb77263f4 100644 --- a/Tests/DependencyInjection/SerializerPassTest.php +++ b/Tests/DependencyInjection/SerializerPassTest.php @@ -104,17 +104,15 @@ public function testNormalizersAndEncodersAreDecoredAndOrderedWhenCollectingData $serializerPass = new SerializerPass(); $serializerPass->process($container); - $traceableNormalizerDefinition = $container->getDefinition('debug.n'); - $traceableEncoderDefinition = $container->getDefinition('debug.e'); + $traceableNormalizerDefinition = $container->getDefinition('.debug.serializer.normalizer.n'); + $traceableEncoderDefinition = $container->getDefinition('.debug.serializer.encoder.e'); $this->assertEquals(TraceableNormalizer::class, $traceableNormalizerDefinition->getClass()); - $this->assertEquals(['n', null, 0], $traceableNormalizerDefinition->getDecoratedService()); - $this->assertEquals(new Reference('debug.n.inner'), $traceableNormalizerDefinition->getArgument(0)); + $this->assertEquals(new Reference('n'), $traceableNormalizerDefinition->getArgument(0)); $this->assertEquals(new Reference('serializer.data_collector'), $traceableNormalizerDefinition->getArgument(1)); $this->assertEquals(TraceableEncoder::class, $traceableEncoderDefinition->getClass()); - $this->assertEquals(['e', null, 0], $traceableEncoderDefinition->getDecoratedService()); - $this->assertEquals(new Reference('debug.e.inner'), $traceableEncoderDefinition->getArgument(0)); + $this->assertEquals(new Reference('e'), $traceableEncoderDefinition->getArgument(0)); $this->assertEquals(new Reference('serializer.data_collector'), $traceableEncoderDefinition->getArgument(1)); } } From e1e8e32284e9a29235795d5d8337e71dcd5342af Mon Sep 17 00:00:00 2001 From: Christian Kolb Date: Tue, 28 Feb 2023 07:59:29 +0100 Subject: [PATCH 107/297] [Serializer] Add flag to require all properties to be listed in the input --- CHANGELOG.md | 1 + .../Normalizer/AbstractNormalizerContextBuilder.php | 9 +++++++++ Normalizer/AbstractNormalizer.php | 8 +++++++- .../AbstractNormalizerContextBuilderTest.php | 3 +++ Tests/Normalizer/AbstractNormalizerTest.php | 10 ++++++++++ 5 files changed, 30 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 692226968..8154d3688 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,7 @@ CHANGELOG 6.3 --- + * Add `AbstractNormalizer::REQUIRE_ALL_PROPERTIES` context flag to require all properties to be listed in the input instead of falling back to null for nullable ones * Add `XmlEncoder::SAVE_OPTIONS` context option * Add `BackedEnumNormalizer::ALLOW_INVALID_VALUES` context option * Add `UnsupportedFormatException` which is thrown when there is no decoder for a given format diff --git a/Context/Normalizer/AbstractNormalizerContextBuilder.php b/Context/Normalizer/AbstractNormalizerContextBuilder.php index 670543540..ecb328dd6 100644 --- a/Context/Normalizer/AbstractNormalizerContextBuilder.php +++ b/Context/Normalizer/AbstractNormalizerContextBuilder.php @@ -164,4 +164,13 @@ public function withIgnoredAttributes(?array $ignoredAttributes): static { return $this->with(AbstractNormalizer::IGNORED_ATTRIBUTES, $ignoredAttributes); } + + /** + * Configures requiring all properties to be listed in the input instead + * of falling back to null for nullable ones. + */ + public function withRequireAllProperties(?bool $requireAllProperties = true): static + { + return $this->with(AbstractNormalizer::REQUIRE_ALL_PROPERTIES, $requireAllProperties); + } } diff --git a/Normalizer/AbstractNormalizer.php b/Normalizer/AbstractNormalizer.php index bd33dbc1d..079b1e7a9 100644 --- a/Normalizer/AbstractNormalizer.php +++ b/Normalizer/AbstractNormalizer.php @@ -112,6 +112,12 @@ abstract class AbstractNormalizer implements NormalizerInterface, DenormalizerIn */ public const IGNORED_ATTRIBUTES = 'ignored_attributes'; + /** + * Require all properties to be listed in the input instead of falling + * back to null for nullable ones. + */ + public const REQUIRE_ALL_PROPERTIES = 'require_all_properties'; + /** * @internal */ @@ -383,7 +389,7 @@ protected function instantiateObject(array &$data, string $class, array &$contex $params[] = $this->defaultContext[self::DEFAULT_CONSTRUCTOR_ARGUMENTS][$class][$key]; } elseif ($constructorParameter->isDefaultValueAvailable()) { $params[] = $constructorParameter->getDefaultValue(); - } elseif ($constructorParameter->hasType() && $constructorParameter->getType()->allowsNull()) { + } elseif (!($context[self::REQUIRE_ALL_PROPERTIES] ?? $this->defaultContext[self::REQUIRE_ALL_PROPERTIES] ?? false) && $constructorParameter->hasType() && $constructorParameter->getType()->allowsNull()) { $params[] = null; } else { if (!isset($context['not_normalizable_value_exceptions'])) { diff --git a/Tests/Context/Normalizer/AbstractNormalizerContextBuilderTest.php b/Tests/Context/Normalizer/AbstractNormalizerContextBuilderTest.php index 4c36a8ff9..158fa8fea 100644 --- a/Tests/Context/Normalizer/AbstractNormalizerContextBuilderTest.php +++ b/Tests/Context/Normalizer/AbstractNormalizerContextBuilderTest.php @@ -45,6 +45,7 @@ public function testWithers(array $values) ->withCallbacks($values[AbstractNormalizer::CALLBACKS]) ->withCircularReferenceHandler($values[AbstractNormalizer::CIRCULAR_REFERENCE_HANDLER]) ->withIgnoredAttributes($values[AbstractNormalizer::IGNORED_ATTRIBUTES]) + ->withRequireAllProperties($values[AbstractNormalizer::REQUIRE_ALL_PROPERTIES]) ->toArray(); $this->assertEquals($values, $context); @@ -65,6 +66,7 @@ public static function withersDataProvider(): iterable AbstractNormalizer::CALLBACKS => [static function (): void {}], AbstractNormalizer::CIRCULAR_REFERENCE_HANDLER => static function (): void {}, AbstractNormalizer::IGNORED_ATTRIBUTES => ['attribute3'], + AbstractNormalizer::REQUIRE_ALL_PROPERTIES => true, ]]; yield 'With null values' => [[ @@ -77,6 +79,7 @@ public static function withersDataProvider(): iterable AbstractNormalizer::CALLBACKS => null, AbstractNormalizer::CIRCULAR_REFERENCE_HANDLER => null, AbstractNormalizer::IGNORED_ATTRIBUTES => null, + AbstractNormalizer::REQUIRE_ALL_PROPERTIES => null, ]]; } diff --git a/Tests/Normalizer/AbstractNormalizerTest.php b/Tests/Normalizer/AbstractNormalizerTest.php index 33f444305..16e39440e 100644 --- a/Tests/Normalizer/AbstractNormalizerTest.php +++ b/Tests/Normalizer/AbstractNormalizerTest.php @@ -15,6 +15,7 @@ use PHPUnit\Framework\TestCase; use Symfony\Component\PropertyInfo\Extractor\PhpDocExtractor; use Symfony\Component\Serializer\Encoder\JsonEncoder; +use Symfony\Component\Serializer\Exception\MissingConstructorArgumentsException; use Symfony\Component\Serializer\Mapping\AttributeMetadata; use Symfony\Component\Serializer\Mapping\ClassMetadata; use Symfony\Component\Serializer\Mapping\Factory\ClassMetadataFactory; @@ -166,6 +167,15 @@ public function testObjectWithNullableNonOptionalConstructorArgumentWithoutInput $this->assertNull($dummy->getFoo()); } + public function testObjectWithNullableNonOptionalConstructorArgumentWithoutInputAndRequireAllProperties() + { + $normalizer = new ObjectNormalizer(); + + $this->expectException(MissingConstructorArgumentsException::class); + + $normalizer->denormalize([], NullableConstructorArgumentDummy::class, null, [AbstractNormalizer::REQUIRE_ALL_PROPERTIES => true]); + } + /** * @dataProvider getNormalizer */ From ff4111b5e12358d030fe95ce6ee87c8da5af79f6 Mon Sep 17 00:00:00 2001 From: HypeMC Date: Sat, 6 May 2023 19:22:54 +0200 Subject: [PATCH 108/297] [Serializer] Fix SerializedPath not working with constructor arguments --- Normalizer/AbstractObjectNormalizer.php | 68 +++---- .../SerializedPathInConstructorDummy.php | 25 +++ .../SerializedPathInConstructorDummy.php | 22 +++ Tests/Fixtures/serialization.xml | 4 + Tests/Fixtures/serialization.yml | 4 + .../ClassMetadataFactoryCompilerTest.php | 13 +- .../Loader/AnnotationLoaderTestCase.php | 9 + Tests/Mapping/Loader/XmlFileLoaderTest.php | 9 + Tests/Mapping/Loader/YamlFileLoaderTest.php | 9 + .../AbstractObjectNormalizerTest.php | 170 +++++++++++++++++- 10 files changed, 298 insertions(+), 35 deletions(-) create mode 100644 Tests/Fixtures/Annotations/SerializedPathInConstructorDummy.php create mode 100644 Tests/Fixtures/Attributes/SerializedPathInConstructorDummy.php diff --git a/Normalizer/AbstractObjectNormalizer.php b/Normalizer/AbstractObjectNormalizer.php index afd3acabc..69dc534e4 100644 --- a/Normalizer/AbstractObjectNormalizer.php +++ b/Normalizer/AbstractObjectNormalizer.php @@ -128,8 +128,8 @@ public function __construct(ClassMetadataFactoryInterface $classMetadataFactory $this->propertyTypeExtractor = $propertyTypeExtractor; - if (null === $classDiscriminatorResolver && null !== $classMetadataFactory) { - $classDiscriminatorResolver = new ClassDiscriminatorFromClassMetadata($classMetadataFactory); + if ($classMetadataFactory) { + $classDiscriminatorResolver ??= new ClassDiscriminatorFromClassMetadata($classMetadataFactory); } $this->classDiscriminatorResolver = $classDiscriminatorResolver; $this->objectClassResolver = $objectClassResolver; @@ -217,7 +217,7 @@ public function normalize(mixed $object, string $format = null, array $context = } $preserveEmptyObjects = $context[self::PRESERVE_EMPTY_OBJECTS] ?? $this->defaultContext[self::PRESERVE_EMPTY_OBJECTS] ?? false; - if ($preserveEmptyObjects && !\count($data)) { + if ($preserveEmptyObjects && !$data) { return new \ArrayObject(); } @@ -226,19 +226,8 @@ public function normalize(mixed $object, string $format = null, array $context = protected function instantiateObject(array &$data, string $class, array &$context, \ReflectionClass $reflectionClass, array|bool $allowedAttributes, string $format = null) { - if ($this->classDiscriminatorResolver && $mapping = $this->classDiscriminatorResolver->getMappingForClass($class)) { - if (!isset($data[$mapping->getTypeProperty()])) { - 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(), false); - } - - $type = $data[$mapping->getTypeProperty()]; - if (null === ($mappedClass = $mapping->getClassForType($type))) { - 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); - } - - if ($mappedClass !== $class) { - return $this->instantiateObject($data, $mappedClass, $context, new \ReflectionClass($mappedClass), $allowedAttributes, $format); - } + if ($class !== $mappedClass = $this->getMappedClass($data, $class, $context)) { + return $this->instantiateObject($data, $mappedClass, $context, new \ReflectionClass($mappedClass), $allowedAttributes, $format); } return parent::instantiateObject($data, $class, $context, $reflectionClass, $allowedAttributes, $format); @@ -270,7 +259,7 @@ protected function getAttributes(object $object, ?string $format, array $context $attributes = $this->extractAttributes($object, $format, $context); - if ($this->classDiscriminatorResolver && $mapping = $this->classDiscriminatorResolver->getMappingForMappedObject($object)) { + if ($mapping = $this->classDiscriminatorResolver?->getMappingForMappedObject($object)) { array_unshift($attributes, $mapping->getTypeProperty()); } @@ -319,11 +308,9 @@ public function denormalize(mixed $data, string $type, string $format = null, ar $normalizedData = $this->prepareForDenormalization($data); $extraAttributes = []; - $reflectionClass = new \ReflectionClass($type); - $object = $this->instantiateObject($normalizedData, $type, $context, $reflectionClass, $allowedAttributes, $format); - $resolvedClass = $this->objectClassResolver ? ($this->objectClassResolver)($object) : $object::class; + $mappedClass = $this->getMappedClass($normalizedData, $type, $context); - $nestedAttributes = $this->getNestedAttributes($resolvedClass); + $nestedAttributes = $this->getNestedAttributes($mappedClass); $nestedData = []; $propertyAccessor = PropertyAccess::createPropertyAccessor(); foreach ($nestedAttributes as $property => $serializedPath) { @@ -336,6 +323,9 @@ public function denormalize(mixed $data, string $type, string $format = null, ar $normalizedData = array_merge($normalizedData, $nestedData); + $object = $this->instantiateObject($normalizedData, $mappedClass, $context, new \ReflectionClass($mappedClass), $allowedAttributes, $format); + $resolvedClass = $this->objectClassResolver ? ($this->objectClassResolver)($object) : $object::class; + foreach ($normalizedData as $attribute => $value) { if ($this->nameConverter) { $notConverted = $attribute; @@ -675,11 +665,8 @@ private function updateData(array $data, string $attribute, mixed $attributeValu */ private function isMaxDepthReached(array $attributesMetadata, string $class, string $attribute, array &$context): bool { - $enableMaxDepth = $context[self::ENABLE_MAX_DEPTH] ?? $this->defaultContext[self::ENABLE_MAX_DEPTH] ?? false; - if ( - !$enableMaxDepth || - !isset($attributesMetadata[$attribute]) || - null === $maxDepth = $attributesMetadata[$attribute]->getMaxDepth() + if (!($enableMaxDepth = $context[self::ENABLE_MAX_DEPTH] ?? $this->defaultContext[self::ENABLE_MAX_DEPTH] ?? false) + || null === $maxDepth = $attributesMetadata[$attribute]?->getMaxDepth() ) { return false; } @@ -755,7 +742,7 @@ private function isUninitializedValueError(\Error $e): bool */ private function getNestedAttributes(string $class): array { - if (!$this->classMetadataFactory || !$this->classMetadataFactory->hasMetadataFor($class)) { + if (!$this->classMetadataFactory?->hasMetadataFor($class)) { return []; } @@ -781,15 +768,30 @@ private function getNestedAttributes(string $class): array private function removeNestedValue(array $path, array $data): array { $element = array_shift($path); - if ([] === $path) { + if (!$path || !$data[$element] = $this->removeNestedValue($path, $data[$element])) { unset($data[$element]); - } else { - $data[$element] = $this->removeNestedValue($path, $data[$element]); - if ([] === $data[$element]) { - unset($data[$element]); - } } return $data; } + + /** + * @return class-string + */ + private function getMappedClass(array $data, string $class, array $context): string + { + if (!$mapping = $this->classDiscriminatorResolver?->getMappingForClass($class)) { + return $class; + } + + if (null === $type = $data[$mapping->getTypeProperty()] ?? null) { + 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(), false); + } + + if (null === $mappedClass = $mapping->getClassForType($type)) { + 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); + } + + return $mappedClass; + } } diff --git a/Tests/Fixtures/Annotations/SerializedPathInConstructorDummy.php b/Tests/Fixtures/Annotations/SerializedPathInConstructorDummy.php new file mode 100644 index 000000000..a6d510908 --- /dev/null +++ b/Tests/Fixtures/Annotations/SerializedPathInConstructorDummy.php @@ -0,0 +1,25 @@ + + * + * 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\Annotations; + +use Symfony\Component\Serializer\Annotation\SerializedPath; + +class SerializedPathInConstructorDummy +{ + public function __construct( + /** + * @SerializedPath("[one][two]") + */ + public $three, + ) { + } +} diff --git a/Tests/Fixtures/Attributes/SerializedPathInConstructorDummy.php b/Tests/Fixtures/Attributes/SerializedPathInConstructorDummy.php new file mode 100644 index 000000000..90aee1154 --- /dev/null +++ b/Tests/Fixtures/Attributes/SerializedPathInConstructorDummy.php @@ -0,0 +1,22 @@ + + * + * 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\Attributes; + +use Symfony\Component\Serializer\Annotation\SerializedPath; + +class SerializedPathInConstructorDummy +{ + public function __construct( + #[SerializedPath('[one][two]')] public $three, + ) { + } +} diff --git a/Tests/Fixtures/serialization.xml b/Tests/Fixtures/serialization.xml index 4890f56bf..5ca6ac412 100644 --- a/Tests/Fixtures/serialization.xml +++ b/Tests/Fixtures/serialization.xml @@ -30,6 +30,10 @@ + + + + diff --git a/Tests/Fixtures/serialization.yml b/Tests/Fixtures/serialization.yml index 7519b979e..e052d65a8 100644 --- a/Tests/Fixtures/serialization.yml +++ b/Tests/Fixtures/serialization.yml @@ -22,6 +22,10 @@ serialized_path: '[one][two]' seven: serialized_path: '[three][four]' +'Symfony\Component\Serializer\Tests\Fixtures\Annotations\SerializedPathInConstructorDummy': + attributes: + three: + serialized_path: '[one][two]' 'Symfony\Component\Serializer\Tests\Fixtures\Annotations\AbstractDummy': discriminator_map: type_property: type diff --git a/Tests/Mapping/Factory/ClassMetadataFactoryCompilerTest.php b/Tests/Mapping/Factory/ClassMetadataFactoryCompilerTest.php index 5ce1931ba..903612c68 100644 --- a/Tests/Mapping/Factory/ClassMetadataFactoryCompilerTest.php +++ b/Tests/Mapping/Factory/ClassMetadataFactoryCompilerTest.php @@ -19,6 +19,7 @@ use Symfony\Component\Serializer\Tests\Fixtures\Annotations\MaxDepthDummy; use Symfony\Component\Serializer\Tests\Fixtures\Annotations\SerializedNameDummy; use Symfony\Component\Serializer\Tests\Fixtures\Annotations\SerializedPathDummy; +use Symfony\Component\Serializer\Tests\Fixtures\Annotations\SerializedPathInConstructorDummy; use Symfony\Component\Serializer\Tests\Fixtures\Dummy; final class ClassMetadataFactoryCompilerTest extends TestCase @@ -46,18 +47,20 @@ public function testItDumpMetadata() $maxDepthDummyMetadata = $classMetatadataFactory->getMetadataFor(MaxDepthDummy::class); $serializedNameDummyMetadata = $classMetatadataFactory->getMetadataFor(SerializedNameDummy::class); $serializedPathDummyMetadata = $classMetatadataFactory->getMetadataFor(SerializedPathDummy::class); + $serializedPathInConstructorDummyMetadata = $classMetatadataFactory->getMetadataFor(SerializedPathInConstructorDummy::class); $code = (new ClassMetadataFactoryCompiler())->compile([ $dummyMetadata, $maxDepthDummyMetadata, $serializedNameDummyMetadata, $serializedPathDummyMetadata, + $serializedPathInConstructorDummyMetadata, ]); file_put_contents($this->dumpPath, $code); $compiledMetadata = require $this->dumpPath; - $this->assertCount(4, $compiledMetadata); + $this->assertCount(5, $compiledMetadata); $this->assertArrayHasKey(Dummy::class, $compiledMetadata); $this->assertEquals([ @@ -99,5 +102,13 @@ public function testItDumpMetadata() ], null, ], $compiledMetadata[SerializedPathDummy::class]); + + $this->assertArrayHasKey(SerializedPathInConstructorDummy::class, $compiledMetadata); + $this->assertEquals([ + [ + 'three' => [[], null, null, '[one][two]'], + ], + null, + ], $compiledMetadata[SerializedPathInConstructorDummy::class]); } } diff --git a/Tests/Mapping/Loader/AnnotationLoaderTestCase.php b/Tests/Mapping/Loader/AnnotationLoaderTestCase.php index de7accd84..2dbd03703 100644 --- a/Tests/Mapping/Loader/AnnotationLoaderTestCase.php +++ b/Tests/Mapping/Loader/AnnotationLoaderTestCase.php @@ -103,6 +103,15 @@ public function testLoadSerializedPath() $this->assertEquals(new PropertyPath('[three][four]'), $attributesMetadata['seven']->getSerializedPath()); } + public function testLoadSerializedPathInConstructor() + { + $classMetadata = new ClassMetadata($this->getNamespace().'\SerializedPathInConstructorDummy'); + $this->loader->loadClassMetadata($classMetadata); + + $attributesMetadata = $classMetadata->getAttributesMetadata(); + $this->assertEquals(new PropertyPath('[one][two]'), $attributesMetadata['three']->getSerializedPath()); + } + public function testLoadClassMetadataAndMerge() { $classMetadata = new ClassMetadata($this->getNamespace().'\GroupDummy'); diff --git a/Tests/Mapping/Loader/XmlFileLoaderTest.php b/Tests/Mapping/Loader/XmlFileLoaderTest.php index b1e9ed722..202534f56 100644 --- a/Tests/Mapping/Loader/XmlFileLoaderTest.php +++ b/Tests/Mapping/Loader/XmlFileLoaderTest.php @@ -94,6 +94,15 @@ public function testSerializedPath() $this->assertEquals('[three][four]', $attributesMetadata['seven']->getSerializedPath()); } + public function testSerializedPathInConstructor() + { + $classMetadata = new ClassMetadata('Symfony\Component\Serializer\Tests\Fixtures\Annotations\SerializedPathInConstructorDummy'); + $this->loader->loadClassMetadata($classMetadata); + + $attributesMetadata = $classMetadata->getAttributesMetadata(); + $this->assertEquals('[one][two]', $attributesMetadata['three']->getSerializedPath()); + } + public function testLoadDiscriminatorMap() { $classMetadata = new ClassMetadata(AbstractDummy::class); diff --git a/Tests/Mapping/Loader/YamlFileLoaderTest.php b/Tests/Mapping/Loader/YamlFileLoaderTest.php index bbe0a99ae..dcfd2b4af 100644 --- a/Tests/Mapping/Loader/YamlFileLoaderTest.php +++ b/Tests/Mapping/Loader/YamlFileLoaderTest.php @@ -108,6 +108,15 @@ public function testSerializedPath() $this->assertEquals(new PropertyPath('[three][four]'), $attributesMetadata['seven']->getSerializedPath()); } + public function testSerializedPathInConstructor() + { + $classMetadata = new ClassMetadata('Symfony\Component\Serializer\Tests\Fixtures\Annotations\SerializedPathInConstructorDummy'); + $this->loader->loadClassMetadata($classMetadata); + + $attributesMetadata = $classMetadata->getAttributesMetadata(); + $this->assertEquals(new PropertyPath('[one][two]'), $attributesMetadata['three']->getSerializedPath()); + } + public function testLoadDiscriminatorMap() { $classMetadata = new ClassMetadata(AbstractDummy::class); diff --git a/Tests/Normalizer/AbstractObjectNormalizerTest.php b/Tests/Normalizer/AbstractObjectNormalizerTest.php index 0c342b64d..d876193c8 100644 --- a/Tests/Normalizer/AbstractObjectNormalizerTest.php +++ b/Tests/Normalizer/AbstractObjectNormalizerTest.php @@ -18,6 +18,7 @@ use Symfony\Component\PropertyInfo\PropertyInfoExtractor; use Symfony\Component\PropertyInfo\Type; use Symfony\Component\Serializer\Annotation\Context; +use Symfony\Component\Serializer\Annotation\DiscriminatorMap; use Symfony\Component\Serializer\Annotation\SerializedName; use Symfony\Component\Serializer\Annotation\SerializedPath; use Symfony\Component\Serializer\Exception\ExtraAttributesException; @@ -171,6 +172,53 @@ public function testDenormalizeWithNestedAttributesDuplicateKeys() $normalizer->denormalize($data, DuplicateKeyNestedDummy::class, 'any'); } + public function testDenormalizeWithNestedAttributesInConstructor() + { + $normalizer = new AbstractObjectNormalizerWithMetadata(); + $data = [ + 'one' => [ + 'two' => [ + 'three' => 'foo', + ], + 'four' => 'quux', + ], + 'foo' => 'notfoo', + 'baz' => 'baz', + ]; + $test = $normalizer->denormalize($data, NestedDummyWithConstructor::class, 'any'); + $this->assertSame('foo', $test->foo); + $this->assertSame('quux', $test->quux); + $this->assertSame('notfoo', $test->notfoo); + $this->assertSame('baz', $test->baz); + } + + public function testDenormalizeWithNestedAttributesInConstructorAndDiscriminatorMap() + { + $normalizer = new AbstractObjectNormalizerWithMetadata(); + $data = [ + 'one' => [ + 'two' => [ + 'three' => 'foo', + ], + 'four' => 'quux', + ], + 'foo' => 'notfoo', + 'baz' => 'baz', + ]; + + $test1 = $normalizer->denormalize($data + ['type' => 'first'], AbstractNestedDummyWithConstructorAndDiscriminator::class, 'any'); + $this->assertInstanceOf(FirstNestedDummyWithConstructorAndDiscriminator::class, $test1); + $this->assertSame('foo', $test1->foo); + $this->assertSame('notfoo', $test1->notfoo); + $this->assertSame('baz', $test1->baz); + + $test2 = $normalizer->denormalize($data + ['type' => 'second'], AbstractNestedDummyWithConstructorAndDiscriminator::class, 'any'); + $this->assertInstanceOf(SecondNestedDummyWithConstructorAndDiscriminator::class, $test2); + $this->assertSame('quux', $test2->quux); + $this->assertSame('notfoo', $test2->notfoo); + $this->assertSame('baz', $test2->baz); + } + public function testNormalizeWithNestedAttributesMixingArrayTypes() { $this->expectException(LogicException::class); @@ -236,6 +284,52 @@ public function testNormalizeWithNestedAttributesWithoutMetadata() $this->assertSame($data, $test); } + public function testNormalizeWithNestedAttributesInConstructor() + { + $classMetadataFactory = new ClassMetadataFactory(new AnnotationLoader(new AnnotationReader())); + $normalizer = new ObjectNormalizer($classMetadataFactory, new MetadataAwareNameConverter($classMetadataFactory)); + + $test = $normalizer->normalize(new NestedDummyWithConstructor('foo', 'quux', 'notfoo', 'baz'), 'any'); + $this->assertSame([ + 'one' => [ + 'two' => [ + 'three' => 'foo', + ], + 'four' => 'quux', + ], + 'foo' => 'notfoo', + 'baz' => 'baz', + ], $test); + } + + public function testNormalizeWithNestedAttributesInConstructorAndDiscriminatorMap() + { + $classMetadataFactory = new ClassMetadataFactory(new AnnotationLoader(new AnnotationReader())); + $normalizer = new ObjectNormalizer($classMetadataFactory, new MetadataAwareNameConverter($classMetadataFactory)); + + $test1 = $normalizer->normalize(new FirstNestedDummyWithConstructorAndDiscriminator('foo', 'notfoo', 'baz'), 'any'); + $this->assertSame([ + 'type' => 'first', + 'one' => [ + 'two' => [ + 'three' => 'foo', + ], + ], + 'foo' => 'notfoo', + 'baz' => 'baz', + ], $test1); + + $test2 = $normalizer->normalize(new SecondNestedDummyWithConstructorAndDiscriminator('quux', 'notfoo', 'baz'), 'any'); + $this->assertSame([ + 'type' => 'second', + 'one' => [ + 'four' => 'quux', + ], + 'foo' => 'notfoo', + 'baz' => 'baz', + ], $test2); + } + public function testDenormalizeCollectionDecodedFromXmlWithOneChild() { $denormalizer = $this->getDenormalizerForDummyCollection(); @@ -661,6 +755,78 @@ class NestedDummy public $baz; } +class NestedDummyWithConstructor +{ + public function __construct( + /** + * @SerializedPath("[one][two][three]") + */ + public $foo, + + /** + * @SerializedPath("[one][four]") + */ + public $quux, + + /** + * @SerializedPath("[foo]") + */ + public $notfoo, + + public $baz, + ) { + } +} + +/** + * @DiscriminatorMap(typeProperty="type", mapping={ + * "first" = FirstNestedDummyWithConstructorAndDiscriminator::class, + * "second" = SecondNestedDummyWithConstructorAndDiscriminator::class, + * }) + */ +abstract class AbstractNestedDummyWithConstructorAndDiscriminator +{ + public function __construct( + /** + * @SerializedPath("[foo]") + */ + public $notfoo, + + public $baz, + ) { + } +} + +class FirstNestedDummyWithConstructorAndDiscriminator extends AbstractNestedDummyWithConstructorAndDiscriminator +{ + public function __construct( + /** + * @SerializedPath("[one][two][three]") + */ + public $foo, + + $notfoo, + $baz, + ) { + parent::__construct($notfoo, $baz); + } +} + +class SecondNestedDummyWithConstructorAndDiscriminator extends AbstractNestedDummyWithConstructorAndDiscriminator +{ + public function __construct( + /** + * @SerializedPath("[one][four]") + */ + public $quux, + + $notfoo, + $baz, + ) { + parent::__construct($notfoo, $baz); + } +} + class DuplicateKeyNestedDummy { /** @@ -711,7 +877,9 @@ protected function getAttributeValue(object $object, string $attribute, string $ protected function setAttributeValue(object $object, string $attribute, $value, string $format = null, array $context = []) { - $object->$attribute = $value; + if (property_exists($object, $attribute)) { + $object->$attribute = $value; + } } } From bf9ee06da94025d100708baacd71d7414a07c0c6 Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Thu, 11 May 2023 10:20:44 +0200 Subject: [PATCH 109/297] Remove unnecessary usages of DateTime --- Normalizer/DateTimeNormalizer.php | 18 +++++++-------- Tests/Encoder/XmlEncoderTest.php | 10 ++++----- Tests/Normalizer/DateTimeNormalizerTest.php | 22 +++++++++---------- .../Features/CallbacksTestTrait.php | 8 +++---- .../Features/ContextMetadataTestTrait.php | 14 ++++++------ Tests/SerializerTest.php | 8 +++---- 6 files changed, 40 insertions(+), 40 deletions(-) diff --git a/Normalizer/DateTimeNormalizer.php b/Normalizer/DateTimeNormalizer.php index df222b381..a0fd3e75c 100644 --- a/Normalizer/DateTimeNormalizer.php +++ b/Normalizer/DateTimeNormalizer.php @@ -29,7 +29,7 @@ class DateTimeNormalizer implements NormalizerInterface, DenormalizerInterface, public const TIMEZONE_KEY = 'datetime_timezone'; private array $defaultContext = [ - self::FORMAT_KEY => \DateTime::RFC3339, + self::FORMAT_KEY => \DateTimeInterface::RFC3339, self::TIMEZONE_KEY => null, ]; @@ -108,14 +108,16 @@ public function denormalize(mixed $data, string $type, string $format = null, ar } try { - if (null !== $dateTimeFormat) { - $object = \DateTime::class === $type ? \DateTime::createFromFormat($dateTimeFormat, $data, $timezone) : \DateTimeImmutable::createFromFormat($dateTimeFormat, $data, $timezone); + if (\DateTimeInterface::class === $type) { + $type = \DateTimeImmutable::class; + } - if (false !== $object) { + if (null !== $dateTimeFormat) { + if (false !== $object = $type::createFromFormat($dateTimeFormat, $data, $timezone)) { return $object; } - $dateTimeErrors = \DateTime::class === $type ? \DateTime::getLastErrors() : \DateTimeImmutable::getLastErrors(); + $dateTimeErrors = $type::getLastErrors(); throw NotNormalizableValueException::createForUnexpectedDataType(sprintf('Parsing datetime string "%s" using format "%s" resulted in %d errors: ', $data, $dateTimeFormat, $dateTimeErrors['error_count'])."\n".implode("\n", $this->formatDateTimeErrors($dateTimeErrors['errors'])), $data, [Type::BUILTIN_TYPE_STRING], $context['deserialization_path'] ?? null, true); } @@ -123,14 +125,12 @@ public function denormalize(mixed $data, string $type, string $format = null, ar $defaultDateTimeFormat = $this->defaultContext[self::FORMAT_KEY] ?? null; if (null !== $defaultDateTimeFormat) { - $object = \DateTime::class === $type ? \DateTime::createFromFormat($defaultDateTimeFormat, $data, $timezone) : \DateTimeImmutable::createFromFormat($defaultDateTimeFormat, $data, $timezone); - - if (false !== $object) { + if (false !== $object = $type::createFromFormat($defaultDateTimeFormat, $data, $timezone)) { return $object; } } - return \DateTime::class === $type ? new \DateTime($data, $timezone) : new \DateTimeImmutable($data, $timezone); + return new $type($data, $timezone); } catch (NotNormalizableValueException $e) { throw $e; } catch (\Exception $e) { diff --git a/Tests/Encoder/XmlEncoderTest.php b/Tests/Encoder/XmlEncoderTest.php index b98447b54..965256d37 100644 --- a/Tests/Encoder/XmlEncoderTest.php +++ b/Tests/Encoder/XmlEncoderTest.php @@ -826,7 +826,7 @@ public function testEncodeXmlWithDateTimeObjectValue() { $xmlEncoder = $this->createXmlEncoderWithDateTimeNormalizer(); - $actualXml = $xmlEncoder->encode(['dateTime' => new \DateTime($this->exampleDateTimeString)], 'xml'); + $actualXml = $xmlEncoder->encode(['dateTime' => new \DateTimeImmutable($this->exampleDateTimeString)], 'xml'); $this->assertEquals($this->createXmlWithDateTime(), $actualXml); } @@ -835,7 +835,7 @@ public function testEncodeXmlWithDateTimeObjectField() { $xmlEncoder = $this->createXmlEncoderWithDateTimeNormalizer(); - $actualXml = $xmlEncoder->encode(['foo' => ['@dateTime' => new \DateTime($this->exampleDateTimeString)]], 'xml'); + $actualXml = $xmlEncoder->encode(['foo' => ['@dateTime' => new \DateTimeImmutable($this->exampleDateTimeString)]], 'xml'); $this->assertEquals($this->createXmlWithDateTimeField(), $actualXml); } @@ -939,18 +939,18 @@ private function createMockDateTimeNormalizer(): MockObject&NormalizerInterface $mock ->expects($this->once()) ->method('normalize') - ->with(new \DateTime($this->exampleDateTimeString), 'xml', []) + ->with(new \DateTimeImmutable($this->exampleDateTimeString), 'xml', []) ->willReturn($this->exampleDateTimeString); $mock ->expects($this->once()) ->method('getSupportedTypes') - ->willReturn([\DateTime::class => true]); + ->willReturn([\DateTimeImmutable::class => true]); $mock ->expects($this->once()) ->method('supportsNormalization') - ->with(new \DateTime($this->exampleDateTimeString), 'xml') + ->with(new \DateTimeImmutable($this->exampleDateTimeString), 'xml') ->willReturn(true); return $mock; diff --git a/Tests/Normalizer/DateTimeNormalizerTest.php b/Tests/Normalizer/DateTimeNormalizerTest.php index 25b7c784f..5f30f1be9 100644 --- a/Tests/Normalizer/DateTimeNormalizerTest.php +++ b/Tests/Normalizer/DateTimeNormalizerTest.php @@ -46,21 +46,21 @@ public function testNormalize() public function testNormalizeUsingFormatPassedInContext() { - $this->assertEquals('2016', $this->normalizer->normalize(new \DateTime('2016/01/01'), null, [DateTimeNormalizer::FORMAT_KEY => 'Y'])); + $this->assertEquals('2016', $this->normalizer->normalize(new \DateTimeImmutable('2016/01/01'), null, [DateTimeNormalizer::FORMAT_KEY => 'Y'])); } public function testNormalizeUsingFormatPassedInConstructor() { $normalizer = new DateTimeNormalizer([DateTimeNormalizer::FORMAT_KEY => 'y']); - $this->assertEquals('16', $normalizer->normalize(new \DateTime('2016/01/01', new \DateTimeZone('UTC')))); + $this->assertEquals('16', $normalizer->normalize(new \DateTimeImmutable('2016/01/01', new \DateTimeZone('UTC')))); } public function testNormalizeUsingTimeZonePassedInConstructor() { $normalizer = new DateTimeNormalizer([DateTimeNormalizer::TIMEZONE_KEY => new \DateTimeZone('Japan')]); - $this->assertSame('2016-12-01T00:00:00+09:00', $normalizer->normalize(new \DateTime('2016/12/01', new \DateTimeZone('Japan')))); - $this->assertSame('2016-12-01T09:00:00+09:00', $normalizer->normalize(new \DateTime('2016/12/01', new \DateTimeZone('UTC')))); + $this->assertSame('2016-12-01T00:00:00+09:00', $normalizer->normalize(new \DateTimeImmutable('2016/12/01', new \DateTimeZone('Japan')))); + $this->assertSame('2016-12-01T09:00:00+09:00', $normalizer->normalize(new \DateTimeImmutable('2016/12/01', new \DateTimeZone('UTC')))); } /** @@ -75,10 +75,10 @@ public function testNormalizeUsingTimeZonePassedInContext($expected, $input, $ti public static function normalizeUsingTimeZonePassedInContextProvider() { - yield ['2016-12-01T00:00:00+00:00', new \DateTime('2016/12/01', new \DateTimeZone('UTC')), null]; - yield ['2016-12-01T00:00:00+09:00', new \DateTime('2016/12/01', new \DateTimeZone('Japan')), new \DateTimeZone('Japan')]; - yield ['2016-12-01T09:00:00+09:00', new \DateTime('2016/12/01', new \DateTimeZone('UTC')), new \DateTimeZone('Japan')]; + yield ['2016-12-01T00:00:00+00:00', new \DateTimeImmutable('2016/12/01', new \DateTimeZone('UTC')), null]; + yield ['2016-12-01T00:00:00+09:00', new \DateTimeImmutable('2016/12/01', new \DateTimeZone('Japan')), new \DateTimeZone('Japan')]; yield ['2016-12-01T09:00:00+09:00', new \DateTimeImmutable('2016/12/01', new \DateTimeZone('UTC')), new \DateTimeZone('Japan')]; + yield ['2016-12-01T09:00:00+09:00', new \DateTime('2016/12/01', new \DateTimeZone('UTC')), new \DateTimeZone('Japan')]; } /** @@ -185,10 +185,10 @@ public function testDenormalize() public function testDenormalizeUsingTimezonePassedInConstructor() { $timezone = new \DateTimeZone('Japan'); - $expected = new \DateTime('2016/12/01 17:35:00', $timezone); + $expected = new \DateTimeImmutable('2016/12/01 17:35:00', $timezone); $normalizer = new DateTimeNormalizer([DateTimeNormalizer::TIMEZONE_KEY => $timezone]); - $this->assertEquals($expected, $normalizer->denormalize('2016.12.01 17:35:00', \DateTime::class, null, [ + $this->assertEquals($expected, $normalizer->denormalize('2016.12.01 17:35:00', \DateTimeImmutable::class, null, [ DateTimeNormalizer::FORMAT_KEY => 'Y.m.d H:i:s', ])); } @@ -235,7 +235,7 @@ public static function denormalizeUsingTimezonePassedInContextProvider() '2016-12-01T17:35:00Z', new \DateTimeImmutable('2016/12/01 17:35:00', new \DateTimeZone('UTC')), 'Europe/Paris', - \DateTime::RFC3339, + \DateTimeInterface::RFC3339, ]; } @@ -294,7 +294,7 @@ public function testDenormalizeDateTimeStringWithDefaultContextFormat() public function testDenormalizeDateTimeStringWithDefaultContextAllowsErrorFormat() { $format = 'd/m/Y'; // the default format - $string = '2020-01-01'; // the value which is in the wrong format, but is accepted because of `new \DateTime` in DateTimeNormalizer::denormalize + $string = '2020-01-01'; // the value which is in the wrong format, but is accepted because of `new \DateTimeImmutable` in DateTimeNormalizer::denormalize $normalizer = new DateTimeNormalizer([DateTimeNormalizer::FORMAT_KEY => $format]); $denormalizedDate = $normalizer->denormalize($string, \DateTimeInterface::class); diff --git a/Tests/Normalizer/Features/CallbacksTestTrait.php b/Tests/Normalizer/Features/CallbacksTestTrait.php index e9a9721fd..a3aebb442 100644 --- a/Tests/Normalizer/Features/CallbacksTestTrait.php +++ b/Tests/Normalizer/Features/CallbacksTestTrait.php @@ -156,12 +156,12 @@ public function provideNormalizeCallbacks() 'Format a date' => [ [ 'bar' => function ($bar) { - $this->assertInstanceOf(\DateTime::class, $bar); + $this->assertInstanceOf(\DateTimeImmutable::class, $bar); return $bar->format('d-m-Y H:i:s'); }, ], - new \DateTime('2011-09-10 06:30:00'), + new \DateTimeImmutable('2011-09-10 06:30:00'), ['bar' => '10-09-2011 06:30:00', 'foo' => null], ], 'Collect a property' => [ @@ -220,11 +220,11 @@ public function provideDenormalizeCallbacks(): array 'bar' => function ($bar) { $this->assertIsString($bar); - return \DateTime::createFromFormat('d-m-Y H:i:s', $bar); + return \DateTimeImmutable::createFromFormat('d-m-Y H:i:s', $bar); }, ], '10-09-2011 06:30:00', - new CallbacksObject(new \DateTime('2011-09-10 06:30:00')), + new CallbacksObject(new \DateTimeImmutable('2011-09-10 06:30:00')), ], 'Collect a property' => [ [ diff --git a/Tests/Normalizer/Features/ContextMetadataTestTrait.php b/Tests/Normalizer/Features/ContextMetadataTestTrait.php index b4938bdef..cb436dbc3 100644 --- a/Tests/Normalizer/Features/ContextMetadataTestTrait.php +++ b/Tests/Normalizer/Features/ContextMetadataTestTrait.php @@ -39,7 +39,7 @@ public function testContextMetadataNormalize(string $contextMetadataDummyClass) new Serializer([new DateTimeNormalizer(), $normalizer]); $dummy = new $contextMetadataDummyClass(); - $dummy->date = new \DateTime('2011-07-28T08:44:00.123+00:00'); + $dummy->date = new \DateTimeImmutable('2011-07-28T08:44:00.123+00:00'); self::assertEquals(['date' => '2011-07-28T08:44:00+00:00'], $normalizer->normalize($dummy)); @@ -63,13 +63,13 @@ public function testContextMetadataContextDenormalize(string $contextMetadataDum /** @var ContextMetadataDummy|ContextChildMetadataDummy $dummy */ $dummy = $normalizer->denormalize(['date' => '2011-07-28T08:44:00+00:00'], $contextMetadataDummyClass); - self::assertEquals(new \DateTime('2011-07-28T08:44:00+00:00'), $dummy->date); + self::assertEquals(new \DateTimeImmutable('2011-07-28T08:44:00+00:00'), $dummy->date); /** @var ContextMetadataDummy|ContextChildMetadataDummy $dummy */ $dummy = $normalizer->denormalize(['date' => '2011-07-28T08:44:00+00:00'], ContextMetadataDummy::class, null, [ ObjectNormalizer::GROUPS => 'extended', ]); - self::assertEquals(new \DateTime('2011-07-28T08:44:00+00:00'), $dummy->date, 'base denormalization context is unchanged for this group'); + self::assertEquals(new \DateTimeImmutable('2011-07-28T08:44:00+00:00'), $dummy->date, 'base denormalization context is unchanged for this group'); /** @var ContextMetadataDummy|ContextChildMetadataDummy $dummy */ $dummy = $normalizer->denormalize(['date' => '28/07/2011'], $contextMetadataDummyClass, null, [ @@ -105,9 +105,9 @@ class ContextMetadataDummy * * @Groups({ "extended", "simple" }) * - * @Context({ DateTimeNormalizer::FORMAT_KEY = \DateTime::RFC3339 }) + * @Context({ DateTimeNormalizer::FORMAT_KEY = \DateTimeInterface::RFC3339 }) * @Context( - * normalizationContext = { DateTimeNormalizer::FORMAT_KEY = \DateTime::RFC3339_EXTENDED }, + * normalizationContext = { DateTimeNormalizer::FORMAT_KEY = \DateTimeInterface::RFC3339_EXTENDED }, * groups = {"extended"} * ) * @Context( @@ -125,9 +125,9 @@ class ContextChildMetadataDummy * * @Groups({ "extended", "simple" }) * - * @DummyContextChild({ DateTimeNormalizer::FORMAT_KEY = \DateTime::RFC3339 }) + * @DummyContextChild({ DateTimeNormalizer::FORMAT_KEY = \DateTimeInterface::RFC3339 }) * @DummyContextChild( - * normalizationContext = { DateTimeNormalizer::FORMAT_KEY = \DateTime::RFC3339_EXTENDED }, + * normalizationContext = { DateTimeNormalizer::FORMAT_KEY = \DateTimeInterface::RFC3339_EXTENDED }, * groups = {"extended"} * ) * @DummyContextChild( diff --git a/Tests/SerializerTest.php b/Tests/SerializerTest.php index 914458fb4..ff3307077 100644 --- a/Tests/SerializerTest.php +++ b/Tests/SerializerTest.php @@ -722,20 +722,20 @@ public function testUnionTypeDeserializable() ); $actual = $serializer->deserialize('{ "changed": null }', DummyUnionType::class, 'json', [ - DateTimeNormalizer::FORMAT_KEY => \DateTime::ISO8601, + DateTimeNormalizer::FORMAT_KEY => \DateTimeinterface::ATOM, ]); $this->assertEquals((new DummyUnionType())->setChanged(null), $actual, 'Union type denormalization first case failed.'); $actual = $serializer->deserialize('{ "changed": "2022-03-22T16:15:05+0000" }', DummyUnionType::class, 'json', [ - DateTimeNormalizer::FORMAT_KEY => \DateTime::ISO8601, + DateTimeNormalizer::FORMAT_KEY => \DateTimeinterface::ATOM, ]); - $expectedDateTime = \DateTime::createFromFormat(\DateTime::ISO8601, '2022-03-22T16:15:05+0000'); + $expectedDateTime = \DateTimeImmutable::createFromFormat(\DateTimeinterface::ATOM, '2022-03-22T16:15:05+0000'); $this->assertEquals((new DummyUnionType())->setChanged($expectedDateTime), $actual, 'Union type denormalization second case failed.'); $actual = $serializer->deserialize('{ "changed": false }', DummyUnionType::class, 'json', [ - DateTimeNormalizer::FORMAT_KEY => \DateTime::ISO8601, + DateTimeNormalizer::FORMAT_KEY => \DateTimeinterface::ATOM, ]); $this->assertEquals(new DummyUnionType(), $actual, 'Union type denormalization third case failed.'); From c721240e061012465295e25024b678bf80aa2268 Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Tue, 23 May 2023 17:38:00 +0200 Subject: [PATCH 110/297] [6.4] Allow 7.0 deps --- composer.json | 34 +++++++++++++++++----------------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/composer.json b/composer.json index 4c59800bd..ee0c10bc4 100644 --- a/composer.json +++ b/composer.json @@ -22,23 +22,23 @@ "require-dev": { "doctrine/annotations": "^1.12|^2", "phpdocumentor/reflection-docblock": "^3.2|^4.0|^5.0", - "symfony/cache": "^5.4|^6.0", - "symfony/config": "^5.4|^6.0", - "symfony/console": "^5.4|^6.0", - "symfony/dependency-injection": "^5.4|^6.0", - "symfony/error-handler": "^5.4|^6.0", - "symfony/filesystem": "^5.4|^6.0", - "symfony/form": "^5.4|^6.0", - "symfony/http-foundation": "^5.4|^6.0", - "symfony/http-kernel": "^5.4|^6.0", - "symfony/mime": "^5.4|^6.0", - "symfony/property-access": "^5.4|^6.0", - "symfony/property-info": "^5.4|^6.0", - "symfony/uid": "^5.4|^6.0", - "symfony/validator": "^5.4|^6.0", - "symfony/var-dumper": "^5.4|^6.0", - "symfony/var-exporter": "^5.4|^6.0", - "symfony/yaml": "^5.4|^6.0" + "symfony/cache": "^5.4|^6.0|^7.0", + "symfony/config": "^5.4|^6.0|^7.0", + "symfony/console": "^5.4|^6.0|^7.0", + "symfony/dependency-injection": "^5.4|^6.0|^7.0", + "symfony/error-handler": "^5.4|^6.0|^7.0", + "symfony/filesystem": "^5.4|^6.0|^7.0", + "symfony/form": "^5.4|^6.0|^7.0", + "symfony/http-foundation": "^5.4|^6.0|^7.0", + "symfony/http-kernel": "^5.4|^6.0|^7.0", + "symfony/mime": "^5.4|^6.0|^7.0", + "symfony/property-access": "^5.4|^6.0|^7.0", + "symfony/property-info": "^5.4|^6.0|^7.0", + "symfony/uid": "^5.4|^6.0|^7.0", + "symfony/validator": "^5.4|^6.0|^7.0", + "symfony/var-dumper": "^5.4|^6.0|^7.0", + "symfony/var-exporter": "^5.4|^6.0|^7.0", + "symfony/yaml": "^5.4|^6.0|^7.0" }, "conflict": { "doctrine/annotations": "<1.12", From 584eb93021d1b700411ef144c9e1472a4610a399 Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Tue, 23 May 2023 17:24:39 +0200 Subject: [PATCH 111/297] [7.0] Bump to PHP 8.2 minimum --- Tests/SerializerTest.php | 6 ------ composer.json | 46 ++++++++++++++++++++-------------------- 2 files changed, 23 insertions(+), 29 deletions(-) diff --git a/Tests/SerializerTest.php b/Tests/SerializerTest.php index 914458fb4..c7f89ffb6 100644 --- a/Tests/SerializerTest.php +++ b/Tests/SerializerTest.php @@ -776,9 +776,6 @@ public function testUnionTypeDeserializableWithoutAllowedExtraAttributes() ]); } - /** - * @requires PHP 8.2 - */ public function testFalseBuiltInTypes() { $extractor = new PropertyInfoExtractor([], [new ReflectionExtractor()]); @@ -789,9 +786,6 @@ public function testFalseBuiltInTypes() $this->assertEquals(new FalseBuiltInDummy(), $actual); } - /** - * @requires PHP 8.2 - */ public function testTrueBuiltInTypes() { $extractor = new PropertyInfoExtractor([], [new ReflectionExtractor()]); diff --git a/composer.json b/composer.json index ee0c10bc4..d7368258b 100644 --- a/composer.json +++ b/composer.json @@ -16,39 +16,39 @@ } ], "require": { - "php": ">=8.1", + "php": ">=8.2", "symfony/polyfill-ctype": "~1.8" }, "require-dev": { "doctrine/annotations": "^1.12|^2", "phpdocumentor/reflection-docblock": "^3.2|^4.0|^5.0", - "symfony/cache": "^5.4|^6.0|^7.0", - "symfony/config": "^5.4|^6.0|^7.0", - "symfony/console": "^5.4|^6.0|^7.0", - "symfony/dependency-injection": "^5.4|^6.0|^7.0", - "symfony/error-handler": "^5.4|^6.0|^7.0", - "symfony/filesystem": "^5.4|^6.0|^7.0", - "symfony/form": "^5.4|^6.0|^7.0", - "symfony/http-foundation": "^5.4|^6.0|^7.0", - "symfony/http-kernel": "^5.4|^6.0|^7.0", - "symfony/mime": "^5.4|^6.0|^7.0", - "symfony/property-access": "^5.4|^6.0|^7.0", - "symfony/property-info": "^5.4|^6.0|^7.0", - "symfony/uid": "^5.4|^6.0|^7.0", - "symfony/validator": "^5.4|^6.0|^7.0", - "symfony/var-dumper": "^5.4|^6.0|^7.0", - "symfony/var-exporter": "^5.4|^6.0|^7.0", - "symfony/yaml": "^5.4|^6.0|^7.0" + "symfony/cache": "^6.4|^7.0", + "symfony/config": "^6.4|^7.0", + "symfony/console": "^6.4|^7.0", + "symfony/dependency-injection": "^6.4|^7.0", + "symfony/error-handler": "^6.4|^7.0", + "symfony/filesystem": "^6.4|^7.0", + "symfony/form": "^6.4|^7.0", + "symfony/http-foundation": "^6.4|^7.0", + "symfony/http-kernel": "^6.4|^7.0", + "symfony/mime": "^6.4|^7.0", + "symfony/property-access": "^6.4|^7.0", + "symfony/property-info": "^6.4|^7.0", + "symfony/uid": "^6.4|^7.0", + "symfony/validator": "^6.4|^7.0", + "symfony/var-dumper": "^6.4|^7.0", + "symfony/var-exporter": "^6.4|^7.0", + "symfony/yaml": "^6.4|^7.0" }, "conflict": { "doctrine/annotations": "<1.12", "phpdocumentor/reflection-docblock": "<3.2.2", "phpdocumentor/type-resolver": "<1.4.0", - "symfony/dependency-injection": "<5.4", - "symfony/property-access": "<5.4", - "symfony/property-info": "<5.4", - "symfony/uid": "<5.4", - "symfony/yaml": "<5.4" + "symfony/dependency-injection": "<6.4", + "symfony/property-access": "<6.4", + "symfony/property-info": "<6.4", + "symfony/uid": "<6.4", + "symfony/yaml": "<6.4" }, "autoload": { "psr-4": { "Symfony\\Component\\Serializer\\": "" }, From 7be4b2fd8f385904887aa09ee08a4e4adee4c0d2 Mon Sep 17 00:00:00 2001 From: HypeMC Date: Tue, 30 May 2023 19:07:47 +0200 Subject: [PATCH 112/297] [Serializer] Fix discriminator map not working with `AbstractNormalizer::OBJECT_TO_POPULATE` --- Normalizer/AbstractObjectNormalizer.php | 4 ++ .../AbstractObjectNormalizerTest.php | 57 +++++++++++++++++++ 2 files changed, 61 insertions(+) diff --git a/Normalizer/AbstractObjectNormalizer.php b/Normalizer/AbstractObjectNormalizer.php index 69dc534e4..98ccfca4e 100644 --- a/Normalizer/AbstractObjectNormalizer.php +++ b/Normalizer/AbstractObjectNormalizer.php @@ -780,6 +780,10 @@ private function removeNestedValue(array $path, array $data): array */ private function getMappedClass(array $data, string $class, array $context): string { + if (null !== $object = $this->extractObjectToPopulate($class, $context, self::OBJECT_TO_POPULATE)) { + return $object::class; + } + if (!$mapping = $this->classDiscriminatorResolver?->getMappingForClass($class)) { return $class; } diff --git a/Tests/Normalizer/AbstractObjectNormalizerTest.php b/Tests/Normalizer/AbstractObjectNormalizerTest.php index d876193c8..53ef55bd1 100644 --- a/Tests/Normalizer/AbstractObjectNormalizerTest.php +++ b/Tests/Normalizer/AbstractObjectNormalizerTest.php @@ -34,6 +34,7 @@ use Symfony\Component\Serializer\Mapping\Factory\ClassMetadataFactoryInterface; use Symfony\Component\Serializer\Mapping\Loader\AnnotationLoader; use Symfony\Component\Serializer\NameConverter\MetadataAwareNameConverter; +use Symfony\Component\Serializer\Normalizer\AbstractNormalizer; use Symfony\Component\Serializer\Normalizer\AbstractObjectNormalizer; use Symfony\Component\Serializer\Normalizer\DateTimeNormalizer; use Symfony\Component\Serializer\Normalizer\DenormalizerInterface; @@ -44,6 +45,7 @@ use Symfony\Component\Serializer\Tests\Fixtures\Annotations\AbstractDummy; use Symfony\Component\Serializer\Tests\Fixtures\Annotations\AbstractDummyFirstChild; use Symfony\Component\Serializer\Tests\Fixtures\Annotations\AbstractDummySecondChild; +use Symfony\Component\Serializer\Tests\Fixtures\DummyFirstChildQuux; use Symfony\Component\Serializer\Tests\Fixtures\DummySecondChildQuux; use Symfony\Component\Serializer\Tests\Normalizer\Features\ObjectDummyWithContextAttribute; @@ -480,6 +482,61 @@ public function hasMetadataFor($value): bool $this->assertInstanceOf(DummySecondChildQuux::class, $normalizedData->quux); } + public function testDenormalizeWithDiscriminatorMapAndObjectToPopulateUsesCorrectClassname() + { + $factory = new ClassMetadataFactory(new AnnotationLoader(new AnnotationReader())); + + $loaderMock = new class() implements ClassMetadataFactoryInterface { + public function getMetadataFor($value): ClassMetadataInterface + { + if (AbstractDummy::class === $value) { + return new ClassMetadata( + AbstractDummy::class, + new ClassDiscriminatorMapping('type', [ + 'first' => AbstractDummyFirstChild::class, + 'second' => AbstractDummySecondChild::class, + ]) + ); + } + + throw new InvalidArgumentException(); + } + + public function hasMetadataFor($value): bool + { + return AbstractDummy::class === $value; + } + }; + + $discriminatorResolver = new ClassDiscriminatorFromClassMetadata($loaderMock); + $normalizer = new AbstractObjectNormalizerDummy($factory, null, new PhpDocExtractor(), $discriminatorResolver); + $serializer = new Serializer([$normalizer]); + $normalizer->setSerializer($serializer); + + $data = [ + 'foo' => 'foo', + 'quux' => ['value' => 'quux'], + ]; + + $normalizedData1 = $normalizer->denormalize($data + ['bar' => 'bar'], AbstractDummy::class, 'any', [ + AbstractNormalizer::OBJECT_TO_POPULATE => new AbstractDummyFirstChild('notfoo', 'notbar'), + ]); + $this->assertInstanceOf(AbstractDummyFirstChild::class, $normalizedData1); + $this->assertSame('foo', $normalizedData1->foo); + $this->assertSame('notbar', $normalizedData1->bar); + $this->assertInstanceOf(DummyFirstChildQuux::class, $normalizedData1->quux); + $this->assertSame('quux', $normalizedData1->quux->getValue()); + + $normalizedData2 = $normalizer->denormalize($data + ['baz' => 'baz'], AbstractDummy::class, 'any', [ + AbstractNormalizer::OBJECT_TO_POPULATE => new AbstractDummySecondChild('notfoo', 'notbaz'), + ]); + $this->assertInstanceOf(AbstractDummySecondChild::class, $normalizedData2); + $this->assertSame('foo', $normalizedData2->foo); + $this->assertSame('baz', $normalizedData2->baz); + $this->assertInstanceOf(DummySecondChildQuux::class, $normalizedData2->quux); + $this->assertSame('quux', $normalizedData2->quux->getValue()); + } + public function testDenormalizeWithNestedDiscriminatorMap() { $classDiscriminatorResolver = new class() implements ClassDiscriminatorResolverInterface { From 4d465068ed33a34a208069341438534fdb0e5a96 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Petar=20Obradovi=C4=87?= Date: Thu, 1 Jun 2023 11:47:23 +0200 Subject: [PATCH 113/297] [Serializer] Fix ignoring objects that only implement DenormalizableInterface --- Normalizer/CustomNormalizer.php | 1 + Tests/SerializerTest.php | 9 +++++++++ 2 files changed, 10 insertions(+) diff --git a/Normalizer/CustomNormalizer.php b/Normalizer/CustomNormalizer.php index dd64c9dca..7e67a31a9 100644 --- a/Normalizer/CustomNormalizer.php +++ b/Normalizer/CustomNormalizer.php @@ -28,6 +28,7 @@ public function getSupportedTypes(?string $format): array { return [ NormalizableInterface::class => __CLASS__ === static::class || $this->hasCacheableSupportsMethod(), + DenormalizableInterface::class => __CLASS__ === static::class || $this->hasCacheableSupportsMethod(), ]; } diff --git a/Tests/SerializerTest.php b/Tests/SerializerTest.php index 914458fb4..f4a164dc5 100644 --- a/Tests/SerializerTest.php +++ b/Tests/SerializerTest.php @@ -50,6 +50,7 @@ use Symfony\Component\Serializer\Tests\Fixtures\Annotations\AbstractDummy; use Symfony\Component\Serializer\Tests\Fixtures\Annotations\AbstractDummyFirstChild; use Symfony\Component\Serializer\Tests\Fixtures\Annotations\AbstractDummySecondChild; +use Symfony\Component\Serializer\Tests\Fixtures\DenormalizableDummy; use Symfony\Component\Serializer\Tests\Fixtures\DummyFirstChildQuux; use Symfony\Component\Serializer\Tests\Fixtures\DummyMessageInterface; use Symfony\Component\Serializer\Tests\Fixtures\DummyMessageNumberOne; @@ -120,6 +121,14 @@ public function testDenormalizeNoMatch() $serializer->denormalize('foo', 'stdClass'); } + public function testDenormalizeOnObjectThatOnlySupportsDenormalization() + { + $serializer = new Serializer([new CustomNormalizer()]); + + $obj = $serializer->denormalize('foo', (new DenormalizableDummy())::class, 'xml'); + $this->assertInstanceOf(DenormalizableDummy::class, $obj); + } + public function testDenormalizeOnNormalizer() { $this->expectException(UnexpectedValueException::class); From ef4de68c078af366983e232de5dcac5936687fd3 Mon Sep 17 00:00:00 2001 From: Thomas Calvet Date: Mon, 5 Jun 2023 11:18:04 +0200 Subject: [PATCH 114/297] [Serializer] Remove abstract uid denormalization code --- CHANGELOG.md | 6 ++++++ Normalizer/UidNormalizer.php | 19 ------------------- Tests/Normalizer/UidNormalizerTest.php | 19 +++++-------------- 3 files changed, 11 insertions(+), 33 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8154d3688..208b20702 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,12 @@ CHANGELOG ========= +7.0 +--- + + * Remove denormalization support for `AbstractUid` in `UidNormalizer`, use one of `AbstractUid` child class instead + * Denormalizing to an abstract class in `UidNormalizer` now throws an `\Error` + 6.3 --- diff --git a/Normalizer/UidNormalizer.php b/Normalizer/UidNormalizer.php index 0dfeae7b8..d3e793ed5 100644 --- a/Normalizer/UidNormalizer.php +++ b/Normalizer/UidNormalizer.php @@ -15,7 +15,6 @@ use Symfony\Component\Serializer\Exception\LogicException; use Symfony\Component\Serializer\Exception\NotNormalizableValueException; use Symfony\Component\Uid\AbstractUid; -use Symfony\Component\Uid\Uuid; final class UidNormalizer implements NormalizerInterface, DenormalizerInterface, CacheableSupportsMethodInterface { @@ -70,32 +69,14 @@ public function supportsNormalization(mixed $data, string $format = null, array public function denormalize(mixed $data, string $type, string $format = null, array $context = []): mixed { try { - if (AbstractUid::class === $type) { - trigger_deprecation('symfony/serializer', '6.1', 'Denormalizing to an abstract class in "%s" is deprecated.', __CLASS__); - - return Uuid::fromString($data); - } - return $type::fromString($data); } catch (\InvalidArgumentException|\TypeError) { throw NotNormalizableValueException::createForUnexpectedDataType(sprintf('The data is not a valid "%s" string representation.', $type), $data, [Type::BUILTIN_TYPE_STRING], $context['deserialization_path'] ?? null, true); - } catch (\Error $e) { // @deprecated remove this catch block in 7.0 - if (str_starts_with($e->getMessage(), 'Cannot instantiate abstract class')) { - return $this->denormalize($data, AbstractUid::class, $format, $context); - } - - throw $e; } } public function supportsDenormalization(mixed $data, string $type, string $format = null, array $context = []): bool { - if (AbstractUid::class === $type) { - trigger_deprecation('symfony/serializer', '6.1', 'Supporting denormalization for the "%s" type in "%s" is deprecated, use one of "%s" child class instead.', AbstractUid::class, __CLASS__, AbstractUid::class); - - return true; - } - return is_subclass_of($type, AbstractUid::class, true); } diff --git a/Tests/Normalizer/UidNormalizerTest.php b/Tests/Normalizer/UidNormalizerTest.php index ea00e1161..b1e542301 100644 --- a/Tests/Normalizer/UidNormalizerTest.php +++ b/Tests/Normalizer/UidNormalizerTest.php @@ -146,14 +146,9 @@ public function testSupportsDenormalizationForNonUid() $this->assertFalse($this->normalizer->supportsDenormalization('foo', \stdClass::class)); } - /** - * @group legacy - */ public function testSupportOurAbstractUid() { - $this->expectDeprecation('Since symfony/serializer 6.1: Supporting denormalization for the "Symfony\Component\Uid\AbstractUid" type in "Symfony\Component\Serializer\Normalizer\UidNormalizer" is deprecated, use one of "Symfony\Component\Uid\AbstractUid" child class instead.'); - - $this->assertTrue($this->normalizer->supportsDenormalization('1ea6ecef-eb9a-66fe-b62b-957b45f17e43', AbstractUid::class)); + $this->assertFalse($this->normalizer->supportsDenormalization('1ea6ecef-eb9a-66fe-b62b-957b45f17e43', AbstractUid::class)); } public function testSupportCustomAbstractUid() @@ -169,22 +164,18 @@ public function testDenormalize($uuidString, $class) $this->assertEquals($class::fromString($uuidString), $this->normalizer->denormalize($uuidString, $class)); } - /** - * @group legacy - */ public function testDenormalizeOurAbstractUid() { - $this->expectDeprecation('Since symfony/serializer 6.1: Denormalizing to an abstract class in "Symfony\Component\Serializer\Normalizer\UidNormalizer" is deprecated.'); + $this->expectException(\Error::class); + $this->expectExceptionMessage('Cannot call abstract method Symfony\Component\Uid\AbstractUid::fromString()'); $this->assertEquals(Uuid::fromString($uuidString = '1ea6ecef-eb9a-66fe-b62b-957b45f17e43'), $this->normalizer->denormalize($uuidString, AbstractUid::class)); } - /** - * @group legacy - */ public function testDenormalizeCustomAbstractUid() { - $this->expectDeprecation('Since symfony/serializer 6.1: Denormalizing to an abstract class in "Symfony\Component\Serializer\Normalizer\UidNormalizer" is deprecated.'); + $this->expectException(\Error::class); + $this->expectExceptionMessage('Cannot instantiate abstract class Symfony\Component\Serializer\Tests\Normalizer\TestAbstractCustomUid'); $this->assertEquals(Uuid::fromString($uuidString = '1ea6ecef-eb9a-66fe-b62b-957b45f17e43'), $this->normalizer->denormalize($uuidString, TestAbstractCustomUid::class)); } From 1d238ee3180bc047f8ab713bfb73848d553f4407 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gr=C3=A9goire=20Pineau?= Date: Wed, 21 Jun 2023 21:35:27 +0200 Subject: [PATCH 115/297] [Serializer] Refactor tests to not extends ObjectNormalizer --- .../AbstractObjectNormalizerTest.php | 21 +++++++++++++++++++ Tests/Normalizer/ObjectNormalizerTest.php | 13 ------------ 2 files changed, 21 insertions(+), 13 deletions(-) diff --git a/Tests/Normalizer/AbstractObjectNormalizerTest.php b/Tests/Normalizer/AbstractObjectNormalizerTest.php index c90ec1185..78046229e 100644 --- a/Tests/Normalizer/AbstractObjectNormalizerTest.php +++ b/Tests/Normalizer/AbstractObjectNormalizerTest.php @@ -724,6 +724,27 @@ public function testNormalizeUsesContextAttributeForProperties() $this->assertSame(['propertyWithoutNullSkipNullValues' => 'foo'], $data); } + + public function testDefaultExcludeFromCacheKey() + { + $object = new DummyChild(); + $object->bar = 'not called'; + + $normalizer = new class(null, null, null, null, null, [AbstractObjectNormalizer::EXCLUDE_FROM_CACHE_KEY => ['foo']]) extends AbstractObjectNormalizerDummy { + public function supportsNormalization(mixed $data, string $format = null, array $context = []): bool + { + AbstractObjectNormalizerTest::assertContains('foo', $this->defaultContext[ObjectNormalizer::EXCLUDE_FROM_CACHE_KEY]); + $data->bar = 'called'; + + return true; + } + }; + + $serializer = new Serializer([$normalizer]); + $serializer->normalize($object); + + $this->assertSame('called', $object->bar); + } } class AbstractObjectNormalizerDummy extends AbstractObjectNormalizer diff --git a/Tests/Normalizer/ObjectNormalizerTest.php b/Tests/Normalizer/ObjectNormalizerTest.php index 5eba0707c..d87b7a67a 100644 --- a/Tests/Normalizer/ObjectNormalizerTest.php +++ b/Tests/Normalizer/ObjectNormalizerTest.php @@ -680,19 +680,6 @@ public function testNormalizeNotSerializableContext() }])); } - public function testDefaultExcludeFromCacheKey() - { - $normalizer = new class(null, null, null, null, null, null, [ObjectNormalizer::EXCLUDE_FROM_CACHE_KEY => ['foo']]) extends ObjectNormalizer { - protected function isCircularReference($object, &$context): bool - { - ObjectNormalizerTest::assertContains('foo', $this->defaultContext[ObjectNormalizer::EXCLUDE_FROM_CACHE_KEY]); - - return false; - } - }; - $normalizer->normalize(new ObjectDummy()); - } - public function testThrowUnexpectedValueException() { $this->expectException(UnexpectedValueException::class); From c6e01401af13a1097291c39229417fe57005bd0a Mon Sep 17 00:00:00 2001 From: Shyim Date: Mon, 26 Jun 2023 09:28:30 +0200 Subject: [PATCH 116/297] [Serializer] Fix type error not be accessed before initialization --- Mapping/ClassMetadata.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Mapping/ClassMetadata.php b/Mapping/ClassMetadata.php index bcec5bf96..3a711634a 100644 --- a/Mapping/ClassMetadata.php +++ b/Mapping/ClassMetadata.php @@ -39,7 +39,7 @@ class ClassMetadata implements ClassMetadataInterface * class' serialized representation. Do not access it. Use * {@link getClassDiscriminatorMapping()} instead. */ - public ?ClassDiscriminatorMapping $classDiscriminatorMapping; + public ?ClassDiscriminatorMapping $classDiscriminatorMapping = null; /** * Constructs a metadata for the given class. From 5314755e68920629952230c5d516e5df6f269acd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Tamarelle?= Date: Fri, 30 Jun 2023 03:20:49 +0200 Subject: [PATCH 117/297] Remove ExpectDeprecationTrait where it is not used --- Tests/Normalizer/UidNormalizerTest.php | 3 --- 1 file changed, 3 deletions(-) diff --git a/Tests/Normalizer/UidNormalizerTest.php b/Tests/Normalizer/UidNormalizerTest.php index b1e542301..8e5ae7686 100644 --- a/Tests/Normalizer/UidNormalizerTest.php +++ b/Tests/Normalizer/UidNormalizerTest.php @@ -12,7 +12,6 @@ namespace Symfony\Component\Serializer\Tests\Normalizer; use PHPUnit\Framework\TestCase; -use Symfony\Bridge\PhpUnit\ExpectDeprecationTrait; use Symfony\Component\Serializer\Exception\LogicException; use Symfony\Component\Serializer\Normalizer\UidNormalizer; use Symfony\Component\Uid\AbstractUid; @@ -26,8 +25,6 @@ class UidNormalizerTest extends TestCase { - use ExpectDeprecationTrait; - /** * @var UidNormalizer */ From d916b0a96fb7c935bf51887b5dc5d986b116cafe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gr=C3=A9goire=20Pineau?= Date: Wed, 21 Jun 2023 22:12:30 +0200 Subject: [PATCH 118/297] [Serializer] Remove BC layer --- CHANGELOG.md | 6 ++++ Debug/TraceableNormalizer.php | 21 +------------ Debug/TraceableSerializer.php | 9 ------ Mapping/AttributeMetadata.php | 6 +--- Mapping/ClassMetadata.php | 5 +-- Normalizer/AbstractNormalizer.php | 12 +------ Normalizer/AbstractObjectNormalizer.php | 8 ++--- Normalizer/ArrayDenormalizer.php | 16 +--------- Normalizer/BackedEnumNormalizer.php | 12 +------ .../CacheableSupportsMethodInterface.php | 28 ----------------- .../ConstraintViolationListNormalizer.php | 19 ++---------- .../ContextAwareDenormalizerInterface.php | 27 ---------------- .../ContextAwareNormalizerInterface.php | 27 ---------------- Normalizer/CustomNormalizer.php | 22 +++---------- Normalizer/DataUriNormalizer.php | 30 ++++-------------- Normalizer/DateIntervalNormalizer.php | 24 +++----------- Normalizer/DateTimeNormalizer.php | 30 ++++-------------- Normalizer/DateTimeZoneNormalizer.php | 24 +++----------- Normalizer/DenormalizerInterface.php | 11 +++---- Normalizer/FormErrorNormalizer.php | 12 +------ Normalizer/GetSetMethodNormalizer.php | 22 ++----------- Normalizer/JsonSerializableNormalizer.php | 22 ++----------- Normalizer/MimeMessageNormalizer.php | 24 ++++---------- Normalizer/NormalizerInterface.php | 9 +++--- Normalizer/ObjectNormalizer.php | 12 +------ Normalizer/ProblemNormalizer.php | 19 ++---------- Normalizer/PropertyNormalizer.php | 22 ++----------- Normalizer/UidNormalizer.php | 12 +------ Normalizer/UnwrappingDenormalizer.php | 12 +------ Serializer.php | 31 +------------------ Tests/Debug/TraceableNormalizerTest.php | 4 +-- .../UpcomingDenormalizerInterface.php | 20 ------------ .../Fixtures/UpcomingNormalizerInterface.php | 20 ------------ Tests/Normalizer/ArrayDenormalizerTest.php | 2 +- Tests/Normalizer/ObjectNormalizerTest.php | 14 --------- Tests/SerializerTest.php | 4 +-- 36 files changed, 79 insertions(+), 519 deletions(-) delete mode 100644 Normalizer/CacheableSupportsMethodInterface.php delete mode 100644 Normalizer/ContextAwareDenormalizerInterface.php delete mode 100644 Normalizer/ContextAwareNormalizerInterface.php delete mode 100644 Tests/Fixtures/UpcomingDenormalizerInterface.php delete mode 100644 Tests/Fixtures/UpcomingNormalizerInterface.php diff --git a/CHANGELOG.md b/CHANGELOG.md index 208b20702..f82945bf0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,12 @@ CHANGELOG * Remove denormalization support for `AbstractUid` in `UidNormalizer`, use one of `AbstractUid` child class instead * Denormalizing to an abstract class in `UidNormalizer` now throws an `\Error` + * Remove `ContextAwareDenormalizerInterface`, use `DenormalizerInterface` instead + * Remove `ContextAwareNormalizerInterface`, use `NormalizerInterface` instead + * Remove `CacheableSupportsMethodInterface`, use `NormalizerInterface` and `DenormalizerInterface` instead + * First argument of `ClassMetadata::setSerializedName()` is now required + * Third argument `array $context = []` of the `NormalizerInterface::supportsNormalization()` is now required + * Fourth argument `array $context = []` of the `DenormalizerInterface::supportsDenormalization()` is now required 6.3 --- diff --git a/Debug/TraceableNormalizer.php b/Debug/TraceableNormalizer.php index d4cf6fcbd..5d4c79e93 100644 --- a/Debug/TraceableNormalizer.php +++ b/Debug/TraceableNormalizer.php @@ -12,7 +12,6 @@ namespace Symfony\Component\Serializer\Debug; use Symfony\Component\Serializer\DataCollector\SerializerDataCollector; -use Symfony\Component\Serializer\Normalizer\CacheableSupportsMethodInterface; use Symfony\Component\Serializer\Normalizer\DenormalizerAwareInterface; use Symfony\Component\Serializer\Normalizer\DenormalizerInterface; use Symfony\Component\Serializer\Normalizer\NormalizerAwareInterface; @@ -27,24 +26,16 @@ * * @internal */ -class TraceableNormalizer implements NormalizerInterface, DenormalizerInterface, SerializerAwareInterface, NormalizerAwareInterface, DenormalizerAwareInterface, CacheableSupportsMethodInterface +class TraceableNormalizer implements NormalizerInterface, DenormalizerInterface, SerializerAwareInterface, NormalizerAwareInterface, DenormalizerAwareInterface { public function __construct( private NormalizerInterface|DenormalizerInterface $normalizer, private SerializerDataCollector $dataCollector, ) { - if (!method_exists($normalizer, 'getSupportedTypes')) { - trigger_deprecation('symfony/serializer', '6.3', 'Not implementing the "NormalizerInterface::getSupportedTypes()" in "%s" is deprecated.', get_debug_type($normalizer)); - } } public function getSupportedTypes(?string $format): array { - // @deprecated remove condition in 7.0 - if (!method_exists($this->normalizer, 'getSupportedTypes')) { - return ['*' => $this->normalizer instanceof CacheableSupportsMethodInterface && $this->normalizer->hasCacheableSupportsMethod()]; - } - return $this->normalizer->getSupportedTypes($format); } @@ -127,16 +118,6 @@ public function setDenormalizer(DenormalizerInterface $denormalizer): void $this->normalizer->setDenormalizer($denormalizer); } - /** - * @deprecated since Symfony 6.3, use "getSupportedTypes()" instead - */ - public function hasCacheableSupportsMethod(): bool - { - trigger_deprecation('symfony/serializer', '6.3', 'The "%s()" method is deprecated, use "getSupportedTypes()" instead.', __METHOD__); - - return $this->normalizer instanceof CacheableSupportsMethodInterface && $this->normalizer->hasCacheableSupportsMethod(); - } - /** * Proxies all method calls to the original normalizer. */ diff --git a/Debug/TraceableSerializer.php b/Debug/TraceableSerializer.php index 2a8e96c7a..d7673ed10 100644 --- a/Debug/TraceableSerializer.php +++ b/Debug/TraceableSerializer.php @@ -14,7 +14,6 @@ use Symfony\Component\Serializer\DataCollector\SerializerDataCollector; use Symfony\Component\Serializer\Encoder\DecoderInterface; use Symfony\Component\Serializer\Encoder\EncoderInterface; -use Symfony\Component\Serializer\Normalizer\CacheableSupportsMethodInterface; use Symfony\Component\Serializer\Normalizer\DenormalizerInterface; use Symfony\Component\Serializer\Normalizer\NormalizerInterface; use Symfony\Component\Serializer\SerializerInterface; @@ -34,9 +33,6 @@ public function __construct( private SerializerInterface&NormalizerInterface&DenormalizerInterface&EncoderInterface&DecoderInterface $serializer, private SerializerDataCollector $dataCollector, ) { - if (!method_exists($serializer, 'getSupportedTypes')) { - trigger_deprecation('symfony/serializer', '6.3', 'Not implementing the "NormalizerInterface::getSupportedTypes()" in "%s" is deprecated.', get_debug_type($serializer)); - } } public function serialize(mixed $data, string $format, array $context = []): string @@ -131,11 +127,6 @@ public function decode(string $data, string $format, array $context = []): mixed public function getSupportedTypes(?string $format): array { - // @deprecated remove condition in 7.0 - if (!method_exists($this->serializer, 'getSupportedTypes')) { - return ['*' => $this->serializer instanceof CacheableSupportsMethodInterface && $this->serializer->hasCacheableSupportsMethod()]; - } - return $this->serializer->getSupportedTypes($format); } diff --git a/Mapping/AttributeMetadata.php b/Mapping/AttributeMetadata.php index c77d07e5e..9b04bb7e3 100644 --- a/Mapping/AttributeMetadata.php +++ b/Mapping/AttributeMetadata.php @@ -110,12 +110,8 @@ public function getMaxDepth(): ?int return $this->maxDepth; } - public function setSerializedName(string $serializedName = null): void + public function setSerializedName(?string $serializedName): void { - if (1 > \func_num_args()) { - trigger_deprecation('symfony/serializer', '6.2', 'Calling "%s()" without any arguments is deprecated, pass null explicitly instead.', __METHOD__); - } - $this->serializedName = $serializedName; } diff --git a/Mapping/ClassMetadata.php b/Mapping/ClassMetadata.php index bcec5bf96..dc479b744 100644 --- a/Mapping/ClassMetadata.php +++ b/Mapping/ClassMetadata.php @@ -90,11 +90,8 @@ public function getClassDiscriminatorMapping(): ?ClassDiscriminatorMapping return $this->classDiscriminatorMapping; } - public function setClassDiscriminatorMapping(ClassDiscriminatorMapping $mapping = null): void + public function setClassDiscriminatorMapping(?ClassDiscriminatorMapping $mapping): void { - if (1 > \func_num_args()) { - trigger_deprecation('symfony/serializer', '6.2', 'Calling "%s()" without any arguments is deprecated, pass null explicitly instead.', __METHOD__); - } $this->classDiscriminatorMapping = $mapping; } diff --git a/Normalizer/AbstractNormalizer.php b/Normalizer/AbstractNormalizer.php index 079b1e7a9..efe4a6e0e 100644 --- a/Normalizer/AbstractNormalizer.php +++ b/Normalizer/AbstractNormalizer.php @@ -28,7 +28,7 @@ * * @author Kévin Dunglas */ -abstract class AbstractNormalizer implements NormalizerInterface, DenormalizerInterface, SerializerAwareInterface, CacheableSupportsMethodInterface +abstract class AbstractNormalizer implements NormalizerInterface, DenormalizerInterface, SerializerAwareInterface { use ObjectToPopulateTrait; use SerializerAwareTrait; @@ -156,16 +156,6 @@ public function __construct(ClassMetadataFactoryInterface $classMetadataFactory } } - /** - * @deprecated since Symfony 6.3, use "getSupportedTypes()" instead - */ - public function hasCacheableSupportsMethod(): bool - { - trigger_deprecation('symfony/serializer', '6.3', 'The "%s()" method is deprecated, use "getSupportedTypes()" instead.', __METHOD__); - - return false; - } - /** * Detects if the configured circular reference limit is reached. * diff --git a/Normalizer/AbstractObjectNormalizer.php b/Normalizer/AbstractObjectNormalizer.php index 0dba039bc..4f061239b 100644 --- a/Normalizer/AbstractObjectNormalizer.php +++ b/Normalizer/AbstractObjectNormalizer.php @@ -138,11 +138,9 @@ public function __construct( } /** - * @param array $context - * * @return bool */ - public function supportsNormalization(mixed $data, string $format = null /* , array $context = [] */) + public function supportsNormalization(mixed $data, string $format = null, array $context = []) { return \is_object($data) && !$data instanceof \Traversable; } @@ -295,11 +293,9 @@ abstract protected function extractAttributes(object $object, string $format = n abstract protected function getAttributeValue(object $object, string $attribute, string $format = null, array $context = []); /** - * @param array $context - * * @return bool */ - public function supportsDenormalization(mixed $data, string $type, string $format = null /* , array $context = [] */) + public function supportsDenormalization(mixed $data, string $type, string $format = null, array $context = []) { return class_exists($type) || (interface_exists($type, false) && null !== $this->classDiscriminatorResolver?->getMappingForClass($type)); } diff --git a/Normalizer/ArrayDenormalizer.php b/Normalizer/ArrayDenormalizer.php index b37e9eace..af12ce5e6 100644 --- a/Normalizer/ArrayDenormalizer.php +++ b/Normalizer/ArrayDenormalizer.php @@ -23,16 +23,12 @@ * * @final */ -class ArrayDenormalizer implements ContextAwareDenormalizerInterface, DenormalizerAwareInterface, CacheableSupportsMethodInterface +class ArrayDenormalizer implements DenormalizerInterface, DenormalizerAwareInterface { use DenormalizerAwareTrait; public function setDenormalizer(DenormalizerInterface $denormalizer): void { - if (!method_exists($denormalizer, 'getSupportedTypes')) { - trigger_deprecation('symfony/serializer', '6.3', 'Not implementing the "DenormalizerInterface::getSupportedTypes()" in "%s" is deprecated.', get_debug_type($denormalizer)); - } - $this->denormalizer = $denormalizer; } @@ -82,14 +78,4 @@ public function supportsDenormalization(mixed $data, string $type, string $forma return str_ends_with($type, '[]') && $this->denormalizer->supportsDenormalization($data, substr($type, 0, -2), $format, $context); } - - /** - * @deprecated since Symfony 6.3, use "getSupportedTypes()" instead - */ - public function hasCacheableSupportsMethod(): bool - { - trigger_deprecation('symfony/serializer', '6.3', 'The "%s()" method is deprecated, use "getSupportedTypes()" instead.', __METHOD__); - - return $this->denormalizer instanceof CacheableSupportsMethodInterface && $this->denormalizer->hasCacheableSupportsMethod(); - } } diff --git a/Normalizer/BackedEnumNormalizer.php b/Normalizer/BackedEnumNormalizer.php index 393479447..f81ab5674 100644 --- a/Normalizer/BackedEnumNormalizer.php +++ b/Normalizer/BackedEnumNormalizer.php @@ -20,7 +20,7 @@ * * @author Alexandre Daubois */ -final class BackedEnumNormalizer implements NormalizerInterface, DenormalizerInterface, CacheableSupportsMethodInterface +final class BackedEnumNormalizer implements NormalizerInterface, DenormalizerInterface { /** * If true, will denormalize any invalid value into null. @@ -88,14 +88,4 @@ public function supportsDenormalization(mixed $data, string $type, string $forma { return is_subclass_of($type, \BackedEnum::class); } - - /** - * @deprecated since Symfony 6.3, use "getSupportedTypes()" instead - */ - public function hasCacheableSupportsMethod(): bool - { - trigger_deprecation('symfony/serializer', '6.3', 'The "%s()" method is deprecated, use "getSupportedTypes()" instead.', __METHOD__); - - return true; - } } diff --git a/Normalizer/CacheableSupportsMethodInterface.php b/Normalizer/CacheableSupportsMethodInterface.php deleted file mode 100644 index ea2df15b2..000000000 --- a/Normalizer/CacheableSupportsMethodInterface.php +++ /dev/null @@ -1,28 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Component\Serializer\Normalizer; - -/** - * Marker interface for normalizers and denormalizers that use - * only the type and the format in their supports*() methods. - * - * By implementing this interface, the return value of the - * supports*() methods will be cached by type and format. - * - * @author Kévin Dunglas - * - * @deprecated since Symfony 6.3, implement "getSupportedTypes(?string $format)" instead - */ -interface CacheableSupportsMethodInterface -{ - public function hasCacheableSupportsMethod(): bool; -} diff --git a/Normalizer/ConstraintViolationListNormalizer.php b/Normalizer/ConstraintViolationListNormalizer.php index 1fdf8420d..d3afa41d7 100644 --- a/Normalizer/ConstraintViolationListNormalizer.php +++ b/Normalizer/ConstraintViolationListNormalizer.php @@ -24,7 +24,7 @@ * * @final since Symfony 6.3 */ -class ConstraintViolationListNormalizer implements NormalizerInterface, CacheableSupportsMethodInterface +class ConstraintViolationListNormalizer implements NormalizerInterface { public const INSTANCE = 'instance'; public const STATUS = 'status'; @@ -41,7 +41,7 @@ public function __construct( public function getSupportedTypes(?string $format): array { return [ - ConstraintViolationListInterface::class => __CLASS__ === static::class || $this->hasCacheableSupportsMethod(), + ConstraintViolationListInterface::class => true, ]; } @@ -108,21 +108,8 @@ public function normalize(mixed $object, string $format = null, array $context = return $result + ['violations' => $violations]; } - /** - * @param array $context - */ - public function supportsNormalization(mixed $data, string $format = null /* , array $context = [] */): bool + public function supportsNormalization(mixed $data, string $format = null, array $context = []): bool { return $data instanceof ConstraintViolationListInterface; } - - /** - * @deprecated since Symfony 6.3, use "getSupportedTypes()" instead - */ - public function hasCacheableSupportsMethod(): bool - { - trigger_deprecation('symfony/serializer', '6.3', 'The "%s()" method is deprecated, use "getSupportedTypes()" instead.', __METHOD__); - - return __CLASS__ === static::class; - } } diff --git a/Normalizer/ContextAwareDenormalizerInterface.php b/Normalizer/ContextAwareDenormalizerInterface.php deleted file mode 100644 index a02951093..000000000 --- a/Normalizer/ContextAwareDenormalizerInterface.php +++ /dev/null @@ -1,27 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Component\Serializer\Normalizer; - -/** - * Adds the support of an extra $context parameter for the supportsDenormalization method. - * - * @author Kévin Dunglas - * - * @deprecated since symfony/serializer 6.1, use DenormalizerInterface instead - */ -interface ContextAwareDenormalizerInterface extends DenormalizerInterface -{ - /** - * @param array $context options that denormalizers have access to - */ - public function supportsDenormalization(mixed $data, string $type, string $format = null, array $context = []): bool; -} diff --git a/Normalizer/ContextAwareNormalizerInterface.php b/Normalizer/ContextAwareNormalizerInterface.php deleted file mode 100644 index 44f2f0219..000000000 --- a/Normalizer/ContextAwareNormalizerInterface.php +++ /dev/null @@ -1,27 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Component\Serializer\Normalizer; - -/** - * Adds the support of an extra $context parameter for the supportsNormalization method. - * - * @author Kévin Dunglas - * - * @deprecated since symfony/serializer 6.1, use NormalizerInterface instead - */ -interface ContextAwareNormalizerInterface extends NormalizerInterface -{ - /** - * @param array $context options that normalizers have access to - */ - public function supportsNormalization(mixed $data, string $format = null, array $context = []): bool; -} diff --git a/Normalizer/CustomNormalizer.php b/Normalizer/CustomNormalizer.php index 7e67a31a9..14ba85b51 100644 --- a/Normalizer/CustomNormalizer.php +++ b/Normalizer/CustomNormalizer.php @@ -19,7 +19,7 @@ * * @final since Symfony 6.3 */ -class CustomNormalizer implements NormalizerInterface, DenormalizerInterface, SerializerAwareInterface, CacheableSupportsMethodInterface +class CustomNormalizer implements NormalizerInterface, DenormalizerInterface, SerializerAwareInterface { use ObjectToPopulateTrait; use SerializerAwareTrait; @@ -27,8 +27,8 @@ class CustomNormalizer implements NormalizerInterface, DenormalizerInterface, Se public function getSupportedTypes(?string $format): array { return [ - NormalizableInterface::class => __CLASS__ === static::class || $this->hasCacheableSupportsMethod(), - DenormalizableInterface::class => __CLASS__ === static::class || $this->hasCacheableSupportsMethod(), + NormalizableInterface::class => true, + DenormalizableInterface::class => true, ]; } @@ -50,9 +50,8 @@ public function denormalize(mixed $data, string $type, string $format = null, ar * * @param mixed $data Data to normalize * @param string|null $format The format being (de-)serialized from or into - * @param array $context */ - public function supportsNormalization(mixed $data, string $format = null /* , array $context = [] */): bool + public function supportsNormalization(mixed $data, string $format = null, array $context = []): bool { return $data instanceof NormalizableInterface; } @@ -63,20 +62,9 @@ public function supportsNormalization(mixed $data, string $format = null /* , ar * @param mixed $data Data to denormalize from * @param string $type The class to which the data should be denormalized * @param string|null $format The format being deserialized from - * @param array $context */ - public function supportsDenormalization(mixed $data, string $type, string $format = null /* , array $context = [] */): bool + public function supportsDenormalization(mixed $data, string $type, string $format = null, array $context = []): bool { return is_subclass_of($type, DenormalizableInterface::class); } - - /** - * @deprecated since Symfony 6.3, use "getSupportedTypes()" instead - */ - public function hasCacheableSupportsMethod(): bool - { - trigger_deprecation('symfony/serializer', '6.3', 'The "%s()" method is deprecated, use "getSupportedTypes()" instead.', __METHOD__); - - return __CLASS__ === static::class; - } } diff --git a/Normalizer/DataUriNormalizer.php b/Normalizer/DataUriNormalizer.php index 1bcf81f9b..44e080b90 100644 --- a/Normalizer/DataUriNormalizer.php +++ b/Normalizer/DataUriNormalizer.php @@ -25,7 +25,7 @@ * * @final since Symfony 6.3 */ -class DataUriNormalizer implements NormalizerInterface, DenormalizerInterface, CacheableSupportsMethodInterface +class DataUriNormalizer implements NormalizerInterface, DenormalizerInterface { private const SUPPORTED_TYPES = [ \SplFileInfo::class => true, @@ -46,12 +46,10 @@ public function __construct(MimeTypeGuesserInterface $mimeTypeGuesser = null) public function getSupportedTypes(?string $format): array { - $isCacheable = __CLASS__ === static::class || $this->hasCacheableSupportsMethod(); - return [ - \SplFileInfo::class => $isCacheable, - \SplFileObject::class => $isCacheable, - File::class => $isCacheable, + \SplFileInfo::class => true, + \SplFileObject::class => true, + File::class => true, ]; } @@ -78,10 +76,7 @@ public function normalize(mixed $object, string $format = null, array $context = return sprintf('data:%s;base64,%s', $mimeType, base64_encode($data)); } - /** - * @param array $context - */ - public function supportsNormalization(mixed $data, string $format = null /* , array $context = [] */): bool + public function supportsNormalization(mixed $data, string $format = null, array $context = []): bool { return $data instanceof \SplFileInfo; } @@ -120,24 +115,11 @@ public function denormalize(mixed $data, string $type, string $format = null, ar throw new InvalidArgumentException(sprintf('The class parameter "%s" is not supported. It must be one of "SplFileInfo", "SplFileObject" or "Symfony\Component\HttpFoundation\File\File".', $type)); } - /** - * @param array $context - */ - public function supportsDenormalization(mixed $data, string $type, string $format = null /* , array $context = [] */): bool + public function supportsDenormalization(mixed $data, string $type, string $format = null, array $context = []): bool { return isset(self::SUPPORTED_TYPES[$type]); } - /** - * @deprecated since Symfony 6.3, use "getSupportedTypes()" instead - */ - public function hasCacheableSupportsMethod(): bool - { - trigger_deprecation('symfony/serializer', '6.3', 'The "%s()" method is deprecated, use "getSupportedTypes()" instead.', __METHOD__); - - return __CLASS__ === static::class; - } - /** * Gets the mime type of the object. Defaults to application/octet-stream. */ diff --git a/Normalizer/DateIntervalNormalizer.php b/Normalizer/DateIntervalNormalizer.php index 3cf5b887f..c46737e8b 100644 --- a/Normalizer/DateIntervalNormalizer.php +++ b/Normalizer/DateIntervalNormalizer.php @@ -22,7 +22,7 @@ * * @final since Symfony 6.3 */ -class DateIntervalNormalizer implements NormalizerInterface, DenormalizerInterface, CacheableSupportsMethodInterface +class DateIntervalNormalizer implements NormalizerInterface, DenormalizerInterface { public const FORMAT_KEY = 'dateinterval_format'; @@ -38,7 +38,7 @@ public function __construct(array $defaultContext = []) public function getSupportedTypes(?string $format): array { return [ - \DateInterval::class => __CLASS__ === static::class || $this->hasCacheableSupportsMethod(), + \DateInterval::class => true, ]; } @@ -54,24 +54,11 @@ public function normalize(mixed $object, string $format = null, array $context = return $object->format($context[self::FORMAT_KEY] ?? $this->defaultContext[self::FORMAT_KEY]); } - /** - * @param array $context - */ - public function supportsNormalization(mixed $data, string $format = null /* , array $context = [] */): bool + public function supportsNormalization(mixed $data, string $format = null, array $context = []): bool { return $data instanceof \DateInterval; } - /** - * @deprecated since Symfony 6.3, use "getSupportedTypes()" instead - */ - public function hasCacheableSupportsMethod(): bool - { - trigger_deprecation('symfony/serializer', '6.3', 'The "%s()" method is deprecated, use "getSupportedTypes()" instead.', __METHOD__); - - return __CLASS__ === static::class; - } - /** * @throws InvalidArgumentException * @throws UnexpectedValueException @@ -122,10 +109,7 @@ public function denormalize(mixed $data, string $type, string $format = null, ar } } - /** - * @param array $context - */ - public function supportsDenormalization(mixed $data, string $type, string $format = null /* , array $context = [] */): bool + public function supportsDenormalization(mixed $data, string $type, string $format = null, array $context = []): bool { return \DateInterval::class === $type; } diff --git a/Normalizer/DateTimeNormalizer.php b/Normalizer/DateTimeNormalizer.php index a0fd3e75c..c165317f5 100644 --- a/Normalizer/DateTimeNormalizer.php +++ b/Normalizer/DateTimeNormalizer.php @@ -23,7 +23,7 @@ * * @final since Symfony 6.3 */ -class DateTimeNormalizer implements NormalizerInterface, DenormalizerInterface, CacheableSupportsMethodInterface +class DateTimeNormalizer implements NormalizerInterface, DenormalizerInterface { public const FORMAT_KEY = 'datetime_format'; public const TIMEZONE_KEY = 'datetime_timezone'; @@ -51,12 +51,10 @@ public function setDefaultContext(array $defaultContext): void public function getSupportedTypes(?string $format): array { - $isCacheable = __CLASS__ === static::class || $this->hasCacheableSupportsMethod(); - return [ - \DateTimeInterface::class => $isCacheable, - \DateTimeImmutable::class => $isCacheable, - \DateTime::class => $isCacheable, + \DateTimeInterface::class => true, + \DateTimeImmutable::class => true, + \DateTime::class => true, ]; } @@ -80,10 +78,7 @@ public function normalize(mixed $object, string $format = null, array $context = return $object->format($dateTimeFormat); } - /** - * @param array $context - */ - public function supportsNormalization(mixed $data, string $format = null /* , array $context = [] */): bool + public function supportsNormalization(mixed $data, string $format = null, array $context = []): bool { return $data instanceof \DateTimeInterface; } @@ -138,24 +133,11 @@ public function denormalize(mixed $data, string $type, string $format = null, ar } } - /** - * @param array $context - */ - public function supportsDenormalization(mixed $data, string $type, string $format = null /* , array $context = [] */): bool + public function supportsDenormalization(mixed $data, string $type, string $format = null, array $context = []): bool { return isset(self::SUPPORTED_TYPES[$type]); } - /** - * @deprecated since Symfony 6.3, use "getSupportedTypes()" instead - */ - public function hasCacheableSupportsMethod(): bool - { - trigger_deprecation('symfony/serializer', '6.3', 'The "%s()" method is deprecated, use "getSupportedTypes()" instead.', __METHOD__); - - return __CLASS__ === static::class; - } - /** * Formats datetime errors. * diff --git a/Normalizer/DateTimeZoneNormalizer.php b/Normalizer/DateTimeZoneNormalizer.php index 472f64fc8..595847e87 100644 --- a/Normalizer/DateTimeZoneNormalizer.php +++ b/Normalizer/DateTimeZoneNormalizer.php @@ -22,12 +22,12 @@ * * @final since Symfony 6.3 */ -class DateTimeZoneNormalizer implements NormalizerInterface, DenormalizerInterface, CacheableSupportsMethodInterface +class DateTimeZoneNormalizer implements NormalizerInterface, DenormalizerInterface { public function getSupportedTypes(?string $format): array { return [ - \DateTimeZone::class => __CLASS__ === static::class || $this->hasCacheableSupportsMethod(), + \DateTimeZone::class => true, ]; } @@ -43,10 +43,7 @@ public function normalize(mixed $object, string $format = null, array $context = return $object->getName(); } - /** - * @param array $context - */ - public function supportsNormalization(mixed $data, string $format = null /* , array $context = [] */): bool + public function supportsNormalization(mixed $data, string $format = null, array $context = []): bool { return $data instanceof \DateTimeZone; } @@ -67,21 +64,8 @@ public function denormalize(mixed $data, string $type, string $format = null, ar } } - /** - * @param array $context - */ - public function supportsDenormalization(mixed $data, string $type, string $format = null /* , array $context = [] */): bool + public function supportsDenormalization(mixed $data, string $type, string $format = null, array $context = []): bool { return \DateTimeZone::class === $type; } - - /** - * @deprecated since Symfony 6.3, use "getSupportedTypes()" instead - */ - public function hasCacheableSupportsMethod(): bool - { - trigger_deprecation('symfony/serializer', '6.3', 'The "%s()" method is deprecated, use "getSupportedTypes()" instead.', __METHOD__); - - return __CLASS__ === static::class; - } } diff --git a/Normalizer/DenormalizerInterface.php b/Normalizer/DenormalizerInterface.php index 4edb70096..e4d0ed912 100644 --- a/Normalizer/DenormalizerInterface.php +++ b/Normalizer/DenormalizerInterface.php @@ -51,14 +51,13 @@ public function denormalize(mixed $data, string $type, string $format = null, ar /** * Checks whether the given class is supported for denormalization by this normalizer. * - * @param mixed $data Data to denormalize from - * @param string $type The class to which the data should be denormalized - * @param string|null $format The format being deserialized from - * @param array $context Options available to the denormalizer + * @param mixed $data Data to denormalize from + * @param string $type The class to which the data should be denormalized + * @param string|null $format The format being deserialized from * * @return bool */ - public function supportsDenormalization(mixed $data, string $type, string $format = null /* , array $context = [] */); + public function supportsDenormalization(mixed $data, string $type, string $format = null, array $context = []); /** * Returns the types potentially supported by this denormalizer. @@ -75,5 +74,5 @@ public function supportsDenormalization(mixed $data, string $type, string $forma * * @return array */ - /* public function getSupportedTypes(?string $format): array; */ + public function getSupportedTypes(?string $format): array; } diff --git a/Normalizer/FormErrorNormalizer.php b/Normalizer/FormErrorNormalizer.php index 57d9bd4bb..195921b09 100644 --- a/Normalizer/FormErrorNormalizer.php +++ b/Normalizer/FormErrorNormalizer.php @@ -16,7 +16,7 @@ /** * Normalizes invalid Form instances. */ -final class FormErrorNormalizer implements NormalizerInterface, CacheableSupportsMethodInterface +final class FormErrorNormalizer implements NormalizerInterface { public const TITLE = 'title'; public const TYPE = 'type'; @@ -82,14 +82,4 @@ private function convertFormChildrenToArray(FormInterface $data): array return $children; } - - /** - * @deprecated since Symfony 6.3, use "getSupportedTypes()" instead - */ - public function hasCacheableSupportsMethod(): bool - { - trigger_deprecation('symfony/serializer', '6.3', 'The "%s()" method is deprecated, use "getSupportedTypes()" instead.', __METHOD__); - - return true; - } } diff --git a/Normalizer/GetSetMethodNormalizer.php b/Normalizer/GetSetMethodNormalizer.php index 063d34ea5..3d11567a7 100644 --- a/Normalizer/GetSetMethodNormalizer.php +++ b/Normalizer/GetSetMethodNormalizer.php @@ -42,35 +42,19 @@ class GetSetMethodNormalizer extends AbstractObjectNormalizer public function getSupportedTypes(?string $format): array { - return ['object' => __CLASS__ === static::class || $this->hasCacheableSupportsMethod()]; + return ['object' => true]; } - /** - * @param array $context - */ - public function supportsNormalization(mixed $data, string $format = null /* , array $context = [] */): bool + public function supportsNormalization(mixed $data, string $format = null, array $context = []): bool { return parent::supportsNormalization($data, $format) && $this->supports($data::class); } - /** - * @param array $context - */ - public function supportsDenormalization(mixed $data, string $type, string $format = null /* , array $context = [] */): bool + public function supportsDenormalization(mixed $data, string $type, string $format = null, array $context = []): bool { return parent::supportsDenormalization($data, $type, $format) && $this->supports($type); } - /** - * @deprecated since Symfony 6.3, use "getSupportedTypes()" instead - */ - public function hasCacheableSupportsMethod(): bool - { - trigger_deprecation('symfony/serializer', '6.3', 'The "%s()" method is deprecated, use "getSupportedTypes()" instead.', __METHOD__); - - return __CLASS__ === static::class; - } - /** * Checks if the given class has any getter method. */ diff --git a/Normalizer/JsonSerializableNormalizer.php b/Normalizer/JsonSerializableNormalizer.php index 1c8bbfe4a..60c106262 100644 --- a/Normalizer/JsonSerializableNormalizer.php +++ b/Normalizer/JsonSerializableNormalizer.php @@ -43,22 +43,16 @@ public function normalize(mixed $object, string $format = null, array $context = public function getSupportedTypes(?string $format): array { return [ - \JsonSerializable::class => __CLASS__ === static::class || $this->hasCacheableSupportsMethod(), + \JsonSerializable::class => true, ]; } - /** - * @param array $context - */ - public function supportsNormalization(mixed $data, string $format = null /* , array $context = [] */): bool + public function supportsNormalization(mixed $data, string $format = null, array $context = []): bool { return $data instanceof \JsonSerializable; } - /** - * @param array $context - */ - public function supportsDenormalization(mixed $data, string $type, string $format = null /* , array $context = [] */): bool + public function supportsDenormalization(mixed $data, string $type, string $format = null, array $context = []): bool { return false; } @@ -67,14 +61,4 @@ public function denormalize(mixed $data, string $type, string $format = null, ar { throw new LogicException(sprintf('Cannot denormalize with "%s".', \JsonSerializable::class)); } - - /** - * @deprecated since Symfony 6.3, use "getSupportedTypes()" instead - */ - public function hasCacheableSupportsMethod(): bool - { - trigger_deprecation('symfony/serializer', '6.3', 'The "%s()" method is deprecated, use "getSupportedTypes()" instead.', __METHOD__); - - return __CLASS__ === static::class; - } } diff --git a/Normalizer/MimeMessageNormalizer.php b/Normalizer/MimeMessageNormalizer.php index 4bf6f42c2..71ffae733 100644 --- a/Normalizer/MimeMessageNormalizer.php +++ b/Normalizer/MimeMessageNormalizer.php @@ -29,7 +29,7 @@ * * Emails using resources for any parts are not serializable. */ -final class MimeMessageNormalizer implements NormalizerInterface, DenormalizerInterface, SerializerAwareInterface, CacheableSupportsMethodInterface +final class MimeMessageNormalizer implements NormalizerInterface, DenormalizerInterface, SerializerAwareInterface { private NormalizerInterface&DenormalizerInterface $serializer; private array $headerClassMap; @@ -43,14 +43,12 @@ public function __construct(private readonly PropertyNormalizer $normalizer) public function getSupportedTypes(?string $format): array { - $isCacheable = __CLASS__ === static::class || $this->hasCacheableSupportsMethod(); - return [ - Message::class => $isCacheable, - Headers::class => $isCacheable, - HeaderInterface::class => $isCacheable, - Address::class => $isCacheable, - AbstractPart::class => $isCacheable, + Message::class => true, + Headers::class => true, + HeaderInterface::class => true, + Address::class => true, + AbstractPart::class => true, ]; } @@ -115,14 +113,4 @@ public function supportsDenormalization(mixed $data, string $type, string $forma { return is_a($type, Message::class, true) || Headers::class === $type || AbstractPart::class === $type; } - - /** - * @deprecated since Symfony 6.3, use "getSupportedTypes()" instead - */ - public function hasCacheableSupportsMethod(): bool - { - trigger_deprecation('symfony/serializer', '6.3', 'The "%s()" method is deprecated, use "getSupportedTypes()" instead.', __METHOD__); - - return true; - } } diff --git a/Normalizer/NormalizerInterface.php b/Normalizer/NormalizerInterface.php index 40779de31..01979d6fc 100644 --- a/Normalizer/NormalizerInterface.php +++ b/Normalizer/NormalizerInterface.php @@ -43,13 +43,12 @@ public function normalize(mixed $object, string $format = null, array $context = /** * Checks whether the given class is supported for normalization by this normalizer. * - * @param mixed $data Data to normalize - * @param string|null $format The format being (de-)serialized from or into - * @param array $context Context options for the normalizer + * @param mixed $data Data to normalize + * @param string|null $format The format being (de-)serialized from or into * * @return bool */ - public function supportsNormalization(mixed $data, string $format = null /* , array $context = [] */); + public function supportsNormalization(mixed $data, string $format = null, array $context = []); /** * Returns the types potentially supported by this normalizer. @@ -66,5 +65,5 @@ public function supportsNormalization(mixed $data, string $format = null /* , ar * * @return array */ - /* public function getSupportedTypes(?string $format): array; */ + public function getSupportedTypes(?string $format): array; } diff --git a/Normalizer/ObjectNormalizer.php b/Normalizer/ObjectNormalizer.php index 357c36426..af530f8d3 100644 --- a/Normalizer/ObjectNormalizer.php +++ b/Normalizer/ObjectNormalizer.php @@ -52,17 +52,7 @@ public function __construct(ClassMetadataFactoryInterface $classMetadataFactory public function getSupportedTypes(?string $format): array { - return ['object' => __CLASS__ === static::class || $this->hasCacheableSupportsMethod()]; - } - - /** - * @deprecated since Symfony 6.3, use "getSupportedTypes()" instead - */ - public function hasCacheableSupportsMethod(): bool - { - trigger_deprecation('symfony/serializer', '6.3', 'The "%s()" method is deprecated, use "getSupportedTypes()" instead.', __METHOD__); - - return __CLASS__ === static::class; + return ['object' => true]; } protected function extractAttributes(object $object, string $format = null, array $context = []): array diff --git a/Normalizer/ProblemNormalizer.php b/Normalizer/ProblemNormalizer.php index 4161d0b1c..aa05b6ee6 100644 --- a/Normalizer/ProblemNormalizer.php +++ b/Normalizer/ProblemNormalizer.php @@ -28,7 +28,7 @@ * @author Kévin Dunglas * @author Yonel Ceruto */ -class ProblemNormalizer implements NormalizerInterface, SerializerAwareInterface, CacheableSupportsMethodInterface +class ProblemNormalizer implements NormalizerInterface, SerializerAwareInterface { use SerializerAwareTrait; @@ -46,7 +46,7 @@ public function __construct( public function getSupportedTypes(?string $format): array { return [ - FlattenException::class => __CLASS__ === self::class || $this->hasCacheableSupportsMethod(), + FlattenException::class => __CLASS__ === self::class, ]; } @@ -106,21 +106,8 @@ public function normalize(mixed $object, string $format = null, array $context = return $data; } - /** - * @param array $context - */ - public function supportsNormalization(mixed $data, string $format = null /* , array $context = [] */): bool + public function supportsNormalization(mixed $data, string $format = null, array $context = []): bool { return $data instanceof FlattenException; } - - /** - * @deprecated since Symfony 6.3, use "getSupportedTypes()" instead - */ - public function hasCacheableSupportsMethod(): bool - { - trigger_deprecation('symfony/serializer', '6.3', 'The "%s()" method is deprecated, use "getSupportedTypes()" instead.', __METHOD__); - - return true; - } } diff --git a/Normalizer/PropertyNormalizer.php b/Normalizer/PropertyNormalizer.php index ec12db9bb..cfe93bc10 100644 --- a/Normalizer/PropertyNormalizer.php +++ b/Normalizer/PropertyNormalizer.php @@ -58,35 +58,19 @@ public function __construct(ClassMetadataFactoryInterface $classMetadataFactory public function getSupportedTypes(?string $format): array { - return ['object' => __CLASS__ === static::class || $this->hasCacheableSupportsMethod()]; + return ['object' => true]; } - /** - * @param array $context - */ - public function supportsNormalization(mixed $data, string $format = null /* , array $context = [] */): bool + public function supportsNormalization(mixed $data, string $format = null, array $context = []): bool { return parent::supportsNormalization($data, $format) && $this->supports($data::class); } - /** - * @param array $context - */ - public function supportsDenormalization(mixed $data, string $type, string $format = null /* , array $context = [] */): bool + public function supportsDenormalization(mixed $data, string $type, string $format = null, array $context = []): bool { return parent::supportsDenormalization($data, $type, $format) && $this->supports($type); } - /** - * @deprecated since Symfony 6.3, use "getSupportedTypes()" instead - */ - public function hasCacheableSupportsMethod(): bool - { - trigger_deprecation('symfony/serializer', '6.3', 'The "%s()" method is deprecated, use "getSupportedTypes()" instead.', __METHOD__); - - return __CLASS__ === static::class; - } - /** * Checks if the given class has any non-static property. */ diff --git a/Normalizer/UidNormalizer.php b/Normalizer/UidNormalizer.php index d3e793ed5..104dddece 100644 --- a/Normalizer/UidNormalizer.php +++ b/Normalizer/UidNormalizer.php @@ -16,7 +16,7 @@ use Symfony\Component\Serializer\Exception\NotNormalizableValueException; use Symfony\Component\Uid\AbstractUid; -final class UidNormalizer implements NormalizerInterface, DenormalizerInterface, CacheableSupportsMethodInterface +final class UidNormalizer implements NormalizerInterface, DenormalizerInterface { public const NORMALIZATION_FORMAT_KEY = 'uid_normalization_format'; @@ -79,14 +79,4 @@ public function supportsDenormalization(mixed $data, string $type, string $forma { return is_subclass_of($type, AbstractUid::class, true); } - - /** - * @deprecated since Symfony 6.3, use "getSupportedTypes()" instead - */ - public function hasCacheableSupportsMethod(): bool - { - trigger_deprecation('symfony/serializer', '6.3', 'The "%s()" method is deprecated, use "getSupportedTypes()" instead.', __METHOD__); - - return true; - } } diff --git a/Normalizer/UnwrappingDenormalizer.php b/Normalizer/UnwrappingDenormalizer.php index 3708b1a08..51397961c 100644 --- a/Normalizer/UnwrappingDenormalizer.php +++ b/Normalizer/UnwrappingDenormalizer.php @@ -20,7 +20,7 @@ /** * @author Eduard Bulava */ -final class UnwrappingDenormalizer implements DenormalizerInterface, SerializerAwareInterface, CacheableSupportsMethodInterface +final class UnwrappingDenormalizer implements DenormalizerInterface, SerializerAwareInterface { use SerializerAwareTrait; @@ -62,14 +62,4 @@ public function supportsDenormalization(mixed $data, string $type, string $forma { return \array_key_exists(self::UNWRAP_PATH, $context) && !isset($context['unwrapped']); } - - /** - * @deprecated since Symfony 6.3, use "getSupportedTypes()" instead - */ - public function hasCacheableSupportsMethod(): bool - { - trigger_deprecation('symfony/serializer', '6.3', 'The "%s()" method is deprecated, use "getSupportedTypes()" instead.', __METHOD__); - - return $this->serializer instanceof CacheableSupportsMethodInterface && $this->serializer->hasCacheableSupportsMethod(); - } } diff --git a/Serializer.php b/Serializer.php index c83da01fb..4eb7d3158 100644 --- a/Serializer.php +++ b/Serializer.php @@ -23,9 +23,6 @@ use Symfony\Component\Serializer\Exception\PartialDenormalizationException; use Symfony\Component\Serializer\Exception\UnsupportedFormatException; use Symfony\Component\Serializer\Normalizer\AbstractObjectNormalizer; -use Symfony\Component\Serializer\Normalizer\CacheableSupportsMethodInterface; -use Symfony\Component\Serializer\Normalizer\ContextAwareDenormalizerInterface; -use Symfony\Component\Serializer\Normalizer\ContextAwareNormalizerInterface; use Symfony\Component\Serializer\Normalizer\DenormalizerAwareInterface; use Symfony\Component\Serializer\Normalizer\DenormalizerInterface; use Symfony\Component\Serializer\Normalizer\NormalizerAwareInterface; @@ -46,7 +43,7 @@ * @author Lukas Kahwe Smith * @author Kévin Dunglas */ -class Serializer implements SerializerInterface, ContextAwareNormalizerInterface, ContextAwareDenormalizerInterface, ContextAwareEncoderInterface, ContextAwareDecoderInterface +class Serializer implements SerializerInterface, NormalizerInterface, DenormalizerInterface, ContextAwareEncoderInterface, ContextAwareDecoderInterface { /** * Flag to control whether an empty array should be transformed to an @@ -274,19 +271,6 @@ private function getNormalizer(mixed $data, ?string $format, array $context): ?N continue; } - if (!method_exists($normalizer, 'getSupportedTypes')) { - trigger_deprecation('symfony/serializer', '6.3', '"%s" should implement "NormalizerInterface::getSupportedTypes(?string $format): array".', $normalizer::class); - - if (!$normalizer instanceof CacheableSupportsMethodInterface || !$normalizer->hasCacheableSupportsMethod()) { - $this->normalizerCache[$format][$type][$k] = false; - } elseif ($normalizer->supportsNormalization($data, $format, $context)) { - $this->normalizerCache[$format][$type][$k] = true; - break; - } - - continue; - } - $supportedTypes = $normalizer->getSupportedTypes($format); foreach ($supportedTypes as $supportedType => $isCacheable) { @@ -344,19 +328,6 @@ private function getDenormalizer(mixed $data, string $class, ?string $format, ar continue; } - if (!method_exists($normalizer, 'getSupportedTypes')) { - trigger_deprecation('symfony/serializer', '6.3', '"%s" should implement "DenormalizerInterface::getSupportedTypes(?string $format): array".', $normalizer::class); - - if (!$normalizer instanceof CacheableSupportsMethodInterface || !$normalizer->hasCacheableSupportsMethod()) { - $this->denormalizerCache[$format][$class][$k] = false; - } elseif ($normalizer->supportsDenormalization(null, $class, $format, $context)) { - $this->denormalizerCache[$format][$class][$k] = true; - break; - } - - continue; - } - $supportedTypes = $normalizer->getSupportedTypes($format); foreach ($supportedTypes as $supportedType => $isCacheable) { diff --git a/Tests/Debug/TraceableNormalizerTest.php b/Tests/Debug/TraceableNormalizerTest.php index 41e3441ed..307bc7b6f 100644 --- a/Tests/Debug/TraceableNormalizerTest.php +++ b/Tests/Debug/TraceableNormalizerTest.php @@ -15,8 +15,8 @@ use Symfony\Component\Serializer\DataCollector\SerializerDataCollector; use Symfony\Component\Serializer\Debug\TraceableNormalizer; use Symfony\Component\Serializer\Debug\TraceableSerializer; -use Symfony\Component\Serializer\Tests\Fixtures\UpcomingDenormalizerInterface as DenormalizerInterface; -use Symfony\Component\Serializer\Tests\Fixtures\UpcomingNormalizerInterface as NormalizerInterface; +use Symfony\Component\Serializer\Normalizer\DenormalizerInterface; +use Symfony\Component\Serializer\Normalizer\NormalizerInterface; class TraceableNormalizerTest extends TestCase { diff --git a/Tests/Fixtures/UpcomingDenormalizerInterface.php b/Tests/Fixtures/UpcomingDenormalizerInterface.php deleted file mode 100644 index 2efc12bb6..000000000 --- a/Tests/Fixtures/UpcomingDenormalizerInterface.php +++ /dev/null @@ -1,20 +0,0 @@ - - * - * 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\DenormalizerInterface; - -// @deprecated remove in 7.0 in favor of direct use of the DenormalizerInterface -interface UpcomingDenormalizerInterface extends DenormalizerInterface -{ - public function getSupportedTypes(?string $format): array; -} diff --git a/Tests/Fixtures/UpcomingNormalizerInterface.php b/Tests/Fixtures/UpcomingNormalizerInterface.php deleted file mode 100644 index 59972bb5e..000000000 --- a/Tests/Fixtures/UpcomingNormalizerInterface.php +++ /dev/null @@ -1,20 +0,0 @@ - - * - * 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\NormalizerInterface; - -// @deprecated remove in 7.0 in favor of direct use of the NormalizerInterface -interface UpcomingNormalizerInterface extends NormalizerInterface -{ - public function getSupportedTypes(?string $format): array; -} diff --git a/Tests/Normalizer/ArrayDenormalizerTest.php b/Tests/Normalizer/ArrayDenormalizerTest.php index 8f7a75b11..be4a30d86 100644 --- a/Tests/Normalizer/ArrayDenormalizerTest.php +++ b/Tests/Normalizer/ArrayDenormalizerTest.php @@ -14,7 +14,7 @@ use PHPUnit\Framework\MockObject\MockObject; use PHPUnit\Framework\TestCase; use Symfony\Component\Serializer\Normalizer\ArrayDenormalizer; -use Symfony\Component\Serializer\Tests\Fixtures\UpcomingDenormalizerInterface as DenormalizerInterface; +use Symfony\Component\Serializer\Normalizer\DenormalizerInterface; class ArrayDenormalizerTest extends TestCase { diff --git a/Tests/Normalizer/ObjectNormalizerTest.php b/Tests/Normalizer/ObjectNormalizerTest.php index d87b7a67a..fbb88f7b6 100644 --- a/Tests/Normalizer/ObjectNormalizerTest.php +++ b/Tests/Normalizer/ObjectNormalizerTest.php @@ -731,20 +731,6 @@ public function testDoesntHaveIssuesWithUnionConstTypes() })::class)->foo); } - /** - * @group legacy - */ - public function testExtractAttributesRespectsFormat() - { - $normalizer = new FormatAndContextAwareNormalizer(); - - $data = new ObjectDummy(); - $data->setFoo('bar'); - $data->bar = 'foo'; - - $this->assertSame(['foo' => 'bar', 'bar' => 'foo'], $normalizer->normalize($data, 'foo_and_bar_included')); - } - public function testExtractAttributesRespectsContext() { $normalizer = new ObjectNormalizer(); diff --git a/Tests/SerializerTest.php b/Tests/SerializerTest.php index 554993ee0..669d545b7 100644 --- a/Tests/SerializerTest.php +++ b/Tests/SerializerTest.php @@ -40,8 +40,10 @@ use Symfony\Component\Serializer\Normalizer\DateTimeNormalizer; use Symfony\Component\Serializer\Normalizer\DateTimeZoneNormalizer; use Symfony\Component\Serializer\Normalizer\DenormalizerAwareInterface; +use Symfony\Component\Serializer\Normalizer\DenormalizerInterface; use Symfony\Component\Serializer\Normalizer\GetSetMethodNormalizer; use Symfony\Component\Serializer\Normalizer\NormalizerAwareInterface; +use Symfony\Component\Serializer\Normalizer\NormalizerInterface; use Symfony\Component\Serializer\Normalizer\ObjectNormalizer; use Symfony\Component\Serializer\Normalizer\PropertyNormalizer; use Symfony\Component\Serializer\Normalizer\UidNormalizer; @@ -63,8 +65,6 @@ use Symfony\Component\Serializer\Tests\Fixtures\Php80WithPromotedTypedConstructor; use Symfony\Component\Serializer\Tests\Fixtures\TraversableDummy; use Symfony\Component\Serializer\Tests\Fixtures\TrueBuiltInDummy; -use Symfony\Component\Serializer\Tests\Fixtures\UpcomingDenormalizerInterface as DenormalizerInterface; -use Symfony\Component\Serializer\Tests\Fixtures\UpcomingNormalizerInterface as NormalizerInterface; use Symfony\Component\Serializer\Tests\Normalizer\TestDenormalizer; use Symfony\Component\Serializer\Tests\Normalizer\TestNormalizer; From c9222bd5dfb817ff385c5e5b59a6c991b0323c49 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gr=C3=A9goire=20Pineau?= Date: Wed, 14 Jun 2023 15:02:10 +0200 Subject: [PATCH 119/297] Make some classes final --- Normalizer/ConstraintViolationListNormalizer.php | 4 +--- Normalizer/CustomNormalizer.php | 4 +--- Normalizer/DataUriNormalizer.php | 4 +--- Normalizer/DateIntervalNormalizer.php | 4 +--- Normalizer/DateTimeNormalizer.php | 4 +--- Normalizer/DateTimeZoneNormalizer.php | 4 +--- Normalizer/JsonSerializableNormalizer.php | 4 +--- Normalizer/ObjectNormalizer.php | 4 +--- Tests/Encoder/XmlEncoderTest.php | 2 +- Tests/SerializerTest.php | 4 ++-- 10 files changed, 11 insertions(+), 27 deletions(-) diff --git a/Normalizer/ConstraintViolationListNormalizer.php b/Normalizer/ConstraintViolationListNormalizer.php index d3afa41d7..1e042b21c 100644 --- a/Normalizer/ConstraintViolationListNormalizer.php +++ b/Normalizer/ConstraintViolationListNormalizer.php @@ -21,10 +21,8 @@ * * @author Grégoire Pineau * @author Kévin Dunglas - * - * @final since Symfony 6.3 */ -class ConstraintViolationListNormalizer implements NormalizerInterface +final class ConstraintViolationListNormalizer implements NormalizerInterface { public const INSTANCE = 'instance'; public const STATUS = 'status'; diff --git a/Normalizer/CustomNormalizer.php b/Normalizer/CustomNormalizer.php index 14ba85b51..fcfb6f173 100644 --- a/Normalizer/CustomNormalizer.php +++ b/Normalizer/CustomNormalizer.php @@ -16,10 +16,8 @@ /** * @author Jordi Boggiano - * - * @final since Symfony 6.3 */ -class CustomNormalizer implements NormalizerInterface, DenormalizerInterface, SerializerAwareInterface +final class CustomNormalizer implements NormalizerInterface, DenormalizerInterface, SerializerAwareInterface { use ObjectToPopulateTrait; use SerializerAwareTrait; diff --git a/Normalizer/DataUriNormalizer.php b/Normalizer/DataUriNormalizer.php index 44e080b90..c1aa9695b 100644 --- a/Normalizer/DataUriNormalizer.php +++ b/Normalizer/DataUriNormalizer.php @@ -22,10 +22,8 @@ * Denormalizes a data URI to a {@see \SplFileObject} object. * * @author Kévin Dunglas - * - * @final since Symfony 6.3 */ -class DataUriNormalizer implements NormalizerInterface, DenormalizerInterface +final class DataUriNormalizer implements NormalizerInterface, DenormalizerInterface { private const SUPPORTED_TYPES = [ \SplFileInfo::class => true, diff --git a/Normalizer/DateIntervalNormalizer.php b/Normalizer/DateIntervalNormalizer.php index c46737e8b..b94324607 100644 --- a/Normalizer/DateIntervalNormalizer.php +++ b/Normalizer/DateIntervalNormalizer.php @@ -19,10 +19,8 @@ * Denormalizes an interval string to an instance of {@see \DateInterval}. * * @author Jérôme Parmentier - * - * @final since Symfony 6.3 */ -class DateIntervalNormalizer implements NormalizerInterface, DenormalizerInterface +final class DateIntervalNormalizer implements NormalizerInterface, DenormalizerInterface { public const FORMAT_KEY = 'dateinterval_format'; diff --git a/Normalizer/DateTimeNormalizer.php b/Normalizer/DateTimeNormalizer.php index c165317f5..8bb007103 100644 --- a/Normalizer/DateTimeNormalizer.php +++ b/Normalizer/DateTimeNormalizer.php @@ -20,10 +20,8 @@ * Denormalizes a date string to an instance of {@see \DateTime} or {@see \DateTimeImmutable}. * * @author Kévin Dunglas - * - * @final since Symfony 6.3 */ -class DateTimeNormalizer implements NormalizerInterface, DenormalizerInterface +final class DateTimeNormalizer implements NormalizerInterface, DenormalizerInterface { public const FORMAT_KEY = 'datetime_format'; public const TIMEZONE_KEY = 'datetime_timezone'; diff --git a/Normalizer/DateTimeZoneNormalizer.php b/Normalizer/DateTimeZoneNormalizer.php index 595847e87..f5a08e1f3 100644 --- a/Normalizer/DateTimeZoneNormalizer.php +++ b/Normalizer/DateTimeZoneNormalizer.php @@ -19,10 +19,8 @@ * Normalizes a {@see \DateTimeZone} object to a timezone string. * * @author Jérôme Desjardins - * - * @final since Symfony 6.3 */ -class DateTimeZoneNormalizer implements NormalizerInterface, DenormalizerInterface +final class DateTimeZoneNormalizer implements NormalizerInterface, DenormalizerInterface { public function getSupportedTypes(?string $format): array { diff --git a/Normalizer/JsonSerializableNormalizer.php b/Normalizer/JsonSerializableNormalizer.php index 60c106262..324487c28 100644 --- a/Normalizer/JsonSerializableNormalizer.php +++ b/Normalizer/JsonSerializableNormalizer.php @@ -18,10 +18,8 @@ * A normalizer that uses an objects own JsonSerializable implementation. * * @author Fred Cox - * - * @final since Symfony 6.3 */ -class JsonSerializableNormalizer extends AbstractNormalizer +final class JsonSerializableNormalizer extends AbstractNormalizer { public function normalize(mixed $object, string $format = null, array $context = []): array|string|int|float|bool|\ArrayObject|null { diff --git a/Normalizer/ObjectNormalizer.php b/Normalizer/ObjectNormalizer.php index af530f8d3..533c07e6d 100644 --- a/Normalizer/ObjectNormalizer.php +++ b/Normalizer/ObjectNormalizer.php @@ -25,10 +25,8 @@ * Converts between objects and arrays using the PropertyAccess component. * * @author Kévin Dunglas - * - * @final since Symfony 6.3 */ -class ObjectNormalizer extends AbstractObjectNormalizer +final class ObjectNormalizer extends AbstractObjectNormalizer { protected $propertyAccessor; diff --git a/Tests/Encoder/XmlEncoderTest.php b/Tests/Encoder/XmlEncoderTest.php index 965256d37..9758f2c2a 100644 --- a/Tests/Encoder/XmlEncoderTest.php +++ b/Tests/Encoder/XmlEncoderTest.php @@ -934,7 +934,7 @@ private function createXmlEncoderWithDateTimeNormalizer(): XmlEncoder private function createMockDateTimeNormalizer(): MockObject&NormalizerInterface { - $mock = $this->createMock(CustomNormalizer::class); + $mock = $this->createMock(NormalizerInterface::class); $mock ->expects($this->once()) diff --git a/Tests/SerializerTest.php b/Tests/SerializerTest.php index 669d545b7..9579b75b1 100644 --- a/Tests/SerializerTest.php +++ b/Tests/SerializerTest.php @@ -89,7 +89,7 @@ public function testItThrowsExceptionOnInvalidEncoder() public function testNormalizeNoMatch() { $this->expectException(UnexpectedValueException::class); - $serializer = new Serializer([$this->createMock(CustomNormalizer::class)]); + $serializer = new Serializer([$this->createMock(NormalizerInterface::class)]); $serializer->normalize(new \stdClass(), 'xml'); } @@ -117,7 +117,7 @@ public function testNormalizeOnDenormalizer() public function testDenormalizeNoMatch() { $this->expectException(UnexpectedValueException::class); - $serializer = new Serializer([$this->createMock(CustomNormalizer::class)]); + $serializer = new Serializer([$this->createMock(NormalizerInterface::class)]); $serializer->denormalize('foo', 'stdClass'); } From e9c22393e50450f8365d067d934226bb3bf68311 Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Fri, 30 Jun 2023 18:53:02 +0200 Subject: [PATCH 120/297] Remove BC layers related to new methods and new parameters --- CHANGELOG.md | 6 +++--- Normalizer/DenormalizerInterface.php | 2 -- Normalizer/NormalizerInterface.php | 2 -- 3 files changed, 3 insertions(+), 7 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f82945bf0..2eccea3cb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,14 +4,14 @@ CHANGELOG 7.0 --- + * Add method `getSupportedTypes()` to `DenormalizerInterface` and `NormalizerInterface` * Remove denormalization support for `AbstractUid` in `UidNormalizer`, use one of `AbstractUid` child class instead * Denormalizing to an abstract class in `UidNormalizer` now throws an `\Error` * Remove `ContextAwareDenormalizerInterface`, use `DenormalizerInterface` instead * Remove `ContextAwareNormalizerInterface`, use `NormalizerInterface` instead * Remove `CacheableSupportsMethodInterface`, use `NormalizerInterface` and `DenormalizerInterface` instead - * First argument of `ClassMetadata::setSerializedName()` is now required - * Third argument `array $context = []` of the `NormalizerInterface::supportsNormalization()` is now required - * Fourth argument `array $context = []` of the `DenormalizerInterface::supportsDenormalization()` is now required + * First argument of `AttributeMetadata::setSerializedName()` is now required + * Add argument `$context` to `NormalizerInterface::supportsNormalization()` and `DenormalizerInterface::supportsDenormalization()` 6.3 --- diff --git a/Normalizer/DenormalizerInterface.php b/Normalizer/DenormalizerInterface.php index e4d0ed912..7cb116403 100644 --- a/Normalizer/DenormalizerInterface.php +++ b/Normalizer/DenormalizerInterface.php @@ -21,8 +21,6 @@ /** * @author Jordi Boggiano - * - * @method getSupportedTypes(?string $format): array */ interface DenormalizerInterface { diff --git a/Normalizer/NormalizerInterface.php b/Normalizer/NormalizerInterface.php index 01979d6fc..8d9f751d9 100644 --- a/Normalizer/NormalizerInterface.php +++ b/Normalizer/NormalizerInterface.php @@ -18,8 +18,6 @@ /** * @author Jordi Boggiano - * - * @method getSupportedTypes(?string $format): array */ interface NormalizerInterface { From 94e5f2217e99b33c2bc4e958c518ebdf61067ef7 Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Tue, 4 Jul 2023 14:50:59 +0200 Subject: [PATCH 121/297] [7.0] Remove remaining deprecated code paths --- CHANGELOG.md | 2 +- Normalizer/GetSetMethodNormalizer.php | 4 +--- Normalizer/PropertyNormalizer.php | 4 +--- 3 files changed, 3 insertions(+), 7 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2eccea3cb..fad14fca4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,7 +10,7 @@ CHANGELOG * Remove `ContextAwareDenormalizerInterface`, use `DenormalizerInterface` instead * Remove `ContextAwareNormalizerInterface`, use `NormalizerInterface` instead * Remove `CacheableSupportsMethodInterface`, use `NormalizerInterface` and `DenormalizerInterface` instead - * First argument of `AttributeMetadata::setSerializedName()` is now required + * Require explicit argument when calling `AttributeMetadata::setSerializedName()` and `ClassMetadata::setClassDiscriminatorMapping()` * Add argument `$context` to `NormalizerInterface::supportsNormalization()` and `DenormalizerInterface::supportsDenormalization()` 6.3 diff --git a/Normalizer/GetSetMethodNormalizer.php b/Normalizer/GetSetMethodNormalizer.php index 3d11567a7..7873f651a 100644 --- a/Normalizer/GetSetMethodNormalizer.php +++ b/Normalizer/GetSetMethodNormalizer.php @@ -33,10 +33,8 @@ * * @author Nils Adermann * @author Kévin Dunglas - * - * @final since Symfony 6.3 */ -class GetSetMethodNormalizer extends AbstractObjectNormalizer +final class GetSetMethodNormalizer extends AbstractObjectNormalizer { private static $setterAccessibleCache = []; diff --git a/Normalizer/PropertyNormalizer.php b/Normalizer/PropertyNormalizer.php index cfe93bc10..d4c5a97e8 100644 --- a/Normalizer/PropertyNormalizer.php +++ b/Normalizer/PropertyNormalizer.php @@ -33,10 +33,8 @@ * * @author Matthieu Napoli * @author Kévin Dunglas - * - * @final since Symfony 6.3 */ -class PropertyNormalizer extends AbstractObjectNormalizer +final class PropertyNormalizer extends AbstractObjectNormalizer { public const NORMALIZE_PUBLIC = 1; public const NORMALIZE_PROTECTED = 2; From 41af89b86bbab29e40ab2998ae9c87ed829fac38 Mon Sep 17 00:00:00 2001 From: Wouter de Jong Date: Sun, 2 Jul 2023 23:52:21 +0200 Subject: [PATCH 122/297] [Components] Convert to native return types --- Annotation/MaxDepth.php | 5 +-- DependencyInjection/SerializerPass.php | 5 +-- Encoder/DecoderInterface.php | 8 ++--- Exception/PartialDenormalizationException.php | 5 +-- Normalizer/AbstractNormalizer.php | 10 ++---- Normalizer/AbstractObjectNormalizer.php | 36 +++++-------------- Normalizer/DenormalizableInterface.php | 4 +-- Normalizer/DenormalizerAwareInterface.php | 4 +-- Normalizer/DenormalizerInterface.php | 8 ++--- Normalizer/GetSetMethodNormalizer.php | 5 +-- Normalizer/NormalizerAwareInterface.php | 4 +-- Normalizer/NormalizerAwareTrait.php | 5 +-- Normalizer/NormalizerInterface.php | 6 ++-- Normalizer/ObjectNormalizer.php | 5 +-- Normalizer/PropertyNormalizer.php | 5 +-- SerializerAwareInterface.php | 4 +-- SerializerAwareTrait.php | 5 +-- .../AbstractObjectNormalizerTest.php | 8 ++--- Tests/Normalizer/ObjectNormalizerTest.php | 1 - 19 files changed, 33 insertions(+), 100 deletions(-) diff --git a/Annotation/MaxDepth.php b/Annotation/MaxDepth.php index 36afff6a7..4fe449010 100644 --- a/Annotation/MaxDepth.php +++ b/Annotation/MaxDepth.php @@ -32,10 +32,7 @@ public function __construct(private readonly int $maxDepth) } } - /** - * @return int - */ - public function getMaxDepth() + public function getMaxDepth(): int { return $this->maxDepth; } diff --git a/DependencyInjection/SerializerPass.php b/DependencyInjection/SerializerPass.php index d0b0deb48..2a429054b 100644 --- a/DependencyInjection/SerializerPass.php +++ b/DependencyInjection/SerializerPass.php @@ -31,10 +31,7 @@ class SerializerPass implements CompilerPassInterface { use PriorityTaggedServiceTrait; - /** - * @return void - */ - public function process(ContainerBuilder $container) + public function process(ContainerBuilder $container): void { if (!$container->hasDefinition('serializer')) { return; diff --git a/Encoder/DecoderInterface.php b/Encoder/DecoderInterface.php index 84a84ad1f..09ec9434d 100644 --- a/Encoder/DecoderInterface.php +++ b/Encoder/DecoderInterface.php @@ -30,18 +30,14 @@ interface DecoderInterface * are encouraged to document which formats they support in a non-inherited * phpdoc comment. * - * @return mixed - * * @throws UnexpectedValueException */ - public function decode(string $data, string $format, array $context = []); + public function decode(string $data, string $format, array $context = []): mixed; /** * Checks whether the deserializer can decode from given format. * * @param string $format Format name - * - * @return bool */ - public function supportsDecoding(string $format); + public function supportsDecoding(string $format): bool; } diff --git a/Exception/PartialDenormalizationException.php b/Exception/PartialDenormalizationException.php index b684fddb2..5edc146cf 100644 --- a/Exception/PartialDenormalizationException.php +++ b/Exception/PartialDenormalizationException.php @@ -25,10 +25,7 @@ public function __construct( ) { } - /** - * @return mixed - */ - public function getData() + public function getData(): mixed { return $this->data; } diff --git a/Normalizer/AbstractNormalizer.php b/Normalizer/AbstractNormalizer.php index efe4a6e0e..5a95393ab 100644 --- a/Normalizer/AbstractNormalizer.php +++ b/Normalizer/AbstractNormalizer.php @@ -210,7 +210,7 @@ protected function handleCircularReference(object $object, string $format = null * * @throws LogicException if the 'allow_extra_attributes' context variable is false and no class metadata factory is provided */ - protected function getAllowedAttributes(string|object $classOrObject, array $context, bool $attributesAsString = false) + protected function getAllowedAttributes(string|object $classOrObject, array $context, bool $attributesAsString = false): array|bool { $allowExtraAttributes = $context[self::ALLOW_EXTRA_ATTRIBUTES] ?? $this->defaultContext[self::ALLOW_EXTRA_ATTRIBUTES]; if (!$this->classMetadataFactory) { @@ -257,10 +257,8 @@ protected function getGroups(array $context): array /** * Is this attribute allowed? - * - * @return bool */ - protected function isAllowedAttribute(object|string $classOrObject, string $attribute, string $format = null, array $context = []) + protected function isAllowedAttribute(object|string $classOrObject, string $attribute, string $format = null, array $context = []): bool { $ignoredAttributes = $context[self::IGNORED_ATTRIBUTES] ?? $this->defaultContext[self::IGNORED_ATTRIBUTES]; if (\in_array($attribute, $ignoredAttributes)) { @@ -306,12 +304,10 @@ protected function getConstructor(array &$data, string $class, array &$context, * is removed from the context before being returned to avoid side effects * when recursively normalizing an object graph. * - * @return object - * * @throws RuntimeException * @throws MissingConstructorArgumentsException */ - protected function instantiateObject(array &$data, string $class, array &$context, \ReflectionClass $reflectionClass, array|bool $allowedAttributes, string $format = null) + protected function instantiateObject(array &$data, string $class, array &$context, \ReflectionClass $reflectionClass, array|bool $allowedAttributes, string $format = null): object { if (null !== $object = $this->extractObjectToPopulate($class, $context, self::OBJECT_TO_POPULATE)) { unset($context[self::OBJECT_TO_POPULATE]); diff --git a/Normalizer/AbstractObjectNormalizer.php b/Normalizer/AbstractObjectNormalizer.php index 4f061239b..b08edd2fb 100644 --- a/Normalizer/AbstractObjectNormalizer.php +++ b/Normalizer/AbstractObjectNormalizer.php @@ -137,18 +137,12 @@ public function __construct( $this->objectClassResolver = ($objectClassResolver ?? 'get_class')(...); } - /** - * @return bool - */ - public function supportsNormalization(mixed $data, string $format = null, array $context = []) + public function supportsNormalization(mixed $data, string $format = null, array $context = []): bool { return \is_object($data) && !$data instanceof \Traversable; } - /** - * @return array|string|int|float|bool|\ArrayObject|null - */ - public function normalize(mixed $object, string $format = null, array $context = []) + public function normalize(mixed $object, string $format = null, array $context = []): array|string|int|float|bool|\ArrayObject|null { if (!isset($context['cache_key'])) { $context['cache_key'] = $this->getCacheKey($format, $context); @@ -229,10 +223,7 @@ public function normalize(mixed $object, string $format = null, array $context = return $data; } - /** - * @return object - */ - protected function instantiateObject(array &$data, string $class, array &$context, \ReflectionClass $reflectionClass, array|bool $allowedAttributes, string $format = null) + protected function instantiateObject(array &$data, string $class, array &$context, \ReflectionClass $reflectionClass, array|bool $allowedAttributes, string $format = null): object { if ($class !== $mappedClass = $this->getMappedClass($data, $class, $context)) { return $this->instantiateObject($data, $mappedClass, $context, new \ReflectionClass($mappedClass), $allowedAttributes, $format); @@ -283,27 +274,19 @@ protected function getAttributes(object $object, ?string $format, array $context * * @return string[] */ - abstract protected function extractAttributes(object $object, string $format = null, array $context = []); + abstract protected function extractAttributes(object $object, string $format = null, array $context = []): array; /** * Gets the attribute value. - * - * @return mixed */ - abstract protected function getAttributeValue(object $object, string $attribute, string $format = null, array $context = []); + abstract protected function getAttributeValue(object $object, string $attribute, string $format = null, array $context = []): mixed; - /** - * @return bool - */ - public function supportsDenormalization(mixed $data, string $type, string $format = null, array $context = []) + public function supportsDenormalization(mixed $data, string $type, string $format = null, array $context = []): bool { return class_exists($type) || (interface_exists($type, false) && null !== $this->classDiscriminatorResolver?->getMappingForClass($type)); } - /** - * @return mixed - */ - public function denormalize(mixed $data, string $type, string $format = null, array $context = []) + public function denormalize(mixed $data, string $type, string $format = null, array $context = []): mixed { if (!isset($context['cache_key'])) { $context['cache_key'] = $this->getCacheKey($format, $context); @@ -406,10 +389,7 @@ public function denormalize(mixed $data, string $type, string $format = null, ar return $object; } - /** - * @return void - */ - abstract protected function setAttributeValue(object $object, string $attribute, mixed $value, string $format = null, array $context = []); + abstract protected function setAttributeValue(object $object, string $attribute, mixed $value, string $format = null, array $context = []): void; /** * Validates the submitted data and denormalizes it. diff --git a/Normalizer/DenormalizableInterface.php b/Normalizer/DenormalizableInterface.php index 503f3cb51..458175f31 100644 --- a/Normalizer/DenormalizableInterface.php +++ b/Normalizer/DenormalizableInterface.php @@ -33,8 +33,6 @@ interface DenormalizableInterface * @param string|null $format The format is optionally given to be able to denormalize * differently based on different input formats * @param array $context Options for denormalizing - * - * @return void */ - public function denormalize(DenormalizerInterface $denormalizer, array|string|int|float|bool $data, string $format = null, array $context = []); + public function denormalize(DenormalizerInterface $denormalizer, array|string|int|float|bool $data, string $format = null, array $context = []): void; } diff --git a/Normalizer/DenormalizerAwareInterface.php b/Normalizer/DenormalizerAwareInterface.php index 48e8c3fb5..84bf8d89d 100644 --- a/Normalizer/DenormalizerAwareInterface.php +++ b/Normalizer/DenormalizerAwareInterface.php @@ -18,8 +18,6 @@ interface DenormalizerAwareInterface { /** * Sets the owning Denormalizer object. - * - * @return void */ - public function setDenormalizer(DenormalizerInterface $denormalizer); + public function setDenormalizer(DenormalizerInterface $denormalizer): void; } diff --git a/Normalizer/DenormalizerInterface.php b/Normalizer/DenormalizerInterface.php index 7cb116403..a0ee75345 100644 --- a/Normalizer/DenormalizerInterface.php +++ b/Normalizer/DenormalizerInterface.php @@ -34,8 +34,6 @@ interface DenormalizerInterface * @param string|null $format Format the given data was extracted from * @param array $context Options available to the denormalizer * - * @return mixed - * * @throws BadMethodCallException Occurs when the normalizer is not called in an expected context * @throws InvalidArgumentException Occurs when the arguments are not coherent or not supported * @throws UnexpectedValueException Occurs when the item cannot be hydrated with the given data @@ -44,7 +42,7 @@ interface DenormalizerInterface * @throws RuntimeException Occurs if the class cannot be instantiated * @throws ExceptionInterface Occurs for all the other cases of errors */ - public function denormalize(mixed $data, string $type, string $format = null, array $context = []); + public function denormalize(mixed $data, string $type, string $format = null, array $context = []): mixed; /** * Checks whether the given class is supported for denormalization by this normalizer. @@ -52,10 +50,8 @@ public function denormalize(mixed $data, string $type, string $format = null, ar * @param mixed $data Data to denormalize from * @param string $type The class to which the data should be denormalized * @param string|null $format The format being deserialized from - * - * @return bool */ - public function supportsDenormalization(mixed $data, string $type, string $format = null, array $context = []); + public function supportsDenormalization(mixed $data, string $type, string $format = null, array $context = []): bool; /** * Returns the types potentially supported by this denormalizer. diff --git a/Normalizer/GetSetMethodNormalizer.php b/Normalizer/GetSetMethodNormalizer.php index 7873f651a..35d296cb3 100644 --- a/Normalizer/GetSetMethodNormalizer.php +++ b/Normalizer/GetSetMethodNormalizer.php @@ -125,10 +125,7 @@ protected function getAttributeValue(object $object, string $attribute, string $ return null; } - /** - * @return void - */ - protected function setAttributeValue(object $object, string $attribute, mixed $value, string $format = null, array $context = []) + protected function setAttributeValue(object $object, string $attribute, mixed $value, string $format = null, array $context = []): void { $setter = 'set'.ucfirst($attribute); $key = $object::class.':'.$setter; diff --git a/Normalizer/NormalizerAwareInterface.php b/Normalizer/NormalizerAwareInterface.php index 5f3deaa01..1079ab6dd 100644 --- a/Normalizer/NormalizerAwareInterface.php +++ b/Normalizer/NormalizerAwareInterface.php @@ -18,8 +18,6 @@ interface NormalizerAwareInterface { /** * Sets the owning Normalizer object. - * - * @return void */ - public function setNormalizer(NormalizerInterface $normalizer); + public function setNormalizer(NormalizerInterface $normalizer): void; } diff --git a/Normalizer/NormalizerAwareTrait.php b/Normalizer/NormalizerAwareTrait.php index 40a4fa0e8..98139de6d 100644 --- a/Normalizer/NormalizerAwareTrait.php +++ b/Normalizer/NormalizerAwareTrait.php @@ -21,10 +21,7 @@ trait NormalizerAwareTrait */ protected $normalizer; - /** - * @return void - */ - public function setNormalizer(NormalizerInterface $normalizer) + public function setNormalizer(NormalizerInterface $normalizer): void { $this->normalizer = $normalizer; } diff --git a/Normalizer/NormalizerInterface.php b/Normalizer/NormalizerInterface.php index 8d9f751d9..6759b2199 100644 --- a/Normalizer/NormalizerInterface.php +++ b/Normalizer/NormalizerInterface.php @@ -36,17 +36,15 @@ interface NormalizerInterface * @throws LogicException Occurs when the normalizer is not called in an expected context * @throws ExceptionInterface Occurs for all the other cases of errors */ - public function normalize(mixed $object, string $format = null, array $context = []); + public function normalize(mixed $object, string $format = null, array $context = []): array|string|int|float|bool|\ArrayObject|null; /** * Checks whether the given class is supported for normalization by this normalizer. * * @param mixed $data Data to normalize * @param string|null $format The format being (de-)serialized from or into - * - * @return bool */ - public function supportsNormalization(mixed $data, string $format = null, array $context = []); + public function supportsNormalization(mixed $data, string $format = null, array $context = []): bool; /** * Returns the types potentially supported by this normalizer. diff --git a/Normalizer/ObjectNormalizer.php b/Normalizer/ObjectNormalizer.php index 533c07e6d..33d3f5d65 100644 --- a/Normalizer/ObjectNormalizer.php +++ b/Normalizer/ObjectNormalizer.php @@ -130,10 +130,7 @@ protected function getAttributeValue(object $object, string $attribute, string $ return $attribute === $this->discriminatorCache[$cacheKey] ? $this->classDiscriminatorResolver->getTypeForMappedObject($object) : $this->propertyAccessor->getValue($object, $attribute); } - /** - * @return void - */ - protected function setAttributeValue(object $object, string $attribute, mixed $value, string $format = null, array $context = []) + protected function setAttributeValue(object $object, string $attribute, mixed $value, string $format = null, array $context = []): void { try { $this->propertyAccessor->setValue($object, $attribute, $value); diff --git a/Normalizer/PropertyNormalizer.php b/Normalizer/PropertyNormalizer.php index d4c5a97e8..2e893f611 100644 --- a/Normalizer/PropertyNormalizer.php +++ b/Normalizer/PropertyNormalizer.php @@ -165,10 +165,7 @@ protected function getAttributeValue(object $object, string $attribute, string $ return $reflectionProperty->getValue($object); } - /** - * @return void - */ - protected function setAttributeValue(object $object, string $attribute, mixed $value, string $format = null, array $context = []) + protected function setAttributeValue(object $object, string $attribute, mixed $value, string $format = null, array $context = []): void { try { $reflectionProperty = $this->getReflectionProperty($object, $attribute); diff --git a/SerializerAwareInterface.php b/SerializerAwareInterface.php index 4919436d9..0b4db4c77 100644 --- a/SerializerAwareInterface.php +++ b/SerializerAwareInterface.php @@ -18,8 +18,6 @@ interface SerializerAwareInterface { /** * Sets the owning Serializer object. - * - * @return void */ - public function setSerializer(SerializerInterface $serializer); + public function setSerializer(SerializerInterface $serializer): void; } diff --git a/SerializerAwareTrait.php b/SerializerAwareTrait.php index 183556874..f74f56b41 100644 --- a/SerializerAwareTrait.php +++ b/SerializerAwareTrait.php @@ -21,10 +21,7 @@ trait SerializerAwareTrait */ protected $serializer; - /** - * @return void - */ - public function setSerializer(SerializerInterface $serializer) + public function setSerializer(SerializerInterface $serializer): void { $this->serializer = $serializer; } diff --git a/Tests/Normalizer/AbstractObjectNormalizerTest.php b/Tests/Normalizer/AbstractObjectNormalizerTest.php index 78046229e..bed7c33ce 100644 --- a/Tests/Normalizer/AbstractObjectNormalizerTest.php +++ b/Tests/Normalizer/AbstractObjectNormalizerTest.php @@ -763,7 +763,7 @@ protected function getAttributeValue(object $object, string $attribute, string $ { } - protected function setAttributeValue(object $object, string $attribute, $value, string $format = null, array $context = []) + protected function setAttributeValue(object $object, string $attribute, $value, string $format = null, array $context = []): void { $object->$attribute = $value; } @@ -963,7 +963,7 @@ protected function getAttributeValue(object $object, string $attribute, string $ { } - protected function setAttributeValue(object $object, string $attribute, $value, string $format = null, array $context = []) + protected function setAttributeValue(object $object, string $attribute, $value, string $format = null, array $context = []): void { if (property_exists($object, $attribute)) { $object->$attribute = $value; @@ -1084,7 +1084,7 @@ protected function getAttributeValue(object $object, string $attribute, string $ { } - protected function setAttributeValue(object $object, string $attribute, $value, string $format = null, array $context = []) + protected function setAttributeValue(object $object, string $attribute, $value, string $format = null, array $context = []): void { $object->$attribute = $value; } @@ -1141,7 +1141,7 @@ public function supportsDenormalization($data, string $type, string $format = nu && $this->serializer->supportsDenormalization($data, substr($type, 0, -2), $format, $context); } - public function setSerializer(SerializerInterface $serializer) + public function setSerializer(SerializerInterface $serializer): void { $this->serializer = $serializer; } diff --git a/Tests/Normalizer/ObjectNormalizerTest.php b/Tests/Normalizer/ObjectNormalizerTest.php index fbb88f7b6..89e96d036 100644 --- a/Tests/Normalizer/ObjectNormalizerTest.php +++ b/Tests/Normalizer/ObjectNormalizerTest.php @@ -37,7 +37,6 @@ use Symfony\Component\Serializer\Tests\Fixtures\Annotations\GroupDummy; use Symfony\Component\Serializer\Tests\Fixtures\CircularReferenceDummy; use Symfony\Component\Serializer\Tests\Fixtures\DummyPrivatePropertyWithoutGetter; -use Symfony\Component\Serializer\Tests\Fixtures\FormatAndContextAwareNormalizer; use Symfony\Component\Serializer\Tests\Fixtures\OtherSerializedNameDummy; use Symfony\Component\Serializer\Tests\Fixtures\Php74Dummy; use Symfony\Component\Serializer\Tests\Fixtures\Php74DummyPrivate; From 92a40ad00fdc3767be6c67d795ba574798e01ab2 Mon Sep 17 00:00:00 2001 From: "Alexander M. Turek" Date: Fri, 14 Jul 2023 20:02:50 +0200 Subject: [PATCH 123/297] [Serializer] Deprecate annotations in favor of attributes --- CHANGELOG.md | 6 + Mapping/Loader/AnnotationLoader.php | 59 +++++++- Tests/Fixtures/Annotations/GroupDummy.php | 2 +- .../{ => Annotations}/GroupDummyInterface.php | 2 +- Tests/Fixtures/Attributes/GroupDummy.php | 1 - .../GroupDummyChild.php | 2 +- .../Attributes/GroupDummyInterface.php | 23 +++ Tests/Fixtures/DummyMessageInterface.php | 9 +- Tests/Fixtures/DummyMessageNumberOne.php | 4 +- Tests/Fixtures/OtherSerializedNameDummy.php | 9 +- Tests/Fixtures/invalid-ignore.yml | 2 +- Tests/Fixtures/serialization.xml | 22 +-- Tests/Fixtures/serialization.yml | 22 +-- .../Mapping/ClassDiscriminatorMappingTest.php | 6 +- .../ClassMetadataFactoryCompilerTest.php | 16 +- .../Factory/ClassMetadataFactoryTest.php | 18 ++- .../CompiledClassMetadataFactoryTest.php | 2 +- .../Loader/AnnotationLoaderTestCase.php | 4 +- ...ationLoaderWithDoctrineAnnotationsTest.php | 142 ++++++++++++++++++ .../Features/ContextMappingTestTrait.php | 6 +- Tests/Mapping/Loader/XmlFileLoaderTest.php | 34 ++--- Tests/Mapping/Loader/YamlFileLoaderTest.php | 33 ++-- Tests/Mapping/TestClassMetadataFactory.php | 3 +- .../MetadataAwareNameConverterTest.php | 45 +++--- Tests/Normalizer/AbstractNormalizerTest.php | 2 +- .../AbstractObjectNormalizerTest.php | 109 +++++--------- .../Features/ContextMetadataTestTrait.php | 56 +++---- Tests/Normalizer/Features/GroupsTestTrait.php | 2 +- .../Normalizer/Features/MaxDepthTestTrait.php | 2 +- .../Features/TypedPropertiesObject.php | 12 +- .../Normalizer/GetSetMethodNormalizerTest.php | 44 +++--- Tests/Normalizer/MapDenormalizationTest.php | 3 +- Tests/Normalizer/ObjectNormalizerTest.php | 42 +++--- Tests/Normalizer/PropertyNormalizerTest.php | 36 ++--- Tests/SerializerTest.php | 17 +-- 35 files changed, 463 insertions(+), 334 deletions(-) rename Tests/Fixtures/{ => Annotations}/GroupDummyInterface.php (87%) rename Tests/Fixtures/{Annotations => Attributes}/GroupDummyChild.php (87%) create mode 100644 Tests/Fixtures/Attributes/GroupDummyInterface.php diff --git a/CHANGELOG.md b/CHANGELOG.md index 8154d3688..4ab3defdd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,12 @@ CHANGELOG ========= +6.4 +--- + + * Deprecate Doctrine annotations support in favor of native attributes + * Deprecate passing an annotation reader to the constructor of `AnnotationLoader` + 6.3 --- diff --git a/Mapping/Loader/AnnotationLoader.php b/Mapping/Loader/AnnotationLoader.php index 6d03511cf..94d0f45dc 100644 --- a/Mapping/Loader/AnnotationLoader.php +++ b/Mapping/Loader/AnnotationLoader.php @@ -46,6 +46,9 @@ class AnnotationLoader implements LoaderInterface public function __construct( private readonly ?Reader $reader = null, ) { + if ($reader) { + trigger_deprecation('symfony/validator', '6.4', 'Passing a "%s" instance as argument 1 to "%s()" is deprecated, pass null or omit the parameter instead.', get_debug_type($reader), __METHOD__); + } } public function loadClassMetadata(ClassMetadataInterface $classMetadata): bool @@ -163,10 +166,7 @@ public function loadClassMetadata(ClassMetadataInterface $classMetadata): bool return $loaded; } - /** - * @param \ReflectionClass|\ReflectionMethod|\ReflectionProperty $reflector - */ - public function loadAnnotations(object $reflector): iterable + public function loadAnnotations(\ReflectionMethod|\ReflectionClass|\ReflectionProperty $reflector): iterable { foreach ($reflector->getAttributes() as $attribute) { if ($this->isKnownAttribute($attribute->getName())) { @@ -193,13 +193,13 @@ public function loadAnnotations(object $reflector): iterable } if ($reflector instanceof \ReflectionClass) { - yield from $this->reader->getClassAnnotations($reflector); + yield from $this->getClassAnnotations($reflector); } if ($reflector instanceof \ReflectionMethod) { - yield from $this->reader->getMethodAnnotations($reflector); + yield from $this->getMethodAnnotations($reflector); } if ($reflector instanceof \ReflectionProperty) { - yield from $this->reader->getPropertyAnnotations($reflector); + yield from $this->getPropertyAnnotations($reflector); } } @@ -229,4 +229,49 @@ private function isKnownAttribute(string $attributeName): bool return false; } + + /** + * @return object[] + */ + private function getClassAnnotations(\ReflectionClass $reflector): array + { + if ($annotations = array_filter( + $this->reader->getClassAnnotations($reflector), + fn (object $annotation): bool => $this->isKnownAttribute($annotation::class), + )) { + trigger_deprecation('symfony/serializer', '6.4', 'Class "%s" uses Doctrine Annotations to configure serialization, which is deprecated. Use PHP attributes instead.', $reflector->getName()); + } + + return $annotations; + } + + /** + * @return object[] + */ + private function getMethodAnnotations(\ReflectionMethod $reflector): array + { + if ($annotations = array_filter( + $this->reader->getMethodAnnotations($reflector), + fn (object $annotation): bool => $this->isKnownAttribute($annotation::class), + )) { + trigger_deprecation('symfony/serializer', '6.4', 'Method "%s::%s()" uses Doctrine Annotations to configure serialization, which is deprecated. Use PHP attributes instead.', $reflector->getDeclaringClass()->getName(), $reflector->getName()); + } + + return $annotations; + } + + /** + * @return object[] + */ + private function getPropertyAnnotations(\ReflectionProperty $reflector): array + { + if ($annotations = array_filter( + $this->reader->getPropertyAnnotations($reflector), + fn (object $annotation): bool => $this->isKnownAttribute($annotation::class), + )) { + trigger_deprecation('symfony/serializer', '6.4', 'Property "%s::$%s" uses Doctrine Annotations to configure serialization, which is deprecated. Use PHP attributes instead.', $reflector->getDeclaringClass()->getName(), $reflector->getName()); + } + + return $annotations; + } } diff --git a/Tests/Fixtures/Annotations/GroupDummy.php b/Tests/Fixtures/Annotations/GroupDummy.php index 36a63e6f8..92935f490 100644 --- a/Tests/Fixtures/Annotations/GroupDummy.php +++ b/Tests/Fixtures/Annotations/GroupDummy.php @@ -13,7 +13,7 @@ use Symfony\Component\Serializer\Annotation\Groups; use Symfony\Component\Serializer\Tests\Fixtures\ChildOfGroupsAnnotationDummy; -use Symfony\Component\Serializer\Tests\Fixtures\GroupDummyInterface; +use Symfony\Component\Serializer\Tests\Fixtures\Annotations\GroupDummyInterface; /** * @author Kévin Dunglas diff --git a/Tests/Fixtures/GroupDummyInterface.php b/Tests/Fixtures/Annotations/GroupDummyInterface.php similarity index 87% rename from Tests/Fixtures/GroupDummyInterface.php rename to Tests/Fixtures/Annotations/GroupDummyInterface.php index a60629e6d..c9a736bfa 100644 --- a/Tests/Fixtures/GroupDummyInterface.php +++ b/Tests/Fixtures/Annotations/GroupDummyInterface.php @@ -9,7 +9,7 @@ * file that was distributed with this source code. */ -namespace Symfony\Component\Serializer\Tests\Fixtures; +namespace Symfony\Component\Serializer\Tests\Fixtures\Annotations; use Symfony\Component\Serializer\Annotation\Groups; diff --git a/Tests/Fixtures/Attributes/GroupDummy.php b/Tests/Fixtures/Attributes/GroupDummy.php index 1cb2026d9..c0a6c6d8e 100644 --- a/Tests/Fixtures/Attributes/GroupDummy.php +++ b/Tests/Fixtures/Attributes/GroupDummy.php @@ -13,7 +13,6 @@ use Symfony\Component\Serializer\Annotation\Groups; use Symfony\Component\Serializer\Tests\Fixtures\ChildOfGroupsAnnotationDummy; -use Symfony\Component\Serializer\Tests\Fixtures\GroupDummyInterface; /** * @author Kévin Dunglas diff --git a/Tests/Fixtures/Annotations/GroupDummyChild.php b/Tests/Fixtures/Attributes/GroupDummyChild.php similarity index 87% rename from Tests/Fixtures/Annotations/GroupDummyChild.php rename to Tests/Fixtures/Attributes/GroupDummyChild.php index 7e4081873..7167a0b18 100644 --- a/Tests/Fixtures/Annotations/GroupDummyChild.php +++ b/Tests/Fixtures/Attributes/GroupDummyChild.php @@ -9,7 +9,7 @@ * file that was distributed with this source code. */ -namespace Symfony\Component\Serializer\Tests\Fixtures\Annotations; +namespace Symfony\Component\Serializer\Tests\Fixtures\Attributes; class GroupDummyChild extends GroupDummy { diff --git a/Tests/Fixtures/Attributes/GroupDummyInterface.php b/Tests/Fixtures/Attributes/GroupDummyInterface.php new file mode 100644 index 000000000..7920173ae --- /dev/null +++ b/Tests/Fixtures/Attributes/GroupDummyInterface.php @@ -0,0 +1,23 @@ + + * + * 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\Attributes; + +use Symfony\Component\Serializer\Annotation\Groups; + +/** + * @author Kévin Dunglas + */ +interface GroupDummyInterface +{ + #[Groups(['a', 'name_converter'])] + public function getSymfony(); +} diff --git a/Tests/Fixtures/DummyMessageInterface.php b/Tests/Fixtures/DummyMessageInterface.php index 55bb00bc8..8f7589085 100644 --- a/Tests/Fixtures/DummyMessageInterface.php +++ b/Tests/Fixtures/DummyMessageInterface.php @@ -14,13 +14,12 @@ use Symfony\Component\Serializer\Annotation\DiscriminatorMap; /** - * @DiscriminatorMap(typeProperty="type", mapping={ - * "one"="Symfony\Component\Serializer\Tests\Fixtures\DummyMessageNumberOne", - * "two"="Symfony\Component\Serializer\Tests\Fixtures\DummyMessageNumberTwo" - * }) - * * @author Samuel Roze */ +#[DiscriminatorMap(typeProperty: 'type', mapping: [ + 'one' => DummyMessageNumberOne::class, + 'two' => DummyMessageNumberTwo::class, +])] interface DummyMessageInterface { } diff --git a/Tests/Fixtures/DummyMessageNumberOne.php b/Tests/Fixtures/DummyMessageNumberOne.php index 200476b54..663961b3f 100644 --- a/Tests/Fixtures/DummyMessageNumberOne.php +++ b/Tests/Fixtures/DummyMessageNumberOne.php @@ -20,8 +20,6 @@ class DummyMessageNumberOne implements DummyMessageInterface { public $one; - /** - * @Groups({"two"}) - */ + #[Groups(['two'])] public $two; } diff --git a/Tests/Fixtures/OtherSerializedNameDummy.php b/Tests/Fixtures/OtherSerializedNameDummy.php index 1ac543ca6..58c5628e6 100644 --- a/Tests/Fixtures/OtherSerializedNameDummy.php +++ b/Tests/Fixtures/OtherSerializedNameDummy.php @@ -19,9 +19,7 @@ */ class OtherSerializedNameDummy { - /** - * @Groups({"a"}) - */ + #[Groups(['a'])] private $buz; public function setBuz($buz) @@ -34,10 +32,7 @@ public function getBuz() return $this->buz; } - /** - * @Groups({"b"}) - * @SerializedName("buz") - */ + #[Groups(['b']), SerializedName('buz')] public function getBuzForExport() { return $this->buz.' Rocks'; diff --git a/Tests/Fixtures/invalid-ignore.yml b/Tests/Fixtures/invalid-ignore.yml index 00aa0fa96..f81636cc5 100644 --- a/Tests/Fixtures/invalid-ignore.yml +++ b/Tests/Fixtures/invalid-ignore.yml @@ -1,4 +1,4 @@ -'Symfony\Component\Serializer\Tests\Fixtures\Annotations\IgnoreDummy': +'Symfony\Component\Serializer\Tests\Fixtures\Attributes\IgnoreDummy': attributes: ignored1: ignore: foo diff --git a/Tests/Fixtures/serialization.xml b/Tests/Fixtures/serialization.xml index 5ca6ac412..512736db4 100644 --- a/Tests/Fixtures/serialization.xml +++ b/Tests/Fixtures/serialization.xml @@ -4,7 +4,7 @@ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://symfony.com/schema/dic/serializer-mapping https://symfony.com/schema/dic/serializer-mapping/serializer-mapping-1.0.xsd"> - + group1 group2 @@ -15,41 +15,41 @@ - + - + - + - + - + - - + + - + - + dummy_parent_value @@ -62,7 +62,7 @@ - + value diff --git a/Tests/Fixtures/serialization.yml b/Tests/Fixtures/serialization.yml index e052d65a8..4371016e3 100644 --- a/Tests/Fixtures/serialization.yml +++ b/Tests/Fixtures/serialization.yml @@ -1,47 +1,47 @@ -'Symfony\Component\Serializer\Tests\Fixtures\Annotations\GroupDummy': +'Symfony\Component\Serializer\Tests\Fixtures\Attributes\GroupDummy': attributes: foo: groups: ['group1', 'group2'] bar: groups: ['group2'] -'Symfony\Component\Serializer\Tests\Fixtures\Annotations\MaxDepthDummy': +'Symfony\Component\Serializer\Tests\Fixtures\Attributes\MaxDepthDummy': attributes: foo: max_depth: 2 bar: max_depth: 3 -'Symfony\Component\Serializer\Tests\Fixtures\Annotations\SerializedNameDummy': +'Symfony\Component\Serializer\Tests\Fixtures\Attributes\SerializedNameDummy': attributes: foo: serialized_name: 'baz' bar: serialized_name: 'qux' -'Symfony\Component\Serializer\Tests\Fixtures\Annotations\SerializedPathDummy': +'Symfony\Component\Serializer\Tests\Fixtures\Attributes\SerializedPathDummy': attributes: three: serialized_path: '[one][two]' seven: serialized_path: '[three][four]' -'Symfony\Component\Serializer\Tests\Fixtures\Annotations\SerializedPathInConstructorDummy': +'Symfony\Component\Serializer\Tests\Fixtures\Attributes\SerializedPathInConstructorDummy': attributes: three: serialized_path: '[one][two]' -'Symfony\Component\Serializer\Tests\Fixtures\Annotations\AbstractDummy': +'Symfony\Component\Serializer\Tests\Fixtures\Attributes\AbstractDummy': discriminator_map: type_property: type mapping: - first: 'Symfony\Component\Serializer\Tests\Fixtures\Annotations\AbstractDummyFirstChild' - second: 'Symfony\Component\Serializer\Tests\Fixtures\Annotations\AbstractDummySecondChild' + first: 'Symfony\Component\Serializer\Tests\Fixtures\Attributes\AbstractDummyFirstChild' + second: 'Symfony\Component\Serializer\Tests\Fixtures\Attributes\AbstractDummySecondChild' attributes: foo: ~ -'Symfony\Component\Serializer\Tests\Fixtures\Annotations\IgnoreDummy': +'Symfony\Component\Serializer\Tests\Fixtures\Attributes\IgnoreDummy': attributes: ignored1: ignore: true ignored2: ignore: true -Symfony\Component\Serializer\Tests\Fixtures\Annotations\ContextDummyParent: +Symfony\Component\Serializer\Tests\Fixtures\Attributes\ContextDummyParent: attributes: parentProperty: contexts: @@ -50,7 +50,7 @@ Symfony\Component\Serializer\Tests\Fixtures\Annotations\ContextDummyParent: contexts: - { normalization_context: { prop: dummy_parent_value } } -Symfony\Component\Serializer\Tests\Fixtures\Annotations\ContextDummy: +Symfony\Component\Serializer\Tests\Fixtures\Attributes\ContextDummy: attributes: foo: contexts: diff --git a/Tests/Mapping/ClassDiscriminatorMappingTest.php b/Tests/Mapping/ClassDiscriminatorMappingTest.php index 4dcd64eca..a8c736b8c 100644 --- a/Tests/Mapping/ClassDiscriminatorMappingTest.php +++ b/Tests/Mapping/ClassDiscriminatorMappingTest.php @@ -13,9 +13,9 @@ use PHPUnit\Framework\TestCase; use Symfony\Component\Serializer\Mapping\ClassDiscriminatorMapping; -use Symfony\Component\Serializer\Tests\Fixtures\Annotations\AbstractDummyFirstChild; -use Symfony\Component\Serializer\Tests\Fixtures\Annotations\AbstractDummySecondChild; -use Symfony\Component\Serializer\Tests\Fixtures\Annotations\AbstractDummyThirdChild; +use Symfony\Component\Serializer\Tests\Fixtures\Attributes\AbstractDummyFirstChild; +use Symfony\Component\Serializer\Tests\Fixtures\Attributes\AbstractDummySecondChild; +use Symfony\Component\Serializer\Tests\Fixtures\Attributes\AbstractDummyThirdChild; /** * @author Samuel Roze diff --git a/Tests/Mapping/Factory/ClassMetadataFactoryCompilerTest.php b/Tests/Mapping/Factory/ClassMetadataFactoryCompilerTest.php index 903612c68..fc61b3752 100644 --- a/Tests/Mapping/Factory/ClassMetadataFactoryCompilerTest.php +++ b/Tests/Mapping/Factory/ClassMetadataFactoryCompilerTest.php @@ -11,23 +11,19 @@ namespace Symfony\Component\Serializer\Tests\Mapping\Factory; -use Doctrine\Common\Annotations\AnnotationReader; use PHPUnit\Framework\TestCase; use Symfony\Component\Serializer\Mapping\Factory\ClassMetadataFactory; use Symfony\Component\Serializer\Mapping\Factory\ClassMetadataFactoryCompiler; use Symfony\Component\Serializer\Mapping\Loader\AnnotationLoader; -use Symfony\Component\Serializer\Tests\Fixtures\Annotations\MaxDepthDummy; -use Symfony\Component\Serializer\Tests\Fixtures\Annotations\SerializedNameDummy; -use Symfony\Component\Serializer\Tests\Fixtures\Annotations\SerializedPathDummy; -use Symfony\Component\Serializer\Tests\Fixtures\Annotations\SerializedPathInConstructorDummy; +use Symfony\Component\Serializer\Tests\Fixtures\Attributes\MaxDepthDummy; +use Symfony\Component\Serializer\Tests\Fixtures\Attributes\SerializedNameDummy; +use Symfony\Component\Serializer\Tests\Fixtures\Attributes\SerializedPathDummy; +use Symfony\Component\Serializer\Tests\Fixtures\Attributes\SerializedPathInConstructorDummy; use Symfony\Component\Serializer\Tests\Fixtures\Dummy; final class ClassMetadataFactoryCompilerTest extends TestCase { - /** - * @var string - */ - private $dumpPath; + private string $dumpPath; protected function setUp(): void { @@ -41,7 +37,7 @@ protected function tearDown(): void public function testItDumpMetadata() { - $classMetatadataFactory = new ClassMetadataFactory(new AnnotationLoader(new AnnotationReader())); + $classMetatadataFactory = new ClassMetadataFactory(new AnnotationLoader()); $dummyMetadata = $classMetatadataFactory->getMetadataFor(Dummy::class); $maxDepthDummyMetadata = $classMetatadataFactory->getMetadataFor(MaxDepthDummy::class); diff --git a/Tests/Mapping/Factory/ClassMetadataFactoryTest.php b/Tests/Mapping/Factory/ClassMetadataFactoryTest.php index bef034a8f..d034a06c6 100644 --- a/Tests/Mapping/Factory/ClassMetadataFactoryTest.php +++ b/Tests/Mapping/Factory/ClassMetadataFactoryTest.php @@ -11,12 +11,14 @@ namespace Symfony\Component\Serializer\Tests\Mapping\Factory; -use Doctrine\Common\Annotations\AnnotationReader; use PHPUnit\Framework\TestCase; use Symfony\Component\Serializer\Mapping\Factory\ClassMetadataFactory; use Symfony\Component\Serializer\Mapping\Factory\ClassMetadataFactoryInterface; use Symfony\Component\Serializer\Mapping\Loader\AnnotationLoader; use Symfony\Component\Serializer\Mapping\Loader\LoaderChain; +use Symfony\Component\Serializer\Tests\Fixtures\Attributes\GroupDummy; +use Symfony\Component\Serializer\Tests\Fixtures\Attributes\GroupDummyInterface; +use Symfony\Component\Serializer\Tests\Fixtures\Attributes\GroupDummyParent; use Symfony\Component\Serializer\Tests\Mapping\TestClassMetadataFactory; /** @@ -32,18 +34,18 @@ public function testInterface() public function testGetMetadataFor() { - $factory = new ClassMetadataFactory(new AnnotationLoader(new AnnotationReader())); - $classMetadata = $factory->getMetadataFor('Symfony\Component\Serializer\Tests\Fixtures\Annotations\GroupDummy'); + $factory = new ClassMetadataFactory(new AnnotationLoader()); + $classMetadata = $factory->getMetadataFor(GroupDummy::class); - $this->assertEquals(TestClassMetadataFactory::createClassMetadata('Symfony\Component\Serializer\Tests\Fixtures\Annotations', true, true), $classMetadata); + $this->assertEquals(TestClassMetadataFactory::createClassMetadata('Symfony\Component\Serializer\Tests\Fixtures\Attributes', true, true), $classMetadata); } public function testHasMetadataFor() { - $factory = new ClassMetadataFactory(new AnnotationLoader(new AnnotationReader())); - $this->assertTrue($factory->hasMetadataFor('Symfony\Component\Serializer\Tests\Fixtures\Annotations\GroupDummy')); - $this->assertTrue($factory->hasMetadataFor('Symfony\Component\Serializer\Tests\Fixtures\Annotations\GroupDummyParent')); - $this->assertTrue($factory->hasMetadataFor('Symfony\Component\Serializer\Tests\Fixtures\GroupDummyInterface')); + $factory = new ClassMetadataFactory(new AnnotationLoader()); + $this->assertTrue($factory->hasMetadataFor(GroupDummy::class)); + $this->assertTrue($factory->hasMetadataFor(GroupDummyParent::class)); + $this->assertTrue($factory->hasMetadataFor(GroupDummyInterface::class)); $this->assertFalse($factory->hasMetadataFor('Dunglas\Entity')); } } diff --git a/Tests/Mapping/Factory/CompiledClassMetadataFactoryTest.php b/Tests/Mapping/Factory/CompiledClassMetadataFactoryTest.php index d82431a8a..683f445df 100644 --- a/Tests/Mapping/Factory/CompiledClassMetadataFactoryTest.php +++ b/Tests/Mapping/Factory/CompiledClassMetadataFactoryTest.php @@ -16,7 +16,7 @@ use Symfony\Component\Serializer\Mapping\ClassMetadata; use Symfony\Component\Serializer\Mapping\Factory\ClassMetadataFactoryInterface; use Symfony\Component\Serializer\Mapping\Factory\CompiledClassMetadataFactory; -use Symfony\Component\Serializer\Tests\Fixtures\Annotations\SerializedNameDummy; +use Symfony\Component\Serializer\Tests\Fixtures\Attributes\SerializedNameDummy; use Symfony\Component\Serializer\Tests\Fixtures\Dummy; /** diff --git a/Tests/Mapping/Loader/AnnotationLoaderTestCase.php b/Tests/Mapping/Loader/AnnotationLoaderTestCase.php index 2dbd03703..40d5e3cfe 100644 --- a/Tests/Mapping/Loader/AnnotationLoaderTestCase.php +++ b/Tests/Mapping/Loader/AnnotationLoaderTestCase.php @@ -172,7 +172,7 @@ public function testCanHandleUnrelatedIgnoredMethods() $loader->loadClassMetadata($metadata); } - public function testIgnoreGetterWirhRequiredParameterIfIgnoreAnnotationIsUsed() + public function testIgnoreGetterWithRequiredParameterIfIgnoreAnnotationIsUsed() { $classMetadata = new ClassMetadata($this->getNamespace().'\IgnoreDummyAdditionalGetter'); $this->getLoaderForContextMapping()->loadClassMetadata($classMetadata); @@ -182,7 +182,7 @@ public function testIgnoreGetterWirhRequiredParameterIfIgnoreAnnotationIsUsed() self::assertArrayHasKey('extraValue2', $attributes); } - public function testIgnoreGetterWirhRequiredParameterIfIgnoreAnnotationIsNotUsed() + public function testIgnoreGetterWithRequiredParameterIfIgnoreAnnotationIsNotUsed() { $classMetadata = new ClassMetadata($this->getNamespace().'\IgnoreDummyAdditionalGetterWithoutIgnoreAnnotations'); $this->getLoaderForContextMapping()->loadClassMetadata($classMetadata); diff --git a/Tests/Mapping/Loader/AnnotationLoaderWithDoctrineAnnotationsTest.php b/Tests/Mapping/Loader/AnnotationLoaderWithDoctrineAnnotationsTest.php index cfb1547c5..348f8c71c 100644 --- a/Tests/Mapping/Loader/AnnotationLoaderWithDoctrineAnnotationsTest.php +++ b/Tests/Mapping/Loader/AnnotationLoaderWithDoctrineAnnotationsTest.php @@ -12,10 +12,152 @@ namespace Symfony\Component\Serializer\Tests\Mapping\Loader; use Doctrine\Common\Annotations\AnnotationReader; +use Symfony\Bridge\PhpUnit\ExpectDeprecationTrait; use Symfony\Component\Serializer\Mapping\Loader\AnnotationLoader; +/** + * @group legacy + */ class AnnotationLoaderWithDoctrineAnnotationsTest extends AnnotationLoaderTestCase { + use ExpectDeprecationTrait; + + protected function setUp(): void + { + $this->expectDeprecation('Since symfony/validator 6.4: Passing a "Doctrine\Common\Annotations\AnnotationReader" instance as argument 1 to "Symfony\Component\Serializer\Mapping\Loader\AnnotationLoader::__construct()" is deprecated, pass null or omit the parameter instead.'); + + parent::setUp(); + } + + public function testLoadClassMetadataReturnsTrueIfSuccessful() + { + $this->expectDeprecation('Since symfony/serializer 6.4: Property "Symfony\Component\Serializer\Tests\Fixtures\Annotations\GroupDummy::$foo" uses Doctrine Annotations to configure serialization, which is deprecated. Use PHP attributes instead.'); + $this->expectDeprecation('Since symfony/serializer 6.4: Property "Symfony\Component\Serializer\Tests\Fixtures\Annotations\GroupDummy::$bar" uses Doctrine Annotations to configure serialization, which is deprecated. Use PHP attributes instead.'); + $this->expectDeprecation('Since symfony/serializer 6.4: Property "Symfony\Component\Serializer\Tests\Fixtures\Annotations\GroupDummy::$quux" uses Doctrine Annotations to configure serialization, which is deprecated. Use PHP attributes instead.'); + $this->expectDeprecation('Since symfony/serializer 6.4: Method "Symfony\Component\Serializer\Tests\Fixtures\Annotations\GroupDummy::setBar()" uses Doctrine Annotations to configure serialization, which is deprecated. Use PHP attributes instead.'); + $this->expectDeprecation('Since symfony/serializer 6.4: Method "Symfony\Component\Serializer\Tests\Fixtures\Annotations\GroupDummy::getBar()" uses Doctrine Annotations to configure serialization, which is deprecated. Use PHP attributes instead.'); + $this->expectDeprecation('Since symfony/serializer 6.4: Method "Symfony\Component\Serializer\Tests\Fixtures\Annotations\GroupDummy::isFooBar()" uses Doctrine Annotations to configure serialization, which is deprecated. Use PHP attributes instead.'); + + parent::testLoadClassMetadataReturnsTrueIfSuccessful(); + } + + public function testLoadGroups() + { + $this->expectDeprecation('Since symfony/serializer 6.4: Property "Symfony\Component\Serializer\Tests\Fixtures\Annotations\GroupDummy::$foo" uses Doctrine Annotations to configure serialization, which is deprecated. Use PHP attributes instead.'); + $this->expectDeprecation('Since symfony/serializer 6.4: Property "Symfony\Component\Serializer\Tests\Fixtures\Annotations\GroupDummy::$bar" uses Doctrine Annotations to configure serialization, which is deprecated. Use PHP attributes instead.'); + $this->expectDeprecation('Since symfony/serializer 6.4: Property "Symfony\Component\Serializer\Tests\Fixtures\Annotations\GroupDummy::$quux" uses Doctrine Annotations to configure serialization, which is deprecated. Use PHP attributes instead.'); + $this->expectDeprecation('Since symfony/serializer 6.4: Method "Symfony\Component\Serializer\Tests\Fixtures\Annotations\GroupDummy::setBar()" uses Doctrine Annotations to configure serialization, which is deprecated. Use PHP attributes instead.'); + $this->expectDeprecation('Since symfony/serializer 6.4: Method "Symfony\Component\Serializer\Tests\Fixtures\Annotations\GroupDummy::getBar()" uses Doctrine Annotations to configure serialization, which is deprecated. Use PHP attributes instead.'); + $this->expectDeprecation('Since symfony/serializer 6.4: Method "Symfony\Component\Serializer\Tests\Fixtures\Annotations\GroupDummy::isFooBar()" uses Doctrine Annotations to configure serialization, which is deprecated. Use PHP attributes instead.'); + + parent::testLoadGroups(); + } + + public function testLoadDiscriminatorMap() + { + $this->expectDeprecation('Since symfony/serializer 6.4: Class "Symfony\Component\Serializer\Tests\Fixtures\Annotations\AbstractDummy" uses Doctrine Annotations to configure serialization, which is deprecated. Use PHP attributes instead.'); + + parent::testLoadDiscriminatorMap(); + } + + public function testLoadMaxDepth() + { + $this->expectDeprecation('Since symfony/serializer 6.4: Property "Symfony\Component\Serializer\Tests\Fixtures\Annotations\MaxDepthDummy::$foo" uses Doctrine Annotations to configure serialization, which is deprecated. Use PHP attributes instead.'); + $this->expectDeprecation('Since symfony/serializer 6.4: Method "Symfony\Component\Serializer\Tests\Fixtures\Annotations\MaxDepthDummy::getBar()" uses Doctrine Annotations to configure serialization, which is deprecated. Use PHP attributes instead.'); + + parent::testLoadMaxDepth(); + } + + public function testLoadSerializedName() + { + $this->expectDeprecation('Since symfony/serializer 6.4: Property "Symfony\Component\Serializer\Tests\Fixtures\Annotations\SerializedNameDummy::$foo" uses Doctrine Annotations to configure serialization, which is deprecated. Use PHP attributes instead.'); + $this->expectDeprecation('Since symfony/serializer 6.4: Method "Symfony\Component\Serializer\Tests\Fixtures\Annotations\SerializedNameDummy::getBar()" uses Doctrine Annotations to configure serialization, which is deprecated. Use PHP attributes instead.'); + + parent::testLoadSerializedName(); + } + + public function testLoadSerializedPath() + { + $this->expectDeprecation('Since symfony/serializer 6.4: Property "Symfony\Component\Serializer\Tests\Fixtures\Annotations\SerializedPathDummy::$three" uses Doctrine Annotations to configure serialization, which is deprecated. Use PHP attributes instead.'); + $this->expectDeprecation('Since symfony/serializer 6.4: Method "Symfony\Component\Serializer\Tests\Fixtures\Annotations\SerializedPathDummy::getSeven()" uses Doctrine Annotations to configure serialization, which is deprecated. Use PHP attributes instead.'); + + parent::testLoadSerializedPath(); + } + + public function testLoadSerializedPathInConstructor() + { + $this->expectDeprecation('Since symfony/serializer 6.4: Property "Symfony\Component\Serializer\Tests\Fixtures\Annotations\SerializedPathInConstructorDummy::$three" uses Doctrine Annotations to configure serialization, which is deprecated. Use PHP attributes instead.'); + + parent::testLoadSerializedPathInConstructor(); + } + + public function testLoadClassMetadataAndMerge() + { + $this->expectDeprecation('Since symfony/serializer 6.4: Property "Symfony\Component\Serializer\Tests\Fixtures\Annotations\GroupDummyParent::$kevin" uses Doctrine Annotations to configure serialization, which is deprecated. Use PHP attributes instead.'); + $this->expectDeprecation('Since symfony/serializer 6.4: Method "Symfony\Component\Serializer\Tests\Fixtures\Annotations\GroupDummyParent::getCoopTilleuls()" uses Doctrine Annotations to configure serialization, which is deprecated. Use PHP attributes instead.'); + $this->expectDeprecation('Since symfony/serializer 6.4: Property "Symfony\Component\Serializer\Tests\Fixtures\Annotations\GroupDummy::$foo" uses Doctrine Annotations to configure serialization, which is deprecated. Use PHP attributes instead.'); + $this->expectDeprecation('Since symfony/serializer 6.4: Property "Symfony\Component\Serializer\Tests\Fixtures\Annotations\GroupDummy::$bar" uses Doctrine Annotations to configure serialization, which is deprecated. Use PHP attributes instead.'); + $this->expectDeprecation('Since symfony/serializer 6.4: Property "Symfony\Component\Serializer\Tests\Fixtures\Annotations\GroupDummy::$quux" uses Doctrine Annotations to configure serialization, which is deprecated. Use PHP attributes instead.'); + $this->expectDeprecation('Since symfony/serializer 6.4: Method "Symfony\Component\Serializer\Tests\Fixtures\Annotations\GroupDummy::setBar()" uses Doctrine Annotations to configure serialization, which is deprecated. Use PHP attributes instead.'); + $this->expectDeprecation('Since symfony/serializer 6.4: Method "Symfony\Component\Serializer\Tests\Fixtures\Annotations\GroupDummy::getBar()" uses Doctrine Annotations to configure serialization, which is deprecated. Use PHP attributes instead.'); + $this->expectDeprecation('Since symfony/serializer 6.4: Method "Symfony\Component\Serializer\Tests\Fixtures\Annotations\GroupDummy::isFooBar()" uses Doctrine Annotations to configure serialization, which is deprecated. Use PHP attributes instead.'); + + parent::testLoadClassMetadataAndMerge(); + } + + public function testLoadIgnore() + { + $this->expectDeprecation('Since symfony/serializer 6.4: Property "Symfony\Component\Serializer\Tests\Fixtures\Annotations\IgnoreDummy::$ignored1" uses Doctrine Annotations to configure serialization, which is deprecated. Use PHP attributes instead.'); + $this->expectDeprecation('Since symfony/serializer 6.4: Method "Symfony\Component\Serializer\Tests\Fixtures\Annotations\IgnoreDummy::getIgnored2()" uses Doctrine Annotations to configure serialization, which is deprecated. Use PHP attributes instead.'); + + parent::testLoadIgnore(); + } + + public function testLoadContexts() + { + $this->expectDeprecation('Since symfony/serializer 6.4: Property "Symfony\Component\Serializer\Tests\Fixtures\Annotations\ContextDummyParent::$parentProperty" uses Doctrine Annotations to configure serialization, which is deprecated. Use PHP attributes instead.'); + $this->expectDeprecation('Since symfony/serializer 6.4: Property "Symfony\Component\Serializer\Tests\Fixtures\Annotations\ContextDummyParent::$overriddenParentProperty" uses Doctrine Annotations to configure serialization, which is deprecated. Use PHP attributes instead.'); + $this->expectDeprecation('Since symfony/serializer 6.4: Property "Symfony\Component\Serializer\Tests\Fixtures\Annotations\ContextDummy::$foo" uses Doctrine Annotations to configure serialization, which is deprecated. Use PHP attributes instead.'); + $this->expectDeprecation('Since symfony/serializer 6.4: Property "Symfony\Component\Serializer\Tests\Fixtures\Annotations\ContextDummy::$bar" uses Doctrine Annotations to configure serialization, which is deprecated. Use PHP attributes instead.'); + $this->expectDeprecation('Since symfony/serializer 6.4: Property "Symfony\Component\Serializer\Tests\Fixtures\Annotations\ContextDummy::$overriddenParentProperty" uses Doctrine Annotations to configure serialization, which is deprecated. Use PHP attributes instead.'); + $this->expectDeprecation('Since symfony/serializer 6.4: Method "Symfony\Component\Serializer\Tests\Fixtures\Annotations\ContextDummy::getMethodWithContext()" uses Doctrine Annotations to configure serialization, which is deprecated. Use PHP attributes instead.'); + + parent::testLoadContexts(); + } + + public function testLoadContextsPropertiesPromoted() + { + $this->expectDeprecation('Since symfony/serializer 6.4: Property "Symfony\Component\Serializer\Tests\Fixtures\Annotations\ContextDummyParent::$parentProperty" uses Doctrine Annotations to configure serialization, which is deprecated. Use PHP attributes instead.'); + $this->expectDeprecation('Since symfony/serializer 6.4: Property "Symfony\Component\Serializer\Tests\Fixtures\Annotations\ContextDummyParent::$overriddenParentProperty" uses Doctrine Annotations to configure serialization, which is deprecated. Use PHP attributes instead.'); + $this->expectDeprecation('Since symfony/serializer 6.4: Property "Symfony\Component\Serializer\Tests\Fixtures\Annotations\ContextDummyPromotedProperties::$foo" uses Doctrine Annotations to configure serialization, which is deprecated. Use PHP attributes instead.'); + $this->expectDeprecation('Since symfony/serializer 6.4: Property "Symfony\Component\Serializer\Tests\Fixtures\Annotations\ContextDummyPromotedProperties::$bar" uses Doctrine Annotations to configure serialization, which is deprecated. Use PHP attributes instead.'); + $this->expectDeprecation('Since symfony/serializer 6.4: Property "Symfony\Component\Serializer\Tests\Fixtures\Annotations\ContextDummyPromotedProperties::$overriddenParentProperty" uses Doctrine Annotations to configure serialization, which is deprecated. Use PHP attributes instead.'); + $this->expectDeprecation('Since symfony/serializer 6.4: Method "Symfony\Component\Serializer\Tests\Fixtures\Annotations\ContextDummyPromotedProperties::getMethodWithContext()" uses Doctrine Annotations to configure serialization, which is deprecated. Use PHP attributes instead.'); + + parent::testLoadContextsPropertiesPromoted(); + } + + public function testThrowsOnContextOnInvalidMethod() + { + $this->expectDeprecation('Since symfony/serializer 6.4: Method "Symfony\Component\Serializer\Tests\Fixtures\Annotations\BadMethodContextDummy::badMethod()" uses Doctrine Annotations to configure serialization, which is deprecated. Use PHP attributes instead.'); + + parent::testThrowsOnContextOnInvalidMethod(); + } + + public function testCanHandleUnrelatedIgnoredMethods() + { + $this->expectDeprecation('Since symfony/serializer 6.4: Method "Symfony\Component\Serializer\Tests\Fixtures\Annotations\Entity45016::badIgnore()" uses Doctrine Annotations to configure serialization, which is deprecated. Use PHP attributes instead.'); + + parent::testCanHandleUnrelatedIgnoredMethods(); + } + + public function testIgnoreGetterWithRequiredParameterIfIgnoreAnnotationIsUsed() + { + $this->expectDeprecation('Since symfony/serializer 6.4: Method "Symfony\Component\Serializer\Tests\Fixtures\Annotations\IgnoreDummyAdditionalGetter::getMyValue()" uses Doctrine Annotations to configure serialization, which is deprecated. Use PHP attributes instead.'); + + parent::testIgnoreGetterWithRequiredParameterIfIgnoreAnnotationIsUsed(); + } + protected function createLoader(): AnnotationLoader { return new AnnotationLoader(new AnnotationReader()); diff --git a/Tests/Mapping/Loader/Features/ContextMappingTestTrait.php b/Tests/Mapping/Loader/Features/ContextMappingTestTrait.php index 97c70c149..c33d8a904 100644 --- a/Tests/Mapping/Loader/Features/ContextMappingTestTrait.php +++ b/Tests/Mapping/Loader/Features/ContextMappingTestTrait.php @@ -14,8 +14,8 @@ use PHPUnit\Framework\Assert; use Symfony\Component\Serializer\Mapping\ClassMetadata; use Symfony\Component\Serializer\Mapping\Loader\LoaderInterface; -use Symfony\Component\Serializer\Tests\Fixtures\Annotations\ContextDummy; -use Symfony\Component\Serializer\Tests\Fixtures\Annotations\ContextDummyParent; +use Symfony\Component\Serializer\Tests\Fixtures\Attributes\ContextDummy; +use Symfony\Component\Serializer\Tests\Fixtures\Attributes\ContextDummyParent; /** * @author Maxime Steinhausser @@ -29,7 +29,7 @@ public function testLoadContexts() $this->assertLoadedContexts(); } - public function assertLoadedContexts(string $dummyClass = ContextDummy::class, string $parentClass = ContextDummyParent::class) + public function assertLoadedContexts(string $dummyClass = ContextDummy::class, string $parentClass = ContextDummyParent::class): void { $loader = $this->getLoaderForContextMapping(); diff --git a/Tests/Mapping/Loader/XmlFileLoaderTest.php b/Tests/Mapping/Loader/XmlFileLoaderTest.php index 202534f56..c0298129e 100644 --- a/Tests/Mapping/Loader/XmlFileLoaderTest.php +++ b/Tests/Mapping/Loader/XmlFileLoaderTest.php @@ -17,10 +17,15 @@ use Symfony\Component\Serializer\Mapping\ClassMetadata; use Symfony\Component\Serializer\Mapping\Loader\LoaderInterface; use Symfony\Component\Serializer\Mapping\Loader\XmlFileLoader; -use Symfony\Component\Serializer\Tests\Fixtures\Annotations\AbstractDummy; -use Symfony\Component\Serializer\Tests\Fixtures\Annotations\AbstractDummyFirstChild; -use Symfony\Component\Serializer\Tests\Fixtures\Annotations\AbstractDummySecondChild; -use Symfony\Component\Serializer\Tests\Fixtures\Annotations\IgnoreDummy; +use Symfony\Component\Serializer\Tests\Fixtures\Attributes\AbstractDummy; +use Symfony\Component\Serializer\Tests\Fixtures\Attributes\AbstractDummyFirstChild; +use Symfony\Component\Serializer\Tests\Fixtures\Attributes\AbstractDummySecondChild; +use Symfony\Component\Serializer\Tests\Fixtures\Attributes\GroupDummy; +use Symfony\Component\Serializer\Tests\Fixtures\Attributes\IgnoreDummy; +use Symfony\Component\Serializer\Tests\Fixtures\Attributes\MaxDepthDummy; +use Symfony\Component\Serializer\Tests\Fixtures\Attributes\SerializedNameDummy; +use Symfony\Component\Serializer\Tests\Fixtures\Attributes\SerializedPathDummy; +use Symfony\Component\Serializer\Tests\Fixtures\Attributes\SerializedPathInConstructorDummy; use Symfony\Component\Serializer\Tests\Mapping\Loader\Features\ContextMappingTestTrait; use Symfony\Component\Serializer\Tests\Mapping\TestClassMetadataFactory; @@ -31,20 +36,13 @@ class XmlFileLoaderTest extends TestCase { use ContextMappingTestTrait; - /** - * @var XmlFileLoader - */ - private $loader; - - /** - * @var ClassMetadata - */ - private $metadata; + private XmlFileLoader $loader; + private ClassMetadata $metadata; protected function setUp(): void { $this->loader = new XmlFileLoader(__DIR__.'/../../Fixtures/serialization.xml'); - $this->metadata = new ClassMetadata('Symfony\Component\Serializer\Tests\Fixtures\Annotations\GroupDummy'); + $this->metadata = new ClassMetadata(GroupDummy::class); } public function testInterface() @@ -66,7 +64,7 @@ public function testLoadClassMetadata() public function testMaxDepth() { - $classMetadata = new ClassMetadata('Symfony\Component\Serializer\Tests\Fixtures\Annotations\MaxDepthDummy'); + $classMetadata = new ClassMetadata(MaxDepthDummy::class); $this->loader->loadClassMetadata($classMetadata); $attributesMetadata = $classMetadata->getAttributesMetadata(); @@ -76,7 +74,7 @@ public function testMaxDepth() public function testSerializedName() { - $classMetadata = new ClassMetadata('Symfony\Component\Serializer\Tests\Fixtures\Annotations\SerializedNameDummy'); + $classMetadata = new ClassMetadata(SerializedNameDummy::class); $this->loader->loadClassMetadata($classMetadata); $attributesMetadata = $classMetadata->getAttributesMetadata(); @@ -86,7 +84,7 @@ public function testSerializedName() public function testSerializedPath() { - $classMetadata = new ClassMetadata('Symfony\Component\Serializer\Tests\Fixtures\Annotations\SerializedPathDummy'); + $classMetadata = new ClassMetadata(SerializedPathDummy::class); $this->loader->loadClassMetadata($classMetadata); $attributesMetadata = $classMetadata->getAttributesMetadata(); @@ -96,7 +94,7 @@ public function testSerializedPath() public function testSerializedPathInConstructor() { - $classMetadata = new ClassMetadata('Symfony\Component\Serializer\Tests\Fixtures\Annotations\SerializedPathInConstructorDummy'); + $classMetadata = new ClassMetadata(SerializedPathInConstructorDummy::class); $this->loader->loadClassMetadata($classMetadata); $attributesMetadata = $classMetadata->getAttributesMetadata(); diff --git a/Tests/Mapping/Loader/YamlFileLoaderTest.php b/Tests/Mapping/Loader/YamlFileLoaderTest.php index dcfd2b4af..ea81a9d8a 100644 --- a/Tests/Mapping/Loader/YamlFileLoaderTest.php +++ b/Tests/Mapping/Loader/YamlFileLoaderTest.php @@ -19,10 +19,15 @@ use Symfony\Component\Serializer\Mapping\ClassMetadata; use Symfony\Component\Serializer\Mapping\Loader\LoaderInterface; use Symfony\Component\Serializer\Mapping\Loader\YamlFileLoader; -use Symfony\Component\Serializer\Tests\Fixtures\Annotations\AbstractDummy; -use Symfony\Component\Serializer\Tests\Fixtures\Annotations\AbstractDummyFirstChild; -use Symfony\Component\Serializer\Tests\Fixtures\Annotations\AbstractDummySecondChild; -use Symfony\Component\Serializer\Tests\Fixtures\Annotations\IgnoreDummy; +use Symfony\Component\Serializer\Tests\Fixtures\Attributes\AbstractDummy; +use Symfony\Component\Serializer\Tests\Fixtures\Attributes\AbstractDummyFirstChild; +use Symfony\Component\Serializer\Tests\Fixtures\Attributes\AbstractDummySecondChild; +use Symfony\Component\Serializer\Tests\Fixtures\Attributes\GroupDummy; +use Symfony\Component\Serializer\Tests\Fixtures\Attributes\IgnoreDummy; +use Symfony\Component\Serializer\Tests\Fixtures\Attributes\MaxDepthDummy; +use Symfony\Component\Serializer\Tests\Fixtures\Attributes\SerializedNameDummy; +use Symfony\Component\Serializer\Tests\Fixtures\Attributes\SerializedPathDummy; +use Symfony\Component\Serializer\Tests\Fixtures\Attributes\SerializedPathInConstructorDummy; use Symfony\Component\Serializer\Tests\Mapping\Loader\Features\ContextMappingTestTrait; use Symfony\Component\Serializer\Tests\Mapping\TestClassMetadataFactory; @@ -33,19 +38,13 @@ class YamlFileLoaderTest extends TestCase { use ContextMappingTestTrait; - /** - * @var YamlFileLoader - */ - private $loader; - /** - * @var ClassMetadata - */ - private $metadata; + private YamlFileLoader $loader; + private ClassMetadata $metadata; protected function setUp(): void { $this->loader = new YamlFileLoader(__DIR__.'/../../Fixtures/serialization.yml'); - $this->metadata = new ClassMetadata('Symfony\Component\Serializer\Tests\Fixtures\Annotations\GroupDummy'); + $this->metadata = new ClassMetadata(GroupDummy::class); } public function testInterface() @@ -80,7 +79,7 @@ public function testLoadClassMetadata() public function testMaxDepth() { - $classMetadata = new ClassMetadata('Symfony\Component\Serializer\Tests\Fixtures\Annotations\MaxDepthDummy'); + $classMetadata = new ClassMetadata(MaxDepthDummy::class); $this->loader->loadClassMetadata($classMetadata); $attributesMetadata = $classMetadata->getAttributesMetadata(); @@ -90,7 +89,7 @@ public function testMaxDepth() public function testSerializedName() { - $classMetadata = new ClassMetadata('Symfony\Component\Serializer\Tests\Fixtures\Annotations\SerializedNameDummy'); + $classMetadata = new ClassMetadata(SerializedNameDummy::class); $this->loader->loadClassMetadata($classMetadata); $attributesMetadata = $classMetadata->getAttributesMetadata(); @@ -100,7 +99,7 @@ public function testSerializedName() public function testSerializedPath() { - $classMetadata = new ClassMetadata('Symfony\Component\Serializer\Tests\Fixtures\Annotations\SerializedPathDummy'); + $classMetadata = new ClassMetadata(SerializedPathDummy::class); $this->loader->loadClassMetadata($classMetadata); $attributesMetadata = $classMetadata->getAttributesMetadata(); @@ -110,7 +109,7 @@ public function testSerializedPath() public function testSerializedPathInConstructor() { - $classMetadata = new ClassMetadata('Symfony\Component\Serializer\Tests\Fixtures\Annotations\SerializedPathInConstructorDummy'); + $classMetadata = new ClassMetadata(SerializedPathInConstructorDummy::class); $this->loader->loadClassMetadata($classMetadata); $attributesMetadata = $classMetadata->getAttributesMetadata(); diff --git a/Tests/Mapping/TestClassMetadataFactory.php b/Tests/Mapping/TestClassMetadataFactory.php index a33ded3d8..61147316a 100644 --- a/Tests/Mapping/TestClassMetadataFactory.php +++ b/Tests/Mapping/TestClassMetadataFactory.php @@ -13,6 +13,7 @@ use Symfony\Component\Serializer\Mapping\AttributeMetadata; use Symfony\Component\Serializer\Mapping\ClassMetadata; +use Symfony\Component\Serializer\Tests\Fixtures\Attributes\GroupDummy; /** * @author Kévin Dunglas @@ -70,7 +71,7 @@ public static function createClassMetadata(string $namespace, bool $withParent = public static function createXmlCLassMetadata(): ClassMetadata { - $expected = new ClassMetadata('Symfony\Component\Serializer\Tests\Fixtures\Annotations\GroupDummy'); + $expected = new ClassMetadata(GroupDummy::class); $foo = new AttributeMetadata('foo'); $foo->addGroup('group1'); diff --git a/Tests/NameConverter/MetadataAwareNameConverterTest.php b/Tests/NameConverter/MetadataAwareNameConverterTest.php index 9491206ee..9ee6fcdf7 100644 --- a/Tests/NameConverter/MetadataAwareNameConverterTest.php +++ b/Tests/NameConverter/MetadataAwareNameConverterTest.php @@ -11,7 +11,6 @@ namespace Symfony\Component\Serializer\Tests\NameConverter; -use Doctrine\Common\Annotations\AnnotationReader; use PHPUnit\Framework\TestCase; use Symfony\Component\Serializer\Annotation\SerializedName; use Symfony\Component\Serializer\Annotation\SerializedPath; @@ -21,7 +20,7 @@ use Symfony\Component\Serializer\Mapping\Loader\AnnotationLoader; use Symfony\Component\Serializer\NameConverter\MetadataAwareNameConverter; use Symfony\Component\Serializer\NameConverter\NameConverterInterface; -use Symfony\Component\Serializer\Tests\Fixtures\Annotations\SerializedNameDummy; +use Symfony\Component\Serializer\Tests\Fixtures\Attributes\SerializedNameDummy; use Symfony\Component\Serializer\Tests\Fixtures\OtherSerializedNameDummy; /** @@ -39,9 +38,9 @@ public function testInterface() /** * @dataProvider attributeProvider */ - public function testNormalize($propertyName, $expected) + public function testNormalize(string|int $propertyName, string|int $expected) { - $classMetadataFactory = new ClassMetadataFactory(new AnnotationLoader(new AnnotationReader())); + $classMetadataFactory = new ClassMetadataFactory(new AnnotationLoader()); $nameConverter = new MetadataAwareNameConverter($classMetadataFactory); @@ -51,14 +50,14 @@ public function testNormalize($propertyName, $expected) /** * @dataProvider fallbackAttributeProvider */ - public function testNormalizeWithFallback($propertyName, $expected) + public function testNormalizeWithFallback(string|int $propertyName, string|int $expected) { - $classMetadataFactory = new ClassMetadataFactory(new AnnotationLoader(new AnnotationReader())); + $classMetadataFactory = new ClassMetadataFactory(new AnnotationLoader()); $fallback = $this->createMock(NameConverterInterface::class); $fallback ->method('normalize') - ->willReturnCallback(fn ($propertyName) => strtoupper($propertyName)) + ->willReturnCallback(static fn ($propertyName) => strtoupper($propertyName)) ; $nameConverter = new MetadataAwareNameConverter($classMetadataFactory, $fallback); @@ -69,9 +68,9 @@ public function testNormalizeWithFallback($propertyName, $expected) /** * @dataProvider attributeProvider */ - public function testDenormalize($expected, $propertyName) + public function testDenormalize(string|int $expected, string|int $propertyName) { - $classMetadataFactory = new ClassMetadataFactory(new AnnotationLoader(new AnnotationReader())); + $classMetadataFactory = new ClassMetadataFactory(new AnnotationLoader()); $nameConverter = new MetadataAwareNameConverter($classMetadataFactory); @@ -81,14 +80,14 @@ public function testDenormalize($expected, $propertyName) /** * @dataProvider fallbackAttributeProvider */ - public function testDenormalizeWithFallback($expected, $propertyName) + public function testDenormalizeWithFallback(string|int $expected, string|int $propertyName) { - $classMetadataFactory = new ClassMetadataFactory(new AnnotationLoader(new AnnotationReader())); + $classMetadataFactory = new ClassMetadataFactory(new AnnotationLoader()); $fallback = $this->createMock(NameConverterInterface::class); $fallback ->method('denormalize') - ->willReturnCallback(fn ($propertyName) => strtolower($propertyName)) + ->willReturnCallback(static fn ($propertyName) => strtolower($propertyName)) ; $nameConverter = new MetadataAwareNameConverter($classMetadataFactory, $fallback); @@ -119,9 +118,9 @@ public static function fallbackAttributeProvider(): array /** * @dataProvider attributeAndContextProvider */ - public function testNormalizeWithGroups($propertyName, $expected, $context = []) + public function testNormalizeWithGroups(string $propertyName, string $expected, array $context = []) { - $classMetadataFactory = new ClassMetadataFactory(new AnnotationLoader(new AnnotationReader())); + $classMetadataFactory = new ClassMetadataFactory(new AnnotationLoader()); $nameConverter = new MetadataAwareNameConverter($classMetadataFactory); @@ -131,16 +130,16 @@ public function testNormalizeWithGroups($propertyName, $expected, $context = []) /** * @dataProvider attributeAndContextProvider */ - public function testDenormalizeWithGroups($expected, $propertyName, $context = []) + public function testDenormalizeWithGroups(string $expected, string $propertyName, array $context = []) { - $classMetadataFactory = new ClassMetadataFactory(new AnnotationLoader(new AnnotationReader())); + $classMetadataFactory = new ClassMetadataFactory(new AnnotationLoader()); $nameConverter = new MetadataAwareNameConverter($classMetadataFactory); $this->assertEquals($expected, $nameConverter->denormalize($propertyName, OtherSerializedNameDummy::class, null, $context)); } - public static function attributeAndContextProvider() + public static function attributeAndContextProvider(): array { return [ ['buz', 'buz', ['groups' => ['a']]], @@ -154,7 +153,7 @@ public static function attributeAndContextProvider() public function testDenormalizeWithCacheContext() { - $classMetadataFactory = new ClassMetadataFactory(new AnnotationLoader(new AnnotationReader())); + $classMetadataFactory = new ClassMetadataFactory(new AnnotationLoader()); $nameConverter = new MetadataAwareNameConverter($classMetadataFactory); @@ -165,7 +164,7 @@ public function testDenormalizeWithCacheContext() public function testDenormalizeWithNestedPathAndName() { - $classMetadataFactory = new ClassMetadataFactory(new AnnotationLoader(new AnnotationReader())); + $classMetadataFactory = new ClassMetadataFactory(new AnnotationLoader()); $nameConverter = new MetadataAwareNameConverter($classMetadataFactory); $this->expectException(LogicException::class); $this->expectExceptionMessage('Found SerializedName and SerializedPath annotations on property "foo" of class "Symfony\Component\Serializer\Tests\NameConverter\NestedPathAndName".'); @@ -174,7 +173,7 @@ public function testDenormalizeWithNestedPathAndName() public function testNormalizeWithNestedPathAndName() { - $classMetadataFactory = new ClassMetadataFactory(new AnnotationLoader(new AnnotationReader())); + $classMetadataFactory = new ClassMetadataFactory(new AnnotationLoader()); $nameConverter = new MetadataAwareNameConverter($classMetadataFactory); $this->expectException(LogicException::class); $this->expectExceptionMessage('Found SerializedName and SerializedPath annotations on property "foo" of class "Symfony\Component\Serializer\Tests\NameConverter\NestedPathAndName".'); @@ -184,10 +183,6 @@ public function testNormalizeWithNestedPathAndName() class NestedPathAndName { - /** - * @SerializedName("five") - * - * @SerializedPath("[one][two][three]") - */ + #[SerializedName('five'), SerializedPath('[one][two][three]')] public $foo; } diff --git a/Tests/Normalizer/AbstractNormalizerTest.php b/Tests/Normalizer/AbstractNormalizerTest.php index cf51ce840..74018ebb4 100644 --- a/Tests/Normalizer/AbstractNormalizerTest.php +++ b/Tests/Normalizer/AbstractNormalizerTest.php @@ -27,7 +27,7 @@ use Symfony\Component\Serializer\Normalizer\PropertyNormalizer; use Symfony\Component\Serializer\Serializer; use Symfony\Component\Serializer\Tests\Fixtures\AbstractNormalizerDummy; -use Symfony\Component\Serializer\Tests\Fixtures\Annotations\IgnoreDummy; +use Symfony\Component\Serializer\Tests\Fixtures\Attributes\IgnoreDummy; use Symfony\Component\Serializer\Tests\Fixtures\Dummy; use Symfony\Component\Serializer\Tests\Fixtures\NullableConstructorArgumentDummy; use Symfony\Component\Serializer\Tests\Fixtures\NullableOptionalConstructorArgumentDummy; diff --git a/Tests/Normalizer/AbstractObjectNormalizerTest.php b/Tests/Normalizer/AbstractObjectNormalizerTest.php index bed7c33ce..42eed47eb 100644 --- a/Tests/Normalizer/AbstractObjectNormalizerTest.php +++ b/Tests/Normalizer/AbstractObjectNormalizerTest.php @@ -11,7 +11,6 @@ namespace Symfony\Component\Serializer\Tests\Normalizer; -use Doctrine\Common\Annotations\AnnotationReader; use PHPUnit\Framework\TestCase; use Symfony\Component\PropertyInfo\Extractor\PhpDocExtractor; use Symfony\Component\PropertyInfo\Extractor\ReflectionExtractor; @@ -42,9 +41,9 @@ use Symfony\Component\Serializer\Serializer; use Symfony\Component\Serializer\SerializerAwareInterface; use Symfony\Component\Serializer\SerializerInterface; -use Symfony\Component\Serializer\Tests\Fixtures\Annotations\AbstractDummy; -use Symfony\Component\Serializer\Tests\Fixtures\Annotations\AbstractDummyFirstChild; -use Symfony\Component\Serializer\Tests\Fixtures\Annotations\AbstractDummySecondChild; +use Symfony\Component\Serializer\Tests\Fixtures\Attributes\AbstractDummy; +use Symfony\Component\Serializer\Tests\Fixtures\Attributes\AbstractDummyFirstChild; +use Symfony\Component\Serializer\Tests\Fixtures\Attributes\AbstractDummySecondChild; use Symfony\Component\Serializer\Tests\Fixtures\DummyFirstChildQuux; use Symfony\Component\Serializer\Tests\Fixtures\DummySecondChildQuux; use Symfony\Component\Serializer\Tests\Normalizer\Features\ObjectDummyWithContextAttribute; @@ -76,7 +75,7 @@ public function testDenormalizeWithExtraAttribute() { $this->expectException(ExtraAttributesException::class); $this->expectExceptionMessage('Extra attributes are not allowed ("fooFoo" is unknown).'); - $factory = new ClassMetadataFactory(new AnnotationLoader(new AnnotationReader())); + $factory = new ClassMetadataFactory(new AnnotationLoader()); $normalizer = new AbstractObjectNormalizerDummy($factory); $normalizer->denormalize( ['fooFoo' => 'foo'], @@ -90,7 +89,7 @@ public function testDenormalizeWithExtraAttributes() { $this->expectException(ExtraAttributesException::class); $this->expectExceptionMessage('Extra attributes are not allowed ("fooFoo", "fooBar" are unknown).'); - $factory = new ClassMetadataFactory(new AnnotationLoader(new AnnotationReader())); + $factory = new ClassMetadataFactory(new AnnotationLoader()); $normalizer = new AbstractObjectNormalizerDummy($factory); $normalizer->denormalize( ['fooFoo' => 'foo', 'fooBar' => 'bar'], @@ -228,7 +227,7 @@ public function testNormalizeWithNestedAttributesMixingArrayTypes() $foobar = new AlreadyPopulatedNestedDummy(); $foobar->foo = 'foo'; $foobar->bar = 'bar'; - $classMetadataFactory = new ClassMetadataFactory(new AnnotationLoader(new AnnotationReader())); + $classMetadataFactory = new ClassMetadataFactory(new AnnotationLoader()); $normalizer = new ObjectNormalizer($classMetadataFactory, new MetadataAwareNameConverter($classMetadataFactory)); $normalizer->normalize($foobar, 'any'); } @@ -240,7 +239,7 @@ public function testNormalizeWithNestedAttributesElementAlreadySet() $foobar = new DuplicateValueNestedDummy(); $foobar->foo = 'foo'; $foobar->bar = 'bar'; - $classMetadataFactory = new ClassMetadataFactory(new AnnotationLoader(new AnnotationReader())); + $classMetadataFactory = new ClassMetadataFactory(new AnnotationLoader()); $normalizer = new ObjectNormalizer($classMetadataFactory, new MetadataAwareNameConverter($classMetadataFactory)); $normalizer->normalize($foobar, 'any'); } @@ -262,7 +261,7 @@ public function testNormalizeWithNestedAttributes() 'foo' => 'notfoo', 'baz' => 'baz', ]; - $classMetadataFactory = new ClassMetadataFactory(new AnnotationLoader(new AnnotationReader())); + $classMetadataFactory = new ClassMetadataFactory(new AnnotationLoader()); $normalizer = new ObjectNormalizer($classMetadataFactory, new MetadataAwareNameConverter($classMetadataFactory)); $test = $normalizer->normalize($foobar, 'any'); $this->assertSame($data, $test); @@ -288,7 +287,7 @@ public function testNormalizeWithNestedAttributesWithoutMetadata() public function testNormalizeWithNestedAttributesInConstructor() { - $classMetadataFactory = new ClassMetadataFactory(new AnnotationLoader(new AnnotationReader())); + $classMetadataFactory = new ClassMetadataFactory(new AnnotationLoader()); $normalizer = new ObjectNormalizer($classMetadataFactory, new MetadataAwareNameConverter($classMetadataFactory)); $test = $normalizer->normalize(new NestedDummyWithConstructor('foo', 'quux', 'notfoo', 'baz'), 'any'); @@ -306,7 +305,7 @@ public function testNormalizeWithNestedAttributesInConstructor() public function testNormalizeWithNestedAttributesInConstructorAndDiscriminatorMap() { - $classMetadataFactory = new ClassMetadataFactory(new AnnotationLoader(new AnnotationReader())); + $classMetadataFactory = new ClassMetadataFactory(new AnnotationLoader()); $normalizer = new ObjectNormalizer($classMetadataFactory, new MetadataAwareNameConverter($classMetadataFactory)); $test1 = $normalizer->normalize(new FirstNestedDummyWithConstructorAndDiscriminator('foo', 'notfoo', 'baz'), 'any'); @@ -449,7 +448,7 @@ private function getDenormalizerForStringCollection() public function testDenormalizeWithDiscriminatorMapUsesCorrectClassname() { - $factory = new ClassMetadataFactory(new AnnotationLoader(new AnnotationReader())); + $factory = new ClassMetadataFactory(new AnnotationLoader()); $loaderMock = new class() implements ClassMetadataFactoryInterface { public function getMetadataFor($value): ClassMetadataInterface @@ -484,7 +483,7 @@ public function hasMetadataFor($value): bool public function testDenormalizeWithDiscriminatorMapAndObjectToPopulateUsesCorrectClassname() { - $factory = new ClassMetadataFactory(new AnnotationLoader(new AnnotationReader())); + $factory = new ClassMetadataFactory(new AnnotationLoader()); $loaderMock = new class() implements ClassMetadataFactoryInterface { public function getMetadataFor($value): ClassMetadataInterface @@ -683,7 +682,7 @@ public function testDenormalizeRecursiveWithObjectAttributeWithStringValue() public function testDenormalizeUsesContextAttributeForPropertiesInConstructorWithSeralizedName() { - $classMetadataFactory = new ClassMetadataFactory(new AnnotationLoader(new AnnotationReader())); + $classMetadataFactory = new ClassMetadataFactory(new AnnotationLoader()); $extractor = new PropertyInfoExtractor([], [new PhpDocExtractor(), new ReflectionExtractor()]); $normalizer = new ObjectNormalizer($classMetadataFactory, new MetadataAwareNameConverter($classMetadataFactory), null, $extractor); @@ -697,7 +696,7 @@ public function testDenormalizeUsesContextAttributeForPropertiesInConstructorWit public function testNormalizeUsesContextAttributeForPropertiesInConstructorWithSerializedPath() { - $classMetadataFactory = new ClassMetadataFactory(new AnnotationLoader(new AnnotationReader())); + $classMetadataFactory = new ClassMetadataFactory(new AnnotationLoader()); $extractor = new PropertyInfoExtractor([], [new PhpDocExtractor(), new ReflectionExtractor()]); $normalizer = new ObjectNormalizer($classMetadataFactory, new MetadataAwareNameConverter($classMetadataFactory), null, $extractor); @@ -712,7 +711,7 @@ public function testNormalizeUsesContextAttributeForPropertiesInConstructorWithS public function testNormalizeUsesContextAttributeForProperties() { - $classMetadataFactory = new ClassMetadataFactory(new AnnotationLoader(new AnnotationReader())); + $classMetadataFactory = new ClassMetadataFactory(new AnnotationLoader()); $extractor = new PropertyInfoExtractor([], [new PhpDocExtractor(), new ReflectionExtractor()]); $normalizer = new ObjectNormalizer($classMetadataFactory, new MetadataAwareNameConverter($classMetadataFactory), null, $extractor); @@ -792,27 +791,19 @@ class EmptyDummy class AlreadyPopulatedNestedDummy { - /** - * @SerializedPath("[one][two][three]") - */ + #[SerializedPath('[one][two][three]')] public $foo; - /** - * @SerializedPath("[one][two]") - */ + #[SerializedPath('[one][two]')] public $bar; } class DuplicateValueNestedDummy { - /** - * @SerializedPath("[one][two][three]") - */ + #[SerializedPath('[one][two][three]')] public $foo; - /** - * @SerializedPath("[one][two][three]") - */ + #[SerializedPath('[one][two][three]')] public $bar; public $baz; @@ -820,19 +811,13 @@ class DuplicateValueNestedDummy class NestedDummy { - /** - * @SerializedPath("[one][two][three]") - */ + #[SerializedPath('[one][two][three]')] public $foo; - /** - * @SerializedPath("[one][four]") - */ + #[SerializedPath('[one][four]')] public $quux; - /** - * @SerializedPath("[foo]") - */ + #[SerializedPath('[foo]')] public $notfoo; public $baz; @@ -841,19 +826,13 @@ class NestedDummy class NestedDummyWithConstructor { public function __construct( - /** - * @SerializedPath("[one][two][three]") - */ + #[SerializedPath('[one][two][three]')] public $foo, - /** - * @SerializedPath("[one][four]") - */ + #[SerializedPath('[one][four]')] public $quux, - /** - * @SerializedPath("[foo]") - */ + #[SerializedPath('[foo]')] public $notfoo, public $baz, @@ -861,18 +840,14 @@ public function __construct( } } -/** - * @DiscriminatorMap(typeProperty="type", mapping={ - * "first" = FirstNestedDummyWithConstructorAndDiscriminator::class, - * "second" = SecondNestedDummyWithConstructorAndDiscriminator::class, - * }) - */ +#[DiscriminatorMap(typeProperty: 'type', mapping: [ + 'first' => FirstNestedDummyWithConstructorAndDiscriminator::class, + 'second' => SecondNestedDummyWithConstructorAndDiscriminator::class, +])] abstract class AbstractNestedDummyWithConstructorAndDiscriminator { public function __construct( - /** - * @SerializedPath("[foo]") - */ + #[SerializedPath('[foo]')] public $notfoo, public $baz, @@ -883,9 +858,7 @@ public function __construct( class FirstNestedDummyWithConstructorAndDiscriminator extends AbstractNestedDummyWithConstructorAndDiscriminator { public function __construct( - /** - * @SerializedPath("[one][two][three]") - */ + #[SerializedPath('[one][two][three]')] public $foo, $notfoo, @@ -898,9 +871,7 @@ public function __construct( class SecondNestedDummyWithConstructorAndDiscriminator extends AbstractNestedDummyWithConstructorAndDiscriminator { public function __construct( - /** - * @SerializedPath("[one][four]") - */ + #[SerializedPath('[one][four]')] public $quux, $notfoo, @@ -912,14 +883,10 @@ public function __construct( class DuplicateKeyNestedDummy { - /** - * @SerializedPath("[one][four]") - */ + #[SerializedPath('[one][four]')] public $quux; - /** - * @SerializedName("quux") - */ + #[SerializedName('quux')] public $notquux; } @@ -946,7 +913,7 @@ class AbstractObjectNormalizerWithMetadata extends AbstractObjectNormalizer { public function __construct() { - $classMetadataFactory = new ClassMetadataFactory(new AnnotationLoader(new AnnotationReader())); + $classMetadataFactory = new ClassMetadataFactory(new AnnotationLoader()); parent::__construct($classMetadataFactory, new MetadataAwareNameConverter($classMetadataFactory)); } @@ -1110,10 +1077,7 @@ public function deserialize($data, string $type, string $format, array $context class ArrayDenormalizerDummy implements DenormalizerInterface, SerializerAwareInterface { - /** - * @var SerializerInterface|DenormalizerInterface - */ - private $serializer; + private SerializerInterface&DenormalizerInterface $serializer; /** * @throws NotNormalizableValueException @@ -1143,6 +1107,7 @@ public function supportsDenormalization($data, string $type, string $format = nu public function setSerializer(SerializerInterface $serializer): void { + \assert($serializer instanceof DenormalizerInterface); $this->serializer = $serializer; } } diff --git a/Tests/Normalizer/Features/ContextMetadataTestTrait.php b/Tests/Normalizer/Features/ContextMetadataTestTrait.php index cb436dbc3..2eeb76a72 100644 --- a/Tests/Normalizer/Features/ContextMetadataTestTrait.php +++ b/Tests/Normalizer/Features/ContextMetadataTestTrait.php @@ -11,7 +11,6 @@ namespace Symfony\Component\Serializer\Tests\Normalizer\Features; -use Doctrine\Common\Annotations\AnnotationReader; use Symfony\Component\PropertyInfo\Extractor\PhpDocExtractor; use Symfony\Component\Serializer\Annotation\Context; use Symfony\Component\Serializer\Annotation\Groups; @@ -34,7 +33,7 @@ trait ContextMetadataTestTrait */ public function testContextMetadataNormalize(string $contextMetadataDummyClass) { - $classMetadataFactory = new ClassMetadataFactory(new AnnotationLoader(new AnnotationReader())); + $classMetadataFactory = new ClassMetadataFactory(new AnnotationLoader()); $normalizer = new ObjectNormalizer($classMetadataFactory, null, null, new PhpDocExtractor()); new Serializer([new DateTimeNormalizer(), $normalizer]); @@ -57,7 +56,7 @@ public function testContextMetadataNormalize(string $contextMetadataDummyClass) */ public function testContextMetadataContextDenormalize(string $contextMetadataDummyClass) { - $classMetadataFactory = new ClassMetadataFactory(new AnnotationLoader(new AnnotationReader())); + $classMetadataFactory = new ClassMetadataFactory(new AnnotationLoader()); $normalizer = new ObjectNormalizer($classMetadataFactory, null, null, new PhpDocExtractor()); new Serializer([new DateTimeNormalizer(), $normalizer]); @@ -78,7 +77,7 @@ public function testContextMetadataContextDenormalize(string $contextMetadataDum self::assertEquals('2011-07-28', $dummy->date->format('Y-m-d'), 'a specific denormalization context is used for this group'); } - public function contextMetadataDummyProvider() + public function contextMetadataDummyProvider(): array { return [ [ContextMetadataDummy::class], @@ -88,7 +87,7 @@ public function contextMetadataDummyProvider() public function testContextDenormalizeWithNameConverter() { - $classMetadataFactory = new ClassMetadataFactory(new AnnotationLoader(new AnnotationReader())); + $classMetadataFactory = new ClassMetadataFactory(new AnnotationLoader()); $normalizer = new ObjectNormalizer($classMetadataFactory, new CamelCaseToSnakeCaseNameConverter(), null, new PhpDocExtractor()); new Serializer([new DateTimeNormalizer(), $normalizer]); @@ -102,19 +101,17 @@ class ContextMetadataDummy { /** * @var \DateTime - * - * @Groups({ "extended", "simple" }) - * - * @Context({ DateTimeNormalizer::FORMAT_KEY = \DateTimeInterface::RFC3339 }) - * @Context( - * normalizationContext = { DateTimeNormalizer::FORMAT_KEY = \DateTimeInterface::RFC3339_EXTENDED }, - * groups = {"extended"} - * ) - * @Context( - * denormalizationContext = { DateTimeNormalizer::FORMAT_KEY = "d/m/Y" }, - * groups = {"simple"} - * ) */ + #[Groups(['extended', 'simple'])] + #[Context([DateTimeNormalizer::FORMAT_KEY => \DateTimeInterface::RFC3339])] + #[Context( + normalizationContext: [DateTimeNormalizer::FORMAT_KEY => \DateTimeInterface::RFC3339_EXTENDED], + groups: ['extended'], + )] + #[Context( + denormalizationContext: [DateTimeNormalizer::FORMAT_KEY => 'd/m/Y'], + groups: ['simple'], + )] public $date; } @@ -122,19 +119,17 @@ class ContextChildMetadataDummy { /** * @var \DateTime - * - * @Groups({ "extended", "simple" }) - * - * @DummyContextChild({ DateTimeNormalizer::FORMAT_KEY = \DateTimeInterface::RFC3339 }) - * @DummyContextChild( - * normalizationContext = { DateTimeNormalizer::FORMAT_KEY = \DateTimeInterface::RFC3339_EXTENDED }, - * groups = {"extended"} - * ) - * @DummyContextChild( - * denormalizationContext = { DateTimeNormalizer::FORMAT_KEY = "d/m/Y" }, - * groups = {"simple"} - * ) */ + #[Groups(['extended', 'simple'])] + #[Context([DateTimeNormalizer::FORMAT_KEY => \DateTimeInterface::RFC3339])] + #[Context( + normalizationContext: [DateTimeNormalizer::FORMAT_KEY => \DateTimeInterface::RFC3339_EXTENDED], + groups: ['extended'], + )] + #[Context( + denormalizationContext: [DateTimeNormalizer::FORMAT_KEY => 'd/m/Y'], + groups: ['simple'], + )] public $date; } @@ -142,8 +137,7 @@ class ContextMetadataNamingDummy { /** * @var \DateTime - * - * @Context({ DateTimeNormalizer::FORMAT_KEY = "d/m/Y" }) */ + #[Context([DateTimeNormalizer::FORMAT_KEY => 'd/m/Y'])] public $createdAt; } diff --git a/Tests/Normalizer/Features/GroupsTestTrait.php b/Tests/Normalizer/Features/GroupsTestTrait.php index 294109736..08d5e065a 100644 --- a/Tests/Normalizer/Features/GroupsTestTrait.php +++ b/Tests/Normalizer/Features/GroupsTestTrait.php @@ -13,7 +13,7 @@ use Symfony\Component\Serializer\Normalizer\DenormalizerInterface; use Symfony\Component\Serializer\Normalizer\NormalizerInterface; -use Symfony\Component\Serializer\Tests\Fixtures\Annotations\GroupDummy; +use Symfony\Component\Serializer\Tests\Fixtures\Attributes\GroupDummy; /** * Test AbstractNormalizer::GROUPS. diff --git a/Tests/Normalizer/Features/MaxDepthTestTrait.php b/Tests/Normalizer/Features/MaxDepthTestTrait.php index ef3123d58..46c7dfceb 100644 --- a/Tests/Normalizer/Features/MaxDepthTestTrait.php +++ b/Tests/Normalizer/Features/MaxDepthTestTrait.php @@ -12,7 +12,7 @@ namespace Symfony\Component\Serializer\Tests\Normalizer\Features; use Symfony\Component\Serializer\Normalizer\NormalizerInterface; -use Symfony\Component\Serializer\Tests\Fixtures\Annotations\MaxDepthDummy; +use Symfony\Component\Serializer\Tests\Fixtures\Attributes\MaxDepthDummy; /** * Covers AbstractObjectNormalizer::ENABLE_MAX_DEPTH and AbstractObjectNormalizer::MAX_DEPTH_HANDLER. diff --git a/Tests/Normalizer/Features/TypedPropertiesObject.php b/Tests/Normalizer/Features/TypedPropertiesObject.php index 49edde54c..e3acafdec 100644 --- a/Tests/Normalizer/Features/TypedPropertiesObject.php +++ b/Tests/Normalizer/Features/TypedPropertiesObject.php @@ -15,18 +15,12 @@ class TypedPropertiesObject { - /** - * @Groups({"foo"}) - */ + #[Groups(['foo'])] public string $unInitialized; - /** - * @Groups({"foo"}) - */ + #[Groups(['foo'])] public string $initialized = 'value'; - /** - * @Groups({"bar"}) - */ + #[Groups(['bar'])] public string $initialized2 = 'value'; } diff --git a/Tests/Normalizer/GetSetMethodNormalizerTest.php b/Tests/Normalizer/GetSetMethodNormalizerTest.php index e7ce84616..5261b95e1 100644 --- a/Tests/Normalizer/GetSetMethodNormalizerTest.php +++ b/Tests/Normalizer/GetSetMethodNormalizerTest.php @@ -11,7 +11,7 @@ namespace Symfony\Component\Serializer\Tests\Normalizer; -use Doctrine\Common\Annotations\AnnotationReader; +use PHPUnit\Framework\MockObject\MockObject; use PHPUnit\Framework\TestCase; use Symfony\Component\PropertyInfo\Extractor\PhpDocExtractor; use Symfony\Component\PropertyInfo\Extractor\ReflectionExtractor; @@ -28,8 +28,8 @@ use Symfony\Component\Serializer\Normalizer\NormalizerInterface; use Symfony\Component\Serializer\Serializer; use Symfony\Component\Serializer\SerializerInterface; -use Symfony\Component\Serializer\Tests\Fixtures\Annotations\GroupDummy; use Symfony\Component\Serializer\Tests\Fixtures\Attributes\ClassWithIgnoreAttribute; +use Symfony\Component\Serializer\Tests\Fixtures\Attributes\GroupDummy; use Symfony\Component\Serializer\Tests\Fixtures\CircularReferenceDummy; use Symfony\Component\Serializer\Tests\Fixtures\SiblingHolder; use Symfony\Component\Serializer\Tests\Normalizer\Features\CacheableObjectAttributesTestTrait; @@ -57,21 +57,15 @@ class GetSetMethodNormalizerTest extends TestCase use SkipUninitializedValuesTestTrait; use TypeEnforcementTestTrait; - /** - * @var GetSetMethodNormalizer - */ - private $normalizer; - /** - * @var SerializerInterface - */ - private $serializer; + private GetSetMethodNormalizer $normalizer; + private SerializerInterface&NormalizerInterface&MockObject $serializer; protected function setUp(): void { $this->createNormalizer(); } - private function createNormalizer(array $defaultContext = []) + private function createNormalizer(array $defaultContext = []): void { $this->serializer = $this->createMock(SerializerNormalizer::class); $this->normalizer = new GetSetMethodNormalizer(null, null, null, null, null, $defaultContext); @@ -233,21 +227,21 @@ public function testConstructorWArgWithPrivateMutator() protected function getNormalizerForCallbacksWithPropertyTypeExtractor(): GetSetMethodNormalizer { - $classMetadataFactory = new ClassMetadataFactory(new AnnotationLoader(new AnnotationReader())); + $classMetadataFactory = new ClassMetadataFactory(new AnnotationLoader()); return new GetSetMethodNormalizer($classMetadataFactory, new MetadataAwareNameConverter($classMetadataFactory), $this->getCallbackPropertyTypeExtractor()); } protected function getNormalizerForCallbacks(): GetSetMethodNormalizer { - $classMetadataFactory = new ClassMetadataFactory(new AnnotationLoader(new AnnotationReader())); + $classMetadataFactory = new ClassMetadataFactory(new AnnotationLoader()); return new GetSetMethodNormalizer($classMetadataFactory, new MetadataAwareNameConverter($classMetadataFactory)); } protected function getNormalizerForCircularReference(array $defaultContext): GetSetMethodNormalizer { - $classMetadataFactory = new ClassMetadataFactory(new AnnotationLoader(new AnnotationReader())); + $classMetadataFactory = new ClassMetadataFactory(new AnnotationLoader()); $normalizer = new GetSetMethodNormalizer($classMetadataFactory, new MetadataAwareNameConverter($classMetadataFactory), null, null, null, $defaultContext); new Serializer([$normalizer]); @@ -261,7 +255,7 @@ protected function getSelfReferencingModel() protected function getDenormalizerForConstructArguments(): GetSetMethodNormalizer { - $classMetadataFactory = new ClassMetadataFactory(new AnnotationLoader(new AnnotationReader())); + $classMetadataFactory = new ClassMetadataFactory(new AnnotationLoader()); $denormalizer = new GetSetMethodNormalizer($classMetadataFactory, new MetadataAwareNameConverter($classMetadataFactory)); new Serializer([$denormalizer]); @@ -270,21 +264,21 @@ protected function getDenormalizerForConstructArguments(): GetSetMethodNormalize protected function getNormalizerForGroups(): GetSetMethodNormalizer { - $classMetadataFactory = new ClassMetadataFactory(new AnnotationLoader(new AnnotationReader())); + $classMetadataFactory = new ClassMetadataFactory(new AnnotationLoader()); return new GetSetMethodNormalizer($classMetadataFactory); } protected function getDenormalizerForGroups(): GetSetMethodNormalizer { - $classMetadataFactory = new ClassMetadataFactory(new AnnotationLoader(new AnnotationReader())); + $classMetadataFactory = new ClassMetadataFactory(new AnnotationLoader()); return new GetSetMethodNormalizer($classMetadataFactory); } public function testGroupsNormalizeWithNameConverter() { - $classMetadataFactory = new ClassMetadataFactory(new AnnotationLoader(new AnnotationReader())); + $classMetadataFactory = new ClassMetadataFactory(new AnnotationLoader()); $this->normalizer = new GetSetMethodNormalizer($classMetadataFactory, new CamelCaseToSnakeCaseNameConverter()); $this->normalizer->setSerializer($this->serializer); @@ -305,7 +299,7 @@ public function testGroupsNormalizeWithNameConverter() public function testGroupsDenormalizeWithNameConverter() { - $classMetadataFactory = new ClassMetadataFactory(new AnnotationLoader(new AnnotationReader())); + $classMetadataFactory = new ClassMetadataFactory(new AnnotationLoader()); $this->normalizer = new GetSetMethodNormalizer($classMetadataFactory, new CamelCaseToSnakeCaseNameConverter()); $this->normalizer->setSerializer($this->serializer); @@ -320,13 +314,13 @@ public function testGroupsDenormalizeWithNameConverter() 'foo_bar' => '@dunglas', 'symfony' => '@coopTilleuls', 'coop_tilleuls' => 'les-tilleuls.coop', - ], 'Symfony\Component\Serializer\Tests\Fixtures\Annotations\GroupDummy', null, [GetSetMethodNormalizer::GROUPS => ['name_converter']]) + ], GroupDummy::class, null, [GetSetMethodNormalizer::GROUPS => ['name_converter']]) ); } protected function getNormalizerForMaxDepth(): NormalizerInterface { - $classMetadataFactory = new ClassMetadataFactory(new AnnotationLoader(new AnnotationReader())); + $classMetadataFactory = new ClassMetadataFactory(new AnnotationLoader()); $normalizer = new GetSetMethodNormalizer($classMetadataFactory); $serializer = new Serializer([$normalizer]); $normalizer->setSerializer($serializer); @@ -336,7 +330,7 @@ protected function getNormalizerForMaxDepth(): NormalizerInterface protected function getDenormalizerForObjectToPopulate(): DenormalizerInterface { - $classMetadataFactory = new ClassMetadataFactory(new AnnotationLoader(new AnnotationReader())); + $classMetadataFactory = new ClassMetadataFactory(new AnnotationLoader()); $normalizer = new GetSetMethodNormalizer($classMetadataFactory, null, new PhpDocExtractor()); new Serializer([$normalizer]); @@ -360,7 +354,7 @@ public function testRejectInvalidKey() protected function getNormalizerForIgnoredAttributes(): GetSetMethodNormalizer { - $classMetadataFactory = new ClassMetadataFactory(new AnnotationLoader(new AnnotationReader())); + $classMetadataFactory = new ClassMetadataFactory(new AnnotationLoader()); $normalizer = new GetSetMethodNormalizer($classMetadataFactory, null, new PhpDocExtractor()); new Serializer([$normalizer]); @@ -369,7 +363,7 @@ protected function getNormalizerForIgnoredAttributes(): GetSetMethodNormalizer protected function getDenormalizerForIgnoredAttributes(): GetSetMethodNormalizer { - $classMetadataFactory = new ClassMetadataFactory(new AnnotationLoader(new AnnotationReader())); + $classMetadataFactory = new ClassMetadataFactory(new AnnotationLoader()); $normalizer = new GetSetMethodNormalizer($classMetadataFactory, null, new PhpDocExtractor()); new Serializer([$normalizer]); @@ -493,7 +487,7 @@ protected function getNormalizerForCacheableObjectAttributesTest(): GetSetMethod protected function getNormalizerForSkipUninitializedValues(): NormalizerInterface { - return new GetSetMethodNormalizer(new ClassMetadataFactory(new AnnotationLoader(new AnnotationReader()))); + return new GetSetMethodNormalizer(new ClassMetadataFactory(new AnnotationLoader())); } } diff --git a/Tests/Normalizer/MapDenormalizationTest.php b/Tests/Normalizer/MapDenormalizationTest.php index 6c32fb925..8131e9707 100644 --- a/Tests/Normalizer/MapDenormalizationTest.php +++ b/Tests/Normalizer/MapDenormalizationTest.php @@ -11,7 +11,6 @@ namespace Symfony\Component\Serializer\Tests\Normalizer; -use Doctrine\Common\Annotations\AnnotationReader; use PHPUnit\Framework\TestCase; use Symfony\Component\PropertyInfo\Extractor\PhpDocExtractor; use Symfony\Component\Serializer\Exception\InvalidArgumentException; @@ -210,7 +209,7 @@ public function hasMetadataFor($value): bool } }; - $factory = new ClassMetadataFactory(new AnnotationLoader(new AnnotationReader())); + $factory = new ClassMetadataFactory(new AnnotationLoader()); $normalizer = new ObjectNormalizer($factory, null, null, new PhpDocExtractor(), new ClassDiscriminatorFromClassMetadata($loaderMock)); $serializer = new Serializer([$normalizer, new ArrayDenormalizer()]); $normalizer->setSerializer($serializer); diff --git a/Tests/Normalizer/ObjectNormalizerTest.php b/Tests/Normalizer/ObjectNormalizerTest.php index d87b7a67a..ffa029fa5 100644 --- a/Tests/Normalizer/ObjectNormalizerTest.php +++ b/Tests/Normalizer/ObjectNormalizerTest.php @@ -11,8 +11,8 @@ namespace Symfony\Component\Serializer\Tests\Normalizer; -use Doctrine\Common\Annotations\AnnotationReader; use PHPStan\PhpDocParser\Parser\PhpDocParser; +use PHPUnit\Framework\MockObject\MockObject; use PHPUnit\Framework\TestCase; use Symfony\Component\PropertyInfo\Extractor\PhpDocExtractor; use Symfony\Component\PropertyInfo\Extractor\PhpStanExtractor; @@ -34,7 +34,7 @@ use Symfony\Component\Serializer\Normalizer\ObjectNormalizer; use Symfony\Component\Serializer\Serializer; use Symfony\Component\Serializer\SerializerInterface; -use Symfony\Component\Serializer\Tests\Fixtures\Annotations\GroupDummy; +use Symfony\Component\Serializer\Tests\Fixtures\Attributes\GroupDummy; use Symfony\Component\Serializer\Tests\Fixtures\CircularReferenceDummy; use Symfony\Component\Serializer\Tests\Fixtures\DummyPrivatePropertyWithoutGetter; use Symfony\Component\Serializer\Tests\Fixtures\FormatAndContextAwareNormalizer; @@ -79,21 +79,15 @@ class ObjectNormalizerTest extends TestCase use SkipUninitializedValuesTestTrait; use TypeEnforcementTestTrait; - /** - * @var ObjectNormalizer - */ - private $normalizer; - /** - * @var SerializerInterface - */ - private $serializer; + private ObjectNormalizer $normalizer; + private SerializerInterface&NormalizerInterface&MockObject $serializer; protected function setUp(): void { $this->createNormalizer(); } - private function createNormalizer(array $defaultContext = [], ClassMetadataFactoryInterface $classMetadataFactory = null) + private function createNormalizer(array $defaultContext = [], ClassMetadataFactoryInterface $classMetadataFactory = null): void { $this->serializer = $this->createMock(ObjectSerializerNormalizer::class); $this->normalizer = new ObjectNormalizer($classMetadataFactory, null, null, null, null, null, $defaultContext); @@ -342,7 +336,7 @@ protected function getNormalizerForAttributes(): ObjectNormalizer protected function getDenormalizerForAttributes(): ObjectNormalizer { - $classMetadataFactory = new ClassMetadataFactory(new AnnotationLoader(new AnnotationReader())); + $classMetadataFactory = new ClassMetadataFactory(new AnnotationLoader()); $normalizer = new ObjectNormalizer($classMetadataFactory, null, null, new ReflectionExtractor()); new Serializer([$normalizer]); @@ -368,7 +362,7 @@ public function testAttributesContextDenormalizeConstructor() public function testNormalizeSameObjectWithDifferentAttributes() { - $classMetadataFactory = new ClassMetadataFactory(new AnnotationLoader(new AnnotationReader())); + $classMetadataFactory = new ClassMetadataFactory(new AnnotationLoader()); $this->normalizer = new ObjectNormalizer($classMetadataFactory); $serializer = new Serializer([$this->normalizer]); $this->normalizer->setSerializer($serializer); @@ -443,7 +437,7 @@ public function testSiblingReference() protected function getDenormalizerForConstructArguments(): ObjectNormalizer { - $classMetadataFactory = new ClassMetadataFactory(new AnnotationLoader(new AnnotationReader())); + $classMetadataFactory = new ClassMetadataFactory(new AnnotationLoader()); $denormalizer = new ObjectNormalizer($classMetadataFactory, new MetadataAwareNameConverter($classMetadataFactory)); $serializer = new Serializer([$denormalizer]); $denormalizer->setSerializer($serializer); @@ -455,7 +449,7 @@ protected function getDenormalizerForConstructArguments(): ObjectNormalizer protected function getNormalizerForGroups(): ObjectNormalizer { - $classMetadataFactory = new ClassMetadataFactory(new AnnotationLoader(new AnnotationReader())); + $classMetadataFactory = new ClassMetadataFactory(new AnnotationLoader()); $normalizer = new ObjectNormalizer($classMetadataFactory); // instantiate a serializer with the normalizer to handle normalizing recursive structures new Serializer([$normalizer]); @@ -465,14 +459,14 @@ protected function getNormalizerForGroups(): ObjectNormalizer protected function getDenormalizerForGroups(): ObjectNormalizer { - $classMetadataFactory = new ClassMetadataFactory(new AnnotationLoader(new AnnotationReader())); + $classMetadataFactory = new ClassMetadataFactory(new AnnotationLoader()); return new ObjectNormalizer($classMetadataFactory); } public function testGroupsNormalizeWithNameConverter() { - $classMetadataFactory = new ClassMetadataFactory(new AnnotationLoader(new AnnotationReader())); + $classMetadataFactory = new ClassMetadataFactory(new AnnotationLoader()); $this->normalizer = new ObjectNormalizer($classMetadataFactory, new CamelCaseToSnakeCaseNameConverter()); $this->normalizer->setSerializer($this->serializer); @@ -493,7 +487,7 @@ public function testGroupsNormalizeWithNameConverter() public function testGroupsDenormalizeWithNameConverter() { - $classMetadataFactory = new ClassMetadataFactory(new AnnotationLoader(new AnnotationReader())); + $classMetadataFactory = new ClassMetadataFactory(new AnnotationLoader()); $this->normalizer = new ObjectNormalizer($classMetadataFactory, new CamelCaseToSnakeCaseNameConverter()); $this->normalizer->setSerializer($this->serializer); @@ -508,13 +502,13 @@ public function testGroupsDenormalizeWithNameConverter() 'foo_bar' => '@dunglas', 'symfony' => '@coopTilleuls', 'coop_tilleuls' => 'les-tilleuls.coop', - ], 'Symfony\Component\Serializer\Tests\Fixtures\Annotations\GroupDummy', null, [ObjectNormalizer::GROUPS => ['name_converter']]) + ], GroupDummy::class, null, [ObjectNormalizer::GROUPS => ['name_converter']]) ); } public function testGroupsDenormalizeWithMetaDataNameConverter() { - $classMetadataFactory = new ClassMetadataFactory(new AnnotationLoader(new AnnotationReader())); + $classMetadataFactory = new ClassMetadataFactory(new AnnotationLoader()); $this->normalizer = new ObjectNormalizer($classMetadataFactory, new MetadataAwareNameConverter($classMetadataFactory)); $this->normalizer->setSerializer($this->serializer); @@ -542,7 +536,7 @@ protected function getNormalizerForIgnoredAttributes(): ObjectNormalizer protected function getDenormalizerForIgnoredAttributes(): ObjectNormalizer { - $classMetadataFactory = new ClassMetadataFactory(new AnnotationLoader(new AnnotationReader())); + $classMetadataFactory = new ClassMetadataFactory(new AnnotationLoader()); $normalizer = new ObjectNormalizer($classMetadataFactory, null, null, new ReflectionExtractor()); new Serializer([$normalizer]); @@ -553,7 +547,7 @@ protected function getDenormalizerForIgnoredAttributes(): ObjectNormalizer protected function getNormalizerForMaxDepth(): ObjectNormalizer { - $classMetadataFactory = new ClassMetadataFactory(new AnnotationLoader(new AnnotationReader())); + $classMetadataFactory = new ClassMetadataFactory(new AnnotationLoader()); $normalizer = new ObjectNormalizer($classMetadataFactory); $serializer = new Serializer([$normalizer]); $normalizer->setSerializer($serializer); @@ -565,7 +559,7 @@ protected function getNormalizerForMaxDepth(): ObjectNormalizer protected function getDenormalizerForObjectToPopulate(): ObjectNormalizer { - $classMetadataFactory = new ClassMetadataFactory(new AnnotationLoader(new AnnotationReader())); + $classMetadataFactory = new ClassMetadataFactory(new AnnotationLoader()); $normalizer = new ObjectNormalizer($classMetadataFactory, null, null, new PhpDocExtractor()); new Serializer([$normalizer]); @@ -583,7 +577,7 @@ protected function getNormalizerForSkipNullValues(): ObjectNormalizer protected function getNormalizerForSkipUninitializedValues(): ObjectNormalizer { - $classMetadataFactory = new ClassMetadataFactory(new AnnotationLoader(new AnnotationReader())); + $classMetadataFactory = new ClassMetadataFactory(new AnnotationLoader()); return new ObjectNormalizer($classMetadataFactory); } diff --git a/Tests/Normalizer/PropertyNormalizerTest.php b/Tests/Normalizer/PropertyNormalizerTest.php index f1401158f..b424e4156 100644 --- a/Tests/Normalizer/PropertyNormalizerTest.php +++ b/Tests/Normalizer/PropertyNormalizerTest.php @@ -11,7 +11,6 @@ namespace Symfony\Component\Serializer\Tests\Normalizer; -use Doctrine\Common\Annotations\AnnotationReader; use PHPUnit\Framework\TestCase; use Symfony\Component\PropertyInfo\Extractor\PhpDocExtractor; use Symfony\Component\PropertyInfo\Extractor\ReflectionExtractor; @@ -28,8 +27,8 @@ use Symfony\Component\Serializer\Normalizer\PropertyNormalizer; use Symfony\Component\Serializer\Serializer; use Symfony\Component\Serializer\SerializerInterface; -use Symfony\Component\Serializer\Tests\Fixtures\Annotations\GroupDummy; -use Symfony\Component\Serializer\Tests\Fixtures\Annotations\GroupDummyChild; +use Symfony\Component\Serializer\Tests\Fixtures\Attributes\GroupDummy; +use Symfony\Component\Serializer\Tests\Fixtures\Attributes\GroupDummyChild; use Symfony\Component\Serializer\Tests\Fixtures\Dummy; use Symfony\Component\Serializer\Tests\Fixtures\Php74Dummy; use Symfony\Component\Serializer\Tests\Fixtures\PropertyCircularReferenceDummy; @@ -59,22 +58,15 @@ class PropertyNormalizerTest extends TestCase use SkipUninitializedValuesTestTrait; use TypeEnforcementTestTrait; - /** - * @var PropertyNormalizer - */ - private $normalizer; - - /** - * @var SerializerInterface - */ - private $serializer; + private PropertyNormalizer $normalizer; + private SerializerInterface $serializer; protected function setUp(): void { $this->createNormalizer(); } - private function createNormalizer(array $defaultContext = []) + private function createNormalizer(array $defaultContext = []): void { $this->serializer = $this->createMock(SerializerInterface::class); $this->normalizer = new PropertyNormalizer(null, null, null, null, null, $defaultContext); @@ -272,7 +264,7 @@ public function testSiblingReference() protected function getDenormalizerForConstructArguments(): PropertyNormalizer { - $classMetadataFactory = new ClassMetadataFactory(new AnnotationLoader(new AnnotationReader())); + $classMetadataFactory = new ClassMetadataFactory(new AnnotationLoader()); $denormalizer = new PropertyNormalizer($classMetadataFactory, new MetadataAwareNameConverter($classMetadataFactory)); $serializer = new Serializer([$denormalizer]); $denormalizer->setSerializer($serializer); @@ -282,21 +274,21 @@ protected function getDenormalizerForConstructArguments(): PropertyNormalizer protected function getNormalizerForGroups(): PropertyNormalizer { - $classMetadataFactory = new ClassMetadataFactory(new AnnotationLoader(new AnnotationReader())); + $classMetadataFactory = new ClassMetadataFactory(new AnnotationLoader()); return new PropertyNormalizer($classMetadataFactory); } protected function getDenormalizerForGroups(): PropertyNormalizer { - $classMetadataFactory = new ClassMetadataFactory(new AnnotationLoader(new AnnotationReader())); + $classMetadataFactory = new ClassMetadataFactory(new AnnotationLoader()); return new PropertyNormalizer($classMetadataFactory); } public function testGroupsNormalizeWithNameConverter() { - $classMetadataFactory = new ClassMetadataFactory(new AnnotationLoader(new AnnotationReader())); + $classMetadataFactory = new ClassMetadataFactory(new AnnotationLoader()); $this->normalizer = new PropertyNormalizer($classMetadataFactory, new CamelCaseToSnakeCaseNameConverter()); $this->normalizer->setSerializer($this->serializer); @@ -317,7 +309,7 @@ public function testGroupsNormalizeWithNameConverter() public function testGroupsDenormalizeWithNameConverter() { - $classMetadataFactory = new ClassMetadataFactory(new AnnotationLoader(new AnnotationReader())); + $classMetadataFactory = new ClassMetadataFactory(new AnnotationLoader()); $this->normalizer = new PropertyNormalizer($classMetadataFactory, new CamelCaseToSnakeCaseNameConverter()); $this->normalizer->setSerializer($this->serializer); @@ -332,7 +324,7 @@ public function testGroupsDenormalizeWithNameConverter() 'foo_bar' => '@dunglas', 'symfony' => '@coopTilleuls', 'coop_tilleuls' => 'les-tilleuls.coop', - ], 'Symfony\Component\Serializer\Tests\Fixtures\Annotations\GroupDummy', null, [PropertyNormalizer::GROUPS => ['name_converter']]) + ], GroupDummy::class, null, [PropertyNormalizer::GROUPS => ['name_converter']]) ); } @@ -361,7 +353,7 @@ public function testIgnoredAttributesContextDenormalizeInherit() protected function getNormalizerForMaxDepth(): PropertyNormalizer { - $classMetadataFactory = new ClassMetadataFactory(new AnnotationLoader(new AnnotationReader())); + $classMetadataFactory = new ClassMetadataFactory(new AnnotationLoader()); $normalizer = new PropertyNormalizer($classMetadataFactory); $serializer = new Serializer([$normalizer]); $normalizer->setSerializer($serializer); @@ -371,7 +363,7 @@ protected function getNormalizerForMaxDepth(): PropertyNormalizer protected function getDenormalizerForObjectToPopulate(): PropertyNormalizer { - $classMetadataFactory = new ClassMetadataFactory(new AnnotationLoader(new AnnotationReader())); + $classMetadataFactory = new ClassMetadataFactory(new AnnotationLoader()); $normalizer = new PropertyNormalizer($classMetadataFactory, null, new PhpDocExtractor()); new Serializer([$normalizer]); @@ -500,7 +492,7 @@ protected function getNormalizerForCacheableObjectAttributesTest(): AbstractObje protected function getNormalizerForSkipUninitializedValues(): NormalizerInterface { - return new PropertyNormalizer(new ClassMetadataFactory(new AnnotationLoader(new AnnotationReader()))); + return new PropertyNormalizer(new ClassMetadataFactory(new AnnotationLoader())); } } diff --git a/Tests/SerializerTest.php b/Tests/SerializerTest.php index 73d183f19..8c5c89a67 100644 --- a/Tests/SerializerTest.php +++ b/Tests/SerializerTest.php @@ -11,7 +11,6 @@ namespace Symfony\Component\Serializer\Tests; -use Doctrine\Common\Annotations\AnnotationReader; use PHPUnit\Framework\TestCase; use Symfony\Component\PropertyAccess\PropertyAccessor; use Symfony\Component\PropertyInfo\Extractor\PhpDocExtractor; @@ -47,9 +46,9 @@ use Symfony\Component\Serializer\Normalizer\UidNormalizer; use Symfony\Component\Serializer\Normalizer\UnwrappingDenormalizer; use Symfony\Component\Serializer\Serializer; -use Symfony\Component\Serializer\Tests\Fixtures\Annotations\AbstractDummy; -use Symfony\Component\Serializer\Tests\Fixtures\Annotations\AbstractDummyFirstChild; -use Symfony\Component\Serializer\Tests\Fixtures\Annotations\AbstractDummySecondChild; +use Symfony\Component\Serializer\Tests\Fixtures\Attributes\AbstractDummy; +use Symfony\Component\Serializer\Tests\Fixtures\Attributes\AbstractDummyFirstChild; +use Symfony\Component\Serializer\Tests\Fixtures\Attributes\AbstractDummySecondChild; use Symfony\Component\Serializer\Tests\Fixtures\DenormalizableDummy; use Symfony\Component\Serializer\Tests\Fixtures\DummyFirstChildQuux; use Symfony\Component\Serializer\Tests\Fixtures\DummyMessageInterface; @@ -720,7 +719,7 @@ public function testDeserializeWrappedScalar() public function testUnionTypeDeserializable() { - $classMetadataFactory = new ClassMetadataFactory(new AnnotationLoader(new AnnotationReader())); + $classMetadataFactory = new ClassMetadataFactory(new AnnotationLoader()); $extractor = new PropertyInfoExtractor([], [new PhpDocExtractor(), new ReflectionExtractor()]); $serializer = new Serializer( [ @@ -752,7 +751,7 @@ public function testUnionTypeDeserializable() public function testUnionTypeDeserializableWithoutAllowedExtraAttributes() { - $classMetadataFactory = new ClassMetadataFactory(new AnnotationLoader(new AnnotationReader())); + $classMetadataFactory = new ClassMetadataFactory(new AnnotationLoader()); $extractor = new PropertyInfoExtractor([], [new PhpDocExtractor(), new ReflectionExtractor()]); $serializer = new Serializer( [ @@ -813,7 +812,7 @@ public function testTrueBuiltInTypes() private function serializerWithClassDiscriminator() { - $classMetadataFactory = new ClassMetadataFactory(new AnnotationLoader(new AnnotationReader())); + $classMetadataFactory = new ClassMetadataFactory(new AnnotationLoader()); return new Serializer([new ObjectNormalizer($classMetadataFactory, null, null, new ReflectionExtractor(), new ClassDiscriminatorFromClassMetadata($classMetadataFactory))], ['json' => new JsonEncoder()]); } @@ -1279,11 +1278,11 @@ public function testNoCollectDenormalizationErrorsWithWrongEnumOnConstructor() } } - public static function provideCollectDenormalizationErrors() + public static function provideCollectDenormalizationErrors(): array { return [ [null], - [new ClassMetadataFactory(new AnnotationLoader(new AnnotationReader()))], + [new ClassMetadataFactory(new AnnotationLoader())], ]; } From e95f730daaa6f391657842758a0316bffba0e903 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vasilij=20Du=C5=A1ko?= Date: Sun, 23 Jul 2023 21:02:50 +0300 Subject: [PATCH 124/297] More intuitive Logic Exception message --- Encoder/YamlEncoder.php | 2 +- Normalizer/ObjectNormalizer.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Encoder/YamlEncoder.php b/Encoder/YamlEncoder.php index c688c2283..984535c5d 100644 --- a/Encoder/YamlEncoder.php +++ b/Encoder/YamlEncoder.php @@ -43,7 +43,7 @@ class YamlEncoder implements EncoderInterface, DecoderInterface public function __construct(Dumper $dumper = null, Parser $parser = null, array $defaultContext = []) { if (!class_exists(Dumper::class)) { - throw new RuntimeException('The YamlEncoder class requires the "Yaml" component. Install "symfony/yaml" to use it.'); + throw new RuntimeException('The YamlEncoder class requires the "Yaml" component. Try running "composer require symfony/yaml".'); } $this->dumper = $dumper ?? new Dumper(); diff --git a/Normalizer/ObjectNormalizer.php b/Normalizer/ObjectNormalizer.php index 1bce3ebeb..d47e27ec8 100644 --- a/Normalizer/ObjectNormalizer.php +++ b/Normalizer/ObjectNormalizer.php @@ -37,7 +37,7 @@ class ObjectNormalizer extends AbstractObjectNormalizer public function __construct(ClassMetadataFactoryInterface $classMetadataFactory = null, NameConverterInterface $nameConverter = null, PropertyAccessorInterface $propertyAccessor = null, PropertyTypeExtractorInterface $propertyTypeExtractor = null, ClassDiscriminatorResolverInterface $classDiscriminatorResolver = null, callable $objectClassResolver = null, array $defaultContext = []) { if (!class_exists(PropertyAccess::class)) { - throw new LogicException('The ObjectNormalizer class requires the "PropertyAccess" component. Install "symfony/property-access" to use it.'); + throw new LogicException('The ObjectNormalizer class requires the "PropertyAccess" component. Try running "composer require symfony/property-access".'); } parent::__construct($classMetadataFactory, $nameConverter, $propertyTypeExtractor, $classDiscriminatorResolver, $objectClassResolver, $defaultContext); From 2040f03ff5775e8634e89ae67aac25b1e0fd3d9e Mon Sep 17 00:00:00 2001 From: "Alexander M. Turek" Date: Mon, 24 Jul 2023 12:45:07 +0200 Subject: [PATCH 125/297] [Serializer] Remove Doctrine annotations support --- Annotation/Context.php | 6 - Annotation/DiscriminatorMap.php | 6 - Annotation/Groups.php | 6 - Annotation/Ignore.php | 5 - Annotation/MaxDepth.php | 6 - Annotation/SerializedName.php | 6 - Annotation/SerializedPath.php | 6 - CHANGELOG.md | 2 + Mapping/Loader/AnnotationLoader.php | 68 ------- Tests/Fixtures/Annotations/AbstractDummy.php | 31 ---- .../Annotations/AbstractDummyFirstChild.php | 39 ---- .../Annotations/AbstractDummySecondChild.php | 39 ---- .../Annotations/AbstractDummyThirdChild.php | 16 -- .../Annotations/BadMethodContextDummy.php | 28 --- Tests/Fixtures/Annotations/ContextDummy.php | 50 ------ .../Annotations/ContextDummyParent.php | 30 ---- .../ContextDummyPromotedProperties.php | 53 ------ Tests/Fixtures/Annotations/Entity45016.php | 26 --- Tests/Fixtures/Annotations/GroupDummy.php | 97 ---------- .../Annotations/GroupDummyInterface.php | 25 --- .../Fixtures/Annotations/GroupDummyParent.php | 49 ----- Tests/Fixtures/Annotations/IgnoreDummy.php | 35 ---- .../IgnoreDummyAdditionalGetter.php | 27 --- ...ditionalGetterWithoutIgnoreAnnotations.php | 22 --- Tests/Fixtures/Annotations/MaxDepthDummy.php | 50 ------ .../Annotations/SerializedNameDummy.php | 47 ----- .../Annotations/SerializedPathDummy.php | 35 ---- .../SerializedPathInConstructorDummy.php | 25 --- ...rTestCase.php => AnnotationLoaderTest.php} | 91 ++++++---- .../AnnotationLoaderWithAttributesTest.php | 39 ---- ...ationLoaderWithDoctrineAnnotationsTest.php | 170 ------------------ composer.json | 2 - 32 files changed, 55 insertions(+), 1082 deletions(-) delete mode 100644 Tests/Fixtures/Annotations/AbstractDummy.php delete mode 100644 Tests/Fixtures/Annotations/AbstractDummyFirstChild.php delete mode 100644 Tests/Fixtures/Annotations/AbstractDummySecondChild.php delete mode 100644 Tests/Fixtures/Annotations/AbstractDummyThirdChild.php delete mode 100644 Tests/Fixtures/Annotations/BadMethodContextDummy.php delete mode 100644 Tests/Fixtures/Annotations/ContextDummy.php delete mode 100644 Tests/Fixtures/Annotations/ContextDummyParent.php delete mode 100644 Tests/Fixtures/Annotations/ContextDummyPromotedProperties.php delete mode 100644 Tests/Fixtures/Annotations/Entity45016.php delete mode 100644 Tests/Fixtures/Annotations/GroupDummy.php delete mode 100644 Tests/Fixtures/Annotations/GroupDummyInterface.php delete mode 100644 Tests/Fixtures/Annotations/GroupDummyParent.php delete mode 100644 Tests/Fixtures/Annotations/IgnoreDummy.php delete mode 100644 Tests/Fixtures/Annotations/IgnoreDummyAdditionalGetter.php delete mode 100644 Tests/Fixtures/Annotations/IgnoreDummyAdditionalGetterWithoutIgnoreAnnotations.php delete mode 100644 Tests/Fixtures/Annotations/MaxDepthDummy.php delete mode 100644 Tests/Fixtures/Annotations/SerializedNameDummy.php delete mode 100644 Tests/Fixtures/Annotations/SerializedPathDummy.php delete mode 100644 Tests/Fixtures/Annotations/SerializedPathInConstructorDummy.php rename Tests/Mapping/Loader/{AnnotationLoaderTestCase.php => AnnotationLoaderTest.php} (58%) delete mode 100644 Tests/Mapping/Loader/AnnotationLoaderWithAttributesTest.php delete mode 100644 Tests/Mapping/Loader/AnnotationLoaderWithDoctrineAnnotationsTest.php diff --git a/Annotation/Context.php b/Annotation/Context.php index e3878c7be..6995f24de 100644 --- a/Annotation/Context.php +++ b/Annotation/Context.php @@ -14,12 +14,6 @@ use Symfony\Component\Serializer\Exception\InvalidArgumentException; /** - * Annotation class for @Context(). - * - * @Annotation - * @NamedArgumentConstructor - * @Target({"PROPERTY", "METHOD"}) - * * @author Maxime Steinhausser */ #[\Attribute(\Attribute::TARGET_PROPERTY | \Attribute::TARGET_METHOD | \Attribute::IS_REPEATABLE)] diff --git a/Annotation/DiscriminatorMap.php b/Annotation/DiscriminatorMap.php index 8e9ebb830..71aeeeee1 100644 --- a/Annotation/DiscriminatorMap.php +++ b/Annotation/DiscriminatorMap.php @@ -14,12 +14,6 @@ use Symfony\Component\Serializer\Exception\InvalidArgumentException; /** - * Annotation class for @DiscriminatorMap(). - * - * @Annotation - * @NamedArgumentConstructor - * @Target({"CLASS"}) - * * @author Samuel Roze */ #[\Attribute(\Attribute::TARGET_CLASS)] diff --git a/Annotation/Groups.php b/Annotation/Groups.php index 5b16f567c..b9763d4c2 100644 --- a/Annotation/Groups.php +++ b/Annotation/Groups.php @@ -14,12 +14,6 @@ use Symfony\Component\Serializer\Exception\InvalidArgumentException; /** - * Annotation class for @Groups(). - * - * @Annotation - * @NamedArgumentConstructor - * @Target({"PROPERTY", "METHOD"}) - * * @author Kévin Dunglas */ #[\Attribute(\Attribute::TARGET_METHOD | \Attribute::TARGET_PROPERTY)] diff --git a/Annotation/Ignore.php b/Annotation/Ignore.php index b09e7007a..3e691eb7b 100644 --- a/Annotation/Ignore.php +++ b/Annotation/Ignore.php @@ -12,11 +12,6 @@ namespace Symfony\Component\Serializer\Annotation; /** - * Annotation class for @Ignore(). - * - * @Annotation - * @Target({"PROPERTY", "METHOD"}) - * * @author Kévin Dunglas */ #[\Attribute(\Attribute::TARGET_METHOD | \Attribute::TARGET_PROPERTY)] diff --git a/Annotation/MaxDepth.php b/Annotation/MaxDepth.php index 4fe449010..80b1d3542 100644 --- a/Annotation/MaxDepth.php +++ b/Annotation/MaxDepth.php @@ -14,12 +14,6 @@ use Symfony\Component\Serializer\Exception\InvalidArgumentException; /** - * Annotation class for @MaxDepth(). - * - * @Annotation - * @NamedArgumentConstructor - * @Target({"PROPERTY", "METHOD"}) - * * @author Kévin Dunglas */ #[\Attribute(\Attribute::TARGET_METHOD | \Attribute::TARGET_PROPERTY)] diff --git a/Annotation/SerializedName.php b/Annotation/SerializedName.php index 74df79cd2..12d742388 100644 --- a/Annotation/SerializedName.php +++ b/Annotation/SerializedName.php @@ -14,12 +14,6 @@ use Symfony\Component\Serializer\Exception\InvalidArgumentException; /** - * Annotation class for @SerializedName(). - * - * @Annotation - * @NamedArgumentConstructor - * @Target({"PROPERTY", "METHOD"}) - * * @author Fabien Bourigault */ #[\Attribute(\Attribute::TARGET_METHOD | \Attribute::TARGET_PROPERTY)] diff --git a/Annotation/SerializedPath.php b/Annotation/SerializedPath.php index 88ee8e47d..d7aa8c0d4 100644 --- a/Annotation/SerializedPath.php +++ b/Annotation/SerializedPath.php @@ -16,12 +16,6 @@ use Symfony\Component\Serializer\Exception\InvalidArgumentException; /** - * Annotation class for @SerializedPath(). - * - * @Annotation - * @NamedArgumentConstructor - * @Target({"PROPERTY", "METHOD"}) - * * @author Tobias Bönner */ #[\Attribute(\Attribute::TARGET_METHOD | \Attribute::TARGET_PROPERTY)] diff --git a/CHANGELOG.md b/CHANGELOG.md index 7c0c68f4b..917c40542 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,8 @@ CHANGELOG * Remove `CacheableSupportsMethodInterface`, use `NormalizerInterface` and `DenormalizerInterface` instead * Require explicit argument when calling `AttributeMetadata::setSerializedName()` and `ClassMetadata::setClassDiscriminatorMapping()` * Add argument `$context` to `NormalizerInterface::supportsNormalization()` and `DenormalizerInterface::supportsDenormalization()` + * Remove Doctrine annotations support in favor of native attributes + * Remove the annotation reader parameter from the constructor of `AnnotationLoader` 6.4 --- diff --git a/Mapping/Loader/AnnotationLoader.php b/Mapping/Loader/AnnotationLoader.php index 94d0f45dc..2ef9c553d 100644 --- a/Mapping/Loader/AnnotationLoader.php +++ b/Mapping/Loader/AnnotationLoader.php @@ -11,7 +11,6 @@ namespace Symfony\Component\Serializer\Mapping\Loader; -use Doctrine\Common\Annotations\Reader; use Symfony\Component\Serializer\Annotation\Context; use Symfony\Component\Serializer\Annotation\DiscriminatorMap; use Symfony\Component\Serializer\Annotation\Groups; @@ -43,14 +42,6 @@ class AnnotationLoader implements LoaderInterface Context::class, ]; - public function __construct( - private readonly ?Reader $reader = null, - ) { - if ($reader) { - trigger_deprecation('symfony/validator', '6.4', 'Passing a "%s" instance as argument 1 to "%s()" is deprecated, pass null or omit the parameter instead.', get_debug_type($reader), __METHOD__); - } - } - public function loadClassMetadata(ClassMetadataInterface $classMetadata): bool { $reflectionClass = $classMetadata->getReflectionClass(); @@ -187,20 +178,6 @@ public function loadAnnotations(\ReflectionMethod|\ReflectionClass|\ReflectionPr } } } - - if (null === $this->reader) { - return; - } - - if ($reflector instanceof \ReflectionClass) { - yield from $this->getClassAnnotations($reflector); - } - if ($reflector instanceof \ReflectionMethod) { - yield from $this->getMethodAnnotations($reflector); - } - if ($reflector instanceof \ReflectionProperty) { - yield from $this->getPropertyAnnotations($reflector); - } } private function setAttributeContextsForGroups(Context $annotation, AttributeMetadataInterface $attributeMetadata): void @@ -229,49 +206,4 @@ private function isKnownAttribute(string $attributeName): bool return false; } - - /** - * @return object[] - */ - private function getClassAnnotations(\ReflectionClass $reflector): array - { - if ($annotations = array_filter( - $this->reader->getClassAnnotations($reflector), - fn (object $annotation): bool => $this->isKnownAttribute($annotation::class), - )) { - trigger_deprecation('symfony/serializer', '6.4', 'Class "%s" uses Doctrine Annotations to configure serialization, which is deprecated. Use PHP attributes instead.', $reflector->getName()); - } - - return $annotations; - } - - /** - * @return object[] - */ - private function getMethodAnnotations(\ReflectionMethod $reflector): array - { - if ($annotations = array_filter( - $this->reader->getMethodAnnotations($reflector), - fn (object $annotation): bool => $this->isKnownAttribute($annotation::class), - )) { - trigger_deprecation('symfony/serializer', '6.4', 'Method "%s::%s()" uses Doctrine Annotations to configure serialization, which is deprecated. Use PHP attributes instead.', $reflector->getDeclaringClass()->getName(), $reflector->getName()); - } - - return $annotations; - } - - /** - * @return object[] - */ - private function getPropertyAnnotations(\ReflectionProperty $reflector): array - { - if ($annotations = array_filter( - $this->reader->getPropertyAnnotations($reflector), - fn (object $annotation): bool => $this->isKnownAttribute($annotation::class), - )) { - trigger_deprecation('symfony/serializer', '6.4', 'Property "%s::$%s" uses Doctrine Annotations to configure serialization, which is deprecated. Use PHP attributes instead.', $reflector->getDeclaringClass()->getName(), $reflector->getName()); - } - - return $annotations; - } } diff --git a/Tests/Fixtures/Annotations/AbstractDummy.php b/Tests/Fixtures/Annotations/AbstractDummy.php deleted file mode 100644 index fad842b0d..000000000 --- a/Tests/Fixtures/Annotations/AbstractDummy.php +++ /dev/null @@ -1,31 +0,0 @@ - - * - * 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\Annotations; - -use Symfony\Component\Serializer\Annotation\DiscriminatorMap; - -/** - * @DiscriminatorMap(typeProperty="type", mapping={ - * "first"="Symfony\Component\Serializer\Tests\Fixtures\Annotations\AbstractDummyFirstChild", - * "second"="Symfony\Component\Serializer\Tests\Fixtures\Annotations\AbstractDummySecondChild", - * "third"="Symfony\Component\Serializer\Tests\Fixtures\Annotations\AbstractDummyThirdChild", - * }) - */ -abstract class AbstractDummy -{ - public $foo; - - public function __construct($foo = null) - { - $this->foo = $foo; - } -} diff --git a/Tests/Fixtures/Annotations/AbstractDummyFirstChild.php b/Tests/Fixtures/Annotations/AbstractDummyFirstChild.php deleted file mode 100644 index 81f15aaf0..000000000 --- a/Tests/Fixtures/Annotations/AbstractDummyFirstChild.php +++ /dev/null @@ -1,39 +0,0 @@ - - * - * 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\Annotations; - -use Symfony\Component\Serializer\Tests\Fixtures\DummyFirstChildQuux; - -class AbstractDummyFirstChild extends AbstractDummy -{ - public $bar; - - /** @var DummyFirstChildQuux|null */ - public $quux; - - public function __construct($foo = null, $bar = null) - { - parent::__construct($foo); - - $this->bar = $bar; - } - - public function getQuux(): ?DummyFirstChildQuux - { - return $this->quux; - } - - public function setQuux(DummyFirstChildQuux $quux): void - { - $this->quux = $quux; - } -} diff --git a/Tests/Fixtures/Annotations/AbstractDummySecondChild.php b/Tests/Fixtures/Annotations/AbstractDummySecondChild.php deleted file mode 100644 index 4d113066b..000000000 --- a/Tests/Fixtures/Annotations/AbstractDummySecondChild.php +++ /dev/null @@ -1,39 +0,0 @@ - - * - * 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\Annotations; - -use Symfony\Component\Serializer\Tests\Fixtures\DummySecondChildQuux; - -class AbstractDummySecondChild extends AbstractDummy -{ - public $baz; - - /** @var DummySecondChildQuux|null */ - public $quux; - - public function __construct($foo = null, $baz = null) - { - parent::__construct($foo); - - $this->baz = $baz; - } - - public function getQuux(): ?DummySecondChildQuux - { - return $this->quux; - } - - public function setQuux(DummySecondChildQuux $quux): void - { - $this->quux = $quux; - } -} diff --git a/Tests/Fixtures/Annotations/AbstractDummyThirdChild.php b/Tests/Fixtures/Annotations/AbstractDummyThirdChild.php deleted file mode 100644 index 2df26c332..000000000 --- a/Tests/Fixtures/Annotations/AbstractDummyThirdChild.php +++ /dev/null @@ -1,16 +0,0 @@ - - * - * 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\Annotations; - -final class AbstractDummyThirdChild extends AbstractDummyFirstChild -{ -} diff --git a/Tests/Fixtures/Annotations/BadMethodContextDummy.php b/Tests/Fixtures/Annotations/BadMethodContextDummy.php deleted file mode 100644 index 77b3884de..000000000 --- a/Tests/Fixtures/Annotations/BadMethodContextDummy.php +++ /dev/null @@ -1,28 +0,0 @@ - - * - * 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\Annotations; - -use Symfony\Component\Serializer\Annotation\Context; - -/** - * @author Maxime Steinhausser - */ -class BadMethodContextDummy extends ContextDummyParent -{ - /** - * @Context({ "foo" = "bar" }) - */ - public function badMethod() - { - return 'bad_method'; - } -} diff --git a/Tests/Fixtures/Annotations/ContextDummy.php b/Tests/Fixtures/Annotations/ContextDummy.php deleted file mode 100644 index 804df290f..000000000 --- a/Tests/Fixtures/Annotations/ContextDummy.php +++ /dev/null @@ -1,50 +0,0 @@ - - * - * 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\Annotations; - -use Symfony\Component\Serializer\Annotation\Context; - -/** - * @author Maxime Steinhausser - */ -class ContextDummy extends ContextDummyParent -{ - /** - * @Context({ "foo" = "value", "bar" = "value", "nested" = { - * "nested_key" = "nested_value", - * }, "array": { "first", "second" } }) - * @Context({ "bar" = "value_for_group_a" }, groups = "a") - */ - public $foo; - - /** - * @Context( - * normalizationContext = { "format" = "d/m/Y" }, - * denormalizationContext = { "format" = "m-d-Y H:i" }, - * groups = {"a", "b"} - * ) - */ - public $bar; - - /** - * @Context(normalizationContext={ "prop" = "dummy_value" }) - */ - public $overriddenParentProperty; - - /** - * @Context({ "method" = "method_with_context" }) - */ - public function getMethodWithContext() - { - return 'method_with_context'; - } -} diff --git a/Tests/Fixtures/Annotations/ContextDummyParent.php b/Tests/Fixtures/Annotations/ContextDummyParent.php deleted file mode 100644 index b7b286c37..000000000 --- a/Tests/Fixtures/Annotations/ContextDummyParent.php +++ /dev/null @@ -1,30 +0,0 @@ - - * - * 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\Annotations; - -use Symfony\Component\Serializer\Annotation\Context; - -/** - * @author Maxime Steinhausser - */ -class ContextDummyParent -{ - /** - * @Context(normalizationContext={ "prop" = "dummy_parent_value" }) - */ - public $parentProperty; - - /** - * @Context(normalizationContext={ "prop" = "dummy_parent_value" }) - */ - public $overriddenParentProperty; -} diff --git a/Tests/Fixtures/Annotations/ContextDummyPromotedProperties.php b/Tests/Fixtures/Annotations/ContextDummyPromotedProperties.php deleted file mode 100644 index 5aa108d1e..000000000 --- a/Tests/Fixtures/Annotations/ContextDummyPromotedProperties.php +++ /dev/null @@ -1,53 +0,0 @@ - - * - * 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\Annotations; - -use Symfony\Component\Serializer\Annotation\Context; - -/** - * @author Maxime Steinhausser - */ -class ContextDummyPromotedProperties extends ContextDummyParent -{ - public function __construct( - /** - * @Context({ "foo" = "value", "bar" = "value", "nested" = { - * "nested_key" = "nested_value", - * }, "array": { "first", "second" } }) - * @Context({ "bar" = "value_for_group_a" }, groups = "a") - */ - public $foo, - - /** - * @Context( - * normalizationContext = { "format" = "d/m/Y" }, - * denormalizationContext = { "format" = "m-d-Y H:i" }, - * groups = {"a", "b"} - * ) - */ - public $bar, - - /** - * @Context(normalizationContext={ "prop" = "dummy_value" }) - */ - public $overriddenParentProperty, - ) { - } - - /** - * @Context({ "method" = "method_with_context" }) - */ - public function getMethodWithContext() - { - return 'method_with_context'; - } -} diff --git a/Tests/Fixtures/Annotations/Entity45016.php b/Tests/Fixtures/Annotations/Entity45016.php deleted file mode 100644 index a896d9b76..000000000 --- a/Tests/Fixtures/Annotations/Entity45016.php +++ /dev/null @@ -1,26 +0,0 @@ -id; - } - - /** - * @Ignore() - */ - public function badIgnore(): bool - { - return true; - } -} diff --git a/Tests/Fixtures/Annotations/GroupDummy.php b/Tests/Fixtures/Annotations/GroupDummy.php deleted file mode 100644 index 92935f490..000000000 --- a/Tests/Fixtures/Annotations/GroupDummy.php +++ /dev/null @@ -1,97 +0,0 @@ - - * - * 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\Annotations; - -use Symfony\Component\Serializer\Annotation\Groups; -use Symfony\Component\Serializer\Tests\Fixtures\ChildOfGroupsAnnotationDummy; -use Symfony\Component\Serializer\Tests\Fixtures\Annotations\GroupDummyInterface; - -/** - * @author Kévin Dunglas - */ -class GroupDummy extends GroupDummyParent implements GroupDummyInterface -{ - /** - * @Groups({"a"}) - */ - private $foo; - /** - * @Groups({"b", "c", "name_converter"}) - */ - protected $bar; - /** - * @ChildOfGroupsAnnotationDummy - */ - protected $quux; - - private $fooBar; - private $symfony; - - /** - * @Groups({"b"}) - */ - public function setBar($bar) - { - $this->bar = $bar; - } - - /** - * @Groups({"c"}) - */ - public function getBar() - { - return $this->bar; - } - - public function setFoo($foo) - { - $this->foo = $foo; - } - - public function getFoo() - { - return $this->foo; - } - - public function setFooBar($fooBar) - { - $this->fooBar = $fooBar; - } - - /** - * @Groups({"a", "b", "name_converter"}) - */ - public function isFooBar() - { - return $this->fooBar; - } - - public function setSymfony($symfony) - { - $this->symfony = $symfony; - } - - public function getSymfony() - { - return $this->symfony; - } - - public function getQuux() - { - return $this->quux; - } - - public function setQuux($quux): void - { - $this->quux = $quux; - } -} diff --git a/Tests/Fixtures/Annotations/GroupDummyInterface.php b/Tests/Fixtures/Annotations/GroupDummyInterface.php deleted file mode 100644 index c9a736bfa..000000000 --- a/Tests/Fixtures/Annotations/GroupDummyInterface.php +++ /dev/null @@ -1,25 +0,0 @@ - - * - * 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\Annotations; - -use Symfony\Component\Serializer\Annotation\Groups; - -/** - * @author Kévin Dunglas - */ -interface GroupDummyInterface -{ - /** - * @Groups({"a", "name_converter"}) - */ - public function getSymfony(); -} diff --git a/Tests/Fixtures/Annotations/GroupDummyParent.php b/Tests/Fixtures/Annotations/GroupDummyParent.php deleted file mode 100644 index 77d539b94..000000000 --- a/Tests/Fixtures/Annotations/GroupDummyParent.php +++ /dev/null @@ -1,49 +0,0 @@ - - * - * 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\Annotations; - -use Symfony\Component\Serializer\Annotation\Groups; - -/** - * @author Kévin Dunglas - */ -class GroupDummyParent -{ - /** - * @Groups({"a"}) - */ - private $kevin; - private $coopTilleuls; - - public function setKevin($kevin) - { - $this->kevin = $kevin; - } - - public function getKevin() - { - return $this->kevin; - } - - public function setCoopTilleuls($coopTilleuls) - { - $this->coopTilleuls = $coopTilleuls; - } - - /** - * @Groups({"a", "b"}) - */ - public function getCoopTilleuls() - { - return $this->coopTilleuls; - } -} diff --git a/Tests/Fixtures/Annotations/IgnoreDummy.php b/Tests/Fixtures/Annotations/IgnoreDummy.php deleted file mode 100644 index 900447c58..000000000 --- a/Tests/Fixtures/Annotations/IgnoreDummy.php +++ /dev/null @@ -1,35 +0,0 @@ - - * - * 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\Annotations; - -use Symfony\Component\Serializer\Annotation\Ignore; - -/** - * @author Kévin Dunglas - */ -class IgnoreDummy -{ - public $notIgnored; - /** - * @Ignore() - */ - public $ignored1; - private $ignored2; - - /** - * @Ignore() - */ - public function getIgnored2() - { - return $this->ignored2; - } -} diff --git a/Tests/Fixtures/Annotations/IgnoreDummyAdditionalGetter.php b/Tests/Fixtures/Annotations/IgnoreDummyAdditionalGetter.php deleted file mode 100644 index 326a9cd07..000000000 --- a/Tests/Fixtures/Annotations/IgnoreDummyAdditionalGetter.php +++ /dev/null @@ -1,27 +0,0 @@ -myValue; - } - - public function getExtraValue(string $parameter) - { - return $parameter; - } - - public function setExtraValue2(string $parameter) - { - } -} diff --git a/Tests/Fixtures/Annotations/IgnoreDummyAdditionalGetterWithoutIgnoreAnnotations.php b/Tests/Fixtures/Annotations/IgnoreDummyAdditionalGetterWithoutIgnoreAnnotations.php deleted file mode 100644 index 2b717c93a..000000000 --- a/Tests/Fixtures/Annotations/IgnoreDummyAdditionalGetterWithoutIgnoreAnnotations.php +++ /dev/null @@ -1,22 +0,0 @@ -myValue; - } - - public function getExtraValue(string $parameter) - { - return $parameter; - } - - public function setExtraValue2(string $parameter) - { - } -} diff --git a/Tests/Fixtures/Annotations/MaxDepthDummy.php b/Tests/Fixtures/Annotations/MaxDepthDummy.php deleted file mode 100644 index 12be2db03..000000000 --- a/Tests/Fixtures/Annotations/MaxDepthDummy.php +++ /dev/null @@ -1,50 +0,0 @@ - - * - * 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\Annotations; - -use Symfony\Component\Serializer\Annotation\MaxDepth; - -/** - * @author Kévin Dunglas - */ -class MaxDepthDummy -{ - /** - * @MaxDepth(2) - */ - public $foo; - - public $bar; - - /** - * @var self - */ - public $child; - - /** - * @MaxDepth(3) - */ - public function getBar() - { - return $this->bar; - } - - public function getChild() - { - return $this->child; - } - - public function getFoo() - { - return $this->foo; - } -} diff --git a/Tests/Fixtures/Annotations/SerializedNameDummy.php b/Tests/Fixtures/Annotations/SerializedNameDummy.php deleted file mode 100644 index 1eaa579b4..000000000 --- a/Tests/Fixtures/Annotations/SerializedNameDummy.php +++ /dev/null @@ -1,47 +0,0 @@ - - * - * 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\Annotations; - -use Symfony\Component\Serializer\Annotation\SerializedName; - -/** - * @author Fabien Bourigault - */ -class SerializedNameDummy -{ - /** - * @SerializedName("baz") - */ - public $foo; - - public $bar; - - public $quux; - - /** - * @var self - */ - public $child; - - /** - * @SerializedName("qux") - */ - public function getBar() - { - return $this->bar; - } - - public function getChild() - { - return $this->child; - } -} diff --git a/Tests/Fixtures/Annotations/SerializedPathDummy.php b/Tests/Fixtures/Annotations/SerializedPathDummy.php deleted file mode 100644 index cd50b81c3..000000000 --- a/Tests/Fixtures/Annotations/SerializedPathDummy.php +++ /dev/null @@ -1,35 +0,0 @@ - - * - * 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\Annotations; - -use Symfony\Component\Serializer\Annotation\SerializedPath; - -/** - * @author Tobias Bönner - */ -class SerializedPathDummy -{ - /** - * @SerializedPath("[one][two]") - */ - public $three; - - public $seven; - - /** - * @SerializedPath("[three][four]") - */ - public function getSeven() - { - return $this->seven; - } -} diff --git a/Tests/Fixtures/Annotations/SerializedPathInConstructorDummy.php b/Tests/Fixtures/Annotations/SerializedPathInConstructorDummy.php deleted file mode 100644 index a6d510908..000000000 --- a/Tests/Fixtures/Annotations/SerializedPathInConstructorDummy.php +++ /dev/null @@ -1,25 +0,0 @@ - - * - * 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\Annotations; - -use Symfony\Component\Serializer\Annotation\SerializedPath; - -class SerializedPathInConstructorDummy -{ - public function __construct( - /** - * @SerializedPath("[one][two]") - */ - public $three, - ) { - } -} diff --git a/Tests/Mapping/Loader/AnnotationLoaderTestCase.php b/Tests/Mapping/Loader/AnnotationLoaderTest.php similarity index 58% rename from Tests/Mapping/Loader/AnnotationLoaderTestCase.php rename to Tests/Mapping/Loader/AnnotationLoaderTest.php index 40d5e3cfe..ee34e0b90 100644 --- a/Tests/Mapping/Loader/AnnotationLoaderTestCase.php +++ b/Tests/Mapping/Loader/AnnotationLoaderTest.php @@ -19,21 +19,39 @@ use Symfony\Component\Serializer\Mapping\ClassMetadata; use Symfony\Component\Serializer\Mapping\Loader\AnnotationLoader; use Symfony\Component\Serializer\Mapping\Loader\LoaderInterface; +use Symfony\Component\Serializer\Tests\Fixtures\Attributes\AbstractDummy; +use Symfony\Component\Serializer\Tests\Fixtures\Attributes\AbstractDummyFirstChild; +use Symfony\Component\Serializer\Tests\Fixtures\Attributes\AbstractDummySecondChild; +use Symfony\Component\Serializer\Tests\Fixtures\Attributes\AbstractDummyThirdChild; +use Symfony\Component\Serializer\Tests\Fixtures\Attributes\BadAttributeDummy; +use Symfony\Component\Serializer\Tests\Fixtures\Attributes\BadMethodContextDummy; +use Symfony\Component\Serializer\Tests\Fixtures\Attributes\ContextDummyParent; +use Symfony\Component\Serializer\Tests\Fixtures\Attributes\ContextDummyPromotedProperties; +use Symfony\Component\Serializer\Tests\Fixtures\Attributes\Entity45016; +use Symfony\Component\Serializer\Tests\Fixtures\Attributes\GroupDummy; +use Symfony\Component\Serializer\Tests\Fixtures\Attributes\GroupDummyParent; +use Symfony\Component\Serializer\Tests\Fixtures\Attributes\IgnoreDummy; +use Symfony\Component\Serializer\Tests\Fixtures\Attributes\IgnoreDummyAdditionalGetter; +use Symfony\Component\Serializer\Tests\Fixtures\Attributes\IgnoreDummyAdditionalGetterWithoutIgnoreAnnotations; +use Symfony\Component\Serializer\Tests\Fixtures\Attributes\MaxDepthDummy; +use Symfony\Component\Serializer\Tests\Fixtures\Attributes\SerializedNameDummy; +use Symfony\Component\Serializer\Tests\Fixtures\Attributes\SerializedPathDummy; +use Symfony\Component\Serializer\Tests\Fixtures\Attributes\SerializedPathInConstructorDummy; use Symfony\Component\Serializer\Tests\Mapping\Loader\Features\ContextMappingTestTrait; use Symfony\Component\Serializer\Tests\Mapping\TestClassMetadataFactory; /** * @author Kévin Dunglas */ -abstract class AnnotationLoaderTestCase extends TestCase +class AnnotationLoaderTest extends TestCase { use ContextMappingTestTrait; - protected AnnotationLoader $loader; + private AnnotationLoader $loader; protected function setUp(): void { - $this->loader = $this->createLoader(); + $this->loader = new AnnotationLoader(); } public function testInterface() @@ -43,28 +61,28 @@ public function testInterface() public function testLoadClassMetadataReturnsTrueIfSuccessful() { - $classMetadata = new ClassMetadata($this->getNamespace().'\GroupDummy'); + $classMetadata = new ClassMetadata(GroupDummy::class); $this->assertTrue($this->loader->loadClassMetadata($classMetadata)); } public function testLoadGroups() { - $classMetadata = new ClassMetadata($this->getNamespace().'\GroupDummy'); + $classMetadata = new ClassMetadata(GroupDummy::class); $this->loader->loadClassMetadata($classMetadata); - $this->assertEquals(TestClassMetadataFactory::createClassMetadata($this->getNamespace()), $classMetadata); + $this->assertEquals(TestClassMetadataFactory::createClassMetadata('Symfony\Component\Serializer\Tests\Fixtures\Attributes'), $classMetadata); } public function testLoadDiscriminatorMap() { - $classMetadata = new ClassMetadata($this->getNamespace().'\AbstractDummy'); + $classMetadata = new ClassMetadata(AbstractDummy::class); $this->loader->loadClassMetadata($classMetadata); - $expected = new ClassMetadata($this->getNamespace().'\AbstractDummy', new ClassDiscriminatorMapping('type', [ - 'first' => $this->getNamespace().'\AbstractDummyFirstChild', - 'second' => $this->getNamespace().'\AbstractDummySecondChild', - 'third' => $this->getNamespace().'\AbstractDummyThirdChild', + $expected = new ClassMetadata(AbstractDummy::class, new ClassDiscriminatorMapping('type', [ + 'first' => AbstractDummyFirstChild::class, + 'second' => AbstractDummySecondChild::class, + 'third' => AbstractDummyThirdChild::class, ])); $expected->addAttributeMetadata(new AttributeMetadata('foo')); @@ -75,7 +93,7 @@ public function testLoadDiscriminatorMap() public function testLoadMaxDepth() { - $classMetadata = new ClassMetadata($this->getNamespace().'\MaxDepthDummy'); + $classMetadata = new ClassMetadata(MaxDepthDummy::class); $this->loader->loadClassMetadata($classMetadata); $attributesMetadata = $classMetadata->getAttributesMetadata(); @@ -85,7 +103,7 @@ public function testLoadMaxDepth() public function testLoadSerializedName() { - $classMetadata = new ClassMetadata($this->getNamespace().'\SerializedNameDummy'); + $classMetadata = new ClassMetadata(SerializedNameDummy::class); $this->loader->loadClassMetadata($classMetadata); $attributesMetadata = $classMetadata->getAttributesMetadata(); @@ -95,7 +113,7 @@ public function testLoadSerializedName() public function testLoadSerializedPath() { - $classMetadata = new ClassMetadata($this->getNamespace().'\SerializedPathDummy'); + $classMetadata = new ClassMetadata(SerializedPathDummy::class); $this->loader->loadClassMetadata($classMetadata); $attributesMetadata = $classMetadata->getAttributesMetadata(); @@ -105,7 +123,7 @@ public function testLoadSerializedPath() public function testLoadSerializedPathInConstructor() { - $classMetadata = new ClassMetadata($this->getNamespace().'\SerializedPathInConstructorDummy'); + $classMetadata = new ClassMetadata(SerializedPathInConstructorDummy::class); $this->loader->loadClassMetadata($classMetadata); $attributesMetadata = $classMetadata->getAttributesMetadata(); @@ -114,20 +132,20 @@ public function testLoadSerializedPathInConstructor() public function testLoadClassMetadataAndMerge() { - $classMetadata = new ClassMetadata($this->getNamespace().'\GroupDummy'); - $parentClassMetadata = new ClassMetadata($this->getNamespace().'\GroupDummyParent'); + $classMetadata = new ClassMetadata(GroupDummy::class); + $parentClassMetadata = new ClassMetadata(GroupDummyParent::class); $this->loader->loadClassMetadata($parentClassMetadata); $classMetadata->merge($parentClassMetadata); $this->loader->loadClassMetadata($classMetadata); - $this->assertEquals(TestClassMetadataFactory::createClassMetadata($this->getNamespace(), true), $classMetadata); + $this->assertEquals(TestClassMetadataFactory::createClassMetadata('Symfony\Component\Serializer\Tests\Fixtures\Attributes', true), $classMetadata); } public function testLoadIgnore() { - $classMetadata = new ClassMetadata($this->getNamespace().'\IgnoreDummy'); + $classMetadata = new ClassMetadata(IgnoreDummy::class); $this->loader->loadClassMetadata($classMetadata); $attributesMetadata = $classMetadata->getAttributesMetadata(); @@ -135,38 +153,29 @@ public function testLoadIgnore() $this->assertTrue($attributesMetadata['ignored2']->isIgnored()); } - public function testLoadContexts() - { - $this->assertLoadedContexts($this->getNamespace().'\ContextDummy', $this->getNamespace().'\ContextDummyParent'); - } - public function testLoadContextsPropertiesPromoted() { - $this->assertLoadedContexts($this->getNamespace().'\ContextDummyPromotedProperties', $this->getNamespace().'\ContextDummyParent'); + $this->assertLoadedContexts(ContextDummyPromotedProperties::class, ContextDummyParent::class); } public function testThrowsOnContextOnInvalidMethod() { - $class = $this->getNamespace().'\BadMethodContextDummy'; - $this->expectException(MappingException::class); - $this->expectExceptionMessage(sprintf('Context on "%s::badMethod()" cannot be added', $class)); + $this->expectExceptionMessage(sprintf('Context on "%s::badMethod()" cannot be added', BadMethodContextDummy::class)); $loader = $this->getLoaderForContextMapping(); - $classMetadata = new ClassMetadata($class); + $classMetadata = new ClassMetadata(BadMethodContextDummy::class); $loader->loadClassMetadata($classMetadata); } public function testCanHandleUnrelatedIgnoredMethods() { - $class = $this->getNamespace().'\Entity45016'; - $this->expectException(MappingException::class); - $this->expectExceptionMessage(sprintf('Ignore on "%s::badIgnore()" cannot be added', $class)); + $this->expectExceptionMessage(sprintf('Ignore on "%s::badIgnore()" cannot be added', Entity45016::class)); - $metadata = new ClassMetadata($class); + $metadata = new ClassMetadata(Entity45016::class); $loader = $this->getLoaderForContextMapping(); $loader->loadClassMetadata($metadata); @@ -174,7 +183,7 @@ public function testCanHandleUnrelatedIgnoredMethods() public function testIgnoreGetterWithRequiredParameterIfIgnoreAnnotationIsUsed() { - $classMetadata = new ClassMetadata($this->getNamespace().'\IgnoreDummyAdditionalGetter'); + $classMetadata = new ClassMetadata(IgnoreDummyAdditionalGetter::class); $this->getLoaderForContextMapping()->loadClassMetadata($classMetadata); $attributes = $classMetadata->getAttributesMetadata(); @@ -184,7 +193,7 @@ public function testIgnoreGetterWithRequiredParameterIfIgnoreAnnotationIsUsed() public function testIgnoreGetterWithRequiredParameterIfIgnoreAnnotationIsNotUsed() { - $classMetadata = new ClassMetadata($this->getNamespace().'\IgnoreDummyAdditionalGetterWithoutIgnoreAnnotations'); + $classMetadata = new ClassMetadata(IgnoreDummyAdditionalGetterWithoutIgnoreAnnotations::class); $this->getLoaderForContextMapping()->loadClassMetadata($classMetadata); $attributes = $classMetadata->getAttributesMetadata(); @@ -192,11 +201,17 @@ public function testIgnoreGetterWithRequiredParameterIfIgnoreAnnotationIsNotUsed self::assertArrayHasKey('extraValue2', $attributes); } - abstract protected function createLoader(): AnnotationLoader; + public function testLoadWithInvalidAttribute() + { + $this->expectException(MappingException::class); + $this->expectExceptionMessage('Could not instantiate attribute "Symfony\Component\Serializer\Annotation\Groups" on "Symfony\Component\Serializer\Tests\Fixtures\Attributes\BadAttributeDummy::myMethod()".'); + + $classMetadata = new ClassMetadata(BadAttributeDummy::class); - abstract protected function getNamespace(): string; + $this->loader->loadClassMetadata($classMetadata); + } - protected function getLoaderForContextMapping(): LoaderInterface + protected function getLoaderForContextMapping(): AnnotationLoader { return $this->loader; } diff --git a/Tests/Mapping/Loader/AnnotationLoaderWithAttributesTest.php b/Tests/Mapping/Loader/AnnotationLoaderWithAttributesTest.php deleted file mode 100644 index 89e94b663..000000000 --- a/Tests/Mapping/Loader/AnnotationLoaderWithAttributesTest.php +++ /dev/null @@ -1,39 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Component\Serializer\Tests\Mapping\Loader; - -use Symfony\Component\Serializer\Exception\MappingException; -use Symfony\Component\Serializer\Mapping\ClassMetadata; -use Symfony\Component\Serializer\Mapping\Loader\AnnotationLoader; - -class AnnotationLoaderWithAttributesTest extends AnnotationLoaderTestCase -{ - protected function createLoader(): AnnotationLoader - { - return new AnnotationLoader(); - } - - protected function getNamespace(): string - { - return 'Symfony\Component\Serializer\Tests\Fixtures\Attributes'; - } - - public function testLoadWithInvalidAttribute() - { - $this->expectException(MappingException::class); - $this->expectExceptionMessage('Could not instantiate attribute "Symfony\Component\Serializer\Annotation\Groups" on "Symfony\Component\Serializer\Tests\Fixtures\Attributes\BadAttributeDummy::myMethod()".'); - - $classMetadata = new ClassMetadata($this->getNamespace().'\BadAttributeDummy'); - - $this->loader->loadClassMetadata($classMetadata); - } -} diff --git a/Tests/Mapping/Loader/AnnotationLoaderWithDoctrineAnnotationsTest.php b/Tests/Mapping/Loader/AnnotationLoaderWithDoctrineAnnotationsTest.php deleted file mode 100644 index 348f8c71c..000000000 --- a/Tests/Mapping/Loader/AnnotationLoaderWithDoctrineAnnotationsTest.php +++ /dev/null @@ -1,170 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Component\Serializer\Tests\Mapping\Loader; - -use Doctrine\Common\Annotations\AnnotationReader; -use Symfony\Bridge\PhpUnit\ExpectDeprecationTrait; -use Symfony\Component\Serializer\Mapping\Loader\AnnotationLoader; - -/** - * @group legacy - */ -class AnnotationLoaderWithDoctrineAnnotationsTest extends AnnotationLoaderTestCase -{ - use ExpectDeprecationTrait; - - protected function setUp(): void - { - $this->expectDeprecation('Since symfony/validator 6.4: Passing a "Doctrine\Common\Annotations\AnnotationReader" instance as argument 1 to "Symfony\Component\Serializer\Mapping\Loader\AnnotationLoader::__construct()" is deprecated, pass null or omit the parameter instead.'); - - parent::setUp(); - } - - public function testLoadClassMetadataReturnsTrueIfSuccessful() - { - $this->expectDeprecation('Since symfony/serializer 6.4: Property "Symfony\Component\Serializer\Tests\Fixtures\Annotations\GroupDummy::$foo" uses Doctrine Annotations to configure serialization, which is deprecated. Use PHP attributes instead.'); - $this->expectDeprecation('Since symfony/serializer 6.4: Property "Symfony\Component\Serializer\Tests\Fixtures\Annotations\GroupDummy::$bar" uses Doctrine Annotations to configure serialization, which is deprecated. Use PHP attributes instead.'); - $this->expectDeprecation('Since symfony/serializer 6.4: Property "Symfony\Component\Serializer\Tests\Fixtures\Annotations\GroupDummy::$quux" uses Doctrine Annotations to configure serialization, which is deprecated. Use PHP attributes instead.'); - $this->expectDeprecation('Since symfony/serializer 6.4: Method "Symfony\Component\Serializer\Tests\Fixtures\Annotations\GroupDummy::setBar()" uses Doctrine Annotations to configure serialization, which is deprecated. Use PHP attributes instead.'); - $this->expectDeprecation('Since symfony/serializer 6.4: Method "Symfony\Component\Serializer\Tests\Fixtures\Annotations\GroupDummy::getBar()" uses Doctrine Annotations to configure serialization, which is deprecated. Use PHP attributes instead.'); - $this->expectDeprecation('Since symfony/serializer 6.4: Method "Symfony\Component\Serializer\Tests\Fixtures\Annotations\GroupDummy::isFooBar()" uses Doctrine Annotations to configure serialization, which is deprecated. Use PHP attributes instead.'); - - parent::testLoadClassMetadataReturnsTrueIfSuccessful(); - } - - public function testLoadGroups() - { - $this->expectDeprecation('Since symfony/serializer 6.4: Property "Symfony\Component\Serializer\Tests\Fixtures\Annotations\GroupDummy::$foo" uses Doctrine Annotations to configure serialization, which is deprecated. Use PHP attributes instead.'); - $this->expectDeprecation('Since symfony/serializer 6.4: Property "Symfony\Component\Serializer\Tests\Fixtures\Annotations\GroupDummy::$bar" uses Doctrine Annotations to configure serialization, which is deprecated. Use PHP attributes instead.'); - $this->expectDeprecation('Since symfony/serializer 6.4: Property "Symfony\Component\Serializer\Tests\Fixtures\Annotations\GroupDummy::$quux" uses Doctrine Annotations to configure serialization, which is deprecated. Use PHP attributes instead.'); - $this->expectDeprecation('Since symfony/serializer 6.4: Method "Symfony\Component\Serializer\Tests\Fixtures\Annotations\GroupDummy::setBar()" uses Doctrine Annotations to configure serialization, which is deprecated. Use PHP attributes instead.'); - $this->expectDeprecation('Since symfony/serializer 6.4: Method "Symfony\Component\Serializer\Tests\Fixtures\Annotations\GroupDummy::getBar()" uses Doctrine Annotations to configure serialization, which is deprecated. Use PHP attributes instead.'); - $this->expectDeprecation('Since symfony/serializer 6.4: Method "Symfony\Component\Serializer\Tests\Fixtures\Annotations\GroupDummy::isFooBar()" uses Doctrine Annotations to configure serialization, which is deprecated. Use PHP attributes instead.'); - - parent::testLoadGroups(); - } - - public function testLoadDiscriminatorMap() - { - $this->expectDeprecation('Since symfony/serializer 6.4: Class "Symfony\Component\Serializer\Tests\Fixtures\Annotations\AbstractDummy" uses Doctrine Annotations to configure serialization, which is deprecated. Use PHP attributes instead.'); - - parent::testLoadDiscriminatorMap(); - } - - public function testLoadMaxDepth() - { - $this->expectDeprecation('Since symfony/serializer 6.4: Property "Symfony\Component\Serializer\Tests\Fixtures\Annotations\MaxDepthDummy::$foo" uses Doctrine Annotations to configure serialization, which is deprecated. Use PHP attributes instead.'); - $this->expectDeprecation('Since symfony/serializer 6.4: Method "Symfony\Component\Serializer\Tests\Fixtures\Annotations\MaxDepthDummy::getBar()" uses Doctrine Annotations to configure serialization, which is deprecated. Use PHP attributes instead.'); - - parent::testLoadMaxDepth(); - } - - public function testLoadSerializedName() - { - $this->expectDeprecation('Since symfony/serializer 6.4: Property "Symfony\Component\Serializer\Tests\Fixtures\Annotations\SerializedNameDummy::$foo" uses Doctrine Annotations to configure serialization, which is deprecated. Use PHP attributes instead.'); - $this->expectDeprecation('Since symfony/serializer 6.4: Method "Symfony\Component\Serializer\Tests\Fixtures\Annotations\SerializedNameDummy::getBar()" uses Doctrine Annotations to configure serialization, which is deprecated. Use PHP attributes instead.'); - - parent::testLoadSerializedName(); - } - - public function testLoadSerializedPath() - { - $this->expectDeprecation('Since symfony/serializer 6.4: Property "Symfony\Component\Serializer\Tests\Fixtures\Annotations\SerializedPathDummy::$three" uses Doctrine Annotations to configure serialization, which is deprecated. Use PHP attributes instead.'); - $this->expectDeprecation('Since symfony/serializer 6.4: Method "Symfony\Component\Serializer\Tests\Fixtures\Annotations\SerializedPathDummy::getSeven()" uses Doctrine Annotations to configure serialization, which is deprecated. Use PHP attributes instead.'); - - parent::testLoadSerializedPath(); - } - - public function testLoadSerializedPathInConstructor() - { - $this->expectDeprecation('Since symfony/serializer 6.4: Property "Symfony\Component\Serializer\Tests\Fixtures\Annotations\SerializedPathInConstructorDummy::$three" uses Doctrine Annotations to configure serialization, which is deprecated. Use PHP attributes instead.'); - - parent::testLoadSerializedPathInConstructor(); - } - - public function testLoadClassMetadataAndMerge() - { - $this->expectDeprecation('Since symfony/serializer 6.4: Property "Symfony\Component\Serializer\Tests\Fixtures\Annotations\GroupDummyParent::$kevin" uses Doctrine Annotations to configure serialization, which is deprecated. Use PHP attributes instead.'); - $this->expectDeprecation('Since symfony/serializer 6.4: Method "Symfony\Component\Serializer\Tests\Fixtures\Annotations\GroupDummyParent::getCoopTilleuls()" uses Doctrine Annotations to configure serialization, which is deprecated. Use PHP attributes instead.'); - $this->expectDeprecation('Since symfony/serializer 6.4: Property "Symfony\Component\Serializer\Tests\Fixtures\Annotations\GroupDummy::$foo" uses Doctrine Annotations to configure serialization, which is deprecated. Use PHP attributes instead.'); - $this->expectDeprecation('Since symfony/serializer 6.4: Property "Symfony\Component\Serializer\Tests\Fixtures\Annotations\GroupDummy::$bar" uses Doctrine Annotations to configure serialization, which is deprecated. Use PHP attributes instead.'); - $this->expectDeprecation('Since symfony/serializer 6.4: Property "Symfony\Component\Serializer\Tests\Fixtures\Annotations\GroupDummy::$quux" uses Doctrine Annotations to configure serialization, which is deprecated. Use PHP attributes instead.'); - $this->expectDeprecation('Since symfony/serializer 6.4: Method "Symfony\Component\Serializer\Tests\Fixtures\Annotations\GroupDummy::setBar()" uses Doctrine Annotations to configure serialization, which is deprecated. Use PHP attributes instead.'); - $this->expectDeprecation('Since symfony/serializer 6.4: Method "Symfony\Component\Serializer\Tests\Fixtures\Annotations\GroupDummy::getBar()" uses Doctrine Annotations to configure serialization, which is deprecated. Use PHP attributes instead.'); - $this->expectDeprecation('Since symfony/serializer 6.4: Method "Symfony\Component\Serializer\Tests\Fixtures\Annotations\GroupDummy::isFooBar()" uses Doctrine Annotations to configure serialization, which is deprecated. Use PHP attributes instead.'); - - parent::testLoadClassMetadataAndMerge(); - } - - public function testLoadIgnore() - { - $this->expectDeprecation('Since symfony/serializer 6.4: Property "Symfony\Component\Serializer\Tests\Fixtures\Annotations\IgnoreDummy::$ignored1" uses Doctrine Annotations to configure serialization, which is deprecated. Use PHP attributes instead.'); - $this->expectDeprecation('Since symfony/serializer 6.4: Method "Symfony\Component\Serializer\Tests\Fixtures\Annotations\IgnoreDummy::getIgnored2()" uses Doctrine Annotations to configure serialization, which is deprecated. Use PHP attributes instead.'); - - parent::testLoadIgnore(); - } - - public function testLoadContexts() - { - $this->expectDeprecation('Since symfony/serializer 6.4: Property "Symfony\Component\Serializer\Tests\Fixtures\Annotations\ContextDummyParent::$parentProperty" uses Doctrine Annotations to configure serialization, which is deprecated. Use PHP attributes instead.'); - $this->expectDeprecation('Since symfony/serializer 6.4: Property "Symfony\Component\Serializer\Tests\Fixtures\Annotations\ContextDummyParent::$overriddenParentProperty" uses Doctrine Annotations to configure serialization, which is deprecated. Use PHP attributes instead.'); - $this->expectDeprecation('Since symfony/serializer 6.4: Property "Symfony\Component\Serializer\Tests\Fixtures\Annotations\ContextDummy::$foo" uses Doctrine Annotations to configure serialization, which is deprecated. Use PHP attributes instead.'); - $this->expectDeprecation('Since symfony/serializer 6.4: Property "Symfony\Component\Serializer\Tests\Fixtures\Annotations\ContextDummy::$bar" uses Doctrine Annotations to configure serialization, which is deprecated. Use PHP attributes instead.'); - $this->expectDeprecation('Since symfony/serializer 6.4: Property "Symfony\Component\Serializer\Tests\Fixtures\Annotations\ContextDummy::$overriddenParentProperty" uses Doctrine Annotations to configure serialization, which is deprecated. Use PHP attributes instead.'); - $this->expectDeprecation('Since symfony/serializer 6.4: Method "Symfony\Component\Serializer\Tests\Fixtures\Annotations\ContextDummy::getMethodWithContext()" uses Doctrine Annotations to configure serialization, which is deprecated. Use PHP attributes instead.'); - - parent::testLoadContexts(); - } - - public function testLoadContextsPropertiesPromoted() - { - $this->expectDeprecation('Since symfony/serializer 6.4: Property "Symfony\Component\Serializer\Tests\Fixtures\Annotations\ContextDummyParent::$parentProperty" uses Doctrine Annotations to configure serialization, which is deprecated. Use PHP attributes instead.'); - $this->expectDeprecation('Since symfony/serializer 6.4: Property "Symfony\Component\Serializer\Tests\Fixtures\Annotations\ContextDummyParent::$overriddenParentProperty" uses Doctrine Annotations to configure serialization, which is deprecated. Use PHP attributes instead.'); - $this->expectDeprecation('Since symfony/serializer 6.4: Property "Symfony\Component\Serializer\Tests\Fixtures\Annotations\ContextDummyPromotedProperties::$foo" uses Doctrine Annotations to configure serialization, which is deprecated. Use PHP attributes instead.'); - $this->expectDeprecation('Since symfony/serializer 6.4: Property "Symfony\Component\Serializer\Tests\Fixtures\Annotations\ContextDummyPromotedProperties::$bar" uses Doctrine Annotations to configure serialization, which is deprecated. Use PHP attributes instead.'); - $this->expectDeprecation('Since symfony/serializer 6.4: Property "Symfony\Component\Serializer\Tests\Fixtures\Annotations\ContextDummyPromotedProperties::$overriddenParentProperty" uses Doctrine Annotations to configure serialization, which is deprecated. Use PHP attributes instead.'); - $this->expectDeprecation('Since symfony/serializer 6.4: Method "Symfony\Component\Serializer\Tests\Fixtures\Annotations\ContextDummyPromotedProperties::getMethodWithContext()" uses Doctrine Annotations to configure serialization, which is deprecated. Use PHP attributes instead.'); - - parent::testLoadContextsPropertiesPromoted(); - } - - public function testThrowsOnContextOnInvalidMethod() - { - $this->expectDeprecation('Since symfony/serializer 6.4: Method "Symfony\Component\Serializer\Tests\Fixtures\Annotations\BadMethodContextDummy::badMethod()" uses Doctrine Annotations to configure serialization, which is deprecated. Use PHP attributes instead.'); - - parent::testThrowsOnContextOnInvalidMethod(); - } - - public function testCanHandleUnrelatedIgnoredMethods() - { - $this->expectDeprecation('Since symfony/serializer 6.4: Method "Symfony\Component\Serializer\Tests\Fixtures\Annotations\Entity45016::badIgnore()" uses Doctrine Annotations to configure serialization, which is deprecated. Use PHP attributes instead.'); - - parent::testCanHandleUnrelatedIgnoredMethods(); - } - - public function testIgnoreGetterWithRequiredParameterIfIgnoreAnnotationIsUsed() - { - $this->expectDeprecation('Since symfony/serializer 6.4: Method "Symfony\Component\Serializer\Tests\Fixtures\Annotations\IgnoreDummyAdditionalGetter::getMyValue()" uses Doctrine Annotations to configure serialization, which is deprecated. Use PHP attributes instead.'); - - parent::testIgnoreGetterWithRequiredParameterIfIgnoreAnnotationIsUsed(); - } - - protected function createLoader(): AnnotationLoader - { - return new AnnotationLoader(new AnnotationReader()); - } - - protected function getNamespace(): string - { - return 'Symfony\Component\Serializer\Tests\Fixtures\Annotations'; - } -} diff --git a/composer.json b/composer.json index d7368258b..3d89edb06 100644 --- a/composer.json +++ b/composer.json @@ -20,7 +20,6 @@ "symfony/polyfill-ctype": "~1.8" }, "require-dev": { - "doctrine/annotations": "^1.12|^2", "phpdocumentor/reflection-docblock": "^3.2|^4.0|^5.0", "symfony/cache": "^6.4|^7.0", "symfony/config": "^6.4|^7.0", @@ -41,7 +40,6 @@ "symfony/yaml": "^6.4|^7.0" }, "conflict": { - "doctrine/annotations": "<1.12", "phpdocumentor/reflection-docblock": "<3.2.2", "phpdocumentor/type-resolver": "<1.4.0", "symfony/dependency-injection": "<6.4", From e8a122482104652fb29b339397285b53f0806e10 Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Fri, 21 Jul 2023 15:28:24 +0200 Subject: [PATCH 126/297] Use typed properties in tests as much as possible --- Tests/Encoder/ChainDecoderTest.php | 7 ++++--- Tests/Encoder/ChainEncoderTest.php | 7 ++++--- Tests/Encoder/CsvEncoderTest.php | 5 +---- Tests/Encoder/JsonDecodeTest.php | 3 +-- Tests/Encoder/JsonEncodeTest.php | 2 +- Tests/Encoder/JsonEncoderTest.php | 4 ++-- Tests/Encoder/XmlEncoderTest.php | 8 ++------ Tests/Normalizer/AbstractNormalizerTest.php | 11 ++--------- Tests/Normalizer/AbstractObjectNormalizerTest.php | 2 +- Tests/Normalizer/BackedEnumNormalizerTest.php | 5 +---- .../ConstraintViolationListNormalizerTest.php | 2 +- Tests/Normalizer/CustomNormalizerTest.php | 5 +---- Tests/Normalizer/DataUriNormalizerTest.php | 5 +---- Tests/Normalizer/DateIntervalNormalizerTest.php | 5 +---- Tests/Normalizer/DateTimeNormalizerTest.php | 5 +---- Tests/Normalizer/DateTimeZoneNormalizerTest.php | 5 +---- Tests/Normalizer/FormErrorNormalizerTest.php | 11 ++--------- Tests/Normalizer/JsonSerializableNormalizerTest.php | 11 ++--------- Tests/Normalizer/ProblemNormalizerTest.php | 5 +---- Tests/Normalizer/UidNormalizerTest.php | 5 +---- Tests/Normalizer/UnwrappinDenormalizerTest.php | 6 +++--- 21 files changed, 34 insertions(+), 85 deletions(-) diff --git a/Tests/Encoder/ChainDecoderTest.php b/Tests/Encoder/ChainDecoderTest.php index 8f433ce0f..c3bbad2ab 100644 --- a/Tests/Encoder/ChainDecoderTest.php +++ b/Tests/Encoder/ChainDecoderTest.php @@ -11,6 +11,7 @@ namespace Symfony\Component\Serializer\Tests\Encoder; +use PHPUnit\Framework\MockObject\MockObject; use PHPUnit\Framework\TestCase; use Symfony\Component\Serializer\Encoder\ChainDecoder; use Symfony\Component\Serializer\Encoder\ContextAwareDecoderInterface; @@ -23,9 +24,9 @@ class ChainDecoderTest extends TestCase private const FORMAT_2 = 'format2'; private const FORMAT_3 = 'format3'; - private $chainDecoder; - private $decoder1; - private $decoder2; + private ChainDecoder $chainDecoder; + private MockObject&ContextAwareDecoderInterface $decoder1; + private MockObject&DecoderInterface $decoder2; protected function setUp(): void { diff --git a/Tests/Encoder/ChainEncoderTest.php b/Tests/Encoder/ChainEncoderTest.php index 0afd67813..375d08054 100644 --- a/Tests/Encoder/ChainEncoderTest.php +++ b/Tests/Encoder/ChainEncoderTest.php @@ -11,6 +11,7 @@ namespace Symfony\Component\Serializer\Tests\Encoder; +use PHPUnit\Framework\MockObject\MockObject; use PHPUnit\Framework\TestCase; use Symfony\Component\Serializer\Debug\TraceableEncoder; use Symfony\Component\Serializer\Encoder\ChainEncoder; @@ -25,9 +26,9 @@ class ChainEncoderTest extends TestCase private const FORMAT_2 = 'format2'; private const FORMAT_3 = 'format3'; - private $chainEncoder; - private $encoder1; - private $encoder2; + private ChainEncoder $chainEncoder; + private MockObject&ContextAwareEncoderInterface $encoder1; + private MockObject&EncoderInterface $encoder2; protected function setUp(): void { diff --git a/Tests/Encoder/CsvEncoderTest.php b/Tests/Encoder/CsvEncoderTest.php index c0c8ec740..448622c5e 100644 --- a/Tests/Encoder/CsvEncoderTest.php +++ b/Tests/Encoder/CsvEncoderTest.php @@ -20,10 +20,7 @@ */ class CsvEncoderTest extends TestCase { - /** - * @var CsvEncoder - */ - private $encoder; + private CsvEncoder $encoder; protected function setUp(): void { diff --git a/Tests/Encoder/JsonDecodeTest.php b/Tests/Encoder/JsonDecodeTest.php index 62045972d..ffef479e2 100644 --- a/Tests/Encoder/JsonDecodeTest.php +++ b/Tests/Encoder/JsonDecodeTest.php @@ -18,8 +18,7 @@ class JsonDecodeTest extends TestCase { - /** @var \Symfony\Component\Serializer\Encoder\JsonDecode */ - private $decode; + private JsonDecode $decode; protected function setUp(): void { diff --git a/Tests/Encoder/JsonEncodeTest.php b/Tests/Encoder/JsonEncodeTest.php index 779cad6e3..e5e653403 100644 --- a/Tests/Encoder/JsonEncodeTest.php +++ b/Tests/Encoder/JsonEncodeTest.php @@ -18,7 +18,7 @@ class JsonEncodeTest extends TestCase { - private $encode; + private JsonEncode $encode; protected function setUp(): void { diff --git a/Tests/Encoder/JsonEncoderTest.php b/Tests/Encoder/JsonEncoderTest.php index 6cd1f82b1..1b47684ae 100644 --- a/Tests/Encoder/JsonEncoderTest.php +++ b/Tests/Encoder/JsonEncoderTest.php @@ -19,8 +19,8 @@ class JsonEncoderTest extends TestCase { - private $encoder; - private $serializer; + private JsonEncoder $encoder; + private Serializer $serializer; protected function setUp(): void { diff --git a/Tests/Encoder/XmlEncoderTest.php b/Tests/Encoder/XmlEncoderTest.php index 965256d37..a10cfbba9 100644 --- a/Tests/Encoder/XmlEncoderTest.php +++ b/Tests/Encoder/XmlEncoderTest.php @@ -29,12 +29,8 @@ class XmlEncoderTest extends TestCase { - /** - * @var XmlEncoder - */ - private $encoder; - - private $exampleDateTimeString = '2017-02-19T15:16:08+0300'; + private XmlEncoder $encoder; + private string $exampleDateTimeString = '2017-02-19T15:16:08+0300'; protected function setUp(): void { diff --git a/Tests/Normalizer/AbstractNormalizerTest.php b/Tests/Normalizer/AbstractNormalizerTest.php index 74018ebb4..924f07e34 100644 --- a/Tests/Normalizer/AbstractNormalizerTest.php +++ b/Tests/Normalizer/AbstractNormalizerTest.php @@ -42,15 +42,8 @@ */ class AbstractNormalizerTest extends TestCase { - /** - * @var AbstractNormalizerDummy - */ - private $normalizer; - - /** - * @var MockObject&ClassMetadataFactoryInterface - */ - private $classMetadata; + private AbstractNormalizerDummy $normalizer; + private MockObject&ClassMetadataFactoryInterface $classMetadata; protected function setUp(): void { diff --git a/Tests/Normalizer/AbstractObjectNormalizerTest.php b/Tests/Normalizer/AbstractObjectNormalizerTest.php index 42eed47eb..aa9d57ad1 100644 --- a/Tests/Normalizer/AbstractObjectNormalizerTest.php +++ b/Tests/Normalizer/AbstractObjectNormalizerTest.php @@ -996,7 +996,7 @@ class DummyChild class SerializerCollectionDummy implements SerializerInterface, DenormalizerInterface { - private $normalizers; + private array $normalizers; /** * @param DenormalizerInterface[] $normalizers diff --git a/Tests/Normalizer/BackedEnumNormalizerTest.php b/Tests/Normalizer/BackedEnumNormalizerTest.php index 997ac4fa9..46963fe70 100644 --- a/Tests/Normalizer/BackedEnumNormalizerTest.php +++ b/Tests/Normalizer/BackedEnumNormalizerTest.php @@ -24,10 +24,7 @@ */ class BackedEnumNormalizerTest extends TestCase { - /** - * @var BackedEnumNormalizer - */ - private $normalizer; + private BackedEnumNormalizer $normalizer; protected function setUp(): void { diff --git a/Tests/Normalizer/ConstraintViolationListNormalizerTest.php b/Tests/Normalizer/ConstraintViolationListNormalizerTest.php index 8e8df388d..b7a4273f2 100644 --- a/Tests/Normalizer/ConstraintViolationListNormalizerTest.php +++ b/Tests/Normalizer/ConstraintViolationListNormalizerTest.php @@ -24,7 +24,7 @@ */ class ConstraintViolationListNormalizerTest extends TestCase { - private $normalizer; + private ConstraintViolationListNormalizer $normalizer; protected function setUp(): void { diff --git a/Tests/Normalizer/CustomNormalizerTest.php b/Tests/Normalizer/CustomNormalizerTest.php index d69ee8e40..2b4af4054 100644 --- a/Tests/Normalizer/CustomNormalizerTest.php +++ b/Tests/Normalizer/CustomNormalizerTest.php @@ -21,10 +21,7 @@ class CustomNormalizerTest extends TestCase { - /** - * @var CustomNormalizer - */ - private $normalizer; + private CustomNormalizer $normalizer; protected function setUp(): void { diff --git a/Tests/Normalizer/DataUriNormalizerTest.php b/Tests/Normalizer/DataUriNormalizerTest.php index 8c55ac1ab..92e173fe0 100644 --- a/Tests/Normalizer/DataUriNormalizerTest.php +++ b/Tests/Normalizer/DataUriNormalizerTest.php @@ -27,10 +27,7 @@ class DataUriNormalizerTest extends TestCase private const TEST_TXT_DATA = 'data:text/plain,K%C3%A9vin%20Dunglas%0A'; private const TEST_TXT_CONTENT = "Kévin Dunglas\n"; - /** - * @var DataUriNormalizer - */ - private $normalizer; + private DataUriNormalizer $normalizer; protected function setUp(): void { diff --git a/Tests/Normalizer/DateIntervalNormalizerTest.php b/Tests/Normalizer/DateIntervalNormalizerTest.php index cfe8c573c..9c50cc9b0 100644 --- a/Tests/Normalizer/DateIntervalNormalizerTest.php +++ b/Tests/Normalizer/DateIntervalNormalizerTest.php @@ -21,10 +21,7 @@ */ class DateIntervalNormalizerTest extends TestCase { - /** - * @var DateIntervalNormalizer - */ - private $normalizer; + private DateIntervalNormalizer $normalizer; protected function setUp(): void { diff --git a/Tests/Normalizer/DateTimeNormalizerTest.php b/Tests/Normalizer/DateTimeNormalizerTest.php index 5f30f1be9..9c9a25bbb 100644 --- a/Tests/Normalizer/DateTimeNormalizerTest.php +++ b/Tests/Normalizer/DateTimeNormalizerTest.php @@ -21,10 +21,7 @@ */ class DateTimeNormalizerTest extends TestCase { - /** - * @var DateTimeNormalizer - */ - private $normalizer; + private DateTimeNormalizer $normalizer; protected function setUp(): void { diff --git a/Tests/Normalizer/DateTimeZoneNormalizerTest.php b/Tests/Normalizer/DateTimeZoneNormalizerTest.php index b32e39d03..6ee179dbe 100644 --- a/Tests/Normalizer/DateTimeZoneNormalizerTest.php +++ b/Tests/Normalizer/DateTimeZoneNormalizerTest.php @@ -21,10 +21,7 @@ */ class DateTimeZoneNormalizerTest extends TestCase { - /** - * @var DateTimeZoneNormalizer - */ - private $normalizer; + private DateTimeZoneNormalizer $normalizer; protected function setUp(): void { diff --git a/Tests/Normalizer/FormErrorNormalizerTest.php b/Tests/Normalizer/FormErrorNormalizerTest.php index 4f3e6a1e6..bc18125cf 100644 --- a/Tests/Normalizer/FormErrorNormalizerTest.php +++ b/Tests/Normalizer/FormErrorNormalizerTest.php @@ -19,15 +19,8 @@ class FormErrorNormalizerTest extends TestCase { - /** - * @var FormErrorNormalizer - */ - private $normalizer; - - /** - * @var FormInterface - */ - private $form; + private FormErrorNormalizer $normalizer; + private FormInterface $form; protected function setUp(): void { diff --git a/Tests/Normalizer/JsonSerializableNormalizerTest.php b/Tests/Normalizer/JsonSerializableNormalizerTest.php index 177b8c6e7..54a977f55 100644 --- a/Tests/Normalizer/JsonSerializableNormalizerTest.php +++ b/Tests/Normalizer/JsonSerializableNormalizerTest.php @@ -30,15 +30,8 @@ class JsonSerializableNormalizerTest extends TestCase { use CircularReferenceTestTrait; - /** - * @var JsonSerializableNormalizer - */ - private $normalizer; - - /** - * @var MockObject&JsonSerializerNormalizer - */ - private $serializer; + private JsonSerializableNormalizer $normalizer; + private MockObject&JsonSerializerNormalizer $serializer; protected function setUp(): void { diff --git a/Tests/Normalizer/ProblemNormalizerTest.php b/Tests/Normalizer/ProblemNormalizerTest.php index 0937e66d8..e6f267bc8 100644 --- a/Tests/Normalizer/ProblemNormalizerTest.php +++ b/Tests/Normalizer/ProblemNormalizerTest.php @@ -25,10 +25,7 @@ class ProblemNormalizerTest extends TestCase { - /** - * @var ProblemNormalizer - */ - private $normalizer; + private ProblemNormalizer $normalizer; protected function setUp(): void { diff --git a/Tests/Normalizer/UidNormalizerTest.php b/Tests/Normalizer/UidNormalizerTest.php index ea00e1161..9797e2b1d 100644 --- a/Tests/Normalizer/UidNormalizerTest.php +++ b/Tests/Normalizer/UidNormalizerTest.php @@ -28,10 +28,7 @@ class UidNormalizerTest extends TestCase { use ExpectDeprecationTrait; - /** - * @var UidNormalizer - */ - private $normalizer; + private UidNormalizer $normalizer; protected function setUp(): void { diff --git a/Tests/Normalizer/UnwrappinDenormalizerTest.php b/Tests/Normalizer/UnwrappinDenormalizerTest.php index 25063b862..59ddd6da6 100644 --- a/Tests/Normalizer/UnwrappinDenormalizerTest.php +++ b/Tests/Normalizer/UnwrappinDenormalizerTest.php @@ -11,6 +11,7 @@ namespace Symfony\Component\Serializer\Tests\Normalizer; +use PHPUnit\Framework\MockObject\MockObject; use PHPUnit\Framework\TestCase; use Symfony\Component\Serializer\Normalizer\UnwrappingDenormalizer; use Symfony\Component\Serializer\Serializer; @@ -21,9 +22,8 @@ */ class UnwrappinDenormalizerTest extends TestCase { - private $denormalizer; - - private $serializer; + private UnwrappingDenormalizer $denormalizer; + private MockObject&Serializer $serializer; protected function setUp(): void { From 1314777923eb01f4758af9815e27974e9052809b Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Fri, 21 Jul 2023 18:41:43 +0200 Subject: [PATCH 127/297] Add types to private and internal properties --- Encoder/JsonEncoder.php | 2 +- Normalizer/GetSetMethodNormalizer.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Encoder/JsonEncoder.php b/Encoder/JsonEncoder.php index 0d8bf8d09..f81386db3 100644 --- a/Encoder/JsonEncoder.php +++ b/Encoder/JsonEncoder.php @@ -23,7 +23,7 @@ class JsonEncoder implements EncoderInterface, DecoderInterface protected $encodingImpl; protected $decodingImpl; - private $defaultContext = [ + private array $defaultContext = [ JsonDecode::ASSOCIATIVE => true, ]; diff --git a/Normalizer/GetSetMethodNormalizer.php b/Normalizer/GetSetMethodNormalizer.php index 063d34ea5..2e0e80882 100644 --- a/Normalizer/GetSetMethodNormalizer.php +++ b/Normalizer/GetSetMethodNormalizer.php @@ -38,7 +38,7 @@ */ class GetSetMethodNormalizer extends AbstractObjectNormalizer { - private static $setterAccessibleCache = []; + private static array $setterAccessibleCache = []; public function getSupportedTypes(?string $format): array { From 2ae7502541d51fb8e374f999dfc1e67b0c16be1e Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Wed, 26 Jul 2023 17:12:55 +0200 Subject: [PATCH 128/297] More short closures + isset instead of null checks + etc. --- Encoder/JsonDecode.php | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Encoder/JsonDecode.php b/Encoder/JsonDecode.php index f17267fc9..a9ca02845 100644 --- a/Encoder/JsonDecode.php +++ b/Encoder/JsonDecode.php @@ -20,6 +20,9 @@ */ class JsonDecode implements DecoderInterface { + /** + * @deprecated since Symfony 6.4, to be removed in 7.0 + */ protected $serializer; /** From 119c58975bf47648d97afb8c64b4c25222f9cef6 Mon Sep 17 00:00:00 2001 From: "Alexander M. Turek" Date: Thu, 27 Jul 2023 09:50:19 +0200 Subject: [PATCH 129/297] [Serializer] Fix tests for extended Context attributes --- Tests/Normalizer/Features/ContextMetadataTestTrait.php | 6 +++--- Tests/Normalizer/Features/DummyContextChild.php | 9 +-------- 2 files changed, 4 insertions(+), 11 deletions(-) diff --git a/Tests/Normalizer/Features/ContextMetadataTestTrait.php b/Tests/Normalizer/Features/ContextMetadataTestTrait.php index 2eeb76a72..8886e667e 100644 --- a/Tests/Normalizer/Features/ContextMetadataTestTrait.php +++ b/Tests/Normalizer/Features/ContextMetadataTestTrait.php @@ -121,12 +121,12 @@ class ContextChildMetadataDummy * @var \DateTime */ #[Groups(['extended', 'simple'])] - #[Context([DateTimeNormalizer::FORMAT_KEY => \DateTimeInterface::RFC3339])] - #[Context( + #[DummyContextChild([DateTimeNormalizer::FORMAT_KEY => \DateTimeInterface::RFC3339])] + #[DummyContextChild( normalizationContext: [DateTimeNormalizer::FORMAT_KEY => \DateTimeInterface::RFC3339_EXTENDED], groups: ['extended'], )] - #[Context( + #[DummyContextChild( denormalizationContext: [DateTimeNormalizer::FORMAT_KEY => 'd/m/Y'], groups: ['simple'], )] diff --git a/Tests/Normalizer/Features/DummyContextChild.php b/Tests/Normalizer/Features/DummyContextChild.php index 29c684b3e..f66ef702a 100644 --- a/Tests/Normalizer/Features/DummyContextChild.php +++ b/Tests/Normalizer/Features/DummyContextChild.php @@ -13,14 +13,7 @@ use Symfony\Component\Serializer\Annotation\Context; -/** - * Annotation class for @DummyContextChild(). - * - * @Annotation - * @NamedArgumentConstructor - * @Target({"PROPERTY", "METHOD"}) - */ -#[\Attribute(\Attribute::TARGET_METHOD | \Attribute::TARGET_PROPERTY)] +#[\Attribute(\Attribute::TARGET_METHOD | \Attribute::TARGET_PROPERTY | \Attribute::IS_REPEATABLE)] class DummyContextChild extends Context { } From c01af18f859414575398ad55b41b1141ddf8eacc Mon Sep 17 00:00:00 2001 From: "Alexander M. Turek" Date: Thu, 27 Jul 2023 17:31:50 +0200 Subject: [PATCH 130/297] [Serializer] Remove obsolete annotation from fixture --- Tests/Fixtures/ChildOfGroupsAnnotationDummy.php | 4 ---- 1 file changed, 4 deletions(-) diff --git a/Tests/Fixtures/ChildOfGroupsAnnotationDummy.php b/Tests/Fixtures/ChildOfGroupsAnnotationDummy.php index 653758dca..3109c6185 100644 --- a/Tests/Fixtures/ChildOfGroupsAnnotationDummy.php +++ b/Tests/Fixtures/ChildOfGroupsAnnotationDummy.php @@ -4,10 +4,6 @@ use Symfony\Component\Serializer\Annotation\Groups; -/** - * @Annotation - * @Target({"PROPERTY", "METHOD"}) - */ #[\Attribute(\Attribute::TARGET_METHOD | \Attribute::TARGET_PROPERTY)] final class ChildOfGroupsAnnotationDummy extends Groups { From aa5da7e32be8c463254c7b868f9b8c62c31b04f8 Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Fri, 21 Jul 2023 15:36:26 +0200 Subject: [PATCH 131/297] Add types to public and protected properties --- Encoder/JsonDecode.php | 5 ----- Encoder/JsonEncoder.php | 4 ++-- Mapping/Loader/FileLoader.php | 2 +- Normalizer/AbstractNormalizer.php | 14 +++----------- Normalizer/AbstractObjectNormalizer.php | 7 ++----- Normalizer/DenormalizerAwareTrait.php | 5 +---- Normalizer/NormalizerAwareTrait.php | 5 +---- Normalizer/ObjectNormalizer.php | 2 +- Serializer.php | 10 ++-------- SerializerAwareTrait.php | 5 +---- 10 files changed, 14 insertions(+), 45 deletions(-) diff --git a/Encoder/JsonDecode.php b/Encoder/JsonDecode.php index a9ca02845..1be381c18 100644 --- a/Encoder/JsonDecode.php +++ b/Encoder/JsonDecode.php @@ -20,11 +20,6 @@ */ class JsonDecode implements DecoderInterface { - /** - * @deprecated since Symfony 6.4, to be removed in 7.0 - */ - protected $serializer; - /** * True to return the result as an associative array, false for a nested stdClass hierarchy. */ diff --git a/Encoder/JsonEncoder.php b/Encoder/JsonEncoder.php index f81386db3..d6cf495b0 100644 --- a/Encoder/JsonEncoder.php +++ b/Encoder/JsonEncoder.php @@ -20,8 +20,8 @@ class JsonEncoder implements EncoderInterface, DecoderInterface { public const FORMAT = 'json'; - protected $encodingImpl; - protected $decodingImpl; + protected JsonEncode $encodingImpl; + protected JsonDecode $decodingImpl; private array $defaultContext = [ JsonDecode::ASSOCIATIVE => true, diff --git a/Mapping/Loader/FileLoader.php b/Mapping/Loader/FileLoader.php index f4947ef4c..7fda4ebd5 100644 --- a/Mapping/Loader/FileLoader.php +++ b/Mapping/Loader/FileLoader.php @@ -20,7 +20,7 @@ */ abstract class FileLoader implements LoaderInterface { - protected $file; + protected string $file; /** * @param string $file The mapping file to load diff --git a/Normalizer/AbstractNormalizer.php b/Normalizer/AbstractNormalizer.php index c0ce6870a..7bea25cfb 100644 --- a/Normalizer/AbstractNormalizer.php +++ b/Normalizer/AbstractNormalizer.php @@ -123,22 +123,14 @@ abstract class AbstractNormalizer implements NormalizerInterface, DenormalizerIn */ protected const CIRCULAR_REFERENCE_LIMIT_COUNTERS = 'circular_reference_limit_counters'; - protected $defaultContext = [ + protected array $defaultContext = [ self::ALLOW_EXTRA_ATTRIBUTES => true, self::CIRCULAR_REFERENCE_HANDLER => null, self::CIRCULAR_REFERENCE_LIMIT => 1, self::IGNORED_ATTRIBUTES => [], ]; - - /** - * @var ClassMetadataFactoryInterface|null - */ - protected $classMetadataFactory; - - /** - * @var NameConverterInterface|null - */ - protected $nameConverter; + protected ?ClassMetadataFactoryInterface $classMetadataFactory; + protected ?NameConverterInterface $nameConverter; /** * Sets the {@link ClassMetadataFactoryInterface} to use. diff --git a/Normalizer/AbstractObjectNormalizer.php b/Normalizer/AbstractObjectNormalizer.php index 60b37a864..ddca68f58 100644 --- a/Normalizer/AbstractObjectNormalizer.php +++ b/Normalizer/AbstractObjectNormalizer.php @@ -105,15 +105,12 @@ abstract class AbstractObjectNormalizer extends AbstractNormalizer */ public const PRESERVE_EMPTY_OBJECTS = 'preserve_empty_objects'; + protected ?ClassDiscriminatorResolverInterface $classDiscriminatorResolver; + private array $typesCache = []; private array $attributesCache = []; private readonly \Closure $objectClassResolver; - /** - * @var ClassDiscriminatorResolverInterface|null - */ - protected $classDiscriminatorResolver; - public function __construct( ClassMetadataFactoryInterface $classMetadataFactory = null, NameConverterInterface $nameConverter = null, diff --git a/Normalizer/DenormalizerAwareTrait.php b/Normalizer/DenormalizerAwareTrait.php index c5cc86ecf..166e3f69c 100644 --- a/Normalizer/DenormalizerAwareTrait.php +++ b/Normalizer/DenormalizerAwareTrait.php @@ -16,10 +16,7 @@ */ trait DenormalizerAwareTrait { - /** - * @var DenormalizerInterface - */ - protected $denormalizer; + protected DenormalizerInterface $denormalizer; /** * @return void diff --git a/Normalizer/NormalizerAwareTrait.php b/Normalizer/NormalizerAwareTrait.php index 98139de6d..e21c6c175 100644 --- a/Normalizer/NormalizerAwareTrait.php +++ b/Normalizer/NormalizerAwareTrait.php @@ -16,10 +16,7 @@ */ trait NormalizerAwareTrait { - /** - * @var NormalizerInterface - */ - protected $normalizer; + protected NormalizerInterface $normalizer; public function setNormalizer(NormalizerInterface $normalizer): void { diff --git a/Normalizer/ObjectNormalizer.php b/Normalizer/ObjectNormalizer.php index c328dcf40..cad926def 100644 --- a/Normalizer/ObjectNormalizer.php +++ b/Normalizer/ObjectNormalizer.php @@ -28,7 +28,7 @@ */ final class ObjectNormalizer extends AbstractObjectNormalizer { - protected $propertyAccessor; + protected PropertyAccessorInterface $propertyAccessor; /** @var array */ private array $discriminatorCache = []; diff --git a/Serializer.php b/Serializer.php index 4eb7d3158..eb8bb82f4 100644 --- a/Serializer.php +++ b/Serializer.php @@ -58,15 +58,9 @@ class Serializer implements SerializerInterface, NormalizerInterface, Denormaliz 'string' => true, ]; - /** - * @var ChainEncoder - */ - protected $encoder; + protected ChainEncoder $encoder; - /** - * @var ChainDecoder - */ - protected $decoder; + protected ChainDecoder $decoder; /** * @var array>> diff --git a/SerializerAwareTrait.php b/SerializerAwareTrait.php index f74f56b41..e4ba0ecb9 100644 --- a/SerializerAwareTrait.php +++ b/SerializerAwareTrait.php @@ -16,10 +16,7 @@ */ trait SerializerAwareTrait { - /** - * @var SerializerInterface - */ - protected $serializer; + protected SerializerInterface $serializer; public function setSerializer(SerializerInterface $serializer): void { From 33deb86d212893042d7758d452aa39d19ca0efe3 Mon Sep 17 00:00:00 2001 From: Thomas Calvet Date: Fri, 28 Jul 2023 17:01:18 +0200 Subject: [PATCH 132/297] Fix symfony/deprecation-contracts require --- composer.json | 1 + 1 file changed, 1 insertion(+) diff --git a/composer.json b/composer.json index 54cb15458..5efabab21 100644 --- a/composer.json +++ b/composer.json @@ -17,6 +17,7 @@ ], "require": { "php": ">=8.1", + "symfony/deprecation-contracts": "^2.5|^3", "symfony/polyfill-ctype": "~1.8" }, "require-dev": { From d8e4b507c4f8c659b4f49eafd0062abc56dd4d26 Mon Sep 17 00:00:00 2001 From: Brajk19 Date: Fri, 3 Mar 2023 21:44:53 +0100 Subject: [PATCH 133/297] [Serializer] Groups annotation/attribute on class --- Annotation/Groups.php | 4 +- CHANGELOG.md | 1 + Mapping/Loader/AnnotationLoader.php | 10 +++ .../Fixtures/Annotations/GroupClassDummy.php | 62 +++++++++++++++++++ Tests/Fixtures/Attributes/GroupClassDummy.php | 56 +++++++++++++++++ .../Loader/AnnotationLoaderTestCase.php | 18 ++++++ Tests/SerializerTest.php | 22 +++++++ 7 files changed, 171 insertions(+), 2 deletions(-) create mode 100644 Tests/Fixtures/Annotations/GroupClassDummy.php create mode 100644 Tests/Fixtures/Attributes/GroupClassDummy.php diff --git a/Annotation/Groups.php b/Annotation/Groups.php index 5b16f567c..f1ea6f76b 100644 --- a/Annotation/Groups.php +++ b/Annotation/Groups.php @@ -18,11 +18,11 @@ * * @Annotation * @NamedArgumentConstructor - * @Target({"PROPERTY", "METHOD"}) + * @Target({"PROPERTY", "METHOD", "CLASS"}) * * @author Kévin Dunglas */ -#[\Attribute(\Attribute::TARGET_METHOD | \Attribute::TARGET_PROPERTY)] +#[\Attribute(\Attribute::TARGET_METHOD | \Attribute::TARGET_PROPERTY | \Attribute::TARGET_CLASS)] class Groups { /** diff --git a/CHANGELOG.md b/CHANGELOG.md index 4ab3defdd..0fb810b06 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,7 @@ CHANGELOG * Deprecate Doctrine annotations support in favor of native attributes * Deprecate passing an annotation reader to the constructor of `AnnotationLoader` + * Allow the `Groups` attribute/annotation on classes 6.3 --- diff --git a/Mapping/Loader/AnnotationLoader.php b/Mapping/Loader/AnnotationLoader.php index 94d0f45dc..fdeda08b2 100644 --- a/Mapping/Loader/AnnotationLoader.php +++ b/Mapping/Loader/AnnotationLoader.php @@ -56,6 +56,7 @@ public function loadClassMetadata(ClassMetadataInterface $classMetadata): bool $reflectionClass = $classMetadata->getReflectionClass(); $className = $reflectionClass->name; $loaded = false; + $classGroups = []; $attributesMetadata = $classMetadata->getAttributesMetadata(); @@ -65,6 +66,11 @@ public function loadClassMetadata(ClassMetadataInterface $classMetadata): bool $annotation->getTypeProperty(), $annotation->getMapping() )); + continue; + } + + if ($annotation instanceof Groups) { + $classGroups = $annotation->getGroups(); } } @@ -75,6 +81,10 @@ public function loadClassMetadata(ClassMetadataInterface $classMetadata): bool } if ($property->getDeclaringClass()->name === $className) { + foreach ($classGroups as $group) { + $attributesMetadata[$property->name]->addGroup($group); + } + foreach ($this->loadAnnotations($property) as $annotation) { if ($annotation instanceof Groups) { foreach ($annotation->getGroups() as $group) { diff --git a/Tests/Fixtures/Annotations/GroupClassDummy.php b/Tests/Fixtures/Annotations/GroupClassDummy.php new file mode 100644 index 000000000..e97732613 --- /dev/null +++ b/Tests/Fixtures/Annotations/GroupClassDummy.php @@ -0,0 +1,62 @@ + + * + * 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\Annotations; + +use Symfony\Component\Serializer\Annotation\Groups; + +/** + * @Groups({"a"}) + */ +class GroupClassDummy +{ + /** + * @Groups({"b"}) + */ + private $foo; + + /** + * @Groups({"c", "d"}) + */ + private $bar; + + private $baz; + + public function getFoo() + { + return $this->foo; + } + + public function setFoo($foo): void + { + $this->foo = $foo; + } + + public function getBar() + { + return $this->bar; + } + + public function setBar($bar): void + { + $this->bar = $bar; + } + + public function getBaz() + { + return $this->baz; + } + + public function setBaz($baz): void + { + $this->baz = $baz; + } +} diff --git a/Tests/Fixtures/Attributes/GroupClassDummy.php b/Tests/Fixtures/Attributes/GroupClassDummy.php new file mode 100644 index 000000000..68289a9a8 --- /dev/null +++ b/Tests/Fixtures/Attributes/GroupClassDummy.php @@ -0,0 +1,56 @@ + + * + * 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\Attributes; + +use Symfony\Component\Serializer\Annotation\Groups; + +#[Groups('a')] +class GroupClassDummy +{ + #[Groups('b')] + private $foo; + + #[Groups(['c', 'd'])] + private $bar; + + private $baz; + + public function getFoo() + { + return $this->foo; + } + + public function setFoo($foo): void + { + $this->foo = $foo; + } + + public function getBar() + { + return $this->bar; + } + + public function setBar($bar): void + { + $this->bar = $bar; + } + + public function getBaz() + { + return $this->baz; + } + + public function setBaz($baz): void + { + $this->baz = $baz; + } +} diff --git a/Tests/Mapping/Loader/AnnotationLoaderTestCase.php b/Tests/Mapping/Loader/AnnotationLoaderTestCase.php index 40d5e3cfe..69b486177 100644 --- a/Tests/Mapping/Loader/AnnotationLoaderTestCase.php +++ b/Tests/Mapping/Loader/AnnotationLoaderTestCase.php @@ -192,6 +192,24 @@ public function testIgnoreGetterWithRequiredParameterIfIgnoreAnnotationIsNotUsed self::assertArrayHasKey('extraValue2', $attributes); } + public function testLoadGroupsOnClass() + { + $classMetadata = new ClassMetadata($this->getNamespace().'\GroupClassDummy'); + $this->loader->loadClassMetadata($classMetadata); + + $attributesMetadata = $classMetadata->getAttributesMetadata(); + + self::assertCount(3, $classMetadata->getAttributesMetadata()); + + self::assertArrayHasKey('foo', $attributesMetadata); + self::assertArrayHasKey('bar', $attributesMetadata); + self::assertArrayHasKey('baz', $attributesMetadata); + + self::assertSame(['a', 'b'], $attributesMetadata['foo']->getGroups()); + self::assertSame(['a', 'c', 'd'], $attributesMetadata['bar']->getGroups()); + self::assertSame(['a'], $attributesMetadata['baz']->getGroups()); + } + abstract protected function createLoader(): AnnotationLoader; abstract protected function getNamespace(): string; diff --git a/Tests/SerializerTest.php b/Tests/SerializerTest.php index 8c5c89a67..a50662a24 100644 --- a/Tests/SerializerTest.php +++ b/Tests/SerializerTest.php @@ -1278,6 +1278,28 @@ public function testNoCollectDenormalizationErrorsWithWrongEnumOnConstructor() } } + public function testGroupsOnClassSerialization() + { + $obj = new Fixtures\Attributes\GroupClassDummy(); + $obj->setFoo('foo'); + $obj->setBar('bar'); + $obj->setBaz('baz'); + + $serializer = new Serializer( + [ + new ObjectNormalizer(), + ], + [ + 'json' => new JsonEncoder(), + ] + ); + + $this->assertSame( + '{"foo":"foo","bar":"bar","baz":"baz"}', + $serializer->serialize($obj, 'json', ['groups' => ['a']]) + ); + } + public static function provideCollectDenormalizationErrors(): array { return [ From eefce00fbe5eebf1cf0f4bab98b88792e01a11b8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gabriel=20Ostroluck=C3=BD?= Date: Sun, 30 Jul 2023 22:33:19 +0200 Subject: [PATCH 134/297] [Serializer] Add support for seld/jsonlint in order to enhance error messages --- CHANGELOG.md | 1 + Encoder/JsonDecode.php | 27 ++++++++++++++++++++++++--- Tests/Encoder/JsonDecodeTest.php | 11 +++++++---- composer.json | 1 + 4 files changed, 33 insertions(+), 7 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0fb810b06..17221fe61 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,7 @@ CHANGELOG * Deprecate Doctrine annotations support in favor of native attributes * Deprecate passing an annotation reader to the constructor of `AnnotationLoader` * Allow the `Groups` attribute/annotation on classes + * JsonDecode: Add `json_decode_detailed_errors` option 6.3 --- diff --git a/Encoder/JsonDecode.php b/Encoder/JsonDecode.php index a9ca02845..6875ff81c 100644 --- a/Encoder/JsonDecode.php +++ b/Encoder/JsonDecode.php @@ -11,7 +11,9 @@ namespace Symfony\Component\Serializer\Encoder; +use Seld\JsonLint\JsonParser; use Symfony\Component\Serializer\Exception\NotEncodableValueException; +use Symfony\Component\Serializer\Exception\UnsupportedException; /** * Decodes JSON data. @@ -30,6 +32,11 @@ class JsonDecode implements DecoderInterface */ public const ASSOCIATIVE = 'json_decode_associative'; + /** + * True to enable seld/jsonlint as a source for more specific error messages when json_decode fails. + */ + public const DETAILED_ERROR_MESSAGES = 'json_decode_detailed_errors'; + public const OPTIONS = 'json_decode_options'; /** @@ -39,6 +46,7 @@ class JsonDecode implements DecoderInterface private array $defaultContext = [ self::ASSOCIATIVE => false, + self::DETAILED_ERROR_MESSAGES => false, self::OPTIONS => 0, self::RECURSION_DEPTH => 512, ]; @@ -69,6 +77,10 @@ public function __construct(array $defaultContext = []) * json_decode_options: integer * Specifies additional options as per documentation for json_decode * + * json_decode_detailed_errors: bool + * If true, enables seld/jsonlint as a source for more specific error messages when json_decode fails. + * If false or not specified, this method will use default error messages from PHP's json_decode + * * @throws NotEncodableValueException * * @see https://php.net/json_decode @@ -89,11 +101,20 @@ public function decode(string $data, string $format, array $context = []): mixed return $decodedData; } - if (\JSON_ERROR_NONE !== json_last_error()) { - throw new NotEncodableValueException(json_last_error_msg()); + if (\JSON_ERROR_NONE === json_last_error()) { + return $decodedData; + } + $errorMessage = json_last_error_msg(); + + if (!($context[self::DETAILED_ERROR_MESSAGES] ?? $this->defaultContext[self::DETAILED_ERROR_MESSAGES])) { + throw new NotEncodableValueException($errorMessage); + } + + if (!class_exists(JsonParser::class)) { + throw new UnsupportedException(sprintf('Enabling "%s" serializer option requires seld/jsonlint. Try running "composer require seld/jsonlint".', self::DETAILED_ERROR_MESSAGES)); } - return $decodedData; + throw new NotEncodableValueException((new JsonParser())->lint($data)?->getMessage() ?: $errorMessage); } public function supportsDecoding(string $format): bool diff --git a/Tests/Encoder/JsonDecodeTest.php b/Tests/Encoder/JsonDecodeTest.php index ffef479e2..66cd10114 100644 --- a/Tests/Encoder/JsonDecodeTest.php +++ b/Tests/Encoder/JsonDecodeTest.php @@ -58,17 +58,20 @@ public static function decodeProvider() /** * @dataProvider decodeProviderException */ - public function testDecodeWithException($value) + public function testDecodeWithException(string $value, string $expectedExceptionMessage, array $context) { $this->expectException(UnexpectedValueException::class); - $this->decode->decode($value, JsonEncoder::FORMAT); + $this->expectExceptionMessage($expectedExceptionMessage); + $this->decode->decode($value, JsonEncoder::FORMAT, $context); } public static function decodeProviderException() { return [ - ["{'foo': 'bar'}"], - ['kaboom!'], + ["{'foo': 'bar'}", 'Syntax error', []], + ["{'foo': 'bar'}", 'single quotes instead of double quotes', ['json_decode_detailed_errors' => true]], + ['kaboom!', 'Syntax error', ['json_decode_detailed_errors' => false]], + ['kaboom!', "Expected one of: 'STRING', 'NUMBER', 'NULL',", ['json_decode_detailed_errors' => true]], ]; } } diff --git a/composer.json b/composer.json index e0c646d29..91b69a79b 100644 --- a/composer.json +++ b/composer.json @@ -22,6 +22,7 @@ }, "require-dev": { "doctrine/annotations": "^1.12|^2", + "seld/jsonlint": "^1.10", "phpdocumentor/reflection-docblock": "^3.2|^4.0|^5.0", "symfony/cache": "^5.4|^6.0|^7.0", "symfony/config": "^5.4|^6.0|^7.0", From 28f0049e0042973119bd5edfdc446d2e213dec90 Mon Sep 17 00:00:00 2001 From: "hubert.lenoir" Date: Tue, 2 May 2023 13:55:02 +0200 Subject: [PATCH 135/297] [FrameworkBundle][Serializer] Add TranslatableNormalizer --- Normalizer/TranslatableNormalizer.php | 54 ++++++++++++++++ .../Normalizer/TranslatableNormalizerTest.php | 63 +++++++++++++++++++ 2 files changed, 117 insertions(+) create mode 100644 Normalizer/TranslatableNormalizer.php create mode 100644 Tests/Normalizer/TranslatableNormalizerTest.php diff --git a/Normalizer/TranslatableNormalizer.php b/Normalizer/TranslatableNormalizer.php new file mode 100644 index 000000000..b79a0ffb9 --- /dev/null +++ b/Normalizer/TranslatableNormalizer.php @@ -0,0 +1,54 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Serializer\Normalizer; + +use Symfony\Component\Serializer\Exception\InvalidArgumentException; +use Symfony\Contracts\Translation\TranslatableInterface; +use Symfony\Contracts\Translation\TranslatorInterface; + +final class TranslatableNormalizer implements NormalizerInterface +{ + public const NORMALIZATION_LOCALE_KEY = 'translatable_normalization_locale'; + + private array $defaultContext = [ + self::NORMALIZATION_LOCALE_KEY => null, + ]; + + public function __construct( + private readonly TranslatorInterface $translator, + array $defaultContext = [], + ) { + $this->defaultContext = array_merge($this->defaultContext, $defaultContext); + } + + /** + * @throws InvalidArgumentException + */ + public function normalize(mixed $object, string $format = null, array $context = []): string + { + if (!$object instanceof TranslatableInterface) { + throw new InvalidArgumentException(sprintf('The object must implement the "%s".', TranslatableInterface::class)); + } + + return $object->trans($this->translator, $context[self::NORMALIZATION_LOCALE_KEY] ?? $this->defaultContext[self::NORMALIZATION_LOCALE_KEY]); + } + + public function supportsNormalization(mixed $data, string $format = null, array $context = []): bool + { + return $data instanceof TranslatableInterface; + } + + public function getSupportedTypes(?string $format): array + { + return [TranslatableInterface::class => true]; + } +} diff --git a/Tests/Normalizer/TranslatableNormalizerTest.php b/Tests/Normalizer/TranslatableNormalizerTest.php new file mode 100644 index 000000000..54338b07e --- /dev/null +++ b/Tests/Normalizer/TranslatableNormalizerTest.php @@ -0,0 +1,63 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Serializer\Tests\Normalizer; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\Serializer\Normalizer\TranslatableNormalizer; +use Symfony\Contracts\Translation\TranslatableInterface; +use Symfony\Contracts\Translation\TranslatorInterface; + +class TranslatableNormalizerTest extends TestCase +{ + private readonly TranslatableNormalizer $normalizer; + + protected function setUp(): void + { + $this->normalizer = new TranslatableNormalizer($this->createMock(TranslatorInterface::class)); + } + + public function testSupportsNormalization() + { + $this->assertTrue($this->normalizer->supportsNormalization(new TestMessage())); + $this->assertFalse($this->normalizer->supportsNormalization(new \stdClass())); + } + + public function testNormalize() + { + $message = new TestMessage(); + + $this->assertSame('key_null', $this->normalizer->normalize($message)); + $this->assertSame('key_fr', $this->normalizer->normalize($message, context: ['translatable_normalization_locale' => 'fr'])); + $this->assertSame('key_en', $this->normalizer->normalize($message, context: ['translatable_normalization_locale' => 'en'])); + } + + public function testNormalizeWithNormalizationLocalePassedInConstructor() + { + $normalizer = new TranslatableNormalizer( + $this->createMock(TranslatorInterface::class), + ['translatable_normalization_locale' => 'es'], + ); + $message = new TestMessage(); + + $this->assertSame('key_es', $normalizer->normalize($message)); + $this->assertSame('key_fr', $normalizer->normalize($message, context: ['translatable_normalization_locale' => 'fr'])); + $this->assertSame('key_en', $normalizer->normalize($message, context: ['translatable_normalization_locale' => 'en'])); + } +} + +class TestMessage implements TranslatableInterface +{ + public function trans(TranslatorInterface $translator, string $locale = null): string + { + return 'key_'.($locale ?? 'null'); + } +} From 0efbf491595f4ac4a964b8f308bdb06219cd6296 Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Wed, 2 Aug 2023 12:57:19 +0200 Subject: [PATCH 136/297] [Serializer] fix tests --- .../Fixtures/Annotations/GroupClassDummy.php | 62 ------------------- Tests/Mapping/Loader/AnnotationLoaderTest.php | 3 +- 2 files changed, 2 insertions(+), 63 deletions(-) delete mode 100644 Tests/Fixtures/Annotations/GroupClassDummy.php diff --git a/Tests/Fixtures/Annotations/GroupClassDummy.php b/Tests/Fixtures/Annotations/GroupClassDummy.php deleted file mode 100644 index e97732613..000000000 --- a/Tests/Fixtures/Annotations/GroupClassDummy.php +++ /dev/null @@ -1,62 +0,0 @@ - - * - * 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\Annotations; - -use Symfony\Component\Serializer\Annotation\Groups; - -/** - * @Groups({"a"}) - */ -class GroupClassDummy -{ - /** - * @Groups({"b"}) - */ - private $foo; - - /** - * @Groups({"c", "d"}) - */ - private $bar; - - private $baz; - - public function getFoo() - { - return $this->foo; - } - - public function setFoo($foo): void - { - $this->foo = $foo; - } - - public function getBar() - { - return $this->bar; - } - - public function setBar($bar): void - { - $this->bar = $bar; - } - - public function getBaz() - { - return $this->baz; - } - - public function setBaz($baz): void - { - $this->baz = $baz; - } -} diff --git a/Tests/Mapping/Loader/AnnotationLoaderTest.php b/Tests/Mapping/Loader/AnnotationLoaderTest.php index aeb38712f..b30cda410 100644 --- a/Tests/Mapping/Loader/AnnotationLoaderTest.php +++ b/Tests/Mapping/Loader/AnnotationLoaderTest.php @@ -28,6 +28,7 @@ use Symfony\Component\Serializer\Tests\Fixtures\Attributes\ContextDummyParent; use Symfony\Component\Serializer\Tests\Fixtures\Attributes\ContextDummyPromotedProperties; use Symfony\Component\Serializer\Tests\Fixtures\Attributes\Entity45016; +use Symfony\Component\Serializer\Tests\Fixtures\Attributes\GroupClassDummy; use Symfony\Component\Serializer\Tests\Fixtures\Attributes\GroupDummy; use Symfony\Component\Serializer\Tests\Fixtures\Attributes\GroupDummyParent; use Symfony\Component\Serializer\Tests\Fixtures\Attributes\IgnoreDummy; @@ -203,7 +204,7 @@ public function testIgnoreGetterWithRequiredParameterIfIgnoreAnnotationIsNotUsed public function testLoadGroupsOnClass() { - $classMetadata = new ClassMetadata($this->getNamespace().'\GroupClassDummy'); + $classMetadata = new ClassMetadata(GroupClassDummy::class); $this->loader->loadClassMetadata($classMetadata); $attributesMetadata = $classMetadata->getAttributesMetadata(); From be41486be05efc2fc9c17dda8e87b2ab761a2ba8 Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Sat, 5 Aug 2023 16:21:36 +0200 Subject: [PATCH 137/297] [Serializer] Make deprecation message more actionable --- Debug/TraceableNormalizer.php | 2 +- Normalizer/AbstractNormalizer.php | 2 +- Normalizer/ConstraintViolationListNormalizer.php | 2 +- Normalizer/CustomNormalizer.php | 2 +- Normalizer/DataUriNormalizer.php | 2 +- Normalizer/DateIntervalNormalizer.php | 2 +- Normalizer/DateTimeNormalizer.php | 2 +- Normalizer/DateTimeZoneNormalizer.php | 2 +- Normalizer/GetSetMethodNormalizer.php | 2 +- Normalizer/JsonSerializableNormalizer.php | 2 +- Normalizer/MimeMessageNormalizer.php | 2 +- Normalizer/ObjectNormalizer.php | 2 +- Normalizer/ProblemNormalizer.php | 2 +- Normalizer/PropertyNormalizer.php | 2 +- 14 files changed, 14 insertions(+), 14 deletions(-) diff --git a/Debug/TraceableNormalizer.php b/Debug/TraceableNormalizer.php index d4cf6fcbd..4bdd3f7b4 100644 --- a/Debug/TraceableNormalizer.php +++ b/Debug/TraceableNormalizer.php @@ -132,7 +132,7 @@ public function setDenormalizer(DenormalizerInterface $denormalizer): void */ public function hasCacheableSupportsMethod(): bool { - trigger_deprecation('symfony/serializer', '6.3', 'The "%s()" method is deprecated, use "getSupportedTypes()" instead.', __METHOD__); + trigger_deprecation('symfony/serializer', '6.3', 'The "%s()" method is deprecated, implement "%s::getSupportedTypes()" instead.', __METHOD__, get_debug_type($this->normalizer)); return $this->normalizer instanceof CacheableSupportsMethodInterface && $this->normalizer->hasCacheableSupportsMethod(); } diff --git a/Normalizer/AbstractNormalizer.php b/Normalizer/AbstractNormalizer.php index fc5322fa2..39d91bbc7 100644 --- a/Normalizer/AbstractNormalizer.php +++ b/Normalizer/AbstractNormalizer.php @@ -161,7 +161,7 @@ public function __construct(ClassMetadataFactoryInterface $classMetadataFactory */ public function hasCacheableSupportsMethod(): bool { - trigger_deprecation('symfony/serializer', '6.3', 'The "%s()" method is deprecated, use "getSupportedTypes()" instead.', __METHOD__); + trigger_deprecation('symfony/serializer', '6.3', 'The "%s()" method is deprecated, implement "%s::getSupportedTypes()" instead.', __METHOD__, get_debug_type($this)); return false; } diff --git a/Normalizer/ConstraintViolationListNormalizer.php b/Normalizer/ConstraintViolationListNormalizer.php index 1fdf8420d..2b6a8ec2e 100644 --- a/Normalizer/ConstraintViolationListNormalizer.php +++ b/Normalizer/ConstraintViolationListNormalizer.php @@ -121,7 +121,7 @@ public function supportsNormalization(mixed $data, string $format = null /* , ar */ public function hasCacheableSupportsMethod(): bool { - trigger_deprecation('symfony/serializer', '6.3', 'The "%s()" method is deprecated, use "getSupportedTypes()" instead.', __METHOD__); + trigger_deprecation('symfony/serializer', '6.3', 'The "%s()" method is deprecated, implement "%s::getSupportedTypes()" instead.', __METHOD__, get_debug_type($this)); return __CLASS__ === static::class; } diff --git a/Normalizer/CustomNormalizer.php b/Normalizer/CustomNormalizer.php index 7e67a31a9..f45f36296 100644 --- a/Normalizer/CustomNormalizer.php +++ b/Normalizer/CustomNormalizer.php @@ -75,7 +75,7 @@ public function supportsDenormalization(mixed $data, string $type, string $forma */ public function hasCacheableSupportsMethod(): bool { - trigger_deprecation('symfony/serializer', '6.3', 'The "%s()" method is deprecated, use "getSupportedTypes()" instead.', __METHOD__); + trigger_deprecation('symfony/serializer', '6.3', 'The "%s()" method is deprecated, implement "%s::getSupportedTypes()" instead.', __METHOD__, get_debug_type($this)); return __CLASS__ === static::class; } diff --git a/Normalizer/DataUriNormalizer.php b/Normalizer/DataUriNormalizer.php index 1bcf81f9b..0b4d0b273 100644 --- a/Normalizer/DataUriNormalizer.php +++ b/Normalizer/DataUriNormalizer.php @@ -133,7 +133,7 @@ public function supportsDenormalization(mixed $data, string $type, string $forma */ public function hasCacheableSupportsMethod(): bool { - trigger_deprecation('symfony/serializer', '6.3', 'The "%s()" method is deprecated, use "getSupportedTypes()" instead.', __METHOD__); + trigger_deprecation('symfony/serializer', '6.3', 'The "%s()" method is deprecated, implement "%s::getSupportedTypes()" instead.', __METHOD__, get_debug_type($this)); return __CLASS__ === static::class; } diff --git a/Normalizer/DateIntervalNormalizer.php b/Normalizer/DateIntervalNormalizer.php index 3cf5b887f..f0bcfc7e6 100644 --- a/Normalizer/DateIntervalNormalizer.php +++ b/Normalizer/DateIntervalNormalizer.php @@ -67,7 +67,7 @@ public function supportsNormalization(mixed $data, string $format = null /* , ar */ public function hasCacheableSupportsMethod(): bool { - trigger_deprecation('symfony/serializer', '6.3', 'The "%s()" method is deprecated, use "getSupportedTypes()" instead.', __METHOD__); + trigger_deprecation('symfony/serializer', '6.3', 'The "%s()" method is deprecated, implement "%s::getSupportedTypes()" instead.', __METHOD__, get_debug_type($this)); return __CLASS__ === static::class; } diff --git a/Normalizer/DateTimeNormalizer.php b/Normalizer/DateTimeNormalizer.php index df222b381..e6be88289 100644 --- a/Normalizer/DateTimeNormalizer.php +++ b/Normalizer/DateTimeNormalizer.php @@ -151,7 +151,7 @@ public function supportsDenormalization(mixed $data, string $type, string $forma */ public function hasCacheableSupportsMethod(): bool { - trigger_deprecation('symfony/serializer', '6.3', 'The "%s()" method is deprecated, use "getSupportedTypes()" instead.', __METHOD__); + trigger_deprecation('symfony/serializer', '6.3', 'The "%s()" method is deprecated, implement "%s::getSupportedTypes()" instead.', __METHOD__, get_debug_type($this)); return __CLASS__ === static::class; } diff --git a/Normalizer/DateTimeZoneNormalizer.php b/Normalizer/DateTimeZoneNormalizer.php index 472f64fc8..b4e1584ad 100644 --- a/Normalizer/DateTimeZoneNormalizer.php +++ b/Normalizer/DateTimeZoneNormalizer.php @@ -80,7 +80,7 @@ public function supportsDenormalization(mixed $data, string $type, string $forma */ public function hasCacheableSupportsMethod(): bool { - trigger_deprecation('symfony/serializer', '6.3', 'The "%s()" method is deprecated, use "getSupportedTypes()" instead.', __METHOD__); + trigger_deprecation('symfony/serializer', '6.3', 'The "%s()" method is deprecated, implement "%s::getSupportedTypes()" instead.', __METHOD__, get_debug_type($this)); return __CLASS__ === static::class; } diff --git a/Normalizer/GetSetMethodNormalizer.php b/Normalizer/GetSetMethodNormalizer.php index 063d34ea5..9a412ff23 100644 --- a/Normalizer/GetSetMethodNormalizer.php +++ b/Normalizer/GetSetMethodNormalizer.php @@ -66,7 +66,7 @@ public function supportsDenormalization(mixed $data, string $type, string $forma */ public function hasCacheableSupportsMethod(): bool { - trigger_deprecation('symfony/serializer', '6.3', 'The "%s()" method is deprecated, use "getSupportedTypes()" instead.', __METHOD__); + trigger_deprecation('symfony/serializer', '6.3', 'The "%s()" method is deprecated, implement "%s::getSupportedTypes()" instead.', __METHOD__, get_debug_type($this)); return __CLASS__ === static::class; } diff --git a/Normalizer/JsonSerializableNormalizer.php b/Normalizer/JsonSerializableNormalizer.php index 1c8bbfe4a..238cffa1e 100644 --- a/Normalizer/JsonSerializableNormalizer.php +++ b/Normalizer/JsonSerializableNormalizer.php @@ -73,7 +73,7 @@ public function denormalize(mixed $data, string $type, string $format = null, ar */ public function hasCacheableSupportsMethod(): bool { - trigger_deprecation('symfony/serializer', '6.3', 'The "%s()" method is deprecated, use "getSupportedTypes()" instead.', __METHOD__); + trigger_deprecation('symfony/serializer', '6.3', 'The "%s()" method is deprecated, implement "%s::getSupportedTypes()" instead.', __METHOD__, get_debug_type($this)); return __CLASS__ === static::class; } diff --git a/Normalizer/MimeMessageNormalizer.php b/Normalizer/MimeMessageNormalizer.php index ddeade33f..ab9544bf2 100644 --- a/Normalizer/MimeMessageNormalizer.php +++ b/Normalizer/MimeMessageNormalizer.php @@ -122,7 +122,7 @@ public function supportsDenormalization(mixed $data, string $type, string $forma */ public function hasCacheableSupportsMethod(): bool { - trigger_deprecation('symfony/serializer', '6.3', 'The "%s()" method is deprecated, use "getSupportedTypes()" instead.', __METHOD__); + trigger_deprecation('symfony/serializer', '6.3', 'The "%s()" method is deprecated, implement "%s::getSupportedTypes()" instead.', __METHOD__, get_debug_type($this)); return true; } diff --git a/Normalizer/ObjectNormalizer.php b/Normalizer/ObjectNormalizer.php index 357c36426..dd601b828 100644 --- a/Normalizer/ObjectNormalizer.php +++ b/Normalizer/ObjectNormalizer.php @@ -60,7 +60,7 @@ public function getSupportedTypes(?string $format): array */ public function hasCacheableSupportsMethod(): bool { - trigger_deprecation('symfony/serializer', '6.3', 'The "%s()" method is deprecated, use "getSupportedTypes()" instead.', __METHOD__); + trigger_deprecation('symfony/serializer', '6.3', 'The "%s()" method is deprecated, implement "%s::getSupportedTypes()" instead.', __METHOD__, get_debug_type($this)); return __CLASS__ === static::class; } diff --git a/Normalizer/ProblemNormalizer.php b/Normalizer/ProblemNormalizer.php index 4161d0b1c..f7a8077ec 100644 --- a/Normalizer/ProblemNormalizer.php +++ b/Normalizer/ProblemNormalizer.php @@ -119,7 +119,7 @@ public function supportsNormalization(mixed $data, string $format = null /* , ar */ public function hasCacheableSupportsMethod(): bool { - trigger_deprecation('symfony/serializer', '6.3', 'The "%s()" method is deprecated, use "getSupportedTypes()" instead.', __METHOD__); + trigger_deprecation('symfony/serializer', '6.3', 'The "%s()" method is deprecated, implement "%s::getSupportedTypes()" instead.', __METHOD__, get_debug_type($this)); return true; } diff --git a/Normalizer/PropertyNormalizer.php b/Normalizer/PropertyNormalizer.php index ec12db9bb..7e7743f5e 100644 --- a/Normalizer/PropertyNormalizer.php +++ b/Normalizer/PropertyNormalizer.php @@ -82,7 +82,7 @@ public function supportsDenormalization(mixed $data, string $type, string $forma */ public function hasCacheableSupportsMethod(): bool { - trigger_deprecation('symfony/serializer', '6.3', 'The "%s()" method is deprecated, use "getSupportedTypes()" instead.', __METHOD__); + trigger_deprecation('symfony/serializer', '6.3', 'The "%s()" method is deprecated, implement "%s::getSupportedTypes()" instead.', __METHOD__, get_debug_type($this)); return __CLASS__ === static::class; } From 3a5cc601e3c847ebfa40cdced74b7d0d26d1fb05 Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Thu, 17 Aug 2023 10:38:05 +0200 Subject: [PATCH 138/297] Add some missing return types on traits --- Normalizer/DenormalizerAwareTrait.php | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/Normalizer/DenormalizerAwareTrait.php b/Normalizer/DenormalizerAwareTrait.php index 166e3f69c..f4620e1c0 100644 --- a/Normalizer/DenormalizerAwareTrait.php +++ b/Normalizer/DenormalizerAwareTrait.php @@ -18,10 +18,7 @@ trait DenormalizerAwareTrait { protected DenormalizerInterface $denormalizer; - /** - * @return void - */ - public function setDenormalizer(DenormalizerInterface $denormalizer) + public function setDenormalizer(DenormalizerInterface $denormalizer): void { $this->denormalizer = $denormalizer; } From 87a5348854d1676b3a5b3479ab76254ec687c3e1 Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Thu, 17 Aug 2023 18:09:01 +0200 Subject: [PATCH 139/297] [Serializer] Fix deps --- composer.json | 1 + 1 file changed, 1 insertion(+) diff --git a/composer.json b/composer.json index 91b69a79b..09a503c3f 100644 --- a/composer.json +++ b/composer.json @@ -36,6 +36,7 @@ "symfony/mime": "^5.4|^6.0|^7.0", "symfony/property-access": "^5.4|^6.0|^7.0", "symfony/property-info": "^5.4.24|^6.2.11|^7.0", + "symfony/translation-contracts": "^2.5|^3", "symfony/uid": "^5.4|^6.0|^7.0", "symfony/validator": "^5.4|^6.0|^7.0", "symfony/var-dumper": "^5.4|^6.0|^7.0", From a0f9c31e8e723ef58161546d9c90c76c564ec657 Mon Sep 17 00:00:00 2001 From: Mathias Arlaud Date: Tue, 22 Aug 2023 16:46:39 +0200 Subject: [PATCH 140/297] [Serializer] Fix serialized name with groups --- NameConverter/MetadataAwareNameConverter.php | 9 ++++++--- Tests/NameConverter/MetadataAwareNameConverterTest.php | 1 + 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/NameConverter/MetadataAwareNameConverter.php b/NameConverter/MetadataAwareNameConverter.php index 0c97e227c..d1a0b28f2 100644 --- a/NameConverter/MetadataAwareNameConverter.php +++ b/NameConverter/MetadataAwareNameConverter.php @@ -127,11 +127,14 @@ private function getCacheValueForAttributesMetadata(string $class, array $contex throw new LogicException(sprintf('Found SerializedName and SerializedPath annotations on property "%s" of class "%s".', $name, $class)); } - $groups = $metadata->getGroups(); - if (!$groups && ($context[AbstractNormalizer::GROUPS] ?? [])) { + $metadataGroups = $metadata->getGroups(); + $contextGroups = (array) ($context[AbstractNormalizer::GROUPS] ?? []); + + if ($contextGroups && !$metadataGroups) { continue; } - if ($groups && !array_intersect($groups, (array) ($context[AbstractNormalizer::GROUPS] ?? []))) { + + if ($metadataGroups && !array_intersect($metadataGroups, $contextGroups) && !\in_array('*', $contextGroups, true)) { continue; } diff --git a/Tests/NameConverter/MetadataAwareNameConverterTest.php b/Tests/NameConverter/MetadataAwareNameConverterTest.php index 9491206ee..f7200bdda 100644 --- a/Tests/NameConverter/MetadataAwareNameConverterTest.php +++ b/Tests/NameConverter/MetadataAwareNameConverterTest.php @@ -149,6 +149,7 @@ public static function attributeAndContextProvider() ['buzForExport', 'buz', ['groups' => 'b']], ['buz', 'buz', ['groups' => ['c']]], ['buz', 'buz', []], + ['buzForExport', 'buz', ['groups' => ['*']]], ]; } From 8fe0b336fff4edd6ca37d8063019c1c7ad72aeea Mon Sep 17 00:00:00 2001 From: Antonio Pauletich Date: Sat, 12 Aug 2023 17:11:13 +0200 Subject: [PATCH 141/297] [Serializer] Fix deserializing object collection properties --- Serializer.php | 3 ++ Tests/Fixtures/FooDummyInterface.php | 16 ++++++ Tests/Fixtures/FooImplementationDummy.php | 20 ++++++++ .../FooInterfaceDummyDenormalizer.php | 50 +++++++++++++++++++ .../ObjectCollectionPropertyDummy.php | 25 ++++++++++ Tests/SerializerTest.php | 18 +++++++ 6 files changed, 132 insertions(+) create mode 100644 Tests/Fixtures/FooDummyInterface.php create mode 100644 Tests/Fixtures/FooImplementationDummy.php create mode 100644 Tests/Fixtures/FooInterfaceDummyDenormalizer.php create mode 100644 Tests/Fixtures/ObjectCollectionPropertyDummy.php diff --git a/Serializer.php b/Serializer.php index c83da01fb..0f6bb1dc2 100644 --- a/Serializer.php +++ b/Serializer.php @@ -359,9 +359,12 @@ private function getDenormalizer(mixed $data, string $class, ?string $format, ar $supportedTypes = $normalizer->getSupportedTypes($format); + $doesClassRepresentCollection = str_ends_with($class, '[]'); + foreach ($supportedTypes as $supportedType => $isCacheable) { if (\in_array($supportedType, ['*', 'object'], true) || $class !== $supportedType && ('object' !== $genericType || !is_subclass_of($class, $supportedType)) + && !($doesClassRepresentCollection && str_ends_with($supportedType, '[]') && is_subclass_of(strstr($class, '[]', true), strstr($supportedType, '[]', true))) ) { continue; } diff --git a/Tests/Fixtures/FooDummyInterface.php b/Tests/Fixtures/FooDummyInterface.php new file mode 100644 index 000000000..da206e039 --- /dev/null +++ b/Tests/Fixtures/FooDummyInterface.php @@ -0,0 +1,16 @@ + + * + * 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; + +interface FooDummyInterface +{ +} diff --git a/Tests/Fixtures/FooImplementationDummy.php b/Tests/Fixtures/FooImplementationDummy.php new file mode 100644 index 000000000..b7f7194a5 --- /dev/null +++ b/Tests/Fixtures/FooImplementationDummy.php @@ -0,0 +1,20 @@ + + * + * 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; + +final class FooImplementationDummy implements FooDummyInterface +{ + /** + * @var string + */ + public $name; +} diff --git a/Tests/Fixtures/FooInterfaceDummyDenormalizer.php b/Tests/Fixtures/FooInterfaceDummyDenormalizer.php new file mode 100644 index 000000000..a8c45373b --- /dev/null +++ b/Tests/Fixtures/FooInterfaceDummyDenormalizer.php @@ -0,0 +1,50 @@ + + * + * 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\DenormalizerInterface; + +final class FooInterfaceDummyDenormalizer implements DenormalizerInterface +{ + public function denormalize(mixed $data, string $type, string $format = null, array $context = []): array + { + $result = []; + foreach ($data as $foo) { + $fooDummy = new FooImplementationDummy(); + $fooDummy->name = $foo['name']; + $result[] = $fooDummy; + } + + return $result; + } + + public function supportsDenormalization(mixed $data, string $type, string $format = null, array $context = []): bool + { + if (str_ends_with($type, '[]')) { + $className = substr($type, 0, -2); + $classImplements = class_implements($className); + \assert(\is_array($classImplements)); + + return class_exists($className) && \in_array(FooDummyInterface::class, $classImplements, true); + } + + return false; + } + + /** + * @return array + */ + public function getSupportedTypes(?string $format): array + { + return [FooDummyInterface::class.'[]' => false]; + } +} diff --git a/Tests/Fixtures/ObjectCollectionPropertyDummy.php b/Tests/Fixtures/ObjectCollectionPropertyDummy.php new file mode 100644 index 000000000..cbb77987b --- /dev/null +++ b/Tests/Fixtures/ObjectCollectionPropertyDummy.php @@ -0,0 +1,25 @@ + + * + * 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; + +final class ObjectCollectionPropertyDummy +{ + /** + * @var FooImplementationDummy[] + */ + public $foo; + + public function getFoo(): array + { + return $this->foo; + } +} diff --git a/Tests/SerializerTest.php b/Tests/SerializerTest.php index f4a164dc5..76deeb5cd 100644 --- a/Tests/SerializerTest.php +++ b/Tests/SerializerTest.php @@ -58,7 +58,10 @@ use Symfony\Component\Serializer\Tests\Fixtures\DummyObjectWithEnumConstructor; use Symfony\Component\Serializer\Tests\Fixtures\DummyObjectWithEnumProperty; use Symfony\Component\Serializer\Tests\Fixtures\FalseBuiltInDummy; +use Symfony\Component\Serializer\Tests\Fixtures\FooImplementationDummy; +use Symfony\Component\Serializer\Tests\Fixtures\FooInterfaceDummyDenormalizer; use Symfony\Component\Serializer\Tests\Fixtures\NormalizableTraversableDummy; +use Symfony\Component\Serializer\Tests\Fixtures\ObjectCollectionPropertyDummy; use Symfony\Component\Serializer\Tests\Fixtures\Php74Full; use Symfony\Component\Serializer\Tests\Fixtures\Php80WithPromotedTypedConstructor; use Symfony\Component\Serializer\Tests\Fixtures\TraversableDummy; @@ -711,6 +714,21 @@ public function testDeserializeInconsistentScalarArray() $serializer->deserialize('["42"]', 'int[]', 'json'); } + public function testDeserializeOnObjectWithObjectCollectionProperty() + { + $serializer = new Serializer([new FooInterfaceDummyDenormalizer(), new ObjectNormalizer(null, null, null, new PhpDocExtractor())], [new JsonEncoder()]); + + $obj = $serializer->deserialize('{"foo":[{"name":"bar"}]}', ObjectCollectionPropertyDummy::class, 'json'); + $this->assertInstanceOf(ObjectCollectionPropertyDummy::class, $obj); + + $fooDummyObjects = $obj->getFoo(); + $this->assertCount(1, $fooDummyObjects); + + $fooDummyObject = $fooDummyObjects[0]; + $this->assertInstanceOf(FooImplementationDummy::class, $fooDummyObject); + $this->assertSame('bar', $fooDummyObject->name); + } + public function testDeserializeWrappedScalar() { $serializer = new Serializer([new UnwrappingDenormalizer()], ['json' => new JsonEncoder()]); From e468734a4dfc80fe1d89c5e8159079a7157b51c0 Mon Sep 17 00:00:00 2001 From: Viktor Truhanovich Date: Wed, 16 Aug 2023 02:35:19 +0300 Subject: [PATCH 142/297] [Serializer] Fix deserializing of nested snake_case attributes using CamelCaseToSnakeCaseNameConverter --- Normalizer/AbstractObjectNormalizer.php | 8 +++-- .../AbstractObjectNormalizerTest.php | 32 +++++++++++++++++++ 2 files changed, 37 insertions(+), 3 deletions(-) diff --git a/Normalizer/AbstractObjectNormalizer.php b/Normalizer/AbstractObjectNormalizer.php index b252d6219..5187955dd 100644 --- a/Normalizer/AbstractObjectNormalizer.php +++ b/Normalizer/AbstractObjectNormalizer.php @@ -326,13 +326,15 @@ public function denormalize(mixed $data, string $type, string $format = null, ar $mappedClass = $this->getMappedClass($normalizedData, $type, $context); $nestedAttributes = $this->getNestedAttributes($mappedClass); - $nestedData = []; + $nestedData = $originalNestedData = []; $propertyAccessor = PropertyAccess::createPropertyAccessor(); foreach ($nestedAttributes as $property => $serializedPath) { if (null === $value = $propertyAccessor->getValue($normalizedData, $serializedPath)) { continue; } - $nestedData[$property] = $value; + $convertedProperty = $this->nameConverter ? $this->nameConverter->normalize($property, $mappedClass, $format, $context) : $property; + $nestedData[$convertedProperty] = $value; + $originalNestedData[$property] = $value; $normalizedData = $this->removeNestedValue($serializedPath->getElements(), $normalizedData); } @@ -345,7 +347,7 @@ public function denormalize(mixed $data, string $type, string $format = null, ar if ($this->nameConverter) { $notConverted = $attribute; $attribute = $this->nameConverter->denormalize($attribute, $resolvedClass, $format, $context); - if (isset($nestedData[$notConverted]) && !isset($nestedData[$attribute])) { + if (isset($nestedData[$notConverted]) && !isset($originalNestedData[$attribute])) { throw new LogicException(sprintf('Duplicate values for key "%s" found. One value is set via the SerializedPath annotation: "%s", the other one is set via the SerializedName annotation: "%s".', $notConverted, implode('->', $nestedAttributes[$notConverted]->getElements()), $attribute)); } } diff --git a/Tests/Normalizer/AbstractObjectNormalizerTest.php b/Tests/Normalizer/AbstractObjectNormalizerTest.php index bed7c33ce..361f8520d 100644 --- a/Tests/Normalizer/AbstractObjectNormalizerTest.php +++ b/Tests/Normalizer/AbstractObjectNormalizerTest.php @@ -33,6 +33,7 @@ use Symfony\Component\Serializer\Mapping\Factory\ClassMetadataFactory; use Symfony\Component\Serializer\Mapping\Factory\ClassMetadataFactoryInterface; use Symfony\Component\Serializer\Mapping\Loader\AnnotationLoader; +use Symfony\Component\Serializer\NameConverter\CamelCaseToSnakeCaseNameConverter; use Symfony\Component\Serializer\NameConverter\MetadataAwareNameConverter; use Symfony\Component\Serializer\Normalizer\AbstractNormalizer; use Symfony\Component\Serializer\Normalizer\AbstractObjectNormalizer; @@ -140,6 +141,29 @@ public function testDenormalizeWithNestedAttributesWithoutMetadata() $this->assertNull($test->notfoo); } + public function testDenormalizeWithSnakeCaseNestedAttributes() + { + $factory = new ClassMetadataFactory(new AnnotationLoader(new AnnotationReader())); + $normalizer = new ObjectNormalizer($factory, new CamelCaseToSnakeCaseNameConverter()); + $data = [ + 'one' => [ + 'two_three' => 'fooBar', + ], + ]; + $test = $normalizer->denormalize($data, SnakeCaseNestedDummy::class, 'any'); + $this->assertSame('fooBar', $test->fooBar); + } + + public function testNormalizeWithSnakeCaseNestedAttributes() + { + $factory = new ClassMetadataFactory(new AnnotationLoader(new AnnotationReader())); + $normalizer = new ObjectNormalizer($factory, new CamelCaseToSnakeCaseNameConverter()); + $dummy = new SnakeCaseNestedDummy(); + $dummy->fooBar = 'fooBar'; + $test = $normalizer->normalize($dummy, 'any'); + $this->assertSame(['one' => ['two_three' => 'fooBar']], $test); + } + public function testDenormalizeWithNestedAttributes() { $normalizer = new AbstractObjectNormalizerWithMetadata(); @@ -861,6 +885,14 @@ public function __construct( } } +class SnakeCaseNestedDummy +{ + /** + * @SerializedPath("[one][two_three]") + */ + public $fooBar; +} + /** * @DiscriminatorMap(typeProperty="type", mapping={ * "first" = FirstNestedDummyWithConstructorAndDiscriminator::class, From 3b1d742b9472f03dd3033257f7d20eb7b317f040 Mon Sep 17 00:00:00 2001 From: Alexandre Daubois Date: Tue, 22 Aug 2023 15:45:13 +0200 Subject: [PATCH 143/297] [FrameworkBundle][Validator] Remove remaining deprecations --- Tests/Normalizer/AbstractObjectNormalizerTest.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Tests/Normalizer/AbstractObjectNormalizerTest.php b/Tests/Normalizer/AbstractObjectNormalizerTest.php index c845c17c5..1d70753f9 100644 --- a/Tests/Normalizer/AbstractObjectNormalizerTest.php +++ b/Tests/Normalizer/AbstractObjectNormalizerTest.php @@ -142,7 +142,7 @@ public function testDenormalizeWithNestedAttributesWithoutMetadata() public function testDenormalizeWithSnakeCaseNestedAttributes() { - $factory = new ClassMetadataFactory(new AnnotationLoader(new AnnotationReader())); + $factory = new ClassMetadataFactory(new AnnotationLoader()); $normalizer = new ObjectNormalizer($factory, new CamelCaseToSnakeCaseNameConverter()); $data = [ 'one' => [ @@ -155,7 +155,7 @@ public function testDenormalizeWithSnakeCaseNestedAttributes() public function testNormalizeWithSnakeCaseNestedAttributes() { - $factory = new ClassMetadataFactory(new AnnotationLoader(new AnnotationReader())); + $factory = new ClassMetadataFactory(new AnnotationLoader()); $normalizer = new ObjectNormalizer($factory, new CamelCaseToSnakeCaseNameConverter()); $dummy = new SnakeCaseNestedDummy(); $dummy->fooBar = 'fooBar'; From 0ae4bb12ee81e2bd205c54916fffc3ee49e0d34a Mon Sep 17 00:00:00 2001 From: Alexandre Daubois Date: Wed, 23 Aug 2023 13:48:05 +0200 Subject: [PATCH 144/297] [Serializer] Remove `@SerializedPath` annotation from test --- Tests/Normalizer/AbstractObjectNormalizerTest.php | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/Tests/Normalizer/AbstractObjectNormalizerTest.php b/Tests/Normalizer/AbstractObjectNormalizerTest.php index c845c17c5..8d62ecd3c 100644 --- a/Tests/Normalizer/AbstractObjectNormalizerTest.php +++ b/Tests/Normalizer/AbstractObjectNormalizerTest.php @@ -142,7 +142,7 @@ public function testDenormalizeWithNestedAttributesWithoutMetadata() public function testDenormalizeWithSnakeCaseNestedAttributes() { - $factory = new ClassMetadataFactory(new AnnotationLoader(new AnnotationReader())); + $factory = new ClassMetadataFactory(new AnnotationLoader()); $normalizer = new ObjectNormalizer($factory, new CamelCaseToSnakeCaseNameConverter()); $data = [ 'one' => [ @@ -155,7 +155,7 @@ public function testDenormalizeWithSnakeCaseNestedAttributes() public function testNormalizeWithSnakeCaseNestedAttributes() { - $factory = new ClassMetadataFactory(new AnnotationLoader(new AnnotationReader())); + $factory = new ClassMetadataFactory(new AnnotationLoader()); $normalizer = new ObjectNormalizer($factory, new CamelCaseToSnakeCaseNameConverter()); $dummy = new SnakeCaseNestedDummy(); $dummy->fooBar = 'fooBar'; @@ -866,9 +866,7 @@ public function __construct( class SnakeCaseNestedDummy { - /** - * @SerializedPath("[one][two_three]") - */ + #[SerializedPath('[one][two_three]')] public $fooBar; } From 96d28a58d5a128bf77c54534b380eb7c92c8f846 Mon Sep 17 00:00:00 2001 From: Mathias Arlaud Date: Thu, 24 Aug 2023 16:29:20 +0200 Subject: [PATCH 145/297] [Serializer] Fix union of enum denormalization --- Normalizer/AbstractObjectNormalizer.php | 7 ++-- .../AbstractObjectNormalizerTest.php | 36 +++++++++++++++++++ 2 files changed, 40 insertions(+), 3 deletions(-) diff --git a/Normalizer/AbstractObjectNormalizer.php b/Normalizer/AbstractObjectNormalizer.php index 5187955dd..171742616 100644 --- a/Normalizer/AbstractObjectNormalizer.php +++ b/Normalizer/AbstractObjectNormalizer.php @@ -11,7 +11,7 @@ namespace Symfony\Component\Serializer\Normalizer; -use Symfony\Component\PropertyAccess\Exception\InvalidArgumentException; +use Symfony\Component\PropertyAccess\Exception\InvalidArgumentException as PropertyAccessInvalidArgumentException; use Symfony\Component\PropertyAccess\Exception\NoSuchPropertyException; use Symfony\Component\PropertyAccess\Exception\UninitializedPropertyException; use Symfony\Component\PropertyAccess\PropertyAccess; @@ -21,6 +21,7 @@ use Symfony\Component\Serializer\Encoder\JsonEncoder; use Symfony\Component\Serializer\Encoder\XmlEncoder; use Symfony\Component\Serializer\Exception\ExtraAttributesException; +use Symfony\Component\Serializer\Exception\InvalidArgumentException; use Symfony\Component\Serializer\Exception\LogicException; use Symfony\Component\Serializer\Exception\MissingConstructorArgumentsException; use Symfony\Component\Serializer\Exception\NotNormalizableValueException; @@ -387,7 +388,7 @@ public function denormalize(mixed $data, string $type, string $format = null, ar try { $this->setAttributeValue($object, $attribute, $value, $format, $attributeContext); - } catch (InvalidArgumentException $e) { + } catch (PropertyAccessInvalidArgumentException $e) { $exception = NotNormalizableValueException::createForUnexpectedDataType( sprintf('Failed to denormalize attribute "%s" value for class "%s": '.$e->getMessage(), $attribute, $type), $data, @@ -562,7 +563,7 @@ private function validateAndDenormalize(array $types, string $currentClass, stri if (('is_'.$builtinType)($data)) { return $data; } - } catch (NotNormalizableValueException $e) { + } catch (NotNormalizableValueException|InvalidArgumentException $e) { if (!$isUnionType) { throw $e; } diff --git a/Tests/Normalizer/AbstractObjectNormalizerTest.php b/Tests/Normalizer/AbstractObjectNormalizerTest.php index 361f8520d..61345a414 100644 --- a/Tests/Normalizer/AbstractObjectNormalizerTest.php +++ b/Tests/Normalizer/AbstractObjectNormalizerTest.php @@ -37,6 +37,7 @@ use Symfony\Component\Serializer\NameConverter\MetadataAwareNameConverter; use Symfony\Component\Serializer\Normalizer\AbstractNormalizer; use Symfony\Component\Serializer\Normalizer\AbstractObjectNormalizer; +use Symfony\Component\Serializer\Normalizer\BackedEnumNormalizer; use Symfony\Component\Serializer\Normalizer\DateTimeNormalizer; use Symfony\Component\Serializer\Normalizer\DenormalizerInterface; use Symfony\Component\Serializer\Normalizer\ObjectNormalizer; @@ -769,6 +770,23 @@ public function supportsNormalization(mixed $data, string $format = null, array $this->assertSame('called', $object->bar); } + + public function testDenormalizeUnionOfEnums() + { + $serializer = new Serializer([ + new BackedEnumNormalizer(), + new ObjectNormalizer( + classMetadataFactory: new ClassMetadataFactory(new AnnotationLoader()), + propertyTypeExtractor: new PropertyInfoExtractor([], [new ReflectionExtractor()]), + ), + ]); + + $normalized = $serializer->normalize(new DummyWithEnumUnion(EnumA::A)); + $this->assertEquals(new DummyWithEnumUnion(EnumA::A), $serializer->denormalize($normalized, DummyWithEnumUnion::class)); + + $normalized = $serializer->normalize(new DummyWithEnumUnion(EnumB::B)); + $this->assertEquals(new DummyWithEnumUnion(EnumB::B), $serializer->denormalize($normalized, DummyWithEnumUnion::class)); + } } class AbstractObjectNormalizerDummy extends AbstractObjectNormalizer @@ -1186,3 +1204,21 @@ public function __sleep(): array throw new \Error('not serializable'); } } + +enum EnumA: string +{ + case A = 'a'; +} + +enum EnumB: string +{ + case B = 'b'; +} + +class DummyWithEnumUnion +{ + public function __construct( + public readonly EnumA|EnumB $enum, + ) { + } +} From 2fc94ddc0e98caa21edfb0f8976326571aed4a61 Mon Sep 17 00:00:00 2001 From: Mathias Arlaud Date: Thu, 24 Aug 2023 15:35:45 +0200 Subject: [PATCH 146/297] [Serializer] Allow Context to target classes --- Annotation/Context.php | 2 +- CHANGELOG.md | 1 + Mapping/Loader/AnnotationLoader.php | 11 ++++++++ .../Features/ContextMetadataTestTrait.php | 25 ++++++++++++++++--- .../Normalizer/Features/DummyContextChild.php | 2 +- Tests/SerializerTest.php | 4 +-- 6 files changed, 38 insertions(+), 7 deletions(-) diff --git a/Annotation/Context.php b/Annotation/Context.php index e3878c7be..a83c8f079 100644 --- a/Annotation/Context.php +++ b/Annotation/Context.php @@ -22,7 +22,7 @@ * * @author Maxime Steinhausser */ -#[\Attribute(\Attribute::TARGET_PROPERTY | \Attribute::TARGET_METHOD | \Attribute::IS_REPEATABLE)] +#[\Attribute(\Attribute::TARGET_CLASS | \Attribute::TARGET_PROPERTY | \Attribute::TARGET_METHOD | \Attribute::IS_REPEATABLE)] class Context { private array $groups; diff --git a/CHANGELOG.md b/CHANGELOG.md index 17221fe61..b206bbe10 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,7 @@ CHANGELOG 6.4 --- + * Allow `Context` attribute to target classes * Deprecate Doctrine annotations support in favor of native attributes * Deprecate passing an annotation reader to the constructor of `AnnotationLoader` * Allow the `Groups` attribute/annotation on classes diff --git a/Mapping/Loader/AnnotationLoader.php b/Mapping/Loader/AnnotationLoader.php index fdeda08b2..db9612a0b 100644 --- a/Mapping/Loader/AnnotationLoader.php +++ b/Mapping/Loader/AnnotationLoader.php @@ -57,6 +57,7 @@ public function loadClassMetadata(ClassMetadataInterface $classMetadata): bool $className = $reflectionClass->name; $loaded = false; $classGroups = []; + $classContextAnnotation = null; $attributesMetadata = $classMetadata->getAttributesMetadata(); @@ -71,6 +72,12 @@ public function loadClassMetadata(ClassMetadataInterface $classMetadata): bool if ($annotation instanceof Groups) { $classGroups = $annotation->getGroups(); + + continue; + } + + if ($annotation instanceof Context) { + $classContextAnnotation = $annotation; } } @@ -81,6 +88,10 @@ public function loadClassMetadata(ClassMetadataInterface $classMetadata): bool } if ($property->getDeclaringClass()->name === $className) { + if ($classContextAnnotation) { + $this->setAttributeContextsForGroups($classContextAnnotation, $attributesMetadata[$property->name]); + } + foreach ($classGroups as $group) { $attributesMetadata[$property->name]->addGroup($group); } diff --git a/Tests/Normalizer/Features/ContextMetadataTestTrait.php b/Tests/Normalizer/Features/ContextMetadataTestTrait.php index 8886e667e..7375b530d 100644 --- a/Tests/Normalizer/Features/ContextMetadataTestTrait.php +++ b/Tests/Normalizer/Features/ContextMetadataTestTrait.php @@ -82,6 +82,7 @@ public function contextMetadataDummyProvider(): array return [ [ContextMetadataDummy::class], [ContextChildMetadataDummy::class], + [ClassAndPropertyContextMetadataDummy::class], ]; } @@ -100,7 +101,7 @@ public function testContextDenormalizeWithNameConverter() class ContextMetadataDummy { /** - * @var \DateTime + * @var \DateTimeImmutable */ #[Groups(['extended', 'simple'])] #[Context([DateTimeNormalizer::FORMAT_KEY => \DateTimeInterface::RFC3339])] @@ -118,7 +119,7 @@ class ContextMetadataDummy class ContextChildMetadataDummy { /** - * @var \DateTime + * @var \DateTimeImmutable */ #[Groups(['extended', 'simple'])] #[DummyContextChild([DateTimeNormalizer::FORMAT_KEY => \DateTimeInterface::RFC3339])] @@ -133,10 +134,28 @@ class ContextChildMetadataDummy public $date; } +#[Context(context: [DateTimeNormalizer::FORMAT_KEY => \DateTimeInterface::RFC3339])] +#[Context( + context: [DateTimeNormalizer::FORMAT_KEY => \DateTimeInterface::RFC3339_EXTENDED], + groups: ['extended'], +)] +class ClassAndPropertyContextMetadataDummy +{ + /** + * @var \DateTimeImmutable + */ + #[Groups(['extended', 'simple'])] + #[Context( + denormalizationContext: [DateTimeNormalizer::FORMAT_KEY => 'd/m/Y'], + groups: ['simple'], + )] + public $date; +} + class ContextMetadataNamingDummy { /** - * @var \DateTime + * @var \DateTimeImmutable */ #[Context([DateTimeNormalizer::FORMAT_KEY => 'd/m/Y'])] public $createdAt; diff --git a/Tests/Normalizer/Features/DummyContextChild.php b/Tests/Normalizer/Features/DummyContextChild.php index f66ef702a..25f85d61f 100644 --- a/Tests/Normalizer/Features/DummyContextChild.php +++ b/Tests/Normalizer/Features/DummyContextChild.php @@ -13,7 +13,7 @@ use Symfony\Component\Serializer\Annotation\Context; -#[\Attribute(\Attribute::TARGET_METHOD | \Attribute::TARGET_PROPERTY | \Attribute::IS_REPEATABLE)] +#[\Attribute(\Attribute::TARGET_CLASS | \Attribute::TARGET_METHOD | \Attribute::TARGET_PROPERTY | \Attribute::IS_REPEATABLE)] class DummyContextChild extends Context { } diff --git a/Tests/SerializerTest.php b/Tests/SerializerTest.php index f92a50241..63b5c9711 100644 --- a/Tests/SerializerTest.php +++ b/Tests/SerializerTest.php @@ -1477,12 +1477,12 @@ public function __construct($value) class DummyUnionType { /** - * @var \DateTime|bool|null + * @var \DateTimeImmutable|bool|null */ public $changed = false; /** - * @param \DateTime|bool|null + * @param \DateTimeImmutable|bool|null * * @return $this */ From 9bc9c3912d4f62b8d3f96164fe2f49a28018221e Mon Sep 17 00:00:00 2001 From: Mathieu Lechat Date: Fri, 29 Sep 2023 10:01:17 +0200 Subject: [PATCH 147/297] =?UTF-8?q?[Serializer]=20Make=20`ProblemNormalize?= =?UTF-8?q?r`=20give=20details=20about=20Messenger=E2=80=99s=20`Validation?= =?UTF-8?q?FailedException`?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CHANGELOG.md | 1 + Normalizer/ProblemNormalizer.php | 3 ++- Tests/Normalizer/ProblemNormalizerTest.php | 25 ++++++++++++++++++++++ composer.json | 1 + 4 files changed, 29 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b206bbe10..15aee29e0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,7 @@ CHANGELOG * Deprecate passing an annotation reader to the constructor of `AnnotationLoader` * Allow the `Groups` attribute/annotation on classes * JsonDecode: Add `json_decode_detailed_errors` option + * Make `ProblemNormalizer` give details about Messenger's `ValidationFailedException` 6.3 --- diff --git a/Normalizer/ProblemNormalizer.php b/Normalizer/ProblemNormalizer.php index f7a8077ec..1043c9b0c 100644 --- a/Normalizer/ProblemNormalizer.php +++ b/Normalizer/ProblemNormalizer.php @@ -13,6 +13,7 @@ use Symfony\Component\ErrorHandler\Exception\FlattenException; use Symfony\Component\HttpKernel\Exception\HttpExceptionInterface; +use Symfony\Component\Messenger\Exception\ValidationFailedException as MessageValidationFailedException; use Symfony\Component\Serializer\Exception\InvalidArgumentException; use Symfony\Component\Serializer\Exception\PartialDenormalizationException; use Symfony\Component\Serializer\SerializerAwareInterface; @@ -84,7 +85,7 @@ public function normalize(mixed $object, string $format = null, array $context = ), ]; $data['detail'] = implode("\n", array_map(fn ($e) => $e['propertyPath'].': '.$e['title'], $data['violations'])); - } elseif ($exception instanceof ValidationFailedException + } elseif (($exception instanceof ValidationFailedException || $exception instanceof MessageValidationFailedException) && $this->serializer instanceof NormalizerInterface && $this->serializer->supportsNormalization($exception->getViolations(), $format, $context) ) { diff --git a/Tests/Normalizer/ProblemNormalizerTest.php b/Tests/Normalizer/ProblemNormalizerTest.php index e6f267bc8..e0a90229a 100644 --- a/Tests/Normalizer/ProblemNormalizerTest.php +++ b/Tests/Normalizer/ProblemNormalizerTest.php @@ -14,6 +14,7 @@ use PHPUnit\Framework\TestCase; use Symfony\Component\ErrorHandler\Exception\FlattenException; use Symfony\Component\HttpKernel\Exception\HttpException; +use Symfony\Component\Messenger\Exception\ValidationFailedException as MessageValidationFailedException; use Symfony\Component\Serializer\Exception\NotNormalizableValueException; use Symfony\Component\Serializer\Exception\PartialDenormalizationException; use Symfony\Component\Serializer\Normalizer\ConstraintViolationListNormalizer; @@ -102,4 +103,28 @@ public function testNormalizeValidationFailedException() $exception = new HttpException(422, 'Validation Failed', $exception); $this->assertSame($expected, $this->normalizer->normalize(FlattenException::createFromThrowable($exception), null, ['exception' => $exception])); } + + public function testNormalizeMessageValidationFailedException() + { + $this->normalizer->setSerializer(new Serializer([new ConstraintViolationListNormalizer()])); + + $expected = [ + 'type' => 'https://symfony.com/errors/validation', + 'title' => 'Validation Failed', + 'status' => 422, + 'detail' => 'Invalid value', + 'violations' => [ + [ + 'propertyPath' => '', + 'title' => 'Invalid value', + 'template' => '', + 'parameters' => [], + ], + ], + ]; + + $exception = new MessageValidationFailedException(new \stdClass(), new ConstraintViolationList([new ConstraintViolation('Invalid value', '', [], '', null, null)])); + $exception = new HttpException(422, 'Validation Failed', $exception); + $this->assertSame($expected, $this->normalizer->normalize(FlattenException::createFromThrowable($exception), null, ['exception' => $exception])); + } } diff --git a/composer.json b/composer.json index 09a503c3f..407fe6a34 100644 --- a/composer.json +++ b/composer.json @@ -33,6 +33,7 @@ "symfony/form": "^5.4|^6.0|^7.0", "symfony/http-foundation": "^5.4|^6.0|^7.0", "symfony/http-kernel": "^5.4|^6.0|^7.0", + "symfony/messenger": "^5.4|^6.0|^7.0", "symfony/mime": "^5.4|^6.0|^7.0", "symfony/property-access": "^5.4|^6.0|^7.0", "symfony/property-info": "^5.4.24|^6.2.11|^7.0", From 1c1a5f97039b3828a21a9a26034ae56a5337a69e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Laugks?= Date: Thu, 16 Mar 2023 11:31:02 +0100 Subject: [PATCH 148/297] [Serializer] Fix reindex normalizedData array in AbstractObjectNormalizer::denormalize() --- Normalizer/AbstractObjectNormalizer.php | 2 +- .../AbstractObjectNormalizerTest.php | 26 +++++++++++++++++++ 2 files changed, 27 insertions(+), 1 deletion(-) diff --git a/Normalizer/AbstractObjectNormalizer.php b/Normalizer/AbstractObjectNormalizer.php index 171742616..069d2e393 100644 --- a/Normalizer/AbstractObjectNormalizer.php +++ b/Normalizer/AbstractObjectNormalizer.php @@ -339,7 +339,7 @@ public function denormalize(mixed $data, string $type, string $format = null, ar $normalizedData = $this->removeNestedValue($serializedPath->getElements(), $normalizedData); } - $normalizedData = array_merge($normalizedData, $nestedData); + $normalizedData = $normalizedData + $nestedData; $object = $this->instantiateObject($normalizedData, $mappedClass, $context, new \ReflectionClass($mappedClass), $allowedAttributes, $format); $resolvedClass = ($this->objectClassResolver)($object); diff --git a/Tests/Normalizer/AbstractObjectNormalizerTest.php b/Tests/Normalizer/AbstractObjectNormalizerTest.php index 61345a414..8eb77718c 100644 --- a/Tests/Normalizer/AbstractObjectNormalizerTest.php +++ b/Tests/Normalizer/AbstractObjectNormalizerTest.php @@ -787,6 +787,32 @@ classMetadataFactory: new ClassMetadataFactory(new AnnotationLoader()), $normalized = $serializer->normalize(new DummyWithEnumUnion(EnumB::B)); $this->assertEquals(new DummyWithEnumUnion(EnumB::B), $serializer->denormalize($normalized, DummyWithEnumUnion::class)); } + + public function testDenormalizeWithNumberAsSerializedNameAndNoArrayReindex() + { + $normalizer = new AbstractObjectNormalizerWithMetadata(); + + $data = [ + '1' => 'foo', + '99' => 'baz', + ]; + + $obj = new class() { + /** + * @SerializedName("1") + */ + public $foo; + + /** + * @SerializedName("99") + */ + public $baz; + }; + + $test = $normalizer->denormalize($data, $obj::class); + $this->assertSame('foo', $test->foo); + $this->assertSame('baz', $test->baz); + } } class AbstractObjectNormalizerDummy extends AbstractObjectNormalizer From c2037649eb10acca9cb856d3bc82cc38977a3946 Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Fri, 29 Sep 2023 18:36:18 +0200 Subject: [PATCH 149/297] Fix merge --- Tests/Normalizer/AbstractObjectNormalizerTest.php | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/Tests/Normalizer/AbstractObjectNormalizerTest.php b/Tests/Normalizer/AbstractObjectNormalizerTest.php index fc1974b32..570bbdd1e 100644 --- a/Tests/Normalizer/AbstractObjectNormalizerTest.php +++ b/Tests/Normalizer/AbstractObjectNormalizerTest.php @@ -797,14 +797,10 @@ public function testDenormalizeWithNumberAsSerializedNameAndNoArrayReindex() ]; $obj = new class() { - /** - * @SerializedName("1") - */ + #[SerializedName('1')] public $foo; - /** - * @SerializedName("99") - */ + #[SerializedName('99')] public $baz; }; From 94236cbc1b4d14460b460d240af16cdc09a1bba6 Mon Sep 17 00:00:00 2001 From: Jeroen de Graaf Date: Tue, 3 Oct 2023 10:05:29 +0200 Subject: [PATCH 150/297] Fix order array sum normalizedData and nestedData Previously, when `array_merge` was changed array+array in 6.3.5, the combined array result is changed as well. array_merge behaves differently than array+array. e.g.: ``` $a = ['key' => 'value-a']; $b = ['key' => 'value-b']; var_dump(array_merge($a, $b)); // Results in: // array(1) { // ["key"]=> // string(7) "value-b" // } var_dump($a + $b); // Results in: // array(1) { // ["key"]=> // string(7) "value-a" // } ``` By switching left with right, the result will be the same again. --- Normalizer/AbstractObjectNormalizer.php | 2 +- .../AbstractObjectNormalizerTest.php | 22 +++++++++++++++++++ 2 files changed, 23 insertions(+), 1 deletion(-) diff --git a/Normalizer/AbstractObjectNormalizer.php b/Normalizer/AbstractObjectNormalizer.php index 069d2e393..e6efb4983 100644 --- a/Normalizer/AbstractObjectNormalizer.php +++ b/Normalizer/AbstractObjectNormalizer.php @@ -339,7 +339,7 @@ public function denormalize(mixed $data, string $type, string $format = null, ar $normalizedData = $this->removeNestedValue($serializedPath->getElements(), $normalizedData); } - $normalizedData = $normalizedData + $nestedData; + $normalizedData = $nestedData + $normalizedData; $object = $this->instantiateObject($normalizedData, $mappedClass, $context, new \ReflectionClass($mappedClass), $allowedAttributes, $format); $resolvedClass = ($this->objectClassResolver)($object); diff --git a/Tests/Normalizer/AbstractObjectNormalizerTest.php b/Tests/Normalizer/AbstractObjectNormalizerTest.php index 8eb77718c..97f966351 100644 --- a/Tests/Normalizer/AbstractObjectNormalizerTest.php +++ b/Tests/Normalizer/AbstractObjectNormalizerTest.php @@ -813,6 +813,28 @@ public function testDenormalizeWithNumberAsSerializedNameAndNoArrayReindex() $this->assertSame('foo', $test->foo); $this->assertSame('baz', $test->baz); } + + public function testDenormalizeWithCorrectOrderOfAttributeAndProperty() + { + $normalizer = new AbstractObjectNormalizerWithMetadata(); + + $data = [ + 'id' => 'root-level-id', + 'data' => [ + 'id' => 'nested-id', + ], + ]; + + $obj = new class() { + /** + * @SerializedPath("[data][id]") + */ + public $id; + }; + + $test = $normalizer->denormalize($data, $obj::class); + $this->assertSame('nested-id', $test->id); + } } class AbstractObjectNormalizerDummy extends AbstractObjectNormalizer From 76ff9c40e69e8bc880aaee5ecbc020b0ce39ffc2 Mon Sep 17 00:00:00 2001 From: Christian Flothmann Date: Sun, 8 Oct 2023 20:01:50 +0200 Subject: [PATCH 151/297] replace annotation with attribute --- Tests/Normalizer/AbstractObjectNormalizerTest.php | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/Tests/Normalizer/AbstractObjectNormalizerTest.php b/Tests/Normalizer/AbstractObjectNormalizerTest.php index 7c6d6ab3a..00f2de877 100644 --- a/Tests/Normalizer/AbstractObjectNormalizerTest.php +++ b/Tests/Normalizer/AbstractObjectNormalizerTest.php @@ -821,9 +821,7 @@ public function testDenormalizeWithCorrectOrderOfAttributeAndProperty() ]; $obj = new class() { - /** - * @SerializedPath("[data][id]") - */ + #[SerializedPath('[data][id]')] public $id; }; From ed3f9d28221a7235a11cb9cdeca7aa085fda6b32 Mon Sep 17 00:00:00 2001 From: HypeMC Date: Mon, 9 Oct 2023 18:01:37 +0200 Subject: [PATCH 152/297] [Serializer] Fix deprecation message --- Mapping/Loader/AnnotationLoader.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Mapping/Loader/AnnotationLoader.php b/Mapping/Loader/AnnotationLoader.php index db9612a0b..345e25054 100644 --- a/Mapping/Loader/AnnotationLoader.php +++ b/Mapping/Loader/AnnotationLoader.php @@ -47,7 +47,7 @@ public function __construct( private readonly ?Reader $reader = null, ) { if ($reader) { - trigger_deprecation('symfony/validator', '6.4', 'Passing a "%s" instance as argument 1 to "%s()" is deprecated, pass null or omit the parameter instead.', get_debug_type($reader), __METHOD__); + trigger_deprecation('symfony/serializer', '6.4', 'Passing a "%s" instance as argument 1 to "%s()" is deprecated, pass null or omit the parameter instead.', get_debug_type($reader), __METHOD__); } } From f6ee52973ac82c4a61131a454c0064a8d82bad80 Mon Sep 17 00:00:00 2001 From: AndoniLarz Date: Sat, 1 Apr 2023 17:12:13 +0200 Subject: [PATCH 153/297] [Serializer] Add `XmlEncoder::CDATA_WRAPPING` context option --- CHANGELOG.md | 1 + Context/Encoder/XmlEncoderContextBuilder.php | 8 ++++++ Encoder/XmlEncoder.php | 8 ++++-- .../Encoder/XmlEncoderContextBuilderTest.php | 8 ++---- Tests/Encoder/XmlEncoderTest.php | 28 +++++++++++++++++-- 5 files changed, 43 insertions(+), 10 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 15aee29e0..19e57fc59 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ CHANGELOG * Allow the `Groups` attribute/annotation on classes * JsonDecode: Add `json_decode_detailed_errors` option * Make `ProblemNormalizer` give details about Messenger's `ValidationFailedException` + * Add `XmlEncoder::CDATA_WRAPPING` context option 6.3 --- diff --git a/Context/Encoder/XmlEncoderContextBuilder.php b/Context/Encoder/XmlEncoderContextBuilder.php index 78617a2bb..34cf78198 100644 --- a/Context/Encoder/XmlEncoderContextBuilder.php +++ b/Context/Encoder/XmlEncoderContextBuilder.php @@ -144,4 +144,12 @@ public function withVersion(?string $version): static { return $this->with(XmlEncoder::VERSION, $version); } + + /** + * Configures whether to wrap strings within CDATA sections. + */ + public function withCdataWrapping(?bool $cdataWrapping): static + { + return $this->with(XmlEncoder::CDATA_WRAPPING, $cdataWrapping); + } } diff --git a/Encoder/XmlEncoder.php b/Encoder/XmlEncoder.php index 80a3a9321..24d786e38 100644 --- a/Encoder/XmlEncoder.php +++ b/Encoder/XmlEncoder.php @@ -58,6 +58,7 @@ class XmlEncoder implements EncoderInterface, DecoderInterface, NormalizationAwa public const STANDALONE = 'xml_standalone'; public const TYPE_CAST_ATTRIBUTES = 'xml_type_cast_attributes'; public const VERSION = 'xml_version'; + public const CDATA_WRAPPING = 'cdata_wrapping'; private array $defaultContext = [ self::AS_COLLECTION => false, @@ -68,6 +69,7 @@ class XmlEncoder implements EncoderInterface, DecoderInterface, NormalizationAwa self::REMOVE_EMPTY_TAGS => false, self::ROOT_NODE_NAME => 'response', self::TYPE_CAST_ATTRIBUTES => true, + self::CDATA_WRAPPING => true, ]; public function __construct(array $defaultContext = []) @@ -424,9 +426,9 @@ private function appendNode(\DOMNode $parentNode, mixed $data, string $format, a /** * Checks if a value contains any characters which would require CDATA wrapping. */ - private function needsCdataWrapping(string $val): bool + private function needsCdataWrapping(string $val, array $context): bool { - return preg_match('/[<>&]/', $val); + return ($context[self::CDATA_WRAPPING] ?? $this->defaultContext[self::CDATA_WRAPPING]) && preg_match('/[<>&]/', $val); } /** @@ -454,7 +456,7 @@ private function selectNodeType(\DOMNode $node, mixed $val, string $format, arra return $this->selectNodeType($node, $this->serializer->normalize($val, $format, $context), $format, $context); } elseif (is_numeric($val)) { return $this->appendText($node, (string) $val); - } elseif (\is_string($val) && $this->needsCdataWrapping($val)) { + } elseif (\is_string($val) && $this->needsCdataWrapping($val, $context)) { return $this->appendCData($node, $val); } elseif (\is_string($val)) { return $this->appendText($node, $val); diff --git a/Tests/Context/Encoder/XmlEncoderContextBuilderTest.php b/Tests/Context/Encoder/XmlEncoderContextBuilderTest.php index 1701733a8..d1ea307a9 100644 --- a/Tests/Context/Encoder/XmlEncoderContextBuilderTest.php +++ b/Tests/Context/Encoder/XmlEncoderContextBuilderTest.php @@ -29,8 +29,6 @@ protected function setUp(): void /** * @dataProvider withersDataProvider - * - * @param array $values */ public function testWithers(array $values) { @@ -47,14 +45,12 @@ public function testWithers(array $values) ->withStandalone($values[XmlEncoder::STANDALONE]) ->withTypeCastAttributes($values[XmlEncoder::TYPE_CAST_ATTRIBUTES]) ->withVersion($values[XmlEncoder::VERSION]) + ->withCdataWrapping($values[XmlEncoder::CDATA_WRAPPING]) ->toArray(); $this->assertSame($values, $context); } - /** - * @return iterable|}> - */ public static function withersDataProvider(): iterable { yield 'With values' => [[ @@ -70,6 +66,7 @@ public static function withersDataProvider(): iterable XmlEncoder::STANDALONE => false, XmlEncoder::TYPE_CAST_ATTRIBUTES => true, XmlEncoder::VERSION => '1.0', + XmlEncoder::CDATA_WRAPPING => false, ]]; yield 'With null values' => [[ @@ -85,6 +82,7 @@ public static function withersDataProvider(): iterable XmlEncoder::STANDALONE => null, XmlEncoder::TYPE_CAST_ATTRIBUTES => null, XmlEncoder::VERSION => null, + XmlEncoder::CDATA_WRAPPING => null, ]]; } } diff --git a/Tests/Encoder/XmlEncoderTest.php b/Tests/Encoder/XmlEncoderTest.php index 151db3f54..7dbd3519c 100644 --- a/Tests/Encoder/XmlEncoderTest.php +++ b/Tests/Encoder/XmlEncoderTest.php @@ -234,15 +234,39 @@ public function testEncodeRootAttributes() public function testEncodeCdataWrapping() { $array = [ - 'firstname' => 'Paul ', + 'firstname' => 'Paul & Martha ', ]; $expected = ''."\n". - ']]>'."\n"; + ']]>'."\n"; $this->assertEquals($expected, $this->encoder->encode($array, 'xml')); } + public function testEnableCdataWrapping() + { + $array = [ + 'firstname' => 'Paul & Martha ', + ]; + + $expected = ''."\n". + ']]>'."\n"; + + $this->assertEquals($expected, $this->encoder->encode($array, 'xml', ['cdata_wrapping' => true])); + } + + public function testDisableCdataWrapping() + { + $array = [ + 'firstname' => 'Paul & Martha ', + ]; + + $expected = ''."\n". + 'Paul & Martha <or Me>'."\n"; + + $this->assertEquals($expected, $this->encoder->encode($array, 'xml', ['cdata_wrapping' => false])); + } + public function testEncodeScalarWithAttribute() { $array = [ From 54d103fe1fb336c2fd44323d8fd6dd023579c1a3 Mon Sep 17 00:00:00 2001 From: Christian Flothmann Date: Fri, 13 Oct 2023 08:43:13 +0200 Subject: [PATCH 154/297] fix tests --- .../Loader/AnnotationLoaderWithDoctrineAnnotationsTest.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Tests/Mapping/Loader/AnnotationLoaderWithDoctrineAnnotationsTest.php b/Tests/Mapping/Loader/AnnotationLoaderWithDoctrineAnnotationsTest.php index 348f8c71c..67212ee4b 100644 --- a/Tests/Mapping/Loader/AnnotationLoaderWithDoctrineAnnotationsTest.php +++ b/Tests/Mapping/Loader/AnnotationLoaderWithDoctrineAnnotationsTest.php @@ -24,7 +24,7 @@ class AnnotationLoaderWithDoctrineAnnotationsTest extends AnnotationLoaderTestCa protected function setUp(): void { - $this->expectDeprecation('Since symfony/validator 6.4: Passing a "Doctrine\Common\Annotations\AnnotationReader" instance as argument 1 to "Symfony\Component\Serializer\Mapping\Loader\AnnotationLoader::__construct()" is deprecated, pass null or omit the parameter instead.'); + $this->expectDeprecation('Since symfony/serializer 6.4: Passing a "Doctrine\Common\Annotations\AnnotationReader" instance as argument 1 to "Symfony\Component\Serializer\Mapping\Loader\AnnotationLoader::__construct()" is deprecated, pass null or omit the parameter instead.'); parent::setUp(); } From e1b5746f064703af10cd77d6c6f494981585687f Mon Sep 17 00:00:00 2001 From: Quentin Devos <4972091+Okhoshi@users.noreply.github.com> Date: Mon, 9 Oct 2023 12:25:41 +0200 Subject: [PATCH 155/297] [FrameworkBundle][HttpKernel] Introduce `$buildDir` argument to `WarmableInterface::warmup` to warm read-only artefacts in `build_dir` --- CacheWarmer/CompiledClassMetadataCacheWarmer.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CacheWarmer/CompiledClassMetadataCacheWarmer.php b/CacheWarmer/CompiledClassMetadataCacheWarmer.php index a43e9b408..64998e98f 100644 --- a/CacheWarmer/CompiledClassMetadataCacheWarmer.php +++ b/CacheWarmer/CompiledClassMetadataCacheWarmer.php @@ -29,7 +29,7 @@ public function __construct( ) { } - public function warmUp(string $cacheDir): array + public function warmUp(string $cacheDir, string $buildDir = null): array { $metadatas = []; From 7b21a309952bd0decf7caeb1b0510f5131763bdf Mon Sep 17 00:00:00 2001 From: Alexandre Daubois Date: Tue, 22 Aug 2023 16:39:24 +0200 Subject: [PATCH 156/297] [FrameworkBundle][Serializer] Deprecate annotations --- CHANGELOG.md | 2 +- Mapping/Loader/AnnotationLoader.php | 283 +--------------- Mapping/Loader/AttributeLoader.php | 313 ++++++++++++++++++ Tests/Command/DebugCommandTest.php | 4 +- .../ClassMetadataFactoryCompilerTest.php | 4 +- .../Factory/ClassMetadataFactoryTest.php | 6 +- ...ationLoaderWithDoctrineAnnotationsTest.php | 7 +- ...stCase.php => AttributeLoaderTestCase.php} | 16 +- ... => AttributeLoaderWithAttributesTest.php} | 8 +- .../MetadataAwareNameConverterTest.php | 20 +- .../AbstractObjectNormalizerTest.php | 34 +- .../Features/ContextMetadataTestTrait.php | 8 +- .../Normalizer/GetSetMethodNormalizerTest.php | 28 +- Tests/Normalizer/MapDenormalizationTest.php | 4 +- Tests/Normalizer/ObjectNormalizerTest.php | 26 +- Tests/Normalizer/PropertyNormalizerTest.php | 18 +- Tests/SerializerTest.php | 12 +- composer.json | 3 +- 18 files changed, 425 insertions(+), 371 deletions(-) create mode 100644 Mapping/Loader/AttributeLoader.php rename Tests/Mapping/Loader/{AnnotationLoaderTestCase.php => AttributeLoaderTestCase.php} (92%) rename Tests/Mapping/Loader/{AnnotationLoaderWithAttributesTest.php => AttributeLoaderWithAttributesTest.php} (81%) diff --git a/CHANGELOG.md b/CHANGELOG.md index 19e57fc59..fdefebdc0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,11 +6,11 @@ CHANGELOG * Allow `Context` attribute to target classes * Deprecate Doctrine annotations support in favor of native attributes - * Deprecate passing an annotation reader to the constructor of `AnnotationLoader` * Allow the `Groups` attribute/annotation on classes * JsonDecode: Add `json_decode_detailed_errors` option * Make `ProblemNormalizer` give details about Messenger's `ValidationFailedException` * Add `XmlEncoder::CDATA_WRAPPING` context option + * Deprecate `AnnotationLoader`, use `AttributeLoader` instead 6.3 --- diff --git a/Mapping/Loader/AnnotationLoader.php b/Mapping/Loader/AnnotationLoader.php index 345e25054..f5c8f9ccc 100644 --- a/Mapping/Loader/AnnotationLoader.php +++ b/Mapping/Loader/AnnotationLoader.php @@ -11,288 +11,15 @@ namespace Symfony\Component\Serializer\Mapping\Loader; -use Doctrine\Common\Annotations\Reader; -use Symfony\Component\Serializer\Annotation\Context; -use Symfony\Component\Serializer\Annotation\DiscriminatorMap; -use Symfony\Component\Serializer\Annotation\Groups; -use Symfony\Component\Serializer\Annotation\Ignore; -use Symfony\Component\Serializer\Annotation\MaxDepth; -use Symfony\Component\Serializer\Annotation\SerializedName; -use Symfony\Component\Serializer\Annotation\SerializedPath; -use Symfony\Component\Serializer\Exception\MappingException; -use Symfony\Component\Serializer\Mapping\AttributeMetadata; -use Symfony\Component\Serializer\Mapping\AttributeMetadataInterface; -use Symfony\Component\Serializer\Mapping\ClassDiscriminatorMapping; -use Symfony\Component\Serializer\Mapping\ClassMetadataInterface; +trigger_deprecation('symfony/serializer', '6.4', 'The "%s" class is deprecated, use "%s" instead.', AnnotationLoader::class, AttributeLoader::class); -/** - * Loader for Doctrine annotations and PHP 8 attributes. - * - * @author Kévin Dunglas - * @author Alexander M. Turek - */ -class AnnotationLoader implements LoaderInterface -{ - private const KNOWN_ANNOTATIONS = [ - DiscriminatorMap::class, - Groups::class, - Ignore::class, - MaxDepth::class, - SerializedName::class, - SerializedPath::class, - Context::class, - ]; - - public function __construct( - private readonly ?Reader $reader = null, - ) { - if ($reader) { - trigger_deprecation('symfony/serializer', '6.4', 'Passing a "%s" instance as argument 1 to "%s()" is deprecated, pass null or omit the parameter instead.', get_debug_type($reader), __METHOD__); - } - } - - public function loadClassMetadata(ClassMetadataInterface $classMetadata): bool - { - $reflectionClass = $classMetadata->getReflectionClass(); - $className = $reflectionClass->name; - $loaded = false; - $classGroups = []; - $classContextAnnotation = null; - - $attributesMetadata = $classMetadata->getAttributesMetadata(); - - foreach ($this->loadAnnotations($reflectionClass) as $annotation) { - if ($annotation instanceof DiscriminatorMap) { - $classMetadata->setClassDiscriminatorMapping(new ClassDiscriminatorMapping( - $annotation->getTypeProperty(), - $annotation->getMapping() - )); - continue; - } - - if ($annotation instanceof Groups) { - $classGroups = $annotation->getGroups(); - - continue; - } - - if ($annotation instanceof Context) { - $classContextAnnotation = $annotation; - } - } - - foreach ($reflectionClass->getProperties() as $property) { - if (!isset($attributesMetadata[$property->name])) { - $attributesMetadata[$property->name] = new AttributeMetadata($property->name); - $classMetadata->addAttributeMetadata($attributesMetadata[$property->name]); - } - - if ($property->getDeclaringClass()->name === $className) { - if ($classContextAnnotation) { - $this->setAttributeContextsForGroups($classContextAnnotation, $attributesMetadata[$property->name]); - } - - foreach ($classGroups as $group) { - $attributesMetadata[$property->name]->addGroup($group); - } - - foreach ($this->loadAnnotations($property) as $annotation) { - if ($annotation instanceof Groups) { - foreach ($annotation->getGroups() as $group) { - $attributesMetadata[$property->name]->addGroup($group); - } - } elseif ($annotation instanceof MaxDepth) { - $attributesMetadata[$property->name]->setMaxDepth($annotation->getMaxDepth()); - } elseif ($annotation instanceof SerializedName) { - $attributesMetadata[$property->name]->setSerializedName($annotation->getSerializedName()); - } elseif ($annotation instanceof SerializedPath) { - $attributesMetadata[$property->name]->setSerializedPath($annotation->getSerializedPath()); - } elseif ($annotation instanceof Ignore) { - $attributesMetadata[$property->name]->setIgnore(true); - } elseif ($annotation instanceof Context) { - $this->setAttributeContextsForGroups($annotation, $attributesMetadata[$property->name]); - } - - $loaded = true; - } - } - } - - foreach ($reflectionClass->getMethods() as $method) { - if ($method->getDeclaringClass()->name !== $className) { - continue; - } - - if (0 === stripos($method->name, 'get') && $method->getNumberOfRequiredParameters()) { - continue; /* matches the BC behavior in `Symfony\Component\Serializer\Normalizer\ObjectNormalizer::extractAttributes` */ - } - - $accessorOrMutator = preg_match('/^(get|is|has|set)(.+)$/i', $method->name, $matches); - if ($accessorOrMutator) { - $attributeName = lcfirst($matches[2]); - - if (isset($attributesMetadata[$attributeName])) { - $attributeMetadata = $attributesMetadata[$attributeName]; - } else { - $attributesMetadata[$attributeName] = $attributeMetadata = new AttributeMetadata($attributeName); - $classMetadata->addAttributeMetadata($attributeMetadata); - } - } - - foreach ($this->loadAnnotations($method) as $annotation) { - if ($annotation instanceof Groups) { - if (!$accessorOrMutator) { - throw new MappingException(sprintf('Groups on "%s::%s()" cannot be added. Groups can only be added on methods beginning with "get", "is", "has" or "set".', $className, $method->name)); - } - - foreach ($annotation->getGroups() as $group) { - $attributeMetadata->addGroup($group); - } - } elseif ($annotation instanceof MaxDepth) { - if (!$accessorOrMutator) { - throw new MappingException(sprintf('MaxDepth on "%s::%s()" cannot be added. MaxDepth can only be added on methods beginning with "get", "is", "has" or "set".', $className, $method->name)); - } - - $attributeMetadata->setMaxDepth($annotation->getMaxDepth()); - } elseif ($annotation instanceof SerializedName) { - if (!$accessorOrMutator) { - throw new MappingException(sprintf('SerializedName on "%s::%s()" cannot be added. SerializedName can only be added on methods beginning with "get", "is", "has" or "set".', $className, $method->name)); - } - - $attributeMetadata->setSerializedName($annotation->getSerializedName()); - } elseif ($annotation instanceof SerializedPath) { - if (!$accessorOrMutator) { - throw new MappingException(sprintf('SerializedPath on "%s::%s()" cannot be added. SerializedPath can only be added on methods beginning with "get", "is", "has" or "set".', $className, $method->name)); - } - - $attributeMetadata->setSerializedPath($annotation->getSerializedPath()); - } elseif ($annotation instanceof Ignore) { - if (!$accessorOrMutator) { - throw new MappingException(sprintf('Ignore on "%s::%s()" cannot be added. Ignore can only be added on methods beginning with "get", "is", "has" or "set".', $className, $method->name)); - } - - $attributeMetadata->setIgnore(true); - } elseif ($annotation instanceof Context) { - if (!$accessorOrMutator) { - throw new MappingException(sprintf('Context on "%s::%s()" cannot be added. Context can only be added on methods beginning with "get", "is", "has" or "set".', $className, $method->name)); - } - - $this->setAttributeContextsForGroups($annotation, $attributeMetadata); - } - - $loaded = true; - } - } - - return $loaded; - } - - public function loadAnnotations(\ReflectionMethod|\ReflectionClass|\ReflectionProperty $reflector): iterable - { - foreach ($reflector->getAttributes() as $attribute) { - if ($this->isKnownAttribute($attribute->getName())) { - try { - yield $attribute->newInstance(); - } catch (\Error $e) { - if (\Error::class !== $e::class) { - throw $e; - } - $on = match (true) { - $reflector instanceof \ReflectionClass => ' on class '.$reflector->name, - $reflector instanceof \ReflectionMethod => sprintf(' on "%s::%s()"', $reflector->getDeclaringClass()->name, $reflector->name), - $reflector instanceof \ReflectionProperty => sprintf(' on "%s::$%s"', $reflector->getDeclaringClass()->name, $reflector->name), - default => '', - }; - - throw new MappingException(sprintf('Could not instantiate attribute "%s"%s.', $attribute->getName(), $on), 0, $e); - } - } - } - - if (null === $this->reader) { - return; - } - - if ($reflector instanceof \ReflectionClass) { - yield from $this->getClassAnnotations($reflector); - } - if ($reflector instanceof \ReflectionMethod) { - yield from $this->getMethodAnnotations($reflector); - } - if ($reflector instanceof \ReflectionProperty) { - yield from $this->getPropertyAnnotations($reflector); - } - } - - private function setAttributeContextsForGroups(Context $annotation, AttributeMetadataInterface $attributeMetadata): void - { - if ($annotation->getContext()) { - $attributeMetadata->setNormalizationContextForGroups($annotation->getContext(), $annotation->getGroups()); - $attributeMetadata->setDenormalizationContextForGroups($annotation->getContext(), $annotation->getGroups()); - } - - if ($annotation->getNormalizationContext()) { - $attributeMetadata->setNormalizationContextForGroups($annotation->getNormalizationContext(), $annotation->getGroups()); - } - - if ($annotation->getDenormalizationContext()) { - $attributeMetadata->setDenormalizationContextForGroups($annotation->getDenormalizationContext(), $annotation->getGroups()); - } - } - - private function isKnownAttribute(string $attributeName): bool - { - foreach (self::KNOWN_ANNOTATIONS as $knownAnnotation) { - if (is_a($attributeName, $knownAnnotation, true)) { - return true; - } - } - - return false; - } - - /** - * @return object[] - */ - private function getClassAnnotations(\ReflectionClass $reflector): array - { - if ($annotations = array_filter( - $this->reader->getClassAnnotations($reflector), - fn (object $annotation): bool => $this->isKnownAttribute($annotation::class), - )) { - trigger_deprecation('symfony/serializer', '6.4', 'Class "%s" uses Doctrine Annotations to configure serialization, which is deprecated. Use PHP attributes instead.', $reflector->getName()); - } - - return $annotations; - } +class_exists(AttributeLoader::class); +if (false) { /** - * @return object[] + * @deprecated since Symfony 6.4, to be removed in 7.0, use {@link AttributeLoader} instead */ - private function getMethodAnnotations(\ReflectionMethod $reflector): array + class AnnotationLoader { - if ($annotations = array_filter( - $this->reader->getMethodAnnotations($reflector), - fn (object $annotation): bool => $this->isKnownAttribute($annotation::class), - )) { - trigger_deprecation('symfony/serializer', '6.4', 'Method "%s::%s()" uses Doctrine Annotations to configure serialization, which is deprecated. Use PHP attributes instead.', $reflector->getDeclaringClass()->getName(), $reflector->getName()); - } - - return $annotations; - } - - /** - * @return object[] - */ - private function getPropertyAnnotations(\ReflectionProperty $reflector): array - { - if ($annotations = array_filter( - $this->reader->getPropertyAnnotations($reflector), - fn (object $annotation): bool => $this->isKnownAttribute($annotation::class), - )) { - trigger_deprecation('symfony/serializer', '6.4', 'Property "%s::$%s" uses Doctrine Annotations to configure serialization, which is deprecated. Use PHP attributes instead.', $reflector->getDeclaringClass()->getName(), $reflector->getName()); - } - - return $annotations; } } diff --git a/Mapping/Loader/AttributeLoader.php b/Mapping/Loader/AttributeLoader.php new file mode 100644 index 000000000..9b379bdee --- /dev/null +++ b/Mapping/Loader/AttributeLoader.php @@ -0,0 +1,313 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Serializer\Mapping\Loader; + +use Doctrine\Common\Annotations\Reader; +use Symfony\Component\Serializer\Annotation\Context; +use Symfony\Component\Serializer\Annotation\DiscriminatorMap; +use Symfony\Component\Serializer\Annotation\Groups; +use Symfony\Component\Serializer\Annotation\Ignore; +use Symfony\Component\Serializer\Annotation\MaxDepth; +use Symfony\Component\Serializer\Annotation\SerializedName; +use Symfony\Component\Serializer\Annotation\SerializedPath; +use Symfony\Component\Serializer\Exception\MappingException; +use Symfony\Component\Serializer\Mapping\AttributeMetadata; +use Symfony\Component\Serializer\Mapping\AttributeMetadataInterface; +use Symfony\Component\Serializer\Mapping\ClassDiscriminatorMapping; +use Symfony\Component\Serializer\Mapping\ClassMetadataInterface; + +/** + * Loader for PHP attributes. + * + * @author Kévin Dunglas + * @author Alexander M. Turek + * @author Alexandre Daubois + */ +class AttributeLoader implements LoaderInterface +{ + private const KNOWN_ATTRIBUTES = [ + DiscriminatorMap::class, + Groups::class, + Ignore::class, + MaxDepth::class, + SerializedName::class, + SerializedPath::class, + Context::class, + ]; + + public function __construct( + private readonly ?Reader $reader = null, + ) { + if ($reader) { + trigger_deprecation('symfony/serializer', '6.4', 'Passing a "%s" instance as argument 1 to "%s()" is deprecated, pass null or omit the parameter instead.', get_debug_type($reader), __METHOD__); + } + } + + public function loadClassMetadata(ClassMetadataInterface $classMetadata): bool + { + $reflectionClass = $classMetadata->getReflectionClass(); + $className = $reflectionClass->name; + $loaded = false; + $classGroups = []; + $classContextAnnotation = null; + + $attributesMetadata = $classMetadata->getAttributesMetadata(); + + foreach ($this->loadAttributes($reflectionClass) as $annotation) { + if ($annotation instanceof DiscriminatorMap) { + $classMetadata->setClassDiscriminatorMapping(new ClassDiscriminatorMapping( + $annotation->getTypeProperty(), + $annotation->getMapping() + )); + continue; + } + + if ($annotation instanceof Groups) { + $classGroups = $annotation->getGroups(); + + continue; + } + + if ($annotation instanceof Context) { + $classContextAnnotation = $annotation; + } + } + + foreach ($reflectionClass->getProperties() as $property) { + if (!isset($attributesMetadata[$property->name])) { + $attributesMetadata[$property->name] = new AttributeMetadata($property->name); + $classMetadata->addAttributeMetadata($attributesMetadata[$property->name]); + } + + if ($property->getDeclaringClass()->name === $className) { + if ($classContextAnnotation) { + $this->setAttributeContextsForGroups($classContextAnnotation, $attributesMetadata[$property->name]); + } + + foreach ($classGroups as $group) { + $attributesMetadata[$property->name]->addGroup($group); + } + + foreach ($this->loadAttributes($property) as $annotation) { + if ($annotation instanceof Groups) { + foreach ($annotation->getGroups() as $group) { + $attributesMetadata[$property->name]->addGroup($group); + } + } elseif ($annotation instanceof MaxDepth) { + $attributesMetadata[$property->name]->setMaxDepth($annotation->getMaxDepth()); + } elseif ($annotation instanceof SerializedName) { + $attributesMetadata[$property->name]->setSerializedName($annotation->getSerializedName()); + } elseif ($annotation instanceof SerializedPath) { + $attributesMetadata[$property->name]->setSerializedPath($annotation->getSerializedPath()); + } elseif ($annotation instanceof Ignore) { + $attributesMetadata[$property->name]->setIgnore(true); + } elseif ($annotation instanceof Context) { + $this->setAttributeContextsForGroups($annotation, $attributesMetadata[$property->name]); + } + + $loaded = true; + } + } + } + + foreach ($reflectionClass->getMethods() as $method) { + if ($method->getDeclaringClass()->name !== $className) { + continue; + } + + if (0 === stripos($method->name, 'get') && $method->getNumberOfRequiredParameters()) { + continue; /* matches the BC behavior in `Symfony\Component\Serializer\Normalizer\ObjectNormalizer::extractAttributes` */ + } + + $accessorOrMutator = preg_match('/^(get|is|has|set)(.+)$/i', $method->name, $matches); + if ($accessorOrMutator) { + $attributeName = lcfirst($matches[2]); + + if (isset($attributesMetadata[$attributeName])) { + $attributeMetadata = $attributesMetadata[$attributeName]; + } else { + $attributesMetadata[$attributeName] = $attributeMetadata = new AttributeMetadata($attributeName); + $classMetadata->addAttributeMetadata($attributeMetadata); + } + } + + foreach ($this->loadAttributes($method) as $annotation) { + if ($annotation instanceof Groups) { + if (!$accessorOrMutator) { + throw new MappingException(sprintf('Groups on "%s::%s()" cannot be added. Groups can only be added on methods beginning with "get", "is", "has" or "set".', $className, $method->name)); + } + + foreach ($annotation->getGroups() as $group) { + $attributeMetadata->addGroup($group); + } + } elseif ($annotation instanceof MaxDepth) { + if (!$accessorOrMutator) { + throw new MappingException(sprintf('MaxDepth on "%s::%s()" cannot be added. MaxDepth can only be added on methods beginning with "get", "is", "has" or "set".', $className, $method->name)); + } + + $attributeMetadata->setMaxDepth($annotation->getMaxDepth()); + } elseif ($annotation instanceof SerializedName) { + if (!$accessorOrMutator) { + throw new MappingException(sprintf('SerializedName on "%s::%s()" cannot be added. SerializedName can only be added on methods beginning with "get", "is", "has" or "set".', $className, $method->name)); + } + + $attributeMetadata->setSerializedName($annotation->getSerializedName()); + } elseif ($annotation instanceof SerializedPath) { + if (!$accessorOrMutator) { + throw new MappingException(sprintf('SerializedPath on "%s::%s()" cannot be added. SerializedPath can only be added on methods beginning with "get", "is", "has" or "set".', $className, $method->name)); + } + + $attributeMetadata->setSerializedPath($annotation->getSerializedPath()); + } elseif ($annotation instanceof Ignore) { + if (!$accessorOrMutator) { + throw new MappingException(sprintf('Ignore on "%s::%s()" cannot be added. Ignore can only be added on methods beginning with "get", "is", "has" or "set".', $className, $method->name)); + } + + $attributeMetadata->setIgnore(true); + } elseif ($annotation instanceof Context) { + if (!$accessorOrMutator) { + throw new MappingException(sprintf('Context on "%s::%s()" cannot be added. Context can only be added on methods beginning with "get", "is", "has" or "set".', $className, $method->name)); + } + + $this->setAttributeContextsForGroups($annotation, $attributeMetadata); + } + + $loaded = true; + } + } + + return $loaded; + } + + private function loadAttributes(\ReflectionMethod|\ReflectionClass|\ReflectionProperty $reflector): iterable + { + foreach ($reflector->getAttributes() as $attribute) { + if ($this->isKnownAttribute($attribute->getName())) { + try { + yield $attribute->newInstance(); + } catch (\Error $e) { + if (\Error::class !== $e::class) { + throw $e; + } + $on = match (true) { + $reflector instanceof \ReflectionClass => ' on class '.$reflector->name, + $reflector instanceof \ReflectionMethod => sprintf(' on "%s::%s()"', $reflector->getDeclaringClass()->name, $reflector->name), + $reflector instanceof \ReflectionProperty => sprintf(' on "%s::$%s"', $reflector->getDeclaringClass()->name, $reflector->name), + default => '', + }; + + throw new MappingException(sprintf('Could not instantiate attribute "%s"%s.', $attribute->getName(), $on), 0, $e); + } + } + } + + if (null === $this->reader) { + return; + } + + if ($reflector instanceof \ReflectionClass) { + yield from $this->getClassAnnotations($reflector); + } + if ($reflector instanceof \ReflectionMethod) { + yield from $this->getMethodAnnotations($reflector); + } + if ($reflector instanceof \ReflectionProperty) { + yield from $this->getPropertyAnnotations($reflector); + } + } + + /** + * @deprecated since Symfony 6.4 without replacement + */ + public function loadAnnotations(\ReflectionMethod|\ReflectionClass|\ReflectionProperty $reflector): iterable + { + trigger_deprecation('symfony/serializer', '6.4', 'Method "%s()" is deprecated without replacement.', __METHOD__); + + return $this->loadAttributes($reflector); + } + + private function setAttributeContextsForGroups(Context $annotation, AttributeMetadataInterface $attributeMetadata): void + { + if ($annotation->getContext()) { + $attributeMetadata->setNormalizationContextForGroups($annotation->getContext(), $annotation->getGroups()); + $attributeMetadata->setDenormalizationContextForGroups($annotation->getContext(), $annotation->getGroups()); + } + + if ($annotation->getNormalizationContext()) { + $attributeMetadata->setNormalizationContextForGroups($annotation->getNormalizationContext(), $annotation->getGroups()); + } + + if ($annotation->getDenormalizationContext()) { + $attributeMetadata->setDenormalizationContextForGroups($annotation->getDenormalizationContext(), $annotation->getGroups()); + } + } + + private function isKnownAttribute(string $attributeName): bool + { + foreach (self::KNOWN_ATTRIBUTES as $knownAttribute) { + if (is_a($attributeName, $knownAttribute, true)) { + return true; + } + } + + return false; + } + + /** + * @return object[] + */ + private function getClassAnnotations(\ReflectionClass $reflector): array + { + if ($annotations = array_filter( + $this->reader->getClassAnnotations($reflector), + fn (object $annotation): bool => $this->isKnownAttribute($annotation::class), + )) { + trigger_deprecation('symfony/serializer', '6.4', 'Class "%s" uses Doctrine Annotations to configure serialization, which is deprecated. Use PHP attributes instead.', $reflector->getName()); + } + + return $annotations; + } + + /** + * @return object[] + */ + private function getMethodAnnotations(\ReflectionMethod $reflector): array + { + if ($annotations = array_filter( + $this->reader->getMethodAnnotations($reflector), + fn (object $annotation): bool => $this->isKnownAttribute($annotation::class), + )) { + trigger_deprecation('symfony/serializer', '6.4', 'Method "%s::%s()" uses Doctrine Annotations to configure serialization, which is deprecated. Use PHP attributes instead.', $reflector->getDeclaringClass()->getName(), $reflector->getName()); + } + + return $annotations; + } + + /** + * @return object[] + */ + private function getPropertyAnnotations(\ReflectionProperty $reflector): array + { + if ($annotations = array_filter( + $this->reader->getPropertyAnnotations($reflector), + fn (object $annotation): bool => $this->isKnownAttribute($annotation::class), + )) { + trigger_deprecation('symfony/serializer', '6.4', 'Property "%s::$%s" uses Doctrine Annotations to configure serialization, which is deprecated. Use PHP attributes instead.', $reflector->getDeclaringClass()->getName(), $reflector->getName()); + } + + return $annotations; + } +} + +if (!class_exists(AnnotationLoader::class, false)) { + class_alias(AttributeLoader::class, AnnotationLoader::class); +} diff --git a/Tests/Command/DebugCommandTest.php b/Tests/Command/DebugCommandTest.php index a92e73f33..879231160 100644 --- a/Tests/Command/DebugCommandTest.php +++ b/Tests/Command/DebugCommandTest.php @@ -16,7 +16,7 @@ use Symfony\Component\Serializer\Command\DebugCommand; use Symfony\Component\Serializer\Mapping\Factory\ClassMetadataFactory; use Symfony\Component\Serializer\Mapping\Factory\ClassMetadataFactoryInterface; -use Symfony\Component\Serializer\Mapping\Loader\AnnotationLoader; +use Symfony\Component\Serializer\Mapping\Loader\AttributeLoader; use Symfony\Component\Serializer\Tests\Dummy\DummyClassOne; /** @@ -26,7 +26,7 @@ class DebugCommandTest extends TestCase { public function testOutputWithClassArgument() { - $command = new DebugCommand(new ClassMetadataFactory(new AnnotationLoader())); + $command = new DebugCommand(new ClassMetadataFactory(new AttributeLoader())); $tester = new CommandTester($command); $tester->execute(['class' => DummyClassOne::class], ['decorated' => false]); diff --git a/Tests/Mapping/Factory/ClassMetadataFactoryCompilerTest.php b/Tests/Mapping/Factory/ClassMetadataFactoryCompilerTest.php index fc61b3752..1826d3dc4 100644 --- a/Tests/Mapping/Factory/ClassMetadataFactoryCompilerTest.php +++ b/Tests/Mapping/Factory/ClassMetadataFactoryCompilerTest.php @@ -14,7 +14,7 @@ use PHPUnit\Framework\TestCase; use Symfony\Component\Serializer\Mapping\Factory\ClassMetadataFactory; use Symfony\Component\Serializer\Mapping\Factory\ClassMetadataFactoryCompiler; -use Symfony\Component\Serializer\Mapping\Loader\AnnotationLoader; +use Symfony\Component\Serializer\Mapping\Loader\AttributeLoader; use Symfony\Component\Serializer\Tests\Fixtures\Attributes\MaxDepthDummy; use Symfony\Component\Serializer\Tests\Fixtures\Attributes\SerializedNameDummy; use Symfony\Component\Serializer\Tests\Fixtures\Attributes\SerializedPathDummy; @@ -37,7 +37,7 @@ protected function tearDown(): void public function testItDumpMetadata() { - $classMetatadataFactory = new ClassMetadataFactory(new AnnotationLoader()); + $classMetatadataFactory = new ClassMetadataFactory(new AttributeLoader()); $dummyMetadata = $classMetatadataFactory->getMetadataFor(Dummy::class); $maxDepthDummyMetadata = $classMetatadataFactory->getMetadataFor(MaxDepthDummy::class); diff --git a/Tests/Mapping/Factory/ClassMetadataFactoryTest.php b/Tests/Mapping/Factory/ClassMetadataFactoryTest.php index d034a06c6..140623ab0 100644 --- a/Tests/Mapping/Factory/ClassMetadataFactoryTest.php +++ b/Tests/Mapping/Factory/ClassMetadataFactoryTest.php @@ -14,7 +14,7 @@ use PHPUnit\Framework\TestCase; use Symfony\Component\Serializer\Mapping\Factory\ClassMetadataFactory; use Symfony\Component\Serializer\Mapping\Factory\ClassMetadataFactoryInterface; -use Symfony\Component\Serializer\Mapping\Loader\AnnotationLoader; +use Symfony\Component\Serializer\Mapping\Loader\AttributeLoader; use Symfony\Component\Serializer\Mapping\Loader\LoaderChain; use Symfony\Component\Serializer\Tests\Fixtures\Attributes\GroupDummy; use Symfony\Component\Serializer\Tests\Fixtures\Attributes\GroupDummyInterface; @@ -34,7 +34,7 @@ public function testInterface() public function testGetMetadataFor() { - $factory = new ClassMetadataFactory(new AnnotationLoader()); + $factory = new ClassMetadataFactory(new AttributeLoader()); $classMetadata = $factory->getMetadataFor(GroupDummy::class); $this->assertEquals(TestClassMetadataFactory::createClassMetadata('Symfony\Component\Serializer\Tests\Fixtures\Attributes', true, true), $classMetadata); @@ -42,7 +42,7 @@ public function testGetMetadataFor() public function testHasMetadataFor() { - $factory = new ClassMetadataFactory(new AnnotationLoader()); + $factory = new ClassMetadataFactory(new AttributeLoader()); $this->assertTrue($factory->hasMetadataFor(GroupDummy::class)); $this->assertTrue($factory->hasMetadataFor(GroupDummyParent::class)); $this->assertTrue($factory->hasMetadataFor(GroupDummyInterface::class)); diff --git a/Tests/Mapping/Loader/AnnotationLoaderWithDoctrineAnnotationsTest.php b/Tests/Mapping/Loader/AnnotationLoaderWithDoctrineAnnotationsTest.php index 67212ee4b..9d786bde5 100644 --- a/Tests/Mapping/Loader/AnnotationLoaderWithDoctrineAnnotationsTest.php +++ b/Tests/Mapping/Loader/AnnotationLoaderWithDoctrineAnnotationsTest.php @@ -14,17 +14,18 @@ use Doctrine\Common\Annotations\AnnotationReader; use Symfony\Bridge\PhpUnit\ExpectDeprecationTrait; use Symfony\Component\Serializer\Mapping\Loader\AnnotationLoader; +use Symfony\Component\Serializer\Mapping\Loader\AttributeLoader; /** * @group legacy */ -class AnnotationLoaderWithDoctrineAnnotationsTest extends AnnotationLoaderTestCase +class AnnotationLoaderWithDoctrineAnnotationsTest extends AttributeLoaderTestCase { use ExpectDeprecationTrait; protected function setUp(): void { - $this->expectDeprecation('Since symfony/serializer 6.4: Passing a "Doctrine\Common\Annotations\AnnotationReader" instance as argument 1 to "Symfony\Component\Serializer\Mapping\Loader\AnnotationLoader::__construct()" is deprecated, pass null or omit the parameter instead.'); + $this->expectDeprecation('Since symfony/serializer 6.4: Passing a "Doctrine\Common\Annotations\AnnotationReader" instance as argument 1 to "Symfony\Component\Serializer\Mapping\Loader\AttributeLoader::__construct()" is deprecated, pass null or omit the parameter instead.'); parent::setUp(); } @@ -158,7 +159,7 @@ public function testIgnoreGetterWithRequiredParameterIfIgnoreAnnotationIsUsed() parent::testIgnoreGetterWithRequiredParameterIfIgnoreAnnotationIsUsed(); } - protected function createLoader(): AnnotationLoader + protected function createLoader(): AttributeLoader { return new AnnotationLoader(new AnnotationReader()); } diff --git a/Tests/Mapping/Loader/AnnotationLoaderTestCase.php b/Tests/Mapping/Loader/AttributeLoaderTestCase.php similarity index 92% rename from Tests/Mapping/Loader/AnnotationLoaderTestCase.php rename to Tests/Mapping/Loader/AttributeLoaderTestCase.php index 69b486177..3fa3fc433 100644 --- a/Tests/Mapping/Loader/AnnotationLoaderTestCase.php +++ b/Tests/Mapping/Loader/AttributeLoaderTestCase.php @@ -12,12 +12,14 @@ namespace Symfony\Component\Serializer\Tests\Mapping\Loader; use PHPUnit\Framework\TestCase; +use Symfony\Bridge\PhpUnit\ExpectDeprecationTrait; use Symfony\Component\PropertyAccess\PropertyPath; use Symfony\Component\Serializer\Exception\MappingException; use Symfony\Component\Serializer\Mapping\AttributeMetadata; use Symfony\Component\Serializer\Mapping\ClassDiscriminatorMapping; use Symfony\Component\Serializer\Mapping\ClassMetadata; use Symfony\Component\Serializer\Mapping\Loader\AnnotationLoader; +use Symfony\Component\Serializer\Mapping\Loader\AttributeLoader; use Symfony\Component\Serializer\Mapping\Loader\LoaderInterface; use Symfony\Component\Serializer\Tests\Mapping\Loader\Features\ContextMappingTestTrait; use Symfony\Component\Serializer\Tests\Mapping\TestClassMetadataFactory; @@ -25,9 +27,10 @@ /** * @author Kévin Dunglas */ -abstract class AnnotationLoaderTestCase extends TestCase +abstract class AttributeLoaderTestCase extends TestCase { use ContextMappingTestTrait; + use ExpectDeprecationTrait; protected AnnotationLoader $loader; @@ -210,7 +213,16 @@ public function testLoadGroupsOnClass() self::assertSame(['a'], $attributesMetadata['baz']->getGroups()); } - abstract protected function createLoader(): AnnotationLoader; + /** + * @group legacy + */ + public function testExpectedDeprecationOnLoadAnnotationsCall() + { + $this->expectDeprecation('Since symfony/serializer 6.4: Method "Symfony\Component\Serializer\Mapping\Loader\AttributeLoader::loadAnnotations()" is deprecated without replacement.'); + $this->loader->loadAnnotations(new \ReflectionClass(\stdClass::class)); + } + + abstract protected function createLoader(): AttributeLoader; abstract protected function getNamespace(): string; diff --git a/Tests/Mapping/Loader/AnnotationLoaderWithAttributesTest.php b/Tests/Mapping/Loader/AttributeLoaderWithAttributesTest.php similarity index 81% rename from Tests/Mapping/Loader/AnnotationLoaderWithAttributesTest.php rename to Tests/Mapping/Loader/AttributeLoaderWithAttributesTest.php index 89e94b663..c696b8c24 100644 --- a/Tests/Mapping/Loader/AnnotationLoaderWithAttributesTest.php +++ b/Tests/Mapping/Loader/AttributeLoaderWithAttributesTest.php @@ -13,13 +13,13 @@ use Symfony\Component\Serializer\Exception\MappingException; use Symfony\Component\Serializer\Mapping\ClassMetadata; -use Symfony\Component\Serializer\Mapping\Loader\AnnotationLoader; +use Symfony\Component\Serializer\Mapping\Loader\AttributeLoader; -class AnnotationLoaderWithAttributesTest extends AnnotationLoaderTestCase +class AttributeLoaderWithAttributesTest extends AttributeLoaderTestCase { - protected function createLoader(): AnnotationLoader + protected function createLoader(): AttributeLoader { - return new AnnotationLoader(); + return new AttributeLoader(); } protected function getNamespace(): string diff --git a/Tests/NameConverter/MetadataAwareNameConverterTest.php b/Tests/NameConverter/MetadataAwareNameConverterTest.php index f117378b0..883c30a55 100644 --- a/Tests/NameConverter/MetadataAwareNameConverterTest.php +++ b/Tests/NameConverter/MetadataAwareNameConverterTest.php @@ -17,7 +17,7 @@ use Symfony\Component\Serializer\Exception\LogicException; use Symfony\Component\Serializer\Mapping\Factory\ClassMetadataFactory; use Symfony\Component\Serializer\Mapping\Factory\ClassMetadataFactoryInterface; -use Symfony\Component\Serializer\Mapping\Loader\AnnotationLoader; +use Symfony\Component\Serializer\Mapping\Loader\AttributeLoader; use Symfony\Component\Serializer\NameConverter\MetadataAwareNameConverter; use Symfony\Component\Serializer\NameConverter\NameConverterInterface; use Symfony\Component\Serializer\Tests\Fixtures\Attributes\SerializedNameDummy; @@ -40,7 +40,7 @@ public function testInterface() */ public function testNormalize(string|int $propertyName, string|int $expected) { - $classMetadataFactory = new ClassMetadataFactory(new AnnotationLoader()); + $classMetadataFactory = new ClassMetadataFactory(new AttributeLoader()); $nameConverter = new MetadataAwareNameConverter($classMetadataFactory); @@ -52,7 +52,7 @@ public function testNormalize(string|int $propertyName, string|int $expected) */ public function testNormalizeWithFallback(string|int $propertyName, string|int $expected) { - $classMetadataFactory = new ClassMetadataFactory(new AnnotationLoader()); + $classMetadataFactory = new ClassMetadataFactory(new AttributeLoader()); $fallback = $this->createMock(NameConverterInterface::class); $fallback @@ -70,7 +70,7 @@ public function testNormalizeWithFallback(string|int $propertyName, string|int $ */ public function testDenormalize(string|int $expected, string|int $propertyName) { - $classMetadataFactory = new ClassMetadataFactory(new AnnotationLoader()); + $classMetadataFactory = new ClassMetadataFactory(new AttributeLoader()); $nameConverter = new MetadataAwareNameConverter($classMetadataFactory); @@ -82,7 +82,7 @@ public function testDenormalize(string|int $expected, string|int $propertyName) */ public function testDenormalizeWithFallback(string|int $expected, string|int $propertyName) { - $classMetadataFactory = new ClassMetadataFactory(new AnnotationLoader()); + $classMetadataFactory = new ClassMetadataFactory(new AttributeLoader()); $fallback = $this->createMock(NameConverterInterface::class); $fallback @@ -120,7 +120,7 @@ public static function fallbackAttributeProvider(): array */ public function testNormalizeWithGroups(string $propertyName, string $expected, array $context = []) { - $classMetadataFactory = new ClassMetadataFactory(new AnnotationLoader()); + $classMetadataFactory = new ClassMetadataFactory(new AttributeLoader()); $nameConverter = new MetadataAwareNameConverter($classMetadataFactory); @@ -132,7 +132,7 @@ public function testNormalizeWithGroups(string $propertyName, string $expected, */ public function testDenormalizeWithGroups(string $expected, string $propertyName, array $context = []) { - $classMetadataFactory = new ClassMetadataFactory(new AnnotationLoader()); + $classMetadataFactory = new ClassMetadataFactory(new AttributeLoader()); $nameConverter = new MetadataAwareNameConverter($classMetadataFactory); @@ -154,7 +154,7 @@ public static function attributeAndContextProvider(): array public function testDenormalizeWithCacheContext() { - $classMetadataFactory = new ClassMetadataFactory(new AnnotationLoader()); + $classMetadataFactory = new ClassMetadataFactory(new AttributeLoader()); $nameConverter = new MetadataAwareNameConverter($classMetadataFactory); @@ -165,7 +165,7 @@ public function testDenormalizeWithCacheContext() public function testDenormalizeWithNestedPathAndName() { - $classMetadataFactory = new ClassMetadataFactory(new AnnotationLoader()); + $classMetadataFactory = new ClassMetadataFactory(new AttributeLoader()); $nameConverter = new MetadataAwareNameConverter($classMetadataFactory); $this->expectException(LogicException::class); $this->expectExceptionMessage('Found SerializedName and SerializedPath annotations on property "foo" of class "Symfony\Component\Serializer\Tests\NameConverter\NestedPathAndName".'); @@ -174,7 +174,7 @@ public function testDenormalizeWithNestedPathAndName() public function testNormalizeWithNestedPathAndName() { - $classMetadataFactory = new ClassMetadataFactory(new AnnotationLoader()); + $classMetadataFactory = new ClassMetadataFactory(new AttributeLoader()); $nameConverter = new MetadataAwareNameConverter($classMetadataFactory); $this->expectException(LogicException::class); $this->expectExceptionMessage('Found SerializedName and SerializedPath annotations on property "foo" of class "Symfony\Component\Serializer\Tests\NameConverter\NestedPathAndName".'); diff --git a/Tests/Normalizer/AbstractObjectNormalizerTest.php b/Tests/Normalizer/AbstractObjectNormalizerTest.php index 00f2de877..4ecd0ed9d 100644 --- a/Tests/Normalizer/AbstractObjectNormalizerTest.php +++ b/Tests/Normalizer/AbstractObjectNormalizerTest.php @@ -31,7 +31,7 @@ use Symfony\Component\Serializer\Mapping\ClassMetadataInterface; use Symfony\Component\Serializer\Mapping\Factory\ClassMetadataFactory; use Symfony\Component\Serializer\Mapping\Factory\ClassMetadataFactoryInterface; -use Symfony\Component\Serializer\Mapping\Loader\AnnotationLoader; +use Symfony\Component\Serializer\Mapping\Loader\AttributeLoader; use Symfony\Component\Serializer\NameConverter\CamelCaseToSnakeCaseNameConverter; use Symfony\Component\Serializer\NameConverter\MetadataAwareNameConverter; use Symfony\Component\Serializer\Normalizer\AbstractNormalizer; @@ -77,7 +77,7 @@ public function testDenormalizeWithExtraAttribute() { $this->expectException(ExtraAttributesException::class); $this->expectExceptionMessage('Extra attributes are not allowed ("fooFoo" is unknown).'); - $factory = new ClassMetadataFactory(new AnnotationLoader()); + $factory = new ClassMetadataFactory(new AttributeLoader()); $normalizer = new AbstractObjectNormalizerDummy($factory); $normalizer->denormalize( ['fooFoo' => 'foo'], @@ -91,7 +91,7 @@ public function testDenormalizeWithExtraAttributes() { $this->expectException(ExtraAttributesException::class); $this->expectExceptionMessage('Extra attributes are not allowed ("fooFoo", "fooBar" are unknown).'); - $factory = new ClassMetadataFactory(new AnnotationLoader()); + $factory = new ClassMetadataFactory(new AttributeLoader()); $normalizer = new AbstractObjectNormalizerDummy($factory); $normalizer->denormalize( ['fooFoo' => 'foo', 'fooBar' => 'bar'], @@ -143,7 +143,7 @@ public function testDenormalizeWithNestedAttributesWithoutMetadata() public function testDenormalizeWithSnakeCaseNestedAttributes() { - $factory = new ClassMetadataFactory(new AnnotationLoader()); + $factory = new ClassMetadataFactory(new AttributeLoader()); $normalizer = new ObjectNormalizer($factory, new CamelCaseToSnakeCaseNameConverter()); $data = [ 'one' => [ @@ -156,7 +156,7 @@ public function testDenormalizeWithSnakeCaseNestedAttributes() public function testNormalizeWithSnakeCaseNestedAttributes() { - $factory = new ClassMetadataFactory(new AnnotationLoader()); + $factory = new ClassMetadataFactory(new AttributeLoader()); $normalizer = new ObjectNormalizer($factory, new CamelCaseToSnakeCaseNameConverter()); $dummy = new SnakeCaseNestedDummy(); $dummy->fooBar = 'fooBar'; @@ -252,7 +252,7 @@ public function testNormalizeWithNestedAttributesMixingArrayTypes() $foobar = new AlreadyPopulatedNestedDummy(); $foobar->foo = 'foo'; $foobar->bar = 'bar'; - $classMetadataFactory = new ClassMetadataFactory(new AnnotationLoader()); + $classMetadataFactory = new ClassMetadataFactory(new AttributeLoader()); $normalizer = new ObjectNormalizer($classMetadataFactory, new MetadataAwareNameConverter($classMetadataFactory)); $normalizer->normalize($foobar, 'any'); } @@ -264,7 +264,7 @@ public function testNormalizeWithNestedAttributesElementAlreadySet() $foobar = new DuplicateValueNestedDummy(); $foobar->foo = 'foo'; $foobar->bar = 'bar'; - $classMetadataFactory = new ClassMetadataFactory(new AnnotationLoader()); + $classMetadataFactory = new ClassMetadataFactory(new AttributeLoader()); $normalizer = new ObjectNormalizer($classMetadataFactory, new MetadataAwareNameConverter($classMetadataFactory)); $normalizer->normalize($foobar, 'any'); } @@ -286,7 +286,7 @@ public function testNormalizeWithNestedAttributes() 'foo' => 'notfoo', 'baz' => 'baz', ]; - $classMetadataFactory = new ClassMetadataFactory(new AnnotationLoader()); + $classMetadataFactory = new ClassMetadataFactory(new AttributeLoader()); $normalizer = new ObjectNormalizer($classMetadataFactory, new MetadataAwareNameConverter($classMetadataFactory)); $test = $normalizer->normalize($foobar, 'any'); $this->assertSame($data, $test); @@ -312,7 +312,7 @@ public function testNormalizeWithNestedAttributesWithoutMetadata() public function testNormalizeWithNestedAttributesInConstructor() { - $classMetadataFactory = new ClassMetadataFactory(new AnnotationLoader()); + $classMetadataFactory = new ClassMetadataFactory(new AttributeLoader()); $normalizer = new ObjectNormalizer($classMetadataFactory, new MetadataAwareNameConverter($classMetadataFactory)); $test = $normalizer->normalize(new NestedDummyWithConstructor('foo', 'quux', 'notfoo', 'baz'), 'any'); @@ -330,7 +330,7 @@ public function testNormalizeWithNestedAttributesInConstructor() public function testNormalizeWithNestedAttributesInConstructorAndDiscriminatorMap() { - $classMetadataFactory = new ClassMetadataFactory(new AnnotationLoader()); + $classMetadataFactory = new ClassMetadataFactory(new AttributeLoader()); $normalizer = new ObjectNormalizer($classMetadataFactory, new MetadataAwareNameConverter($classMetadataFactory)); $test1 = $normalizer->normalize(new FirstNestedDummyWithConstructorAndDiscriminator('foo', 'notfoo', 'baz'), 'any'); @@ -473,7 +473,7 @@ private function getDenormalizerForStringCollection() public function testDenormalizeWithDiscriminatorMapUsesCorrectClassname() { - $factory = new ClassMetadataFactory(new AnnotationLoader()); + $factory = new ClassMetadataFactory(new AttributeLoader()); $loaderMock = new class() implements ClassMetadataFactoryInterface { public function getMetadataFor($value): ClassMetadataInterface @@ -508,7 +508,7 @@ public function hasMetadataFor($value): bool public function testDenormalizeWithDiscriminatorMapAndObjectToPopulateUsesCorrectClassname() { - $factory = new ClassMetadataFactory(new AnnotationLoader()); + $factory = new ClassMetadataFactory(new AttributeLoader()); $loaderMock = new class() implements ClassMetadataFactoryInterface { public function getMetadataFor($value): ClassMetadataInterface @@ -707,7 +707,7 @@ public function testDenormalizeRecursiveWithObjectAttributeWithStringValue() public function testDenormalizeUsesContextAttributeForPropertiesInConstructorWithSeralizedName() { - $classMetadataFactory = new ClassMetadataFactory(new AnnotationLoader()); + $classMetadataFactory = new ClassMetadataFactory(new AttributeLoader()); $extractor = new PropertyInfoExtractor([], [new PhpDocExtractor(), new ReflectionExtractor()]); $normalizer = new ObjectNormalizer($classMetadataFactory, new MetadataAwareNameConverter($classMetadataFactory), null, $extractor); @@ -721,7 +721,7 @@ public function testDenormalizeUsesContextAttributeForPropertiesInConstructorWit public function testNormalizeUsesContextAttributeForPropertiesInConstructorWithSerializedPath() { - $classMetadataFactory = new ClassMetadataFactory(new AnnotationLoader()); + $classMetadataFactory = new ClassMetadataFactory(new AttributeLoader()); $extractor = new PropertyInfoExtractor([], [new PhpDocExtractor(), new ReflectionExtractor()]); $normalizer = new ObjectNormalizer($classMetadataFactory, new MetadataAwareNameConverter($classMetadataFactory), null, $extractor); @@ -736,7 +736,7 @@ public function testNormalizeUsesContextAttributeForPropertiesInConstructorWithS public function testNormalizeUsesContextAttributeForProperties() { - $classMetadataFactory = new ClassMetadataFactory(new AnnotationLoader()); + $classMetadataFactory = new ClassMetadataFactory(new AttributeLoader()); $extractor = new PropertyInfoExtractor([], [new PhpDocExtractor(), new ReflectionExtractor()]); $normalizer = new ObjectNormalizer($classMetadataFactory, new MetadataAwareNameConverter($classMetadataFactory), null, $extractor); @@ -775,7 +775,7 @@ public function testDenormalizeUnionOfEnums() $serializer = new Serializer([ new BackedEnumNormalizer(), new ObjectNormalizer( - classMetadataFactory: new ClassMetadataFactory(new AnnotationLoader()), + classMetadataFactory: new ClassMetadataFactory(new AttributeLoader()), propertyTypeExtractor: new PropertyInfoExtractor([], [new ReflectionExtractor()]), ), ]); @@ -1003,7 +1003,7 @@ class AbstractObjectNormalizerWithMetadata extends AbstractObjectNormalizer { public function __construct() { - $classMetadataFactory = new ClassMetadataFactory(new AnnotationLoader()); + $classMetadataFactory = new ClassMetadataFactory(new AttributeLoader()); parent::__construct($classMetadataFactory, new MetadataAwareNameConverter($classMetadataFactory)); } diff --git a/Tests/Normalizer/Features/ContextMetadataTestTrait.php b/Tests/Normalizer/Features/ContextMetadataTestTrait.php index 7375b530d..3c9b18e95 100644 --- a/Tests/Normalizer/Features/ContextMetadataTestTrait.php +++ b/Tests/Normalizer/Features/ContextMetadataTestTrait.php @@ -15,7 +15,7 @@ use Symfony\Component\Serializer\Annotation\Context; use Symfony\Component\Serializer\Annotation\Groups; use Symfony\Component\Serializer\Mapping\Factory\ClassMetadataFactory; -use Symfony\Component\Serializer\Mapping\Loader\AnnotationLoader; +use Symfony\Component\Serializer\Mapping\Loader\AttributeLoader; use Symfony\Component\Serializer\NameConverter\CamelCaseToSnakeCaseNameConverter; use Symfony\Component\Serializer\Normalizer\DateTimeNormalizer; use Symfony\Component\Serializer\Normalizer\ObjectNormalizer; @@ -33,7 +33,7 @@ trait ContextMetadataTestTrait */ public function testContextMetadataNormalize(string $contextMetadataDummyClass) { - $classMetadataFactory = new ClassMetadataFactory(new AnnotationLoader()); + $classMetadataFactory = new ClassMetadataFactory(new AttributeLoader()); $normalizer = new ObjectNormalizer($classMetadataFactory, null, null, new PhpDocExtractor()); new Serializer([new DateTimeNormalizer(), $normalizer]); @@ -56,7 +56,7 @@ public function testContextMetadataNormalize(string $contextMetadataDummyClass) */ public function testContextMetadataContextDenormalize(string $contextMetadataDummyClass) { - $classMetadataFactory = new ClassMetadataFactory(new AnnotationLoader()); + $classMetadataFactory = new ClassMetadataFactory(new AttributeLoader()); $normalizer = new ObjectNormalizer($classMetadataFactory, null, null, new PhpDocExtractor()); new Serializer([new DateTimeNormalizer(), $normalizer]); @@ -88,7 +88,7 @@ public function contextMetadataDummyProvider(): array public function testContextDenormalizeWithNameConverter() { - $classMetadataFactory = new ClassMetadataFactory(new AnnotationLoader()); + $classMetadataFactory = new ClassMetadataFactory(new AttributeLoader()); $normalizer = new ObjectNormalizer($classMetadataFactory, new CamelCaseToSnakeCaseNameConverter(), null, new PhpDocExtractor()); new Serializer([new DateTimeNormalizer(), $normalizer]); diff --git a/Tests/Normalizer/GetSetMethodNormalizerTest.php b/Tests/Normalizer/GetSetMethodNormalizerTest.php index 5261b95e1..df99b4a86 100644 --- a/Tests/Normalizer/GetSetMethodNormalizerTest.php +++ b/Tests/Normalizer/GetSetMethodNormalizerTest.php @@ -18,7 +18,7 @@ use Symfony\Component\PropertyInfo\PropertyInfoExtractor; use Symfony\Component\Serializer\Exception\LogicException; use Symfony\Component\Serializer\Mapping\Factory\ClassMetadataFactory; -use Symfony\Component\Serializer\Mapping\Loader\AnnotationLoader; +use Symfony\Component\Serializer\Mapping\Loader\AttributeLoader; use Symfony\Component\Serializer\NameConverter\CamelCaseToSnakeCaseNameConverter; use Symfony\Component\Serializer\NameConverter\MetadataAwareNameConverter; use Symfony\Component\Serializer\Normalizer\AbstractNormalizer; @@ -227,21 +227,21 @@ public function testConstructorWArgWithPrivateMutator() protected function getNormalizerForCallbacksWithPropertyTypeExtractor(): GetSetMethodNormalizer { - $classMetadataFactory = new ClassMetadataFactory(new AnnotationLoader()); + $classMetadataFactory = new ClassMetadataFactory(new AttributeLoader()); return new GetSetMethodNormalizer($classMetadataFactory, new MetadataAwareNameConverter($classMetadataFactory), $this->getCallbackPropertyTypeExtractor()); } protected function getNormalizerForCallbacks(): GetSetMethodNormalizer { - $classMetadataFactory = new ClassMetadataFactory(new AnnotationLoader()); + $classMetadataFactory = new ClassMetadataFactory(new AttributeLoader()); return new GetSetMethodNormalizer($classMetadataFactory, new MetadataAwareNameConverter($classMetadataFactory)); } protected function getNormalizerForCircularReference(array $defaultContext): GetSetMethodNormalizer { - $classMetadataFactory = new ClassMetadataFactory(new AnnotationLoader()); + $classMetadataFactory = new ClassMetadataFactory(new AttributeLoader()); $normalizer = new GetSetMethodNormalizer($classMetadataFactory, new MetadataAwareNameConverter($classMetadataFactory), null, null, null, $defaultContext); new Serializer([$normalizer]); @@ -255,7 +255,7 @@ protected function getSelfReferencingModel() protected function getDenormalizerForConstructArguments(): GetSetMethodNormalizer { - $classMetadataFactory = new ClassMetadataFactory(new AnnotationLoader()); + $classMetadataFactory = new ClassMetadataFactory(new AttributeLoader()); $denormalizer = new GetSetMethodNormalizer($classMetadataFactory, new MetadataAwareNameConverter($classMetadataFactory)); new Serializer([$denormalizer]); @@ -264,21 +264,21 @@ protected function getDenormalizerForConstructArguments(): GetSetMethodNormalize protected function getNormalizerForGroups(): GetSetMethodNormalizer { - $classMetadataFactory = new ClassMetadataFactory(new AnnotationLoader()); + $classMetadataFactory = new ClassMetadataFactory(new AttributeLoader()); return new GetSetMethodNormalizer($classMetadataFactory); } protected function getDenormalizerForGroups(): GetSetMethodNormalizer { - $classMetadataFactory = new ClassMetadataFactory(new AnnotationLoader()); + $classMetadataFactory = new ClassMetadataFactory(new AttributeLoader()); return new GetSetMethodNormalizer($classMetadataFactory); } public function testGroupsNormalizeWithNameConverter() { - $classMetadataFactory = new ClassMetadataFactory(new AnnotationLoader()); + $classMetadataFactory = new ClassMetadataFactory(new AttributeLoader()); $this->normalizer = new GetSetMethodNormalizer($classMetadataFactory, new CamelCaseToSnakeCaseNameConverter()); $this->normalizer->setSerializer($this->serializer); @@ -299,7 +299,7 @@ public function testGroupsNormalizeWithNameConverter() public function testGroupsDenormalizeWithNameConverter() { - $classMetadataFactory = new ClassMetadataFactory(new AnnotationLoader()); + $classMetadataFactory = new ClassMetadataFactory(new AttributeLoader()); $this->normalizer = new GetSetMethodNormalizer($classMetadataFactory, new CamelCaseToSnakeCaseNameConverter()); $this->normalizer->setSerializer($this->serializer); @@ -320,7 +320,7 @@ public function testGroupsDenormalizeWithNameConverter() protected function getNormalizerForMaxDepth(): NormalizerInterface { - $classMetadataFactory = new ClassMetadataFactory(new AnnotationLoader()); + $classMetadataFactory = new ClassMetadataFactory(new AttributeLoader()); $normalizer = new GetSetMethodNormalizer($classMetadataFactory); $serializer = new Serializer([$normalizer]); $normalizer->setSerializer($serializer); @@ -330,7 +330,7 @@ protected function getNormalizerForMaxDepth(): NormalizerInterface protected function getDenormalizerForObjectToPopulate(): DenormalizerInterface { - $classMetadataFactory = new ClassMetadataFactory(new AnnotationLoader()); + $classMetadataFactory = new ClassMetadataFactory(new AttributeLoader()); $normalizer = new GetSetMethodNormalizer($classMetadataFactory, null, new PhpDocExtractor()); new Serializer([$normalizer]); @@ -354,7 +354,7 @@ public function testRejectInvalidKey() protected function getNormalizerForIgnoredAttributes(): GetSetMethodNormalizer { - $classMetadataFactory = new ClassMetadataFactory(new AnnotationLoader()); + $classMetadataFactory = new ClassMetadataFactory(new AttributeLoader()); $normalizer = new GetSetMethodNormalizer($classMetadataFactory, null, new PhpDocExtractor()); new Serializer([$normalizer]); @@ -363,7 +363,7 @@ protected function getNormalizerForIgnoredAttributes(): GetSetMethodNormalizer protected function getDenormalizerForIgnoredAttributes(): GetSetMethodNormalizer { - $classMetadataFactory = new ClassMetadataFactory(new AnnotationLoader()); + $classMetadataFactory = new ClassMetadataFactory(new AttributeLoader()); $normalizer = new GetSetMethodNormalizer($classMetadataFactory, null, new PhpDocExtractor()); new Serializer([$normalizer]); @@ -487,7 +487,7 @@ protected function getNormalizerForCacheableObjectAttributesTest(): GetSetMethod protected function getNormalizerForSkipUninitializedValues(): NormalizerInterface { - return new GetSetMethodNormalizer(new ClassMetadataFactory(new AnnotationLoader())); + return new GetSetMethodNormalizer(new ClassMetadataFactory(new AttributeLoader())); } } diff --git a/Tests/Normalizer/MapDenormalizationTest.php b/Tests/Normalizer/MapDenormalizationTest.php index 8131e9707..ea4515955 100644 --- a/Tests/Normalizer/MapDenormalizationTest.php +++ b/Tests/Normalizer/MapDenormalizationTest.php @@ -21,7 +21,7 @@ use Symfony\Component\Serializer\Mapping\ClassMetadataInterface; use Symfony\Component\Serializer\Mapping\Factory\ClassMetadataFactory; use Symfony\Component\Serializer\Mapping\Factory\ClassMetadataFactoryInterface; -use Symfony\Component\Serializer\Mapping\Loader\AnnotationLoader; +use Symfony\Component\Serializer\Mapping\Loader\AttributeLoader; use Symfony\Component\Serializer\Normalizer\ArrayDenormalizer; use Symfony\Component\Serializer\Normalizer\ObjectNormalizer; use Symfony\Component\Serializer\Serializer; @@ -209,7 +209,7 @@ public function hasMetadataFor($value): bool } }; - $factory = new ClassMetadataFactory(new AnnotationLoader()); + $factory = new ClassMetadataFactory(new AttributeLoader()); $normalizer = new ObjectNormalizer($factory, null, null, new PhpDocExtractor(), new ClassDiscriminatorFromClassMetadata($loaderMock)); $serializer = new Serializer([$normalizer, new ArrayDenormalizer()]); $normalizer->setSerializer($serializer); diff --git a/Tests/Normalizer/ObjectNormalizerTest.php b/Tests/Normalizer/ObjectNormalizerTest.php index ffa029fa5..30bbecbc8 100644 --- a/Tests/Normalizer/ObjectNormalizerTest.php +++ b/Tests/Normalizer/ObjectNormalizerTest.php @@ -23,7 +23,7 @@ use Symfony\Component\Serializer\Exception\UnexpectedValueException; use Symfony\Component\Serializer\Mapping\Factory\ClassMetadataFactory; use Symfony\Component\Serializer\Mapping\Factory\ClassMetadataFactoryInterface; -use Symfony\Component\Serializer\Mapping\Loader\AnnotationLoader; +use Symfony\Component\Serializer\Mapping\Loader\AttributeLoader; use Symfony\Component\Serializer\NameConverter\AdvancedNameConverterInterface; use Symfony\Component\Serializer\NameConverter\CamelCaseToSnakeCaseNameConverter; use Symfony\Component\Serializer\NameConverter\MetadataAwareNameConverter; @@ -336,7 +336,7 @@ protected function getNormalizerForAttributes(): ObjectNormalizer protected function getDenormalizerForAttributes(): ObjectNormalizer { - $classMetadataFactory = new ClassMetadataFactory(new AnnotationLoader()); + $classMetadataFactory = new ClassMetadataFactory(new AttributeLoader()); $normalizer = new ObjectNormalizer($classMetadataFactory, null, null, new ReflectionExtractor()); new Serializer([$normalizer]); @@ -362,7 +362,7 @@ public function testAttributesContextDenormalizeConstructor() public function testNormalizeSameObjectWithDifferentAttributes() { - $classMetadataFactory = new ClassMetadataFactory(new AnnotationLoader()); + $classMetadataFactory = new ClassMetadataFactory(new AttributeLoader()); $this->normalizer = new ObjectNormalizer($classMetadataFactory); $serializer = new Serializer([$this->normalizer]); $this->normalizer->setSerializer($serializer); @@ -437,7 +437,7 @@ public function testSiblingReference() protected function getDenormalizerForConstructArguments(): ObjectNormalizer { - $classMetadataFactory = new ClassMetadataFactory(new AnnotationLoader()); + $classMetadataFactory = new ClassMetadataFactory(new AttributeLoader()); $denormalizer = new ObjectNormalizer($classMetadataFactory, new MetadataAwareNameConverter($classMetadataFactory)); $serializer = new Serializer([$denormalizer]); $denormalizer->setSerializer($serializer); @@ -449,7 +449,7 @@ protected function getDenormalizerForConstructArguments(): ObjectNormalizer protected function getNormalizerForGroups(): ObjectNormalizer { - $classMetadataFactory = new ClassMetadataFactory(new AnnotationLoader()); + $classMetadataFactory = new ClassMetadataFactory(new AttributeLoader()); $normalizer = new ObjectNormalizer($classMetadataFactory); // instantiate a serializer with the normalizer to handle normalizing recursive structures new Serializer([$normalizer]); @@ -459,14 +459,14 @@ protected function getNormalizerForGroups(): ObjectNormalizer protected function getDenormalizerForGroups(): ObjectNormalizer { - $classMetadataFactory = new ClassMetadataFactory(new AnnotationLoader()); + $classMetadataFactory = new ClassMetadataFactory(new AttributeLoader()); return new ObjectNormalizer($classMetadataFactory); } public function testGroupsNormalizeWithNameConverter() { - $classMetadataFactory = new ClassMetadataFactory(new AnnotationLoader()); + $classMetadataFactory = new ClassMetadataFactory(new AttributeLoader()); $this->normalizer = new ObjectNormalizer($classMetadataFactory, new CamelCaseToSnakeCaseNameConverter()); $this->normalizer->setSerializer($this->serializer); @@ -487,7 +487,7 @@ public function testGroupsNormalizeWithNameConverter() public function testGroupsDenormalizeWithNameConverter() { - $classMetadataFactory = new ClassMetadataFactory(new AnnotationLoader()); + $classMetadataFactory = new ClassMetadataFactory(new AttributeLoader()); $this->normalizer = new ObjectNormalizer($classMetadataFactory, new CamelCaseToSnakeCaseNameConverter()); $this->normalizer->setSerializer($this->serializer); @@ -508,7 +508,7 @@ public function testGroupsDenormalizeWithNameConverter() public function testGroupsDenormalizeWithMetaDataNameConverter() { - $classMetadataFactory = new ClassMetadataFactory(new AnnotationLoader()); + $classMetadataFactory = new ClassMetadataFactory(new AttributeLoader()); $this->normalizer = new ObjectNormalizer($classMetadataFactory, new MetadataAwareNameConverter($classMetadataFactory)); $this->normalizer->setSerializer($this->serializer); @@ -536,7 +536,7 @@ protected function getNormalizerForIgnoredAttributes(): ObjectNormalizer protected function getDenormalizerForIgnoredAttributes(): ObjectNormalizer { - $classMetadataFactory = new ClassMetadataFactory(new AnnotationLoader()); + $classMetadataFactory = new ClassMetadataFactory(new AttributeLoader()); $normalizer = new ObjectNormalizer($classMetadataFactory, null, null, new ReflectionExtractor()); new Serializer([$normalizer]); @@ -547,7 +547,7 @@ protected function getDenormalizerForIgnoredAttributes(): ObjectNormalizer protected function getNormalizerForMaxDepth(): ObjectNormalizer { - $classMetadataFactory = new ClassMetadataFactory(new AnnotationLoader()); + $classMetadataFactory = new ClassMetadataFactory(new AttributeLoader()); $normalizer = new ObjectNormalizer($classMetadataFactory); $serializer = new Serializer([$normalizer]); $normalizer->setSerializer($serializer); @@ -559,7 +559,7 @@ protected function getNormalizerForMaxDepth(): ObjectNormalizer protected function getDenormalizerForObjectToPopulate(): ObjectNormalizer { - $classMetadataFactory = new ClassMetadataFactory(new AnnotationLoader()); + $classMetadataFactory = new ClassMetadataFactory(new AttributeLoader()); $normalizer = new ObjectNormalizer($classMetadataFactory, null, null, new PhpDocExtractor()); new Serializer([$normalizer]); @@ -577,7 +577,7 @@ protected function getNormalizerForSkipNullValues(): ObjectNormalizer protected function getNormalizerForSkipUninitializedValues(): ObjectNormalizer { - $classMetadataFactory = new ClassMetadataFactory(new AnnotationLoader()); + $classMetadataFactory = new ClassMetadataFactory(new AttributeLoader()); return new ObjectNormalizer($classMetadataFactory); } diff --git a/Tests/Normalizer/PropertyNormalizerTest.php b/Tests/Normalizer/PropertyNormalizerTest.php index b424e4156..178087be4 100644 --- a/Tests/Normalizer/PropertyNormalizerTest.php +++ b/Tests/Normalizer/PropertyNormalizerTest.php @@ -17,7 +17,7 @@ use Symfony\Component\PropertyInfo\PropertyInfoExtractor; use Symfony\Component\Serializer\Exception\LogicException; use Symfony\Component\Serializer\Mapping\Factory\ClassMetadataFactory; -use Symfony\Component\Serializer\Mapping\Loader\AnnotationLoader; +use Symfony\Component\Serializer\Mapping\Loader\AttributeLoader; use Symfony\Component\Serializer\NameConverter\CamelCaseToSnakeCaseNameConverter; use Symfony\Component\Serializer\NameConverter\MetadataAwareNameConverter; use Symfony\Component\Serializer\Normalizer\AbstractObjectNormalizer; @@ -264,7 +264,7 @@ public function testSiblingReference() protected function getDenormalizerForConstructArguments(): PropertyNormalizer { - $classMetadataFactory = new ClassMetadataFactory(new AnnotationLoader()); + $classMetadataFactory = new ClassMetadataFactory(new AttributeLoader()); $denormalizer = new PropertyNormalizer($classMetadataFactory, new MetadataAwareNameConverter($classMetadataFactory)); $serializer = new Serializer([$denormalizer]); $denormalizer->setSerializer($serializer); @@ -274,21 +274,21 @@ protected function getDenormalizerForConstructArguments(): PropertyNormalizer protected function getNormalizerForGroups(): PropertyNormalizer { - $classMetadataFactory = new ClassMetadataFactory(new AnnotationLoader()); + $classMetadataFactory = new ClassMetadataFactory(new AttributeLoader()); return new PropertyNormalizer($classMetadataFactory); } protected function getDenormalizerForGroups(): PropertyNormalizer { - $classMetadataFactory = new ClassMetadataFactory(new AnnotationLoader()); + $classMetadataFactory = new ClassMetadataFactory(new AttributeLoader()); return new PropertyNormalizer($classMetadataFactory); } public function testGroupsNormalizeWithNameConverter() { - $classMetadataFactory = new ClassMetadataFactory(new AnnotationLoader()); + $classMetadataFactory = new ClassMetadataFactory(new AttributeLoader()); $this->normalizer = new PropertyNormalizer($classMetadataFactory, new CamelCaseToSnakeCaseNameConverter()); $this->normalizer->setSerializer($this->serializer); @@ -309,7 +309,7 @@ public function testGroupsNormalizeWithNameConverter() public function testGroupsDenormalizeWithNameConverter() { - $classMetadataFactory = new ClassMetadataFactory(new AnnotationLoader()); + $classMetadataFactory = new ClassMetadataFactory(new AttributeLoader()); $this->normalizer = new PropertyNormalizer($classMetadataFactory, new CamelCaseToSnakeCaseNameConverter()); $this->normalizer->setSerializer($this->serializer); @@ -353,7 +353,7 @@ public function testIgnoredAttributesContextDenormalizeInherit() protected function getNormalizerForMaxDepth(): PropertyNormalizer { - $classMetadataFactory = new ClassMetadataFactory(new AnnotationLoader()); + $classMetadataFactory = new ClassMetadataFactory(new AttributeLoader()); $normalizer = new PropertyNormalizer($classMetadataFactory); $serializer = new Serializer([$normalizer]); $normalizer->setSerializer($serializer); @@ -363,7 +363,7 @@ protected function getNormalizerForMaxDepth(): PropertyNormalizer protected function getDenormalizerForObjectToPopulate(): PropertyNormalizer { - $classMetadataFactory = new ClassMetadataFactory(new AnnotationLoader()); + $classMetadataFactory = new ClassMetadataFactory(new AttributeLoader()); $normalizer = new PropertyNormalizer($classMetadataFactory, null, new PhpDocExtractor()); new Serializer([$normalizer]); @@ -492,7 +492,7 @@ protected function getNormalizerForCacheableObjectAttributesTest(): AbstractObje protected function getNormalizerForSkipUninitializedValues(): NormalizerInterface { - return new PropertyNormalizer(new ClassMetadataFactory(new AnnotationLoader())); + return new PropertyNormalizer(new ClassMetadataFactory(new AttributeLoader())); } } diff --git a/Tests/SerializerTest.php b/Tests/SerializerTest.php index 4bb6fded7..b95890b9f 100644 --- a/Tests/SerializerTest.php +++ b/Tests/SerializerTest.php @@ -29,7 +29,7 @@ use Symfony\Component\Serializer\Mapping\ClassMetadataInterface; use Symfony\Component\Serializer\Mapping\Factory\ClassMetadataFactory; use Symfony\Component\Serializer\Mapping\Factory\ClassMetadataFactoryInterface; -use Symfony\Component\Serializer\Mapping\Loader\AnnotationLoader; +use Symfony\Component\Serializer\Mapping\Loader\AttributeLoader; use Symfony\Component\Serializer\Normalizer\AbstractNormalizer; use Symfony\Component\Serializer\Normalizer\AbstractObjectNormalizer; use Symfony\Component\Serializer\Normalizer\ArrayDenormalizer; @@ -751,7 +751,7 @@ public function testDeserializeWrappedScalar() public function testUnionTypeDeserializable() { - $classMetadataFactory = new ClassMetadataFactory(new AnnotationLoader()); + $classMetadataFactory = new ClassMetadataFactory(new AttributeLoader()); $extractor = new PropertyInfoExtractor([], [new PhpDocExtractor(), new ReflectionExtractor()]); $serializer = new Serializer( [ @@ -783,7 +783,7 @@ public function testUnionTypeDeserializable() public function testUnionTypeDeserializableWithoutAllowedExtraAttributes() { - $classMetadataFactory = new ClassMetadataFactory(new AnnotationLoader()); + $classMetadataFactory = new ClassMetadataFactory(new AttributeLoader()); $extractor = new PropertyInfoExtractor([], [new PhpDocExtractor(), new ReflectionExtractor()]); $serializer = new Serializer( [ @@ -844,7 +844,7 @@ public function testTrueBuiltInTypes() private function serializerWithClassDiscriminator() { - $classMetadataFactory = new ClassMetadataFactory(new AnnotationLoader()); + $classMetadataFactory = new ClassMetadataFactory(new AttributeLoader()); return new Serializer([new ObjectNormalizer($classMetadataFactory, null, null, new ReflectionExtractor(), new ClassDiscriminatorFromClassMetadata($classMetadataFactory))], ['json' => new JsonEncoder()]); } @@ -1330,7 +1330,7 @@ public function testCollectDenormalizationErrorsWithEnumConstructor() public function testCollectDenormalizationErrorsWithWrongPropertyWithoutConstruct() { - $classMetadataFactory = new ClassMetadataFactory(new AnnotationLoader()); + $classMetadataFactory = new ClassMetadataFactory(new AttributeLoader()); $reflectionExtractor = new ReflectionExtractor(); $propertyInfoExtractor = new PropertyInfoExtractor([], [$reflectionExtractor], [], [], []); @@ -1415,7 +1415,7 @@ public static function provideCollectDenormalizationErrors(): array { return [ [null], - [new ClassMetadataFactory(new AnnotationLoader())], + [new ClassMetadataFactory(new AttributeLoader())], ]; } diff --git a/composer.json b/composer.json index 407fe6a34..356436c29 100644 --- a/composer.json +++ b/composer.json @@ -39,7 +39,7 @@ "symfony/property-info": "^5.4.24|^6.2.11|^7.0", "symfony/translation-contracts": "^2.5|^3", "symfony/uid": "^5.4|^6.0|^7.0", - "symfony/validator": "^5.4|^6.0|^7.0", + "symfony/validator": "^6.4|^7.0", "symfony/var-dumper": "^5.4|^6.0|^7.0", "symfony/var-exporter": "^5.4|^6.0|^7.0", "symfony/yaml": "^5.4|^6.0|^7.0" @@ -51,6 +51,7 @@ "symfony/dependency-injection": "<5.4", "symfony/property-access": "<5.4", "symfony/property-info": "<5.4.24|>=6,<6.2.11", + "symfony/validator": "<6.4", "symfony/uid": "<5.4", "symfony/yaml": "<5.4" }, From 4054a0f5a663107ee282fb24d3acdf41c9e98217 Mon Sep 17 00:00:00 2001 From: Alexandre Daubois Date: Tue, 17 Oct 2023 16:30:56 +0200 Subject: [PATCH 157/297] [Serializer] Fix Serializer deprecations merge --- CHANGELOG.md | 2 +- Mapping/Loader/AttributeLoader.php | 4 - Tests/Mapping/Loader/AnnotationLoaderTest.php | 257 ------------------ ...erTestCase.php => AttributeLoaderTest.php} | 15 +- .../AttributeLoaderWithAttributesTest.php | 2 +- 5 files changed, 6 insertions(+), 274 deletions(-) delete mode 100644 Tests/Mapping/Loader/AnnotationLoaderTest.php rename Tests/Mapping/Loader/{AttributeLoaderTestCase.php => AttributeLoaderTest.php} (93%) diff --git a/CHANGELOG.md b/CHANGELOG.md index 701d20485..38842b0b3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,7 +13,7 @@ CHANGELOG * Require explicit argument when calling `AttributeMetadata::setSerializedName()` and `ClassMetadata::setClassDiscriminatorMapping()` * Add argument `$context` to `NormalizerInterface::supportsNormalization()` and `DenormalizerInterface::supportsDenormalization()` * Remove Doctrine annotations support in favor of native attributes - * Remove the annotation reader parameter from the constructor of `AnnotationLoader` + * Remove `AnnotationLoader`, use `AttributeLoader` instead 6.4 --- diff --git a/Mapping/Loader/AttributeLoader.php b/Mapping/Loader/AttributeLoader.php index 9b379bdee..14bfa7cc5 100644 --- a/Mapping/Loader/AttributeLoader.php +++ b/Mapping/Loader/AttributeLoader.php @@ -307,7 +307,3 @@ private function getPropertyAnnotations(\ReflectionProperty $reflector): array return $annotations; } } - -if (!class_exists(AnnotationLoader::class, false)) { - class_alias(AttributeLoader::class, AnnotationLoader::class); -} diff --git a/Tests/Mapping/Loader/AnnotationLoaderTest.php b/Tests/Mapping/Loader/AnnotationLoaderTest.php deleted file mode 100644 index c91a3eb18..000000000 --- a/Tests/Mapping/Loader/AnnotationLoaderTest.php +++ /dev/null @@ -1,257 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Component\Serializer\Tests\Mapping\Loader; - -use PHPUnit\Framework\TestCase; -use Symfony\Bridge\PhpUnit\ExpectDeprecationTrait; -use Symfony\Component\PropertyAccess\PropertyPath; -use Symfony\Component\Serializer\Exception\MappingException; -use Symfony\Component\Serializer\Mapping\AttributeMetadata; -use Symfony\Component\Serializer\Mapping\ClassDiscriminatorMapping; -use Symfony\Component\Serializer\Mapping\ClassMetadata; -use Symfony\Component\Serializer\Mapping\Loader\AnnotationLoader; -use Symfony\Component\Serializer\Mapping\Loader\AttributeLoader; -use Symfony\Component\Serializer\Mapping\Loader\LoaderInterface; -use Symfony\Component\Serializer\Tests\Fixtures\Attributes\AbstractDummy; -use Symfony\Component\Serializer\Tests\Fixtures\Attributes\AbstractDummyFirstChild; -use Symfony\Component\Serializer\Tests\Fixtures\Attributes\AbstractDummySecondChild; -use Symfony\Component\Serializer\Tests\Fixtures\Attributes\AbstractDummyThirdChild; -use Symfony\Component\Serializer\Tests\Fixtures\Attributes\BadAttributeDummy; -use Symfony\Component\Serializer\Tests\Fixtures\Attributes\BadMethodContextDummy; -use Symfony\Component\Serializer\Tests\Fixtures\Attributes\ContextDummyParent; -use Symfony\Component\Serializer\Tests\Fixtures\Attributes\ContextDummyPromotedProperties; -use Symfony\Component\Serializer\Tests\Fixtures\Attributes\Entity45016; -use Symfony\Component\Serializer\Tests\Fixtures\Attributes\GroupClassDummy; -use Symfony\Component\Serializer\Tests\Fixtures\Attributes\GroupDummy; -use Symfony\Component\Serializer\Tests\Fixtures\Attributes\GroupDummyParent; -use Symfony\Component\Serializer\Tests\Fixtures\Attributes\IgnoreDummy; -use Symfony\Component\Serializer\Tests\Fixtures\Attributes\IgnoreDummyAdditionalGetter; -use Symfony\Component\Serializer\Tests\Fixtures\Attributes\IgnoreDummyAdditionalGetterWithoutIgnoreAnnotations; -use Symfony\Component\Serializer\Tests\Fixtures\Attributes\MaxDepthDummy; -use Symfony\Component\Serializer\Tests\Fixtures\Attributes\SerializedNameDummy; -use Symfony\Component\Serializer\Tests\Fixtures\Attributes\SerializedPathDummy; -use Symfony\Component\Serializer\Tests\Fixtures\Attributes\SerializedPathInConstructorDummy; -use Symfony\Component\Serializer\Tests\Mapping\Loader\Features\ContextMappingTestTrait; -use Symfony\Component\Serializer\Tests\Mapping\TestClassMetadataFactory; - -/** - * @author Kévin Dunglas - */ -<<<<<<<< HEAD:src/Symfony/Component/Serializer/Tests/Mapping/Loader/AnnotationLoaderTest.php -class AnnotationLoaderTest extends TestCase -======== -abstract class AttributeLoaderTestCase extends TestCase ->>>>>>>> 6.4:src/Symfony/Component/Serializer/Tests/Mapping/Loader/AttributeLoaderTestCase.php -{ - use ContextMappingTestTrait; - use ExpectDeprecationTrait; - - private AnnotationLoader $loader; - - protected function setUp(): void - { - $this->loader = new AnnotationLoader(); - } - - public function testInterface() - { - $this->assertInstanceOf(LoaderInterface::class, $this->loader); - } - - public function testLoadClassMetadataReturnsTrueIfSuccessful() - { - $classMetadata = new ClassMetadata(GroupDummy::class); - - $this->assertTrue($this->loader->loadClassMetadata($classMetadata)); - } - - public function testLoadGroups() - { - $classMetadata = new ClassMetadata(GroupDummy::class); - $this->loader->loadClassMetadata($classMetadata); - - $this->assertEquals(TestClassMetadataFactory::createClassMetadata('Symfony\Component\Serializer\Tests\Fixtures\Attributes'), $classMetadata); - } - - public function testLoadDiscriminatorMap() - { - $classMetadata = new ClassMetadata(AbstractDummy::class); - $this->loader->loadClassMetadata($classMetadata); - - $expected = new ClassMetadata(AbstractDummy::class, new ClassDiscriminatorMapping('type', [ - 'first' => AbstractDummyFirstChild::class, - 'second' => AbstractDummySecondChild::class, - 'third' => AbstractDummyThirdChild::class, - ])); - - $expected->addAttributeMetadata(new AttributeMetadata('foo')); - $expected->getReflectionClass(); - - $this->assertEquals($expected, $classMetadata); - } - - public function testLoadMaxDepth() - { - $classMetadata = new ClassMetadata(MaxDepthDummy::class); - $this->loader->loadClassMetadata($classMetadata); - - $attributesMetadata = $classMetadata->getAttributesMetadata(); - $this->assertEquals(2, $attributesMetadata['foo']->getMaxDepth()); - $this->assertEquals(3, $attributesMetadata['bar']->getMaxDepth()); - } - - public function testLoadSerializedName() - { - $classMetadata = new ClassMetadata(SerializedNameDummy::class); - $this->loader->loadClassMetadata($classMetadata); - - $attributesMetadata = $classMetadata->getAttributesMetadata(); - $this->assertEquals('baz', $attributesMetadata['foo']->getSerializedName()); - $this->assertEquals('qux', $attributesMetadata['bar']->getSerializedName()); - } - - public function testLoadSerializedPath() - { - $classMetadata = new ClassMetadata(SerializedPathDummy::class); - $this->loader->loadClassMetadata($classMetadata); - - $attributesMetadata = $classMetadata->getAttributesMetadata(); - $this->assertEquals(new PropertyPath('[one][two]'), $attributesMetadata['three']->getSerializedPath()); - $this->assertEquals(new PropertyPath('[three][four]'), $attributesMetadata['seven']->getSerializedPath()); - } - - public function testLoadSerializedPathInConstructor() - { - $classMetadata = new ClassMetadata(SerializedPathInConstructorDummy::class); - $this->loader->loadClassMetadata($classMetadata); - - $attributesMetadata = $classMetadata->getAttributesMetadata(); - $this->assertEquals(new PropertyPath('[one][two]'), $attributesMetadata['three']->getSerializedPath()); - } - - public function testLoadClassMetadataAndMerge() - { - $classMetadata = new ClassMetadata(GroupDummy::class); - $parentClassMetadata = new ClassMetadata(GroupDummyParent::class); - - $this->loader->loadClassMetadata($parentClassMetadata); - $classMetadata->merge($parentClassMetadata); - - $this->loader->loadClassMetadata($classMetadata); - - $this->assertEquals(TestClassMetadataFactory::createClassMetadata('Symfony\Component\Serializer\Tests\Fixtures\Attributes', true), $classMetadata); - } - - public function testLoadIgnore() - { - $classMetadata = new ClassMetadata(IgnoreDummy::class); - $this->loader->loadClassMetadata($classMetadata); - - $attributesMetadata = $classMetadata->getAttributesMetadata(); - $this->assertTrue($attributesMetadata['ignored1']->isIgnored()); - $this->assertTrue($attributesMetadata['ignored2']->isIgnored()); - } - - public function testLoadContextsPropertiesPromoted() - { - $this->assertLoadedContexts(ContextDummyPromotedProperties::class, ContextDummyParent::class); - } - - public function testThrowsOnContextOnInvalidMethod() - { - $this->expectException(MappingException::class); - $this->expectExceptionMessage(sprintf('Context on "%s::badMethod()" cannot be added', BadMethodContextDummy::class)); - - $loader = $this->getLoaderForContextMapping(); - - $classMetadata = new ClassMetadata(BadMethodContextDummy::class); - - $loader->loadClassMetadata($classMetadata); - } - - public function testCanHandleUnrelatedIgnoredMethods() - { - $this->expectException(MappingException::class); - $this->expectExceptionMessage(sprintf('Ignore on "%s::badIgnore()" cannot be added', Entity45016::class)); - - $metadata = new ClassMetadata(Entity45016::class); - $loader = $this->getLoaderForContextMapping(); - - $loader->loadClassMetadata($metadata); - } - - public function testIgnoreGetterWithRequiredParameterIfIgnoreAnnotationIsUsed() - { - $classMetadata = new ClassMetadata(IgnoreDummyAdditionalGetter::class); - $this->getLoaderForContextMapping()->loadClassMetadata($classMetadata); - - $attributes = $classMetadata->getAttributesMetadata(); - self::assertArrayNotHasKey('extraValue', $attributes); - self::assertArrayHasKey('extraValue2', $attributes); - } - - public function testIgnoreGetterWithRequiredParameterIfIgnoreAnnotationIsNotUsed() - { - $classMetadata = new ClassMetadata(IgnoreDummyAdditionalGetterWithoutIgnoreAnnotations::class); - $this->getLoaderForContextMapping()->loadClassMetadata($classMetadata); - - $attributes = $classMetadata->getAttributesMetadata(); - self::assertArrayNotHasKey('extraValue', $attributes); - self::assertArrayHasKey('extraValue2', $attributes); - } - - public function testLoadGroupsOnClass() - { - $classMetadata = new ClassMetadata(GroupClassDummy::class); - $this->loader->loadClassMetadata($classMetadata); - - $attributesMetadata = $classMetadata->getAttributesMetadata(); - - self::assertCount(3, $classMetadata->getAttributesMetadata()); - - self::assertArrayHasKey('foo', $attributesMetadata); - self::assertArrayHasKey('bar', $attributesMetadata); - self::assertArrayHasKey('baz', $attributesMetadata); - - self::assertSame(['a', 'b'], $attributesMetadata['foo']->getGroups()); - self::assertSame(['a', 'c', 'd'], $attributesMetadata['bar']->getGroups()); - self::assertSame(['a'], $attributesMetadata['baz']->getGroups()); - } - -<<<<<<<< HEAD:src/Symfony/Component/Serializer/Tests/Mapping/Loader/AnnotationLoaderTest.php - public function testLoadWithInvalidAttribute() - { - $this->expectException(MappingException::class); - $this->expectExceptionMessage('Could not instantiate attribute "Symfony\Component\Serializer\Annotation\Groups" on "Symfony\Component\Serializer\Tests\Fixtures\Attributes\BadAttributeDummy::myMethod()".'); -======== - /** - * @group legacy - */ - public function testExpectedDeprecationOnLoadAnnotationsCall() - { - $this->expectDeprecation('Since symfony/serializer 6.4: Method "Symfony\Component\Serializer\Mapping\Loader\AttributeLoader::loadAnnotations()" is deprecated without replacement.'); - $this->loader->loadAnnotations(new \ReflectionClass(\stdClass::class)); - } - - abstract protected function createLoader(): AttributeLoader; ->>>>>>>> 6.4:src/Symfony/Component/Serializer/Tests/Mapping/Loader/AttributeLoaderTestCase.php - - $classMetadata = new ClassMetadata(BadAttributeDummy::class); - - $this->loader->loadClassMetadata($classMetadata); - } - - protected function getLoaderForContextMapping(): AnnotationLoader - { - return $this->loader; - } -} diff --git a/Tests/Mapping/Loader/AttributeLoaderTestCase.php b/Tests/Mapping/Loader/AttributeLoaderTest.php similarity index 93% rename from Tests/Mapping/Loader/AttributeLoaderTestCase.php rename to Tests/Mapping/Loader/AttributeLoaderTest.php index 156c5585d..fc77abc94 100644 --- a/Tests/Mapping/Loader/AttributeLoaderTestCase.php +++ b/Tests/Mapping/Loader/AttributeLoaderTest.php @@ -18,14 +18,12 @@ use Symfony\Component\Serializer\Mapping\AttributeMetadata; use Symfony\Component\Serializer\Mapping\ClassDiscriminatorMapping; use Symfony\Component\Serializer\Mapping\ClassMetadata; -use Symfony\Component\Serializer\Mapping\Loader\AnnotationLoader; use Symfony\Component\Serializer\Mapping\Loader\AttributeLoader; use Symfony\Component\Serializer\Mapping\Loader\LoaderInterface; use Symfony\Component\Serializer\Tests\Fixtures\Attributes\AbstractDummy; use Symfony\Component\Serializer\Tests\Fixtures\Attributes\AbstractDummyFirstChild; use Symfony\Component\Serializer\Tests\Fixtures\Attributes\AbstractDummySecondChild; use Symfony\Component\Serializer\Tests\Fixtures\Attributes\AbstractDummyThirdChild; -use Symfony\Component\Serializer\Tests\Fixtures\Attributes\BadAttributeDummy; use Symfony\Component\Serializer\Tests\Fixtures\Attributes\BadMethodContextDummy; use Symfony\Component\Serializer\Tests\Fixtures\Attributes\ContextDummyParent; use Symfony\Component\Serializer\Tests\Fixtures\Attributes\ContextDummyPromotedProperties; @@ -46,19 +44,16 @@ /** * @author Kévin Dunglas */ -<<<<<<<< HEAD:src/Symfony/Component/Serializer/Tests/Mapping/Loader/AnnotationLoaderTest.php -======== -abstract class AttributeLoaderTestCase extends TestCase ->>>>>>>> 6.4:src/Symfony/Component/Serializer/Tests/Mapping/Loader/AttributeLoaderTestCase.php +class AttributeLoaderTest extends TestCase { use ContextMappingTestTrait; use ExpectDeprecationTrait; - private AnnotationLoader $loader; + protected AttributeLoader $loader; protected function setUp(): void { - $this->loader = new AnnotationLoader(); + $this->loader = new AttributeLoader(); } public function testInterface() @@ -226,9 +221,7 @@ public function testLoadGroupsOnClass() self::assertSame(['a'], $attributesMetadata['baz']->getGroups()); } - abstract protected function createLoader(): AttributeLoader; - - protected function getLoaderForContextMapping(): AnnotationLoader + protected function getLoaderForContextMapping(): AttributeLoader { return $this->loader; } diff --git a/Tests/Mapping/Loader/AttributeLoaderWithAttributesTest.php b/Tests/Mapping/Loader/AttributeLoaderWithAttributesTest.php index c696b8c24..92e48bfbd 100644 --- a/Tests/Mapping/Loader/AttributeLoaderWithAttributesTest.php +++ b/Tests/Mapping/Loader/AttributeLoaderWithAttributesTest.php @@ -15,7 +15,7 @@ use Symfony\Component\Serializer\Mapping\ClassMetadata; use Symfony\Component\Serializer\Mapping\Loader\AttributeLoader; -class AttributeLoaderWithAttributesTest extends AttributeLoaderTestCase +class AttributeLoaderWithAttributesTest extends AttributeLoaderTest { protected function createLoader(): AttributeLoader { From 9c80b221764f8da39e177d36973ce1ce2e2512f3 Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Thu, 19 Oct 2023 11:39:15 +0200 Subject: [PATCH 158/297] [Serializer] Replace "annotation" wording by "attribute" --- Annotation/Context.php | 4 ++-- Annotation/DiscriminatorMap.php | 4 ++-- Annotation/Groups.php | 4 ++-- Annotation/MaxDepth.php | 2 +- Annotation/SerializedName.php | 2 +- Annotation/SerializedPath.php | 2 +- NameConverter/MetadataAwareNameConverter.php | 4 ++-- Normalizer/AbstractObjectNormalizer.php | 4 ++-- Tests/Annotation/ContextTest.php | 4 ++-- Tests/Annotation/MaxDepthTest.php | 2 +- Tests/Annotation/SerializedNameTest.php | 2 +- Tests/Annotation/SerializedPathTest.php | 2 +- Tests/NameConverter/MetadataAwareNameConverterTest.php | 4 ++-- Tests/Normalizer/AbstractObjectNormalizerTest.php | 2 +- 14 files changed, 21 insertions(+), 21 deletions(-) diff --git a/Annotation/Context.php b/Annotation/Context.php index a83c8f079..b417d6505 100644 --- a/Annotation/Context.php +++ b/Annotation/Context.php @@ -39,14 +39,14 @@ public function __construct( string|array $groups = [], ) { if (!$context && !$normalizationContext && !$denormalizationContext) { - throw new InvalidArgumentException(sprintf('At least one of the "context", "normalizationContext", or "denormalizationContext" options of annotation "%s" must be provided as a non-empty array.', static::class)); + throw new InvalidArgumentException(sprintf('At least one of the "context", "normalizationContext", or "denormalizationContext" options must be provided as a non-empty array to "%s".', static::class)); } $this->groups = (array) $groups; foreach ($this->groups as $group) { if (!\is_string($group)) { - throw new InvalidArgumentException(sprintf('Parameter "groups" of annotation "%s" must be a string or an array of strings. Got "%s".', static::class, get_debug_type($group))); + throw new InvalidArgumentException(sprintf('Parameter "groups" given to "%s" must be a string or an array of strings, "%s" given.', static::class, get_debug_type($group))); } } } diff --git a/Annotation/DiscriminatorMap.php b/Annotation/DiscriminatorMap.php index 8e9ebb830..ad335ec41 100644 --- a/Annotation/DiscriminatorMap.php +++ b/Annotation/DiscriminatorMap.php @@ -30,11 +30,11 @@ public function __construct( private readonly array $mapping, ) { if (empty($typeProperty)) { - throw new InvalidArgumentException(sprintf('Parameter "typeProperty" of annotation "%s" cannot be empty.', static::class)); + throw new InvalidArgumentException(sprintf('Parameter "typeProperty" given to "%s" cannot be empty.', static::class)); } if (empty($mapping)) { - throw new InvalidArgumentException(sprintf('Parameter "mapping" of annotation "%s" cannot be empty.', static::class)); + throw new InvalidArgumentException(sprintf('Parameter "mapping" given to "%s" cannot be empty.', static::class)); } } diff --git a/Annotation/Groups.php b/Annotation/Groups.php index f1ea6f76b..558978cda 100644 --- a/Annotation/Groups.php +++ b/Annotation/Groups.php @@ -38,12 +38,12 @@ public function __construct(string|array $groups) $this->groups = (array) $groups; if (!$this->groups) { - throw new InvalidArgumentException(sprintf('Parameter of annotation "%s" cannot be empty.', static::class)); + throw new InvalidArgumentException(sprintf('Parameter given to "%s" cannot be empty.', static::class)); } foreach ($this->groups as $group) { if (!\is_string($group) || '' === $group) { - throw new InvalidArgumentException(sprintf('Parameter of annotation "%s" must be a string or an array of non-empty strings.', static::class)); + throw new InvalidArgumentException(sprintf('Parameter given to "%s" must be a string or an array of non-empty strings.', static::class)); } } } diff --git a/Annotation/MaxDepth.php b/Annotation/MaxDepth.php index 36afff6a7..8e03f3202 100644 --- a/Annotation/MaxDepth.php +++ b/Annotation/MaxDepth.php @@ -28,7 +28,7 @@ class MaxDepth public function __construct(private readonly int $maxDepth) { if ($maxDepth <= 0) { - throw new InvalidArgumentException(sprintf('Parameter of annotation "%s" must be a positive integer.', static::class)); + throw new InvalidArgumentException(sprintf('Parameter given to "%s" must be a positive integer.', static::class)); } } diff --git a/Annotation/SerializedName.php b/Annotation/SerializedName.php index 74df79cd2..47082dec2 100644 --- a/Annotation/SerializedName.php +++ b/Annotation/SerializedName.php @@ -28,7 +28,7 @@ final class SerializedName public function __construct(private readonly string $serializedName) { if ('' === $serializedName) { - throw new InvalidArgumentException(sprintf('Parameter of annotation "%s" must be a non-empty string.', self::class)); + throw new InvalidArgumentException(sprintf('Parameter given to "%s" must be a non-empty string.', self::class)); } } diff --git a/Annotation/SerializedPath.php b/Annotation/SerializedPath.php index 88ee8e47d..fa0c27145 100644 --- a/Annotation/SerializedPath.php +++ b/Annotation/SerializedPath.php @@ -34,7 +34,7 @@ public function __construct(string $serializedPath) try { $this->serializedPath = new PropertyPath($serializedPath); } catch (InvalidPropertyPathException $pathException) { - throw new InvalidArgumentException(sprintf('Parameter of annotation "%s" must be a valid property path.', self::class)); + throw new InvalidArgumentException(sprintf('Parameter given to "%s" must be a valid property path.', self::class)); } } diff --git a/NameConverter/MetadataAwareNameConverter.php b/NameConverter/MetadataAwareNameConverter.php index d1a0b28f2..00f4c99bd 100644 --- a/NameConverter/MetadataAwareNameConverter.php +++ b/NameConverter/MetadataAwareNameConverter.php @@ -80,7 +80,7 @@ private function getCacheValueForNormalization(string $propertyName, string $cla } if (null !== $attributesMetadata[$propertyName]->getSerializedName() && null !== $attributesMetadata[$propertyName]->getSerializedPath()) { - throw new LogicException(sprintf('Found SerializedName and SerializedPath annotations on property "%s" of class "%s".', $propertyName, $class)); + throw new LogicException(sprintf('Found SerializedName and SerializedPath attributes on property "%s" of class "%s".', $propertyName, $class)); } return $attributesMetadata[$propertyName]->getSerializedName() ?? null; @@ -124,7 +124,7 @@ private function getCacheValueForAttributesMetadata(string $class, array $contex } if (null !== $metadata->getSerializedName() && null !== $metadata->getSerializedPath()) { - throw new LogicException(sprintf('Found SerializedName and SerializedPath annotations on property "%s" of class "%s".', $name, $class)); + throw new LogicException(sprintf('Found SerializedName and SerializedPath attributes on property "%s" of class "%s".', $name, $class)); } $metadataGroups = $metadata->getGroups(); diff --git a/Normalizer/AbstractObjectNormalizer.php b/Normalizer/AbstractObjectNormalizer.php index e6efb4983..9021b0745 100644 --- a/Normalizer/AbstractObjectNormalizer.php +++ b/Normalizer/AbstractObjectNormalizer.php @@ -349,7 +349,7 @@ public function denormalize(mixed $data, string $type, string $format = null, ar $notConverted = $attribute; $attribute = $this->nameConverter->denormalize($attribute, $resolvedClass, $format, $context); if (isset($nestedData[$notConverted]) && !isset($originalNestedData[$attribute])) { - throw new LogicException(sprintf('Duplicate values for key "%s" found. One value is set via the SerializedPath annotation: "%s", the other one is set via the SerializedName annotation: "%s".', $notConverted, implode('->', $nestedAttributes[$notConverted]->getElements()), $attribute)); + throw new LogicException(sprintf('Duplicate values for key "%s" found. One value is set via the SerializedPath attribute: "%s", the other one is set via the SerializedName attribute: "%s".', $notConverted, implode('->', $nestedAttributes[$notConverted]->getElements()), $attribute)); } } @@ -756,7 +756,7 @@ private function isUninitializedValueError(\Error $e): bool } /** - * Returns all attributes with a SerializedPath annotation and the respective path. + * Returns all attributes with a SerializedPath attribute and the respective path. */ private function getNestedAttributes(string $class): array { diff --git a/Tests/Annotation/ContextTest.php b/Tests/Annotation/ContextTest.php index 300aee760..8f5614fc5 100644 --- a/Tests/Annotation/ContextTest.php +++ b/Tests/Annotation/ContextTest.php @@ -32,7 +32,7 @@ protected function setUp(): void public function testThrowsOnEmptyContext() { $this->expectException(InvalidArgumentException::class); - $this->expectExceptionMessage('At least one of the "context", "normalizationContext", or "denormalizationContext" options of annotation "Symfony\Component\Serializer\Annotation\Context" must be provided as a non-empty array.'); + $this->expectExceptionMessage('At least one of the "context", "normalizationContext", or "denormalizationContext" options must be provided as a non-empty array to "Symfony\Component\Serializer\Annotation\Context".'); new Context(); } @@ -40,7 +40,7 @@ public function testThrowsOnEmptyContext() public function testInvalidGroupOption() { $this->expectException(InvalidArgumentException::class); - $this->expectExceptionMessage(sprintf('Parameter "groups" of annotation "%s" must be a string or an array of strings. Got "stdClass"', Context::class)); + $this->expectExceptionMessage(sprintf('Parameter "groups" given to "%s" must be a string or an array of strings, "stdClass" given', Context::class)); new Context(context: ['foo' => 'bar'], groups: ['fine', new \stdClass()]); } diff --git a/Tests/Annotation/MaxDepthTest.php b/Tests/Annotation/MaxDepthTest.php index b5adbc9d8..bed9556f9 100644 --- a/Tests/Annotation/MaxDepthTest.php +++ b/Tests/Annotation/MaxDepthTest.php @@ -27,7 +27,7 @@ class MaxDepthTest extends TestCase public function testNotAnIntMaxDepthParameter(int $value) { $this->expectException(InvalidArgumentException::class); - $this->expectExceptionMessage('Parameter of annotation "Symfony\Component\Serializer\Annotation\MaxDepth" must be a positive integer.'); + $this->expectExceptionMessage('Parameter given to "Symfony\Component\Serializer\Annotation\MaxDepth" must be a positive integer.'); new MaxDepth($value); } diff --git a/Tests/Annotation/SerializedNameTest.php b/Tests/Annotation/SerializedNameTest.php index f4dd82d7f..b19f91e55 100644 --- a/Tests/Annotation/SerializedNameTest.php +++ b/Tests/Annotation/SerializedNameTest.php @@ -23,7 +23,7 @@ class SerializedNameTest extends TestCase public function testNotAStringSerializedNameParameter() { $this->expectException(InvalidArgumentException::class); - $this->expectExceptionMessage('Parameter of annotation "Symfony\Component\Serializer\Annotation\SerializedName" must be a non-empty string.'); + $this->expectExceptionMessage('Parameter given to "Symfony\Component\Serializer\Annotation\SerializedName" must be a non-empty string.'); new SerializedName(''); } diff --git a/Tests/Annotation/SerializedPathTest.php b/Tests/Annotation/SerializedPathTest.php index 58a057bd3..10c27f4b9 100644 --- a/Tests/Annotation/SerializedPathTest.php +++ b/Tests/Annotation/SerializedPathTest.php @@ -24,7 +24,7 @@ class SerializedPathTest extends TestCase public function testEmptyStringSerializedPathParameter() { $this->expectException(InvalidArgumentException::class); - $this->expectExceptionMessage('Parameter of annotation "Symfony\Component\Serializer\Annotation\SerializedPath" must be a valid property path.'); + $this->expectExceptionMessage('Parameter given to "Symfony\Component\Serializer\Annotation\SerializedPath" must be a valid property path.'); new SerializedPath(''); } diff --git a/Tests/NameConverter/MetadataAwareNameConverterTest.php b/Tests/NameConverter/MetadataAwareNameConverterTest.php index 883c30a55..7d6e71b07 100644 --- a/Tests/NameConverter/MetadataAwareNameConverterTest.php +++ b/Tests/NameConverter/MetadataAwareNameConverterTest.php @@ -168,7 +168,7 @@ public function testDenormalizeWithNestedPathAndName() $classMetadataFactory = new ClassMetadataFactory(new AttributeLoader()); $nameConverter = new MetadataAwareNameConverter($classMetadataFactory); $this->expectException(LogicException::class); - $this->expectExceptionMessage('Found SerializedName and SerializedPath annotations on property "foo" of class "Symfony\Component\Serializer\Tests\NameConverter\NestedPathAndName".'); + $this->expectExceptionMessage('Found SerializedName and SerializedPath attributes on property "foo" of class "Symfony\Component\Serializer\Tests\NameConverter\NestedPathAndName".'); $nameConverter->denormalize('foo', NestedPathAndName::class); } @@ -177,7 +177,7 @@ public function testNormalizeWithNestedPathAndName() $classMetadataFactory = new ClassMetadataFactory(new AttributeLoader()); $nameConverter = new MetadataAwareNameConverter($classMetadataFactory); $this->expectException(LogicException::class); - $this->expectExceptionMessage('Found SerializedName and SerializedPath annotations on property "foo" of class "Symfony\Component\Serializer\Tests\NameConverter\NestedPathAndName".'); + $this->expectExceptionMessage('Found SerializedName and SerializedPath attributes on property "foo" of class "Symfony\Component\Serializer\Tests\NameConverter\NestedPathAndName".'); $nameConverter->normalize('foo', NestedPathAndName::class); } } diff --git a/Tests/Normalizer/AbstractObjectNormalizerTest.php b/Tests/Normalizer/AbstractObjectNormalizerTest.php index 4ecd0ed9d..0c8af279b 100644 --- a/Tests/Normalizer/AbstractObjectNormalizerTest.php +++ b/Tests/Normalizer/AbstractObjectNormalizerTest.php @@ -187,7 +187,7 @@ public function testDenormalizeWithNestedAttributes() public function testDenormalizeWithNestedAttributesDuplicateKeys() { $this->expectException(LogicException::class); - $this->expectExceptionMessage('Duplicate values for key "quux" found. One value is set via the SerializedPath annotation: "one->four", the other one is set via the SerializedName annotation: "notquux".'); + $this->expectExceptionMessage('Duplicate values for key "quux" found. One value is set via the SerializedPath attribute: "one->four", the other one is set via the SerializedName attribute: "notquux".'); $normalizer = new AbstractObjectNormalizerWithMetadata(); $data = [ 'one' => [ From 0f236adcd3382c53c419ec264d788a62420e63dd Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Wed, 18 Oct 2023 14:57:55 +0200 Subject: [PATCH 159/297] [7.0] Cleanup legacy code paths --- Mapping/Loader/AttributeLoader.php | 78 +------------------- Tests/Mapping/Loader/AttributeLoaderTest.php | 2 - 2 files changed, 2 insertions(+), 78 deletions(-) diff --git a/Mapping/Loader/AttributeLoader.php b/Mapping/Loader/AttributeLoader.php index 14bfa7cc5..b2455c3b7 100644 --- a/Mapping/Loader/AttributeLoader.php +++ b/Mapping/Loader/AttributeLoader.php @@ -11,7 +11,6 @@ namespace Symfony\Component\Serializer\Mapping\Loader; -use Doctrine\Common\Annotations\Reader; use Symfony\Component\Serializer\Annotation\Context; use Symfony\Component\Serializer\Annotation\DiscriminatorMap; use Symfony\Component\Serializer\Annotation\Groups; @@ -44,12 +43,8 @@ class AttributeLoader implements LoaderInterface Context::class, ]; - public function __construct( - private readonly ?Reader $reader = null, - ) { - if ($reader) { - trigger_deprecation('symfony/serializer', '6.4', 'Passing a "%s" instance as argument 1 to "%s()" is deprecated, pass null or omit the parameter instead.', get_debug_type($reader), __METHOD__); - } + public function __construct() + { } public function loadClassMetadata(ClassMetadataInterface $classMetadata): bool @@ -209,30 +204,6 @@ private function loadAttributes(\ReflectionMethod|\ReflectionClass|\ReflectionPr } } } - - if (null === $this->reader) { - return; - } - - if ($reflector instanceof \ReflectionClass) { - yield from $this->getClassAnnotations($reflector); - } - if ($reflector instanceof \ReflectionMethod) { - yield from $this->getMethodAnnotations($reflector); - } - if ($reflector instanceof \ReflectionProperty) { - yield from $this->getPropertyAnnotations($reflector); - } - } - - /** - * @deprecated since Symfony 6.4 without replacement - */ - public function loadAnnotations(\ReflectionMethod|\ReflectionClass|\ReflectionProperty $reflector): iterable - { - trigger_deprecation('symfony/serializer', '6.4', 'Method "%s()" is deprecated without replacement.', __METHOD__); - - return $this->loadAttributes($reflector); } private function setAttributeContextsForGroups(Context $annotation, AttributeMetadataInterface $attributeMetadata): void @@ -261,49 +232,4 @@ private function isKnownAttribute(string $attributeName): bool return false; } - - /** - * @return object[] - */ - private function getClassAnnotations(\ReflectionClass $reflector): array - { - if ($annotations = array_filter( - $this->reader->getClassAnnotations($reflector), - fn (object $annotation): bool => $this->isKnownAttribute($annotation::class), - )) { - trigger_deprecation('symfony/serializer', '6.4', 'Class "%s" uses Doctrine Annotations to configure serialization, which is deprecated. Use PHP attributes instead.', $reflector->getName()); - } - - return $annotations; - } - - /** - * @return object[] - */ - private function getMethodAnnotations(\ReflectionMethod $reflector): array - { - if ($annotations = array_filter( - $this->reader->getMethodAnnotations($reflector), - fn (object $annotation): bool => $this->isKnownAttribute($annotation::class), - )) { - trigger_deprecation('symfony/serializer', '6.4', 'Method "%s::%s()" uses Doctrine Annotations to configure serialization, which is deprecated. Use PHP attributes instead.', $reflector->getDeclaringClass()->getName(), $reflector->getName()); - } - - return $annotations; - } - - /** - * @return object[] - */ - private function getPropertyAnnotations(\ReflectionProperty $reflector): array - { - if ($annotations = array_filter( - $this->reader->getPropertyAnnotations($reflector), - fn (object $annotation): bool => $this->isKnownAttribute($annotation::class), - )) { - trigger_deprecation('symfony/serializer', '6.4', 'Property "%s::$%s" uses Doctrine Annotations to configure serialization, which is deprecated. Use PHP attributes instead.', $reflector->getDeclaringClass()->getName(), $reflector->getName()); - } - - return $annotations; - } } diff --git a/Tests/Mapping/Loader/AttributeLoaderTest.php b/Tests/Mapping/Loader/AttributeLoaderTest.php index fc77abc94..439c2b79c 100644 --- a/Tests/Mapping/Loader/AttributeLoaderTest.php +++ b/Tests/Mapping/Loader/AttributeLoaderTest.php @@ -12,7 +12,6 @@ namespace Symfony\Component\Serializer\Tests\Mapping\Loader; use PHPUnit\Framework\TestCase; -use Symfony\Bridge\PhpUnit\ExpectDeprecationTrait; use Symfony\Component\PropertyAccess\PropertyPath; use Symfony\Component\Serializer\Exception\MappingException; use Symfony\Component\Serializer\Mapping\AttributeMetadata; @@ -47,7 +46,6 @@ class AttributeLoaderTest extends TestCase { use ContextMappingTestTrait; - use ExpectDeprecationTrait; protected AttributeLoader $loader; From cf7f907e1fa8b0a5d3800bd22d8f8f0f051c8146 Mon Sep 17 00:00:00 2001 From: Dariusz Ruminski Date: Tue, 17 Oct 2023 18:21:46 +0200 Subject: [PATCH 160/297] DX: PHP CS Fixer - update excluded paths and apply some minor CS --- Normalizer/CustomNormalizer.php | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/Normalizer/CustomNormalizer.php b/Normalizer/CustomNormalizer.php index f45f36296..c5a058db9 100644 --- a/Normalizer/CustomNormalizer.php +++ b/Normalizer/CustomNormalizer.php @@ -48,8 +48,8 @@ public function denormalize(mixed $data, string $type, string $format = null, ar /** * Checks if the given class implements the NormalizableInterface. * - * @param mixed $data Data to normalize - * @param string|null $format The format being (de-)serialized from or into + * @param mixed $data Data to normalize + * @param string|null $format The format being (de-)serialized from or into * @param array $context */ public function supportsNormalization(mixed $data, string $format = null /* , array $context = [] */): bool @@ -60,9 +60,9 @@ public function supportsNormalization(mixed $data, string $format = null /* , ar /** * Checks if the given class implements the DenormalizableInterface. * - * @param mixed $data Data to denormalize from - * @param string $type The class to which the data should be denormalized - * @param string|null $format The format being deserialized from + * @param mixed $data Data to denormalize from + * @param string $type The class to which the data should be denormalized + * @param string|null $format The format being deserialized from * @param array $context */ public function supportsDenormalization(mixed $data, string $type, string $format = null /* , array $context = [] */): bool From da47df901314499303079a1884109c2e061a9f0d Mon Sep 17 00:00:00 2001 From: Christian Flothmann Date: Tue, 24 Oct 2023 13:46:50 +0200 Subject: [PATCH 161/297] remove outdated base test class The AttributeLoaderWithAttributesTest class is no longer since the AnnotationLoaderWithDoctrineAnnotationsTest has been removed. --- Tests/Mapping/Loader/AttributeLoaderTest.php | 11 ++++++ .../AttributeLoaderWithAttributesTest.php | 39 ------------------- 2 files changed, 11 insertions(+), 39 deletions(-) delete mode 100644 Tests/Mapping/Loader/AttributeLoaderWithAttributesTest.php diff --git a/Tests/Mapping/Loader/AttributeLoaderTest.php b/Tests/Mapping/Loader/AttributeLoaderTest.php index 439c2b79c..68c3fa9bf 100644 --- a/Tests/Mapping/Loader/AttributeLoaderTest.php +++ b/Tests/Mapping/Loader/AttributeLoaderTest.php @@ -23,6 +23,7 @@ use Symfony\Component\Serializer\Tests\Fixtures\Attributes\AbstractDummyFirstChild; use Symfony\Component\Serializer\Tests\Fixtures\Attributes\AbstractDummySecondChild; use Symfony\Component\Serializer\Tests\Fixtures\Attributes\AbstractDummyThirdChild; +use Symfony\Component\Serializer\Tests\Fixtures\Attributes\BadAttributeDummy; use Symfony\Component\Serializer\Tests\Fixtures\Attributes\BadMethodContextDummy; use Symfony\Component\Serializer\Tests\Fixtures\Attributes\ContextDummyParent; use Symfony\Component\Serializer\Tests\Fixtures\Attributes\ContextDummyPromotedProperties; @@ -219,6 +220,16 @@ public function testLoadGroupsOnClass() self::assertSame(['a'], $attributesMetadata['baz']->getGroups()); } + public function testLoadWithInvalidAttribute() + { + $this->expectException(MappingException::class); + $this->expectExceptionMessage('Could not instantiate attribute "Symfony\Component\Serializer\Annotation\Groups" on "Symfony\Component\Serializer\Tests\Fixtures\Attributes\BadAttributeDummy::myMethod()".'); + + $classMetadata = new ClassMetadata(BadAttributeDummy::class); + + $this->loader->loadClassMetadata($classMetadata); + } + protected function getLoaderForContextMapping(): AttributeLoader { return $this->loader; diff --git a/Tests/Mapping/Loader/AttributeLoaderWithAttributesTest.php b/Tests/Mapping/Loader/AttributeLoaderWithAttributesTest.php deleted file mode 100644 index 92e48bfbd..000000000 --- a/Tests/Mapping/Loader/AttributeLoaderWithAttributesTest.php +++ /dev/null @@ -1,39 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Component\Serializer\Tests\Mapping\Loader; - -use Symfony\Component\Serializer\Exception\MappingException; -use Symfony\Component\Serializer\Mapping\ClassMetadata; -use Symfony\Component\Serializer\Mapping\Loader\AttributeLoader; - -class AttributeLoaderWithAttributesTest extends AttributeLoaderTest -{ - protected function createLoader(): AttributeLoader - { - return new AttributeLoader(); - } - - protected function getNamespace(): string - { - return 'Symfony\Component\Serializer\Tests\Fixtures\Attributes'; - } - - public function testLoadWithInvalidAttribute() - { - $this->expectException(MappingException::class); - $this->expectExceptionMessage('Could not instantiate attribute "Symfony\Component\Serializer\Annotation\Groups" on "Symfony\Component\Serializer\Tests\Fixtures\Attributes\BadAttributeDummy::myMethod()".'); - - $classMetadata = new ClassMetadata($this->getNamespace().'\BadAttributeDummy'); - - $this->loader->loadClassMetadata($classMetadata); - } -} From 5b4f45f8bc3426ac1a922988099d6e7166560875 Mon Sep 17 00:00:00 2001 From: Thomas Calvet Date: Thu, 26 Oct 2023 19:29:53 +0200 Subject: [PATCH 162/297] [7.0] Remove unused test fixture --- .../FormatAndContextAwareNormalizer.php | 22 ------------------- 1 file changed, 22 deletions(-) delete mode 100644 Tests/Fixtures/FormatAndContextAwareNormalizer.php diff --git a/Tests/Fixtures/FormatAndContextAwareNormalizer.php b/Tests/Fixtures/FormatAndContextAwareNormalizer.php deleted file mode 100644 index 404228845..000000000 --- a/Tests/Fixtures/FormatAndContextAwareNormalizer.php +++ /dev/null @@ -1,22 +0,0 @@ - - * - * 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\ObjectNormalizer; - -class FormatAndContextAwareNormalizer extends ObjectNormalizer -{ - protected function isAllowedAttribute($classOrObject, string $attribute, string $format = null, array $context = []): bool - { - return \in_array($attribute, ['foo', 'bar']) && 'foo_and_bar_included' === $format; - } -} From ef45660449657f2522ade40fe03761da42c57b15 Mon Sep 17 00:00:00 2001 From: Christian Flothmann Date: Sat, 28 Oct 2023 13:29:36 +0200 Subject: [PATCH 163/297] throw better exception in TranslatableNormalizer, add to changelog --- CHANGELOG.md | 1 + Normalizer/TranslatableNormalizer.php | 3 ++- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index fdefebdc0..56ed3863c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,7 @@ CHANGELOG 6.4 --- + * Add `TranslatableNormalizer` * Allow `Context` attribute to target classes * Deprecate Doctrine annotations support in favor of native attributes * Allow the `Groups` attribute/annotation on classes diff --git a/Normalizer/TranslatableNormalizer.php b/Normalizer/TranslatableNormalizer.php index b79a0ffb9..a4aef51f8 100644 --- a/Normalizer/TranslatableNormalizer.php +++ b/Normalizer/TranslatableNormalizer.php @@ -12,6 +12,7 @@ namespace Symfony\Component\Serializer\Normalizer; use Symfony\Component\Serializer\Exception\InvalidArgumentException; +use Symfony\Component\Serializer\Exception\NotNormalizableValueException; use Symfony\Contracts\Translation\TranslatableInterface; use Symfony\Contracts\Translation\TranslatorInterface; @@ -36,7 +37,7 @@ public function __construct( public function normalize(mixed $object, string $format = null, array $context = []): string { if (!$object instanceof TranslatableInterface) { - throw new InvalidArgumentException(sprintf('The object must implement the "%s".', TranslatableInterface::class)); + throw NotNormalizableValueException::createForUnexpectedDataType(sprintf('The object must implement the "%s".', TranslatableInterface::class), $object, [TranslatableInterface::class]); } return $object->trans($this->translator, $context[self::NORMALIZATION_LOCALE_KEY] ?? $this->defaultContext[self::NORMALIZATION_LOCALE_KEY]); From 1d600201429c92049b6b7198ea885f0f8fd1a8d1 Mon Sep 17 00:00:00 2001 From: Wouter de Jong Date: Wed, 25 Oct 2023 16:05:35 +0200 Subject: [PATCH 164/297] Add annotation -> attribute aliases --- Annotation/Context.php | 60 ++------------- Annotation/DiscriminatorMap.php | 37 +-------- Annotation/Groups.php | 45 +---------- Annotation/Ignore.php | 18 ++--- Annotation/MaxDepth.php | 29 +------ Annotation/SerializedName.php | 26 +------ Annotation/SerializedPath.php | 32 +------- Attribute/Context.php | 77 +++++++++++++++++++ Attribute/DiscriminatorMap.php | 54 +++++++++++++ Attribute/Groups.php | 62 +++++++++++++++ Attribute/Ignore.php | 29 +++++++ Attribute/MaxDepth.php | 46 +++++++++++ Attribute/SerializedName.php | 43 +++++++++++ Attribute/SerializedPath.php | 49 ++++++++++++ CHANGELOG.md | 1 + Mapping/Loader/AttributeLoader.php | 14 ++-- Normalizer/GetSetMethodNormalizer.php | 2 +- Tests/Annotation/ContextTest.php | 14 ++-- Tests/Annotation/DiscriminatorMapTest.php | 2 +- Tests/Annotation/GroupsTest.php | 2 +- Tests/Annotation/MaxDepthTest.php | 4 +- Tests/Annotation/SerializedNameTest.php | 4 +- Tests/Annotation/SerializedPathTest.php | 4 +- Tests/Dummy/DummyClassOne.php | 10 +-- Tests/Fixtures/Annotations/AbstractDummy.php | 2 +- .../Annotations/BadMethodContextDummy.php | 2 +- Tests/Fixtures/Annotations/ContextDummy.php | 2 +- .../Annotations/ContextDummyParent.php | 2 +- .../ContextDummyPromotedProperties.php | 2 +- Tests/Fixtures/Annotations/Entity45016.php | 2 +- .../Fixtures/Annotations/GroupClassDummy.php | 2 +- Tests/Fixtures/Annotations/GroupDummy.php | 2 +- .../Annotations/GroupDummyInterface.php | 2 +- .../Fixtures/Annotations/GroupDummyParent.php | 2 +- Tests/Fixtures/Annotations/IgnoreDummy.php | 2 +- .../IgnoreDummyAdditionalGetter.php | 2 +- Tests/Fixtures/Annotations/MaxDepthDummy.php | 2 +- .../Annotations/SerializedNameDummy.php | 2 +- .../Annotations/SerializedPathDummy.php | 2 +- .../SerializedPathInConstructorDummy.php | 2 +- Tests/Fixtures/Attributes/AbstractDummy.php | 2 +- .../Fixtures/Attributes/BadAttributeDummy.php | 2 +- .../Attributes/BadMethodContextDummy.php | 2 +- .../Attributes/ClassWithIgnoreAttribute.php | 2 +- Tests/Fixtures/Attributes/ContextDummy.php | 2 +- .../Attributes/ContextDummyParent.php | 2 +- .../ContextDummyPromotedProperties.php | 2 +- Tests/Fixtures/Attributes/Entity45016.php | 2 +- Tests/Fixtures/Attributes/GroupClassDummy.php | 2 +- Tests/Fixtures/Attributes/GroupDummy.php | 2 +- .../Attributes/GroupDummyInterface.php | 2 +- .../Fixtures/Attributes/GroupDummyParent.php | 2 +- Tests/Fixtures/Attributes/IgnoreDummy.php | 2 +- .../IgnoreDummyAdditionalGetter.php | 2 +- Tests/Fixtures/Attributes/MaxDepthDummy.php | 2 +- .../Attributes/SerializedNameDummy.php | 2 +- .../Attributes/SerializedPathDummy.php | 2 +- .../SerializedPathInConstructorDummy.php | 2 +- .../Fixtures/ChildOfGroupsAnnotationDummy.php | 2 +- Tests/Fixtures/DummyMessageInterface.php | 2 +- Tests/Fixtures/DummyMessageNumberOne.php | 2 +- Tests/Fixtures/DummyMessageNumberTwo.php | 2 +- Tests/Fixtures/OtherSerializedNameDummy.php | 4 +- .../AttributeLoaderWithAttributesTest.php | 2 +- .../MetadataAwareNameConverterTest.php | 4 +- .../AbstractObjectNormalizerTest.php | 8 +- .../Features/ContextMetadataTestTrait.php | 4 +- .../Normalizer/Features/DummyContextChild.php | 2 +- .../ObjectDummyWithContextAttribute.php | 4 +- .../Features/TypedPropertiesObject.php | 2 +- 70 files changed, 474 insertions(+), 296 deletions(-) create mode 100644 Attribute/Context.php create mode 100644 Attribute/DiscriminatorMap.php create mode 100644 Attribute/Groups.php create mode 100644 Attribute/Ignore.php create mode 100644 Attribute/MaxDepth.php create mode 100644 Attribute/SerializedName.php create mode 100644 Attribute/SerializedPath.php diff --git a/Annotation/Context.php b/Annotation/Context.php index b417d6505..eef833148 100644 --- a/Annotation/Context.php +++ b/Annotation/Context.php @@ -11,63 +11,13 @@ namespace Symfony\Component\Serializer\Annotation; -use Symfony\Component\Serializer\Exception\InvalidArgumentException; +// do not deprecate in 6.4/7.0, to make it easier for the ecosystem to support 6.4, 7.4 and 8.0 simultaneously -/** - * Annotation class for @Context(). - * - * @Annotation - * @NamedArgumentConstructor - * @Target({"PROPERTY", "METHOD"}) - * - * @author Maxime Steinhausser - */ -#[\Attribute(\Attribute::TARGET_CLASS | \Attribute::TARGET_PROPERTY | \Attribute::TARGET_METHOD | \Attribute::IS_REPEATABLE)] -class Context -{ - private array $groups; - - /** - * @param string|string[] $groups - * - * @throws InvalidArgumentException - */ - public function __construct( - private readonly array $context = [], - private readonly array $normalizationContext = [], - private readonly array $denormalizationContext = [], - string|array $groups = [], - ) { - if (!$context && !$normalizationContext && !$denormalizationContext) { - throw new InvalidArgumentException(sprintf('At least one of the "context", "normalizationContext", or "denormalizationContext" options must be provided as a non-empty array to "%s".', static::class)); - } - - $this->groups = (array) $groups; - - foreach ($this->groups as $group) { - if (!\is_string($group)) { - throw new InvalidArgumentException(sprintf('Parameter "groups" given to "%s" must be a string or an array of strings, "%s" given.', static::class, get_debug_type($group))); - } - } - } - - public function getContext(): array - { - return $this->context; - } - - public function getNormalizationContext(): array - { - return $this->normalizationContext; - } - - public function getDenormalizationContext(): array - { - return $this->denormalizationContext; - } +class_exists(\Symfony\Component\Serializer\Attribute\Context::class); - public function getGroups(): array +if (false) { + #[\Attribute(\Attribute::TARGET_CLASS | \Attribute::TARGET_PROPERTY | \Attribute::TARGET_METHOD | \Attribute::IS_REPEATABLE)] + class Context { - return $this->groups; } } diff --git a/Annotation/DiscriminatorMap.php b/Annotation/DiscriminatorMap.php index ad335ec41..89b2ae9e8 100644 --- a/Annotation/DiscriminatorMap.php +++ b/Annotation/DiscriminatorMap.php @@ -11,40 +11,11 @@ namespace Symfony\Component\Serializer\Annotation; -use Symfony\Component\Serializer\Exception\InvalidArgumentException; +class_exists(\Symfony\Component\Serializer\Attribute\DiscriminatorMap::class); -/** - * Annotation class for @DiscriminatorMap(). - * - * @Annotation - * @NamedArgumentConstructor - * @Target({"CLASS"}) - * - * @author Samuel Roze - */ -#[\Attribute(\Attribute::TARGET_CLASS)] -class DiscriminatorMap -{ - public function __construct( - private readonly string $typeProperty, - private readonly array $mapping, - ) { - if (empty($typeProperty)) { - throw new InvalidArgumentException(sprintf('Parameter "typeProperty" given to "%s" cannot be empty.', static::class)); - } - - if (empty($mapping)) { - throw new InvalidArgumentException(sprintf('Parameter "mapping" given to "%s" cannot be empty.', static::class)); - } - } - - public function getTypeProperty(): string - { - return $this->typeProperty; - } - - public function getMapping(): array +if (false) { + #[\Attribute(\Attribute::TARGET_CLASS)] + class DiscriminatorMap { - return $this->mapping; } } diff --git a/Annotation/Groups.php b/Annotation/Groups.php index 558978cda..89338b05c 100644 --- a/Annotation/Groups.php +++ b/Annotation/Groups.php @@ -11,48 +11,11 @@ namespace Symfony\Component\Serializer\Annotation; -use Symfony\Component\Serializer\Exception\InvalidArgumentException; +class_exists(\Symfony\Component\Serializer\Attribute\Groups::class); -/** - * Annotation class for @Groups(). - * - * @Annotation - * @NamedArgumentConstructor - * @Target({"PROPERTY", "METHOD", "CLASS"}) - * - * @author Kévin Dunglas - */ -#[\Attribute(\Attribute::TARGET_METHOD | \Attribute::TARGET_PROPERTY | \Attribute::TARGET_CLASS)] -class Groups -{ - /** - * @var string[] - */ - private readonly array $groups; - - /** - * @param string|string[] $groups - */ - public function __construct(string|array $groups) - { - $this->groups = (array) $groups; - - if (!$this->groups) { - throw new InvalidArgumentException(sprintf('Parameter given to "%s" cannot be empty.', static::class)); - } - - foreach ($this->groups as $group) { - if (!\is_string($group) || '' === $group) { - throw new InvalidArgumentException(sprintf('Parameter given to "%s" must be a string or an array of non-empty strings.', static::class)); - } - } - } - - /** - * @return string[] - */ - public function getGroups(): array +if (false) { + #[\Attribute(\Attribute::TARGET_METHOD | \Attribute::TARGET_PROPERTY | \Attribute::TARGET_CLASS)] + class Groups { - return $this->groups; } } diff --git a/Annotation/Ignore.php b/Annotation/Ignore.php index b09e7007a..219ea725b 100644 --- a/Annotation/Ignore.php +++ b/Annotation/Ignore.php @@ -11,15 +11,11 @@ namespace Symfony\Component\Serializer\Annotation; -/** - * Annotation class for @Ignore(). - * - * @Annotation - * @Target({"PROPERTY", "METHOD"}) - * - * @author Kévin Dunglas - */ -#[\Attribute(\Attribute::TARGET_METHOD | \Attribute::TARGET_PROPERTY)] -final class Ignore -{ +class_exists(\Symfony\Component\Serializer\Attribute\Ignore::class); + +if (false) { + #[\Attribute(\Attribute::TARGET_METHOD | \Attribute::TARGET_PROPERTY)] + final class Ignore + { + } } diff --git a/Annotation/MaxDepth.php b/Annotation/MaxDepth.php index 8e03f3202..591e68ed9 100644 --- a/Annotation/MaxDepth.php +++ b/Annotation/MaxDepth.php @@ -11,32 +11,11 @@ namespace Symfony\Component\Serializer\Annotation; -use Symfony\Component\Serializer\Exception\InvalidArgumentException; +class_exists(\Symfony\Component\Serializer\Attribute\MaxDepth::class); -/** - * Annotation class for @MaxDepth(). - * - * @Annotation - * @NamedArgumentConstructor - * @Target({"PROPERTY", "METHOD"}) - * - * @author Kévin Dunglas - */ -#[\Attribute(\Attribute::TARGET_METHOD | \Attribute::TARGET_PROPERTY)] -class MaxDepth -{ - public function __construct(private readonly int $maxDepth) - { - if ($maxDepth <= 0) { - throw new InvalidArgumentException(sprintf('Parameter given to "%s" must be a positive integer.', static::class)); - } - } - - /** - * @return int - */ - public function getMaxDepth() +if (false) { + #[\Attribute(\Attribute::TARGET_METHOD | \Attribute::TARGET_PROPERTY)] + class MaxDepth { - return $this->maxDepth; } } diff --git a/Annotation/SerializedName.php b/Annotation/SerializedName.php index 47082dec2..97c6e0aec 100644 --- a/Annotation/SerializedName.php +++ b/Annotation/SerializedName.php @@ -11,29 +11,11 @@ namespace Symfony\Component\Serializer\Annotation; -use Symfony\Component\Serializer\Exception\InvalidArgumentException; +class_exists(\Symfony\Component\Serializer\Attribute\SerializedName::class); -/** - * Annotation class for @SerializedName(). - * - * @Annotation - * @NamedArgumentConstructor - * @Target({"PROPERTY", "METHOD"}) - * - * @author Fabien Bourigault - */ -#[\Attribute(\Attribute::TARGET_METHOD | \Attribute::TARGET_PROPERTY)] -final class SerializedName -{ - public function __construct(private readonly string $serializedName) - { - if ('' === $serializedName) { - throw new InvalidArgumentException(sprintf('Parameter given to "%s" must be a non-empty string.', self::class)); - } - } - - public function getSerializedName(): string +if (false) { + #[\Attribute(\Attribute::TARGET_METHOD | \Attribute::TARGET_PROPERTY)] + final class SerializedName { - return $this->serializedName; } } diff --git a/Annotation/SerializedPath.php b/Annotation/SerializedPath.php index fa0c27145..f555a1086 100644 --- a/Annotation/SerializedPath.php +++ b/Annotation/SerializedPath.php @@ -11,35 +11,11 @@ namespace Symfony\Component\Serializer\Annotation; -use Symfony\Component\PropertyAccess\Exception\InvalidPropertyPathException; -use Symfony\Component\PropertyAccess\PropertyPath; -use Symfony\Component\Serializer\Exception\InvalidArgumentException; +class_exists(\Symfony\Component\Serializer\Attribute\SerializedPath::class); -/** - * Annotation class for @SerializedPath(). - * - * @Annotation - * @NamedArgumentConstructor - * @Target({"PROPERTY", "METHOD"}) - * - * @author Tobias Bönner - */ -#[\Attribute(\Attribute::TARGET_METHOD | \Attribute::TARGET_PROPERTY)] -final class SerializedPath -{ - private PropertyPath $serializedPath; - - public function __construct(string $serializedPath) - { - try { - $this->serializedPath = new PropertyPath($serializedPath); - } catch (InvalidPropertyPathException $pathException) { - throw new InvalidArgumentException(sprintf('Parameter given to "%s" must be a valid property path.', self::class)); - } - } - - public function getSerializedPath(): PropertyPath +if (false) { + #[\Attribute(\Attribute::TARGET_METHOD | \Attribute::TARGET_PROPERTY)] + final class SerializedPath { - return $this->serializedPath; } } diff --git a/Attribute/Context.php b/Attribute/Context.php new file mode 100644 index 000000000..baa958839 --- /dev/null +++ b/Attribute/Context.php @@ -0,0 +1,77 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Serializer\Attribute; + +use Symfony\Component\Serializer\Exception\InvalidArgumentException; + +/** + * Annotation class for @Context(). + * + * @Annotation + * @NamedArgumentConstructor + * @Target({"PROPERTY", "METHOD"}) + * + * @author Maxime Steinhausser + */ +#[\Attribute(\Attribute::TARGET_CLASS | \Attribute::TARGET_PROPERTY | \Attribute::TARGET_METHOD | \Attribute::IS_REPEATABLE)] +class Context +{ + private array $groups; + + /** + * @param string|string[] $groups + * + * @throws InvalidArgumentException + */ + public function __construct( + private readonly array $context = [], + private readonly array $normalizationContext = [], + private readonly array $denormalizationContext = [], + string|array $groups = [], + ) { + if (!$context && !$normalizationContext && !$denormalizationContext) { + throw new InvalidArgumentException(sprintf('At least one of the "context", "normalizationContext", or "denormalizationContext" options must be provided as a non-empty array to "%s".', static::class)); + } + + $this->groups = (array) $groups; + + foreach ($this->groups as $group) { + if (!\is_string($group)) { + throw new InvalidArgumentException(sprintf('Parameter "groups" given to "%s" must be a string or an array of strings, "%s" given.', static::class, get_debug_type($group))); + } + } + } + + public function getContext(): array + { + return $this->context; + } + + public function getNormalizationContext(): array + { + return $this->normalizationContext; + } + + public function getDenormalizationContext(): array + { + return $this->denormalizationContext; + } + + public function getGroups(): array + { + return $this->groups; + } +} + +if (!class_exists(\Symfony\Component\Serializer\Annotation\Context::class, false)) { + class_alias(Context::class, \Symfony\Component\Serializer\Annotation\Context::class); +} diff --git a/Attribute/DiscriminatorMap.php b/Attribute/DiscriminatorMap.php new file mode 100644 index 000000000..4c1f23722 --- /dev/null +++ b/Attribute/DiscriminatorMap.php @@ -0,0 +1,54 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Serializer\Attribute; + +use Symfony\Component\Serializer\Exception\InvalidArgumentException; + +/** + * Annotation class for @DiscriminatorMap(). + * + * @Annotation + * @NamedArgumentConstructor + * @Target({"CLASS"}) + * + * @author Samuel Roze + */ +#[\Attribute(\Attribute::TARGET_CLASS)] +class DiscriminatorMap +{ + public function __construct( + private readonly string $typeProperty, + private readonly array $mapping, + ) { + if (empty($typeProperty)) { + throw new InvalidArgumentException(sprintf('Parameter "typeProperty" given to "%s" cannot be empty.', static::class)); + } + + if (empty($mapping)) { + throw new InvalidArgumentException(sprintf('Parameter "mapping" given to "%s" cannot be empty.', static::class)); + } + } + + public function getTypeProperty(): string + { + return $this->typeProperty; + } + + public function getMapping(): array + { + return $this->mapping; + } +} + +if (!class_exists(\Symfony\Component\Serializer\Annotation\DiscriminatorMap::class, false)) { + class_alias(DiscriminatorMap::class, \Symfony\Component\Serializer\Annotation\DiscriminatorMap::class); +} diff --git a/Attribute/Groups.php b/Attribute/Groups.php new file mode 100644 index 000000000..9a351910a --- /dev/null +++ b/Attribute/Groups.php @@ -0,0 +1,62 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Serializer\Attribute; + +use Symfony\Component\Serializer\Exception\InvalidArgumentException; + +/** + * Annotation class for @Groups(). + * + * @Annotation + * @NamedArgumentConstructor + * @Target({"PROPERTY", "METHOD", "CLASS"}) + * + * @author Kévin Dunglas + */ +#[\Attribute(\Attribute::TARGET_METHOD | \Attribute::TARGET_PROPERTY | \Attribute::TARGET_CLASS)] +class Groups +{ + /** + * @var string[] + */ + private readonly array $groups; + + /** + * @param string|string[] $groups + */ + public function __construct(string|array $groups) + { + $this->groups = (array) $groups; + + if (!$this->groups) { + throw new InvalidArgumentException(sprintf('Parameter given to "%s" cannot be empty.', static::class)); + } + + foreach ($this->groups as $group) { + if (!\is_string($group) || '' === $group) { + throw new InvalidArgumentException(sprintf('Parameter given to "%s" must be a string or an array of non-empty strings.', static::class)); + } + } + } + + /** + * @return string[] + */ + public function getGroups(): array + { + return $this->groups; + } +} + +if (!class_exists(\Symfony\Component\Serializer\Annotation\Groups::class, false)) { + class_alias(Groups::class, \Symfony\Component\Serializer\Annotation\Groups::class); +} diff --git a/Attribute/Ignore.php b/Attribute/Ignore.php new file mode 100644 index 000000000..688a1f3f4 --- /dev/null +++ b/Attribute/Ignore.php @@ -0,0 +1,29 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Serializer\Attribute; + +/** + * Annotation class for @Ignore(). + * + * @Annotation + * @Target({"PROPERTY", "METHOD"}) + * + * @author Kévin Dunglas + */ +#[\Attribute(\Attribute::TARGET_METHOD | \Attribute::TARGET_PROPERTY)] +final class Ignore +{ +} + +if (!class_exists(\Symfony\Component\Serializer\Annotation\Ignore::class, false)) { + class_alias(Ignore::class, \Symfony\Component\Serializer\Annotation\Ignore::class); +} diff --git a/Attribute/MaxDepth.php b/Attribute/MaxDepth.php new file mode 100644 index 000000000..3ecfcb993 --- /dev/null +++ b/Attribute/MaxDepth.php @@ -0,0 +1,46 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Serializer\Attribute; + +use Symfony\Component\Serializer\Exception\InvalidArgumentException; + +/** + * Annotation class for @MaxDepth(). + * + * @Annotation + * @NamedArgumentConstructor + * @Target({"PROPERTY", "METHOD"}) + * + * @author Kévin Dunglas + */ +#[\Attribute(\Attribute::TARGET_METHOD | \Attribute::TARGET_PROPERTY)] +class MaxDepth +{ + public function __construct(private readonly int $maxDepth) + { + if ($maxDepth <= 0) { + throw new InvalidArgumentException(sprintf('Parameter given to "%s" must be a positive integer.', static::class)); + } + } + + /** + * @return int + */ + public function getMaxDepth() + { + return $this->maxDepth; + } +} + +if (!class_exists(\Symfony\Component\Serializer\Annotation\MaxDepth::class, false)) { + class_alias(MaxDepth::class, \Symfony\Component\Serializer\Annotation\MaxDepth::class); +} diff --git a/Attribute/SerializedName.php b/Attribute/SerializedName.php new file mode 100644 index 000000000..a2651282f --- /dev/null +++ b/Attribute/SerializedName.php @@ -0,0 +1,43 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Serializer\Attribute; + +use Symfony\Component\Serializer\Exception\InvalidArgumentException; + +/** + * Annotation class for @SerializedName(). + * + * @Annotation + * @NamedArgumentConstructor + * @Target({"PROPERTY", "METHOD"}) + * + * @author Fabien Bourigault + */ +#[\Attribute(\Attribute::TARGET_METHOD | \Attribute::TARGET_PROPERTY)] +final class SerializedName +{ + public function __construct(private readonly string $serializedName) + { + if ('' === $serializedName) { + throw new InvalidArgumentException(sprintf('Parameter given to "%s" must be a non-empty string.', self::class)); + } + } + + public function getSerializedName(): string + { + return $this->serializedName; + } +} + +if (!class_exists(\Symfony\Component\Serializer\Annotation\SerializedName::class, false)) { + class_alias(SerializedName::class, \Symfony\Component\Serializer\Annotation\SerializedName::class); +} diff --git a/Attribute/SerializedPath.php b/Attribute/SerializedPath.php new file mode 100644 index 000000000..0e5f04087 --- /dev/null +++ b/Attribute/SerializedPath.php @@ -0,0 +1,49 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Serializer\Attribute; + +use Symfony\Component\PropertyAccess\Exception\InvalidPropertyPathException; +use Symfony\Component\PropertyAccess\PropertyPath; +use Symfony\Component\Serializer\Exception\InvalidArgumentException; + +/** + * Annotation class for @SerializedPath(). + * + * @Annotation + * @NamedArgumentConstructor + * @Target({"PROPERTY", "METHOD"}) + * + * @author Tobias Bönner + */ +#[\Attribute(\Attribute::TARGET_METHOD | \Attribute::TARGET_PROPERTY)] +final class SerializedPath +{ + private PropertyPath $serializedPath; + + public function __construct(string $serializedPath) + { + try { + $this->serializedPath = new PropertyPath($serializedPath); + } catch (InvalidPropertyPathException $pathException) { + throw new InvalidArgumentException(sprintf('Parameter given to "%s" must be a valid property path.', self::class)); + } + } + + public function getSerializedPath(): PropertyPath + { + return $this->serializedPath; + } +} + +if (!class_exists(\Symfony\Component\Serializer\Annotation\SerializedPath::class, false)) { + class_alias(SerializedPath::class, \Symfony\Component\Serializer\Annotation\SerializedPath::class); +} diff --git a/CHANGELOG.md b/CHANGELOG.md index 56ed3863c..69829709f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,7 @@ CHANGELOG * Make `ProblemNormalizer` give details about Messenger's `ValidationFailedException` * Add `XmlEncoder::CDATA_WRAPPING` context option * Deprecate `AnnotationLoader`, use `AttributeLoader` instead + * Add aliases for all classes in the `Annotation` namespace to `Attribute` 6.3 --- diff --git a/Mapping/Loader/AttributeLoader.php b/Mapping/Loader/AttributeLoader.php index 9b379bdee..1bfe5d473 100644 --- a/Mapping/Loader/AttributeLoader.php +++ b/Mapping/Loader/AttributeLoader.php @@ -12,13 +12,13 @@ namespace Symfony\Component\Serializer\Mapping\Loader; use Doctrine\Common\Annotations\Reader; -use Symfony\Component\Serializer\Annotation\Context; -use Symfony\Component\Serializer\Annotation\DiscriminatorMap; -use Symfony\Component\Serializer\Annotation\Groups; -use Symfony\Component\Serializer\Annotation\Ignore; -use Symfony\Component\Serializer\Annotation\MaxDepth; -use Symfony\Component\Serializer\Annotation\SerializedName; -use Symfony\Component\Serializer\Annotation\SerializedPath; +use Symfony\Component\Serializer\Attribute\Context; +use Symfony\Component\Serializer\Attribute\DiscriminatorMap; +use Symfony\Component\Serializer\Attribute\Groups; +use Symfony\Component\Serializer\Attribute\Ignore; +use Symfony\Component\Serializer\Attribute\MaxDepth; +use Symfony\Component\Serializer\Attribute\SerializedName; +use Symfony\Component\Serializer\Attribute\SerializedPath; use Symfony\Component\Serializer\Exception\MappingException; use Symfony\Component\Serializer\Mapping\AttributeMetadata; use Symfony\Component\Serializer\Mapping\AttributeMetadataInterface; diff --git a/Normalizer/GetSetMethodNormalizer.php b/Normalizer/GetSetMethodNormalizer.php index d26103e96..703b896d0 100644 --- a/Normalizer/GetSetMethodNormalizer.php +++ b/Normalizer/GetSetMethodNormalizer.php @@ -11,7 +11,7 @@ namespace Symfony\Component\Serializer\Normalizer; -use Symfony\Component\Serializer\Annotation\Ignore; +use Symfony\Component\Serializer\Attribute\Ignore; /** * Converts between objects with getter and setter methods and arrays. diff --git a/Tests/Annotation/ContextTest.php b/Tests/Annotation/ContextTest.php index 8f5614fc5..7efe8dda5 100644 --- a/Tests/Annotation/ContextTest.php +++ b/Tests/Annotation/ContextTest.php @@ -12,7 +12,7 @@ namespace Symfony\Component\Serializer\Tests\Annotation; use PHPUnit\Framework\TestCase; -use Symfony\Component\Serializer\Annotation\Context; +use Symfony\Component\Serializer\Attribute\Context; use Symfony\Component\Serializer\Exception\InvalidArgumentException; use Symfony\Component\VarDumper\Dumper\CliDumper; use Symfony\Component\VarDumper\Test\VarDumperTestTrait; @@ -32,7 +32,7 @@ protected function setUp(): void public function testThrowsOnEmptyContext() { $this->expectException(InvalidArgumentException::class); - $this->expectExceptionMessage('At least one of the "context", "normalizationContext", or "denormalizationContext" options must be provided as a non-empty array to "Symfony\Component\Serializer\Annotation\Context".'); + $this->expectExceptionMessage('At least one of the "context", "normalizationContext", or "denormalizationContext" options must be provided as a non-empty array to "Symfony\Component\Serializer\Attribute\Context".'); new Context(); } @@ -78,7 +78,7 @@ public static function provideValidInputs(): iterable yield 'named arguments: with context option' => [ fn () => new Context(context: ['foo' => 'bar']), << "bar", @@ -92,7 +92,7 @@ public static function provideValidInputs(): iterable yield 'named arguments: with normalization context option' => [ fn () => new Context(normalizationContext: ['foo' => 'bar']), << [ fn () => new Context(denormalizationContext: ['foo' => 'bar']), << [ fn () => new Context(context: ['foo' => 'bar'], groups: 'a'), << [ fn () => new Context(context: ['foo' => 'bar'], groups: ['a', 'b']), <<expectException(InvalidArgumentException::class); - $this->expectExceptionMessage('Parameter given to "Symfony\Component\Serializer\Annotation\MaxDepth" must be a positive integer.'); + $this->expectExceptionMessage('Parameter given to "Symfony\Component\Serializer\Attribute\MaxDepth" must be a positive integer.'); new MaxDepth($value); } diff --git a/Tests/Annotation/SerializedNameTest.php b/Tests/Annotation/SerializedNameTest.php index b19f91e55..c2b5e5f2a 100644 --- a/Tests/Annotation/SerializedNameTest.php +++ b/Tests/Annotation/SerializedNameTest.php @@ -12,7 +12,7 @@ namespace Symfony\Component\Serializer\Tests\Annotation; use PHPUnit\Framework\TestCase; -use Symfony\Component\Serializer\Annotation\SerializedName; +use Symfony\Component\Serializer\Attribute\SerializedName; use Symfony\Component\Serializer\Exception\InvalidArgumentException; /** @@ -23,7 +23,7 @@ class SerializedNameTest extends TestCase public function testNotAStringSerializedNameParameter() { $this->expectException(InvalidArgumentException::class); - $this->expectExceptionMessage('Parameter given to "Symfony\Component\Serializer\Annotation\SerializedName" must be a non-empty string.'); + $this->expectExceptionMessage('Parameter given to "Symfony\Component\Serializer\Attribute\SerializedName" must be a non-empty string.'); new SerializedName(''); } diff --git a/Tests/Annotation/SerializedPathTest.php b/Tests/Annotation/SerializedPathTest.php index 10c27f4b9..f5bbfa62b 100644 --- a/Tests/Annotation/SerializedPathTest.php +++ b/Tests/Annotation/SerializedPathTest.php @@ -13,7 +13,7 @@ use PHPUnit\Framework\TestCase; use Symfony\Component\PropertyAccess\PropertyPath; -use Symfony\Component\Serializer\Annotation\SerializedPath; +use Symfony\Component\Serializer\Attribute\SerializedPath; use Symfony\Component\Serializer\Exception\InvalidArgumentException; /** @@ -24,7 +24,7 @@ class SerializedPathTest extends TestCase public function testEmptyStringSerializedPathParameter() { $this->expectException(InvalidArgumentException::class); - $this->expectExceptionMessage('Parameter given to "Symfony\Component\Serializer\Annotation\SerializedPath" must be a valid property path.'); + $this->expectExceptionMessage('Parameter given to "Symfony\Component\Serializer\Attribute\SerializedPath" must be a valid property path.'); new SerializedPath(''); } diff --git a/Tests/Dummy/DummyClassOne.php b/Tests/Dummy/DummyClassOne.php index 67e80d2c6..2b3c94cb8 100644 --- a/Tests/Dummy/DummyClassOne.php +++ b/Tests/Dummy/DummyClassOne.php @@ -11,11 +11,11 @@ namespace Symfony\Component\Serializer\Tests\Dummy; -use Symfony\Component\Serializer\Annotation\Context; -use Symfony\Component\Serializer\Annotation\Groups; -use Symfony\Component\Serializer\Annotation\Ignore; -use Symfony\Component\Serializer\Annotation\MaxDepth; -use Symfony\Component\Serializer\Annotation\SerializedName; +use Symfony\Component\Serializer\Attribute\Context; +use Symfony\Component\Serializer\Attribute\Groups; +use Symfony\Component\Serializer\Attribute\Ignore; +use Symfony\Component\Serializer\Attribute\MaxDepth; +use Symfony\Component\Serializer\Attribute\SerializedName; class DummyClassOne { diff --git a/Tests/Fixtures/Annotations/AbstractDummy.php b/Tests/Fixtures/Annotations/AbstractDummy.php index fad842b0d..96069b0db 100644 --- a/Tests/Fixtures/Annotations/AbstractDummy.php +++ b/Tests/Fixtures/Annotations/AbstractDummy.php @@ -11,7 +11,7 @@ namespace Symfony\Component\Serializer\Tests\Fixtures\Annotations; -use Symfony\Component\Serializer\Annotation\DiscriminatorMap; +use Symfony\Component\Serializer\Attribute\DiscriminatorMap; /** * @DiscriminatorMap(typeProperty="type", mapping={ diff --git a/Tests/Fixtures/Annotations/BadMethodContextDummy.php b/Tests/Fixtures/Annotations/BadMethodContextDummy.php index 77b3884de..758897b53 100644 --- a/Tests/Fixtures/Annotations/BadMethodContextDummy.php +++ b/Tests/Fixtures/Annotations/BadMethodContextDummy.php @@ -11,7 +11,7 @@ namespace Symfony\Component\Serializer\Tests\Fixtures\Annotations; -use Symfony\Component\Serializer\Annotation\Context; +use Symfony\Component\Serializer\Attribute\Context; /** * @author Maxime Steinhausser diff --git a/Tests/Fixtures/Annotations/ContextDummy.php b/Tests/Fixtures/Annotations/ContextDummy.php index 804df290f..07fce9ebb 100644 --- a/Tests/Fixtures/Annotations/ContextDummy.php +++ b/Tests/Fixtures/Annotations/ContextDummy.php @@ -11,7 +11,7 @@ namespace Symfony\Component\Serializer\Tests\Fixtures\Annotations; -use Symfony\Component\Serializer\Annotation\Context; +use Symfony\Component\Serializer\Attribute\Context; /** * @author Maxime Steinhausser diff --git a/Tests/Fixtures/Annotations/ContextDummyParent.php b/Tests/Fixtures/Annotations/ContextDummyParent.php index b7b286c37..cdb81a778 100644 --- a/Tests/Fixtures/Annotations/ContextDummyParent.php +++ b/Tests/Fixtures/Annotations/ContextDummyParent.php @@ -11,7 +11,7 @@ namespace Symfony\Component\Serializer\Tests\Fixtures\Annotations; -use Symfony\Component\Serializer\Annotation\Context; +use Symfony\Component\Serializer\Attribute\Context; /** * @author Maxime Steinhausser diff --git a/Tests/Fixtures/Annotations/ContextDummyPromotedProperties.php b/Tests/Fixtures/Annotations/ContextDummyPromotedProperties.php index 5aa108d1e..e42985280 100644 --- a/Tests/Fixtures/Annotations/ContextDummyPromotedProperties.php +++ b/Tests/Fixtures/Annotations/ContextDummyPromotedProperties.php @@ -11,7 +11,7 @@ namespace Symfony\Component\Serializer\Tests\Fixtures\Annotations; -use Symfony\Component\Serializer\Annotation\Context; +use Symfony\Component\Serializer\Attribute\Context; /** * @author Maxime Steinhausser diff --git a/Tests/Fixtures/Annotations/Entity45016.php b/Tests/Fixtures/Annotations/Entity45016.php index a896d9b76..686331e58 100644 --- a/Tests/Fixtures/Annotations/Entity45016.php +++ b/Tests/Fixtures/Annotations/Entity45016.php @@ -2,7 +2,7 @@ namespace Symfony\Component\Serializer\Tests\Fixtures\Annotations; -use Symfony\Component\Serializer\Annotation\Ignore; +use Symfony\Component\Serializer\Attribute\Ignore; class Entity45016 { diff --git a/Tests/Fixtures/Annotations/GroupClassDummy.php b/Tests/Fixtures/Annotations/GroupClassDummy.php index e97732613..bbdcfc8a7 100644 --- a/Tests/Fixtures/Annotations/GroupClassDummy.php +++ b/Tests/Fixtures/Annotations/GroupClassDummy.php @@ -11,7 +11,7 @@ namespace Symfony\Component\Serializer\Tests\Fixtures\Annotations; -use Symfony\Component\Serializer\Annotation\Groups; +use Symfony\Component\Serializer\Attribute\Groups; /** * @Groups({"a"}) diff --git a/Tests/Fixtures/Annotations/GroupDummy.php b/Tests/Fixtures/Annotations/GroupDummy.php index 92935f490..6081511fa 100644 --- a/Tests/Fixtures/Annotations/GroupDummy.php +++ b/Tests/Fixtures/Annotations/GroupDummy.php @@ -11,7 +11,7 @@ namespace Symfony\Component\Serializer\Tests\Fixtures\Annotations; -use Symfony\Component\Serializer\Annotation\Groups; +use Symfony\Component\Serializer\Attribute\Groups; use Symfony\Component\Serializer\Tests\Fixtures\ChildOfGroupsAnnotationDummy; use Symfony\Component\Serializer\Tests\Fixtures\Annotations\GroupDummyInterface; diff --git a/Tests/Fixtures/Annotations/GroupDummyInterface.php b/Tests/Fixtures/Annotations/GroupDummyInterface.php index c9a736bfa..4341b1385 100644 --- a/Tests/Fixtures/Annotations/GroupDummyInterface.php +++ b/Tests/Fixtures/Annotations/GroupDummyInterface.php @@ -11,7 +11,7 @@ namespace Symfony\Component\Serializer\Tests\Fixtures\Annotations; -use Symfony\Component\Serializer\Annotation\Groups; +use Symfony\Component\Serializer\Attribute\Groups; /** * @author Kévin Dunglas diff --git a/Tests/Fixtures/Annotations/GroupDummyParent.php b/Tests/Fixtures/Annotations/GroupDummyParent.php index 77d539b94..ad94c87f3 100644 --- a/Tests/Fixtures/Annotations/GroupDummyParent.php +++ b/Tests/Fixtures/Annotations/GroupDummyParent.php @@ -11,7 +11,7 @@ namespace Symfony\Component\Serializer\Tests\Fixtures\Annotations; -use Symfony\Component\Serializer\Annotation\Groups; +use Symfony\Component\Serializer\Attribute\Groups; /** * @author Kévin Dunglas diff --git a/Tests/Fixtures/Annotations/IgnoreDummy.php b/Tests/Fixtures/Annotations/IgnoreDummy.php index 900447c58..fe81d0f76 100644 --- a/Tests/Fixtures/Annotations/IgnoreDummy.php +++ b/Tests/Fixtures/Annotations/IgnoreDummy.php @@ -11,7 +11,7 @@ namespace Symfony\Component\Serializer\Tests\Fixtures\Annotations; -use Symfony\Component\Serializer\Annotation\Ignore; +use Symfony\Component\Serializer\Attribute\Ignore; /** * @author Kévin Dunglas diff --git a/Tests/Fixtures/Annotations/IgnoreDummyAdditionalGetter.php b/Tests/Fixtures/Annotations/IgnoreDummyAdditionalGetter.php index 326a9cd07..257803dfc 100644 --- a/Tests/Fixtures/Annotations/IgnoreDummyAdditionalGetter.php +++ b/Tests/Fixtures/Annotations/IgnoreDummyAdditionalGetter.php @@ -2,7 +2,7 @@ namespace Symfony\Component\Serializer\Tests\Fixtures\Annotations; -use Symfony\Component\Serializer\Annotation\Ignore; +use Symfony\Component\Serializer\Attribute\Ignore; class IgnoreDummyAdditionalGetter { diff --git a/Tests/Fixtures/Annotations/MaxDepthDummy.php b/Tests/Fixtures/Annotations/MaxDepthDummy.php index 12be2db03..4f3044036 100644 --- a/Tests/Fixtures/Annotations/MaxDepthDummy.php +++ b/Tests/Fixtures/Annotations/MaxDepthDummy.php @@ -11,7 +11,7 @@ namespace Symfony\Component\Serializer\Tests\Fixtures\Annotations; -use Symfony\Component\Serializer\Annotation\MaxDepth; +use Symfony\Component\Serializer\Attribute\MaxDepth; /** * @author Kévin Dunglas diff --git a/Tests/Fixtures/Annotations/SerializedNameDummy.php b/Tests/Fixtures/Annotations/SerializedNameDummy.php index 1eaa579b4..b7c6055bc 100644 --- a/Tests/Fixtures/Annotations/SerializedNameDummy.php +++ b/Tests/Fixtures/Annotations/SerializedNameDummy.php @@ -11,7 +11,7 @@ namespace Symfony\Component\Serializer\Tests\Fixtures\Annotations; -use Symfony\Component\Serializer\Annotation\SerializedName; +use Symfony\Component\Serializer\Attribute\SerializedName; /** * @author Fabien Bourigault diff --git a/Tests/Fixtures/Annotations/SerializedPathDummy.php b/Tests/Fixtures/Annotations/SerializedPathDummy.php index cd50b81c3..76ee6bfda 100644 --- a/Tests/Fixtures/Annotations/SerializedPathDummy.php +++ b/Tests/Fixtures/Annotations/SerializedPathDummy.php @@ -11,7 +11,7 @@ namespace Symfony\Component\Serializer\Tests\Fixtures\Annotations; -use Symfony\Component\Serializer\Annotation\SerializedPath; +use Symfony\Component\Serializer\Attribute\SerializedPath; /** * @author Tobias Bönner diff --git a/Tests/Fixtures/Annotations/SerializedPathInConstructorDummy.php b/Tests/Fixtures/Annotations/SerializedPathInConstructorDummy.php index a6d510908..ebbe2b46e 100644 --- a/Tests/Fixtures/Annotations/SerializedPathInConstructorDummy.php +++ b/Tests/Fixtures/Annotations/SerializedPathInConstructorDummy.php @@ -11,7 +11,7 @@ namespace Symfony\Component\Serializer\Tests\Fixtures\Annotations; -use Symfony\Component\Serializer\Annotation\SerializedPath; +use Symfony\Component\Serializer\Attribute\SerializedPath; class SerializedPathInConstructorDummy { diff --git a/Tests/Fixtures/Attributes/AbstractDummy.php b/Tests/Fixtures/Attributes/AbstractDummy.php index 2e66f465b..a8c15fccb 100644 --- a/Tests/Fixtures/Attributes/AbstractDummy.php +++ b/Tests/Fixtures/Attributes/AbstractDummy.php @@ -11,7 +11,7 @@ namespace Symfony\Component\Serializer\Tests\Fixtures\Attributes; -use Symfony\Component\Serializer\Annotation\DiscriminatorMap; +use Symfony\Component\Serializer\Attribute\DiscriminatorMap; #[DiscriminatorMap(typeProperty: 'type', mapping: [ 'first' => AbstractDummyFirstChild::class, diff --git a/Tests/Fixtures/Attributes/BadAttributeDummy.php b/Tests/Fixtures/Attributes/BadAttributeDummy.php index a6bd82915..36c3b945b 100644 --- a/Tests/Fixtures/Attributes/BadAttributeDummy.php +++ b/Tests/Fixtures/Attributes/BadAttributeDummy.php @@ -11,7 +11,7 @@ namespace Symfony\Component\Serializer\Tests\Fixtures\Attributes; -use Symfony\Component\Serializer\Annotation\Groups; +use Symfony\Component\Serializer\Attribute\Groups; class BadAttributeDummy extends ContextDummyParent { diff --git a/Tests/Fixtures/Attributes/BadMethodContextDummy.php b/Tests/Fixtures/Attributes/BadMethodContextDummy.php index 090911af2..7ae9441d8 100644 --- a/Tests/Fixtures/Attributes/BadMethodContextDummy.php +++ b/Tests/Fixtures/Attributes/BadMethodContextDummy.php @@ -11,7 +11,7 @@ namespace Symfony\Component\Serializer\Tests\Fixtures\Attributes; -use Symfony\Component\Serializer\Annotation\Context; +use Symfony\Component\Serializer\Attribute\Context; /** * @author Maxime Steinhausser diff --git a/Tests/Fixtures/Attributes/ClassWithIgnoreAttribute.php b/Tests/Fixtures/Attributes/ClassWithIgnoreAttribute.php index 14d5e9472..fed0614b0 100644 --- a/Tests/Fixtures/Attributes/ClassWithIgnoreAttribute.php +++ b/Tests/Fixtures/Attributes/ClassWithIgnoreAttribute.php @@ -11,7 +11,7 @@ namespace Symfony\Component\Serializer\Tests\Fixtures\Attributes; -use Symfony\Component\Serializer\Annotation\Ignore; +use Symfony\Component\Serializer\Attribute\Ignore; class ClassWithIgnoreAttribute { diff --git a/Tests/Fixtures/Attributes/ContextDummy.php b/Tests/Fixtures/Attributes/ContextDummy.php index 464b9cab6..3d518950b 100644 --- a/Tests/Fixtures/Attributes/ContextDummy.php +++ b/Tests/Fixtures/Attributes/ContextDummy.php @@ -11,7 +11,7 @@ namespace Symfony\Component\Serializer\Tests\Fixtures\Attributes; -use Symfony\Component\Serializer\Annotation\Context; +use Symfony\Component\Serializer\Attribute\Context; /** * @author Maxime Steinhausser diff --git a/Tests/Fixtures/Attributes/ContextDummyParent.php b/Tests/Fixtures/Attributes/ContextDummyParent.php index 9480c953e..1ac1928fb 100644 --- a/Tests/Fixtures/Attributes/ContextDummyParent.php +++ b/Tests/Fixtures/Attributes/ContextDummyParent.php @@ -11,7 +11,7 @@ namespace Symfony\Component\Serializer\Tests\Fixtures\Attributes; -use Symfony\Component\Serializer\Annotation\Context; +use Symfony\Component\Serializer\Attribute\Context; /** * @author Maxime Steinhausser diff --git a/Tests/Fixtures/Attributes/ContextDummyPromotedProperties.php b/Tests/Fixtures/Attributes/ContextDummyPromotedProperties.php index 5dbc7d58e..5d9fb5eff 100644 --- a/Tests/Fixtures/Attributes/ContextDummyPromotedProperties.php +++ b/Tests/Fixtures/Attributes/ContextDummyPromotedProperties.php @@ -11,7 +11,7 @@ namespace Symfony\Component\Serializer\Tests\Fixtures\Attributes; -use Symfony\Component\Serializer\Annotation\Context; +use Symfony\Component\Serializer\Attribute\Context; /** * @author Maxime Steinhausser diff --git a/Tests/Fixtures/Attributes/Entity45016.php b/Tests/Fixtures/Attributes/Entity45016.php index 5a7ace0fd..e9d219a5f 100644 --- a/Tests/Fixtures/Attributes/Entity45016.php +++ b/Tests/Fixtures/Attributes/Entity45016.php @@ -2,7 +2,7 @@ namespace Symfony\Component\Serializer\Tests\Fixtures\Attributes; -use Symfony\Component\Serializer\Annotation\Ignore; +use Symfony\Component\Serializer\Attribute\Ignore; class Entity45016 { diff --git a/Tests/Fixtures/Attributes/GroupClassDummy.php b/Tests/Fixtures/Attributes/GroupClassDummy.php index 68289a9a8..abd7d0b03 100644 --- a/Tests/Fixtures/Attributes/GroupClassDummy.php +++ b/Tests/Fixtures/Attributes/GroupClassDummy.php @@ -11,7 +11,7 @@ namespace Symfony\Component\Serializer\Tests\Fixtures\Attributes; -use Symfony\Component\Serializer\Annotation\Groups; +use Symfony\Component\Serializer\Attribute\Groups; #[Groups('a')] class GroupClassDummy diff --git a/Tests/Fixtures/Attributes/GroupDummy.php b/Tests/Fixtures/Attributes/GroupDummy.php index c0a6c6d8e..749e841a5 100644 --- a/Tests/Fixtures/Attributes/GroupDummy.php +++ b/Tests/Fixtures/Attributes/GroupDummy.php @@ -11,7 +11,7 @@ namespace Symfony\Component\Serializer\Tests\Fixtures\Attributes; -use Symfony\Component\Serializer\Annotation\Groups; +use Symfony\Component\Serializer\Attribute\Groups; use Symfony\Component\Serializer\Tests\Fixtures\ChildOfGroupsAnnotationDummy; /** diff --git a/Tests/Fixtures/Attributes/GroupDummyInterface.php b/Tests/Fixtures/Attributes/GroupDummyInterface.php index 7920173ae..3f9ed159a 100644 --- a/Tests/Fixtures/Attributes/GroupDummyInterface.php +++ b/Tests/Fixtures/Attributes/GroupDummyInterface.php @@ -11,7 +11,7 @@ namespace Symfony\Component\Serializer\Tests\Fixtures\Attributes; -use Symfony\Component\Serializer\Annotation\Groups; +use Symfony\Component\Serializer\Attribute\Groups; /** * @author Kévin Dunglas diff --git a/Tests/Fixtures/Attributes/GroupDummyParent.php b/Tests/Fixtures/Attributes/GroupDummyParent.php index 39c73160f..de758be64 100644 --- a/Tests/Fixtures/Attributes/GroupDummyParent.php +++ b/Tests/Fixtures/Attributes/GroupDummyParent.php @@ -11,7 +11,7 @@ namespace Symfony\Component\Serializer\Tests\Fixtures\Attributes; -use Symfony\Component\Serializer\Annotation\Groups; +use Symfony\Component\Serializer\Attribute\Groups; /** * @author Kévin Dunglas diff --git a/Tests/Fixtures/Attributes/IgnoreDummy.php b/Tests/Fixtures/Attributes/IgnoreDummy.php index 85d7a9ca4..6e12f7c00 100644 --- a/Tests/Fixtures/Attributes/IgnoreDummy.php +++ b/Tests/Fixtures/Attributes/IgnoreDummy.php @@ -11,7 +11,7 @@ namespace Symfony\Component\Serializer\Tests\Fixtures\Attributes; -use Symfony\Component\Serializer\Annotation\Ignore; +use Symfony\Component\Serializer\Attribute\Ignore; /** * @author Kévin Dunglas diff --git a/Tests/Fixtures/Attributes/IgnoreDummyAdditionalGetter.php b/Tests/Fixtures/Attributes/IgnoreDummyAdditionalGetter.php index 274479e63..aa6439b48 100644 --- a/Tests/Fixtures/Attributes/IgnoreDummyAdditionalGetter.php +++ b/Tests/Fixtures/Attributes/IgnoreDummyAdditionalGetter.php @@ -2,7 +2,7 @@ namespace Symfony\Component\Serializer\Tests\Fixtures\Attributes; -use Symfony\Component\Serializer\Annotation\Ignore; +use Symfony\Component\Serializer\Attribute\Ignore; class IgnoreDummyAdditionalGetter { diff --git a/Tests/Fixtures/Attributes/MaxDepthDummy.php b/Tests/Fixtures/Attributes/MaxDepthDummy.php index 7a1dc42c2..8f45cbd78 100644 --- a/Tests/Fixtures/Attributes/MaxDepthDummy.php +++ b/Tests/Fixtures/Attributes/MaxDepthDummy.php @@ -11,7 +11,7 @@ namespace Symfony\Component\Serializer\Tests\Fixtures\Attributes; -use Symfony\Component\Serializer\Annotation\MaxDepth; +use Symfony\Component\Serializer\Attribute\MaxDepth; /** * @author Kévin Dunglas diff --git a/Tests/Fixtures/Attributes/SerializedNameDummy.php b/Tests/Fixtures/Attributes/SerializedNameDummy.php index fe0a67e83..27679b009 100644 --- a/Tests/Fixtures/Attributes/SerializedNameDummy.php +++ b/Tests/Fixtures/Attributes/SerializedNameDummy.php @@ -11,7 +11,7 @@ namespace Symfony\Component\Serializer\Tests\Fixtures\Attributes; -use Symfony\Component\Serializer\Annotation\SerializedName; +use Symfony\Component\Serializer\Attribute\SerializedName; /** * @author Fabien Bourigault diff --git a/Tests/Fixtures/Attributes/SerializedPathDummy.php b/Tests/Fixtures/Attributes/SerializedPathDummy.php index fc5d9f64a..8b627b792 100644 --- a/Tests/Fixtures/Attributes/SerializedPathDummy.php +++ b/Tests/Fixtures/Attributes/SerializedPathDummy.php @@ -11,7 +11,7 @@ namespace Symfony\Component\Serializer\Tests\Fixtures\Attributes; -use Symfony\Component\Serializer\Annotation\SerializedPath; +use Symfony\Component\Serializer\Attribute\SerializedPath; /** * @author Tobias Bönner diff --git a/Tests/Fixtures/Attributes/SerializedPathInConstructorDummy.php b/Tests/Fixtures/Attributes/SerializedPathInConstructorDummy.php index 90aee1154..e463429c0 100644 --- a/Tests/Fixtures/Attributes/SerializedPathInConstructorDummy.php +++ b/Tests/Fixtures/Attributes/SerializedPathInConstructorDummy.php @@ -11,7 +11,7 @@ namespace Symfony\Component\Serializer\Tests\Fixtures\Attributes; -use Symfony\Component\Serializer\Annotation\SerializedPath; +use Symfony\Component\Serializer\Attribute\SerializedPath; class SerializedPathInConstructorDummy { diff --git a/Tests/Fixtures/ChildOfGroupsAnnotationDummy.php b/Tests/Fixtures/ChildOfGroupsAnnotationDummy.php index 653758dca..210256eed 100644 --- a/Tests/Fixtures/ChildOfGroupsAnnotationDummy.php +++ b/Tests/Fixtures/ChildOfGroupsAnnotationDummy.php @@ -2,7 +2,7 @@ namespace Symfony\Component\Serializer\Tests\Fixtures; -use Symfony\Component\Serializer\Annotation\Groups; +use Symfony\Component\Serializer\Attribute\Groups; /** * @Annotation diff --git a/Tests/Fixtures/DummyMessageInterface.php b/Tests/Fixtures/DummyMessageInterface.php index 3ffb85829..31206ea67 100644 --- a/Tests/Fixtures/DummyMessageInterface.php +++ b/Tests/Fixtures/DummyMessageInterface.php @@ -11,7 +11,7 @@ namespace Symfony\Component\Serializer\Tests\Fixtures; -use Symfony\Component\Serializer\Annotation\DiscriminatorMap; +use Symfony\Component\Serializer\Attribute\DiscriminatorMap; /** * @author Samuel Roze diff --git a/Tests/Fixtures/DummyMessageNumberOne.php b/Tests/Fixtures/DummyMessageNumberOne.php index 663961b3f..45e682fbc 100644 --- a/Tests/Fixtures/DummyMessageNumberOne.php +++ b/Tests/Fixtures/DummyMessageNumberOne.php @@ -11,7 +11,7 @@ namespace Symfony\Component\Serializer\Tests\Fixtures; -use Symfony\Component\Serializer\Annotation\Groups; +use Symfony\Component\Serializer\Attribute\Groups; /** * @author Samuel Roze diff --git a/Tests/Fixtures/DummyMessageNumberTwo.php b/Tests/Fixtures/DummyMessageNumberTwo.php index 5a24e7c9f..ec2be9a81 100644 --- a/Tests/Fixtures/DummyMessageNumberTwo.php +++ b/Tests/Fixtures/DummyMessageNumberTwo.php @@ -11,7 +11,7 @@ namespace Symfony\Component\Serializer\Tests\Fixtures; -use Symfony\Component\Serializer\Annotation\Groups; +use Symfony\Component\Serializer\Attribute\Groups; /** * @author Samuel Roze diff --git a/Tests/Fixtures/OtherSerializedNameDummy.php b/Tests/Fixtures/OtherSerializedNameDummy.php index 58c5628e6..86fc6ead1 100644 --- a/Tests/Fixtures/OtherSerializedNameDummy.php +++ b/Tests/Fixtures/OtherSerializedNameDummy.php @@ -11,8 +11,8 @@ namespace Symfony\Component\Serializer\Tests\Fixtures; -use Symfony\Component\Serializer\Annotation\Groups; -use Symfony\Component\Serializer\Annotation\SerializedName; +use Symfony\Component\Serializer\Attribute\Groups; +use Symfony\Component\Serializer\Attribute\SerializedName; /** * @author Anthony GRASSIOT diff --git a/Tests/Mapping/Loader/AttributeLoaderWithAttributesTest.php b/Tests/Mapping/Loader/AttributeLoaderWithAttributesTest.php index c696b8c24..6f14d2fc0 100644 --- a/Tests/Mapping/Loader/AttributeLoaderWithAttributesTest.php +++ b/Tests/Mapping/Loader/AttributeLoaderWithAttributesTest.php @@ -30,7 +30,7 @@ protected function getNamespace(): string public function testLoadWithInvalidAttribute() { $this->expectException(MappingException::class); - $this->expectExceptionMessage('Could not instantiate attribute "Symfony\Component\Serializer\Annotation\Groups" on "Symfony\Component\Serializer\Tests\Fixtures\Attributes\BadAttributeDummy::myMethod()".'); + $this->expectExceptionMessage('Could not instantiate attribute "Symfony\Component\Serializer\Attribute\Groups" on "Symfony\Component\Serializer\Tests\Fixtures\Attributes\BadAttributeDummy::myMethod()".'); $classMetadata = new ClassMetadata($this->getNamespace().'\BadAttributeDummy'); diff --git a/Tests/NameConverter/MetadataAwareNameConverterTest.php b/Tests/NameConverter/MetadataAwareNameConverterTest.php index 7d6e71b07..c6ccd2601 100644 --- a/Tests/NameConverter/MetadataAwareNameConverterTest.php +++ b/Tests/NameConverter/MetadataAwareNameConverterTest.php @@ -12,8 +12,8 @@ namespace Symfony\Component\Serializer\Tests\NameConverter; use PHPUnit\Framework\TestCase; -use Symfony\Component\Serializer\Annotation\SerializedName; -use Symfony\Component\Serializer\Annotation\SerializedPath; +use Symfony\Component\Serializer\Attribute\SerializedName; +use Symfony\Component\Serializer\Attribute\SerializedPath; use Symfony\Component\Serializer\Exception\LogicException; use Symfony\Component\Serializer\Mapping\Factory\ClassMetadataFactory; use Symfony\Component\Serializer\Mapping\Factory\ClassMetadataFactoryInterface; diff --git a/Tests/Normalizer/AbstractObjectNormalizerTest.php b/Tests/Normalizer/AbstractObjectNormalizerTest.php index 0c8af279b..6da3e7392 100644 --- a/Tests/Normalizer/AbstractObjectNormalizerTest.php +++ b/Tests/Normalizer/AbstractObjectNormalizerTest.php @@ -16,10 +16,10 @@ use Symfony\Component\PropertyInfo\Extractor\ReflectionExtractor; use Symfony\Component\PropertyInfo\PropertyInfoExtractor; use Symfony\Component\PropertyInfo\Type; -use Symfony\Component\Serializer\Annotation\Context; -use Symfony\Component\Serializer\Annotation\DiscriminatorMap; -use Symfony\Component\Serializer\Annotation\SerializedName; -use Symfony\Component\Serializer\Annotation\SerializedPath; +use Symfony\Component\Serializer\Attribute\Context; +use Symfony\Component\Serializer\Attribute\DiscriminatorMap; +use Symfony\Component\Serializer\Attribute\SerializedName; +use Symfony\Component\Serializer\Attribute\SerializedPath; use Symfony\Component\Serializer\Exception\ExtraAttributesException; use Symfony\Component\Serializer\Exception\InvalidArgumentException; use Symfony\Component\Serializer\Exception\LogicException; diff --git a/Tests/Normalizer/Features/ContextMetadataTestTrait.php b/Tests/Normalizer/Features/ContextMetadataTestTrait.php index 3c9b18e95..10f5a0030 100644 --- a/Tests/Normalizer/Features/ContextMetadataTestTrait.php +++ b/Tests/Normalizer/Features/ContextMetadataTestTrait.php @@ -12,8 +12,8 @@ namespace Symfony\Component\Serializer\Tests\Normalizer\Features; use Symfony\Component\PropertyInfo\Extractor\PhpDocExtractor; -use Symfony\Component\Serializer\Annotation\Context; -use Symfony\Component\Serializer\Annotation\Groups; +use Symfony\Component\Serializer\Attribute\Context; +use Symfony\Component\Serializer\Attribute\Groups; use Symfony\Component\Serializer\Mapping\Factory\ClassMetadataFactory; use Symfony\Component\Serializer\Mapping\Loader\AttributeLoader; use Symfony\Component\Serializer\NameConverter\CamelCaseToSnakeCaseNameConverter; diff --git a/Tests/Normalizer/Features/DummyContextChild.php b/Tests/Normalizer/Features/DummyContextChild.php index 25f85d61f..8b5fe9ec3 100644 --- a/Tests/Normalizer/Features/DummyContextChild.php +++ b/Tests/Normalizer/Features/DummyContextChild.php @@ -11,7 +11,7 @@ namespace Symfony\Component\Serializer\Tests\Normalizer\Features; -use Symfony\Component\Serializer\Annotation\Context; +use Symfony\Component\Serializer\Attribute\Context; #[\Attribute(\Attribute::TARGET_CLASS | \Attribute::TARGET_METHOD | \Attribute::TARGET_PROPERTY | \Attribute::IS_REPEATABLE)] class DummyContextChild extends Context diff --git a/Tests/Normalizer/Features/ObjectDummyWithContextAttribute.php b/Tests/Normalizer/Features/ObjectDummyWithContextAttribute.php index b846f042f..0421a5c7b 100644 --- a/Tests/Normalizer/Features/ObjectDummyWithContextAttribute.php +++ b/Tests/Normalizer/Features/ObjectDummyWithContextAttribute.php @@ -11,8 +11,8 @@ namespace Symfony\Component\Serializer\Tests\Normalizer\Features; -use Symfony\Component\Serializer\Annotation\Context; -use Symfony\Component\Serializer\Annotation\SerializedName; +use Symfony\Component\Serializer\Attribute\Context; +use Symfony\Component\Serializer\Attribute\SerializedName; use Symfony\Component\Serializer\Normalizer\DateTimeNormalizer; final class ObjectDummyWithContextAttribute diff --git a/Tests/Normalizer/Features/TypedPropertiesObject.php b/Tests/Normalizer/Features/TypedPropertiesObject.php index e3acafdec..e830271cc 100644 --- a/Tests/Normalizer/Features/TypedPropertiesObject.php +++ b/Tests/Normalizer/Features/TypedPropertiesObject.php @@ -11,7 +11,7 @@ namespace Symfony\Component\Serializer\Tests\Normalizer\Features; -use Symfony\Component\Serializer\Annotation\Groups; +use Symfony\Component\Serializer\Attribute\Groups; class TypedPropertiesObject { From 803d8614eda7dac54d44171125aa897bac9ff160 Mon Sep 17 00:00:00 2001 From: Christian Flothmann Date: Tue, 31 Oct 2023 19:13:03 +0100 Subject: [PATCH 165/297] fix test --- Tests/Mapping/Loader/AttributeLoaderTest.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Tests/Mapping/Loader/AttributeLoaderTest.php b/Tests/Mapping/Loader/AttributeLoaderTest.php index 68c3fa9bf..f2353c8fa 100644 --- a/Tests/Mapping/Loader/AttributeLoaderTest.php +++ b/Tests/Mapping/Loader/AttributeLoaderTest.php @@ -223,7 +223,7 @@ public function testLoadGroupsOnClass() public function testLoadWithInvalidAttribute() { $this->expectException(MappingException::class); - $this->expectExceptionMessage('Could not instantiate attribute "Symfony\Component\Serializer\Annotation\Groups" on "Symfony\Component\Serializer\Tests\Fixtures\Attributes\BadAttributeDummy::myMethod()".'); + $this->expectExceptionMessage('Could not instantiate attribute "Symfony\Component\Serializer\Attribute\Groups" on "Symfony\Component\Serializer\Tests\Fixtures\Attributes\BadAttributeDummy::myMethod()".'); $classMetadata = new ClassMetadata(BadAttributeDummy::class); From b3ad1515a276473f7919ac97e560017284a7c4bf Mon Sep 17 00:00:00 2001 From: Oskar Stark Date: Tue, 7 Nov 2023 11:11:23 +0100 Subject: [PATCH 166/297] [Serializer] Fix `@method` annotation --- Normalizer/DenormalizerInterface.php | 2 +- Normalizer/NormalizerInterface.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Normalizer/DenormalizerInterface.php b/Normalizer/DenormalizerInterface.php index 4edb70096..d047ec298 100644 --- a/Normalizer/DenormalizerInterface.php +++ b/Normalizer/DenormalizerInterface.php @@ -22,7 +22,7 @@ /** * @author Jordi Boggiano * - * @method getSupportedTypes(?string $format): array + * @method array getSupportedTypes(?string $format) */ interface DenormalizerInterface { diff --git a/Normalizer/NormalizerInterface.php b/Normalizer/NormalizerInterface.php index 40779de31..5710167f4 100644 --- a/Normalizer/NormalizerInterface.php +++ b/Normalizer/NormalizerInterface.php @@ -19,7 +19,7 @@ /** * @author Jordi Boggiano * - * @method getSupportedTypes(?string $format): array + * @method array getSupportedTypes(?string $format) */ interface NormalizerInterface { From e85ca7192f053b221e0a1dc465eabd492b6b9030 Mon Sep 17 00:00:00 2001 From: Niels Keurentjes Date: Fri, 10 Nov 2023 16:54:38 +0100 Subject: [PATCH 167/297] [Routing] Extend old Annotations from new Attributes --- Annotation/Context.php | 2 +- Annotation/DiscriminatorMap.php | 2 +- Annotation/Groups.php | 2 +- Annotation/Ignore.php | 2 +- Annotation/MaxDepth.php | 2 +- Annotation/SerializedName.php | 2 +- Annotation/SerializedPath.php | 2 +- Attribute/Context.php | 2 ++ Attribute/DiscriminatorMap.php | 2 ++ Attribute/Groups.php | 2 ++ Attribute/Ignore.php | 4 +++- Attribute/MaxDepth.php | 2 ++ Attribute/SerializedName.php | 4 +++- Attribute/SerializedPath.php | 4 +++- 14 files changed, 24 insertions(+), 10 deletions(-) diff --git a/Annotation/Context.php b/Annotation/Context.php index eef833148..962169788 100644 --- a/Annotation/Context.php +++ b/Annotation/Context.php @@ -17,7 +17,7 @@ class_exists(\Symfony\Component\Serializer\Attribute\Context::class); if (false) { #[\Attribute(\Attribute::TARGET_CLASS | \Attribute::TARGET_PROPERTY | \Attribute::TARGET_METHOD | \Attribute::IS_REPEATABLE)] - class Context + class Context extends \Symfony\Component\Serializer\Attribute\Context { } } diff --git a/Annotation/DiscriminatorMap.php b/Annotation/DiscriminatorMap.php index 89b2ae9e8..c1a3021de 100644 --- a/Annotation/DiscriminatorMap.php +++ b/Annotation/DiscriminatorMap.php @@ -15,7 +15,7 @@ class_exists(\Symfony\Component\Serializer\Attribute\DiscriminatorMap::class); if (false) { #[\Attribute(\Attribute::TARGET_CLASS)] - class DiscriminatorMap + class DiscriminatorMap extends \Symfony\Component\Serializer\Attribute\DiscriminatorMap { } } diff --git a/Annotation/Groups.php b/Annotation/Groups.php index 89338b05c..642626f7f 100644 --- a/Annotation/Groups.php +++ b/Annotation/Groups.php @@ -15,7 +15,7 @@ class_exists(\Symfony\Component\Serializer\Attribute\Groups::class); if (false) { #[\Attribute(\Attribute::TARGET_METHOD | \Attribute::TARGET_PROPERTY | \Attribute::TARGET_CLASS)] - class Groups + class Groups extends \Symfony\Component\Serializer\Attribute\Groups { } } diff --git a/Annotation/Ignore.php b/Annotation/Ignore.php index 219ea725b..178eb4e6b 100644 --- a/Annotation/Ignore.php +++ b/Annotation/Ignore.php @@ -15,7 +15,7 @@ class_exists(\Symfony\Component\Serializer\Attribute\Ignore::class); if (false) { #[\Attribute(\Attribute::TARGET_METHOD | \Attribute::TARGET_PROPERTY)] - final class Ignore + class Ignore extends \Symfony\Component\Serializer\Attribute\Ignore { } } diff --git a/Annotation/MaxDepth.php b/Annotation/MaxDepth.php index 591e68ed9..ee1bc1ae7 100644 --- a/Annotation/MaxDepth.php +++ b/Annotation/MaxDepth.php @@ -15,7 +15,7 @@ class_exists(\Symfony\Component\Serializer\Attribute\MaxDepth::class); if (false) { #[\Attribute(\Attribute::TARGET_METHOD | \Attribute::TARGET_PROPERTY)] - class MaxDepth + class MaxDepth extends \Symfony\Component\Serializer\Attribute\MaxDepth { } } diff --git a/Annotation/SerializedName.php b/Annotation/SerializedName.php index 97c6e0aec..167064a0b 100644 --- a/Annotation/SerializedName.php +++ b/Annotation/SerializedName.php @@ -15,7 +15,7 @@ class_exists(\Symfony\Component\Serializer\Attribute\SerializedName::class); if (false) { #[\Attribute(\Attribute::TARGET_METHOD | \Attribute::TARGET_PROPERTY)] - final class SerializedName + class SerializedName extends \Symfony\Component\Serializer\Attribute\SerializedName { } } diff --git a/Annotation/SerializedPath.php b/Annotation/SerializedPath.php index f555a1086..76e4c7eff 100644 --- a/Annotation/SerializedPath.php +++ b/Annotation/SerializedPath.php @@ -15,7 +15,7 @@ class_exists(\Symfony\Component\Serializer\Attribute\SerializedPath::class); if (false) { #[\Attribute(\Attribute::TARGET_METHOD | \Attribute::TARGET_PROPERTY)] - final class SerializedPath + class SerializedPath extends \Symfony\Component\Serializer\Attribute\SerializedPath { } } diff --git a/Attribute/Context.php b/Attribute/Context.php index baa958839..d62c43046 100644 --- a/Attribute/Context.php +++ b/Attribute/Context.php @@ -21,6 +21,8 @@ * @Target({"PROPERTY", "METHOD"}) * * @author Maxime Steinhausser + * + * @final since Symfony 6.4 */ #[\Attribute(\Attribute::TARGET_CLASS | \Attribute::TARGET_PROPERTY | \Attribute::TARGET_METHOD | \Attribute::IS_REPEATABLE)] class Context diff --git a/Attribute/DiscriminatorMap.php b/Attribute/DiscriminatorMap.php index 4c1f23722..7bba10ab0 100644 --- a/Attribute/DiscriminatorMap.php +++ b/Attribute/DiscriminatorMap.php @@ -21,6 +21,8 @@ * @Target({"CLASS"}) * * @author Samuel Roze + * + * @final since Symfony 6.4 */ #[\Attribute(\Attribute::TARGET_CLASS)] class DiscriminatorMap diff --git a/Attribute/Groups.php b/Attribute/Groups.php index 9a351910a..386a2ce00 100644 --- a/Attribute/Groups.php +++ b/Attribute/Groups.php @@ -21,6 +21,8 @@ * @Target({"PROPERTY", "METHOD", "CLASS"}) * * @author Kévin Dunglas + * + * @final since Symfony 6.4 */ #[\Attribute(\Attribute::TARGET_METHOD | \Attribute::TARGET_PROPERTY | \Attribute::TARGET_CLASS)] class Groups diff --git a/Attribute/Ignore.php b/Attribute/Ignore.php index 688a1f3f4..577a77084 100644 --- a/Attribute/Ignore.php +++ b/Attribute/Ignore.php @@ -18,9 +18,11 @@ * @Target({"PROPERTY", "METHOD"}) * * @author Kévin Dunglas + * + * @final since Symfony 6.4 */ #[\Attribute(\Attribute::TARGET_METHOD | \Attribute::TARGET_PROPERTY)] -final class Ignore +class Ignore { } diff --git a/Attribute/MaxDepth.php b/Attribute/MaxDepth.php index 3ecfcb993..bbd190ccf 100644 --- a/Attribute/MaxDepth.php +++ b/Attribute/MaxDepth.php @@ -21,6 +21,8 @@ * @Target({"PROPERTY", "METHOD"}) * * @author Kévin Dunglas + * + * @final since Symfony 6.4 */ #[\Attribute(\Attribute::TARGET_METHOD | \Attribute::TARGET_PROPERTY)] class MaxDepth diff --git a/Attribute/SerializedName.php b/Attribute/SerializedName.php index a2651282f..2afd8d9ed 100644 --- a/Attribute/SerializedName.php +++ b/Attribute/SerializedName.php @@ -21,9 +21,11 @@ * @Target({"PROPERTY", "METHOD"}) * * @author Fabien Bourigault + * + * @final since Symfony 6.4 */ #[\Attribute(\Attribute::TARGET_METHOD | \Attribute::TARGET_PROPERTY)] -final class SerializedName +class SerializedName { public function __construct(private readonly string $serializedName) { diff --git a/Attribute/SerializedPath.php b/Attribute/SerializedPath.php index 0e5f04087..eed46c518 100644 --- a/Attribute/SerializedPath.php +++ b/Attribute/SerializedPath.php @@ -23,9 +23,11 @@ * @Target({"PROPERTY", "METHOD"}) * * @author Tobias Bönner + * + * @final since Symfony 6.4 */ #[\Attribute(\Attribute::TARGET_METHOD | \Attribute::TARGET_PROPERTY)] -final class SerializedPath +class SerializedPath { private PropertyPath $serializedPath; From d823f5e9641ad3f54f66f38803221be9a726681a Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Wed, 15 Nov 2023 16:44:10 +0100 Subject: [PATCH 168/297] [7.0] minor cleanup --- Attribute/Context.php | 2 +- Attribute/DiscriminatorMap.php | 2 +- Attribute/Groups.php | 2 +- Attribute/Ignore.php | 2 +- Attribute/MaxDepth.php | 2 +- Attribute/SerializedName.php | 2 +- Attribute/SerializedPath.php | 2 +- 7 files changed, 7 insertions(+), 7 deletions(-) diff --git a/Attribute/Context.php b/Attribute/Context.php index 5c1f0eee1..dc0301823 100644 --- a/Attribute/Context.php +++ b/Attribute/Context.php @@ -16,7 +16,7 @@ /** * @author Maxime Steinhausser * - * @final since Symfony 6.4 + * @final */ #[\Attribute(\Attribute::TARGET_CLASS | \Attribute::TARGET_PROPERTY | \Attribute::TARGET_METHOD | \Attribute::IS_REPEATABLE)] class Context diff --git a/Attribute/DiscriminatorMap.php b/Attribute/DiscriminatorMap.php index ecba7b903..31a993207 100644 --- a/Attribute/DiscriminatorMap.php +++ b/Attribute/DiscriminatorMap.php @@ -16,7 +16,7 @@ /** * @author Samuel Roze * - * @final since Symfony 6.4 + * @final */ #[\Attribute(\Attribute::TARGET_CLASS)] class DiscriminatorMap diff --git a/Attribute/Groups.php b/Attribute/Groups.php index b90315645..14dabd298 100644 --- a/Attribute/Groups.php +++ b/Attribute/Groups.php @@ -16,7 +16,7 @@ /** * @author Kévin Dunglas * - * @final since Symfony 6.4 + * @final */ #[\Attribute(\Attribute::TARGET_METHOD | \Attribute::TARGET_PROPERTY | \Attribute::TARGET_CLASS)] class Groups diff --git a/Attribute/Ignore.php b/Attribute/Ignore.php index 0121ebfed..da471c2ab 100644 --- a/Attribute/Ignore.php +++ b/Attribute/Ignore.php @@ -14,7 +14,7 @@ /** * @author Kévin Dunglas * - * @final since Symfony 6.4 + * @final */ #[\Attribute(\Attribute::TARGET_METHOD | \Attribute::TARGET_PROPERTY)] class Ignore diff --git a/Attribute/MaxDepth.php b/Attribute/MaxDepth.php index 60cb20890..0373b891d 100644 --- a/Attribute/MaxDepth.php +++ b/Attribute/MaxDepth.php @@ -16,7 +16,7 @@ /** * @author Kévin Dunglas * - * @final since Symfony 6.4 + * @final */ #[\Attribute(\Attribute::TARGET_METHOD | \Attribute::TARGET_PROPERTY)] class MaxDepth diff --git a/Attribute/SerializedName.php b/Attribute/SerializedName.php index 407a2675b..e1fb47814 100644 --- a/Attribute/SerializedName.php +++ b/Attribute/SerializedName.php @@ -16,7 +16,7 @@ /** * @author Fabien Bourigault * - * @final since Symfony 6.4 + * @final */ #[\Attribute(\Attribute::TARGET_METHOD | \Attribute::TARGET_PROPERTY)] class SerializedName diff --git a/Attribute/SerializedPath.php b/Attribute/SerializedPath.php index 42ff34000..ea3c8f90e 100644 --- a/Attribute/SerializedPath.php +++ b/Attribute/SerializedPath.php @@ -18,7 +18,7 @@ /** * @author Tobias Bönner * - * @final since Symfony 6.4 + * @final */ #[\Attribute(\Attribute::TARGET_METHOD | \Attribute::TARGET_PROPERTY)] class SerializedPath From 5caeb92fdb427e0e87afd633c735c76f8cb18468 Mon Sep 17 00:00:00 2001 From: Mathias Arlaud Date: Mon, 20 Nov 2023 09:08:48 +0100 Subject: [PATCH 169/297] [Serializer] Remove wrong final tags --- Attribute/Context.php | 2 -- Attribute/DiscriminatorMap.php | 2 -- Attribute/Groups.php | 2 -- Attribute/Ignore.php | 2 -- Attribute/MaxDepth.php | 2 -- Attribute/SerializedName.php | 2 -- Attribute/SerializedPath.php | 2 -- 7 files changed, 14 deletions(-) diff --git a/Attribute/Context.php b/Attribute/Context.php index d62c43046..baa958839 100644 --- a/Attribute/Context.php +++ b/Attribute/Context.php @@ -21,8 +21,6 @@ * @Target({"PROPERTY", "METHOD"}) * * @author Maxime Steinhausser - * - * @final since Symfony 6.4 */ #[\Attribute(\Attribute::TARGET_CLASS | \Attribute::TARGET_PROPERTY | \Attribute::TARGET_METHOD | \Attribute::IS_REPEATABLE)] class Context diff --git a/Attribute/DiscriminatorMap.php b/Attribute/DiscriminatorMap.php index 7bba10ab0..4c1f23722 100644 --- a/Attribute/DiscriminatorMap.php +++ b/Attribute/DiscriminatorMap.php @@ -21,8 +21,6 @@ * @Target({"CLASS"}) * * @author Samuel Roze - * - * @final since Symfony 6.4 */ #[\Attribute(\Attribute::TARGET_CLASS)] class DiscriminatorMap diff --git a/Attribute/Groups.php b/Attribute/Groups.php index 386a2ce00..9a351910a 100644 --- a/Attribute/Groups.php +++ b/Attribute/Groups.php @@ -21,8 +21,6 @@ * @Target({"PROPERTY", "METHOD", "CLASS"}) * * @author Kévin Dunglas - * - * @final since Symfony 6.4 */ #[\Attribute(\Attribute::TARGET_METHOD | \Attribute::TARGET_PROPERTY | \Attribute::TARGET_CLASS)] class Groups diff --git a/Attribute/Ignore.php b/Attribute/Ignore.php index 577a77084..2e04a45ff 100644 --- a/Attribute/Ignore.php +++ b/Attribute/Ignore.php @@ -18,8 +18,6 @@ * @Target({"PROPERTY", "METHOD"}) * * @author Kévin Dunglas - * - * @final since Symfony 6.4 */ #[\Attribute(\Attribute::TARGET_METHOD | \Attribute::TARGET_PROPERTY)] class Ignore diff --git a/Attribute/MaxDepth.php b/Attribute/MaxDepth.php index bbd190ccf..3ecfcb993 100644 --- a/Attribute/MaxDepth.php +++ b/Attribute/MaxDepth.php @@ -21,8 +21,6 @@ * @Target({"PROPERTY", "METHOD"}) * * @author Kévin Dunglas - * - * @final since Symfony 6.4 */ #[\Attribute(\Attribute::TARGET_METHOD | \Attribute::TARGET_PROPERTY)] class MaxDepth diff --git a/Attribute/SerializedName.php b/Attribute/SerializedName.php index 2afd8d9ed..e278990c9 100644 --- a/Attribute/SerializedName.php +++ b/Attribute/SerializedName.php @@ -21,8 +21,6 @@ * @Target({"PROPERTY", "METHOD"}) * * @author Fabien Bourigault - * - * @final since Symfony 6.4 */ #[\Attribute(\Attribute::TARGET_METHOD | \Attribute::TARGET_PROPERTY)] class SerializedName diff --git a/Attribute/SerializedPath.php b/Attribute/SerializedPath.php index eed46c518..18b84ed53 100644 --- a/Attribute/SerializedPath.php +++ b/Attribute/SerializedPath.php @@ -23,8 +23,6 @@ * @Target({"PROPERTY", "METHOD"}) * * @author Tobias Bönner - * - * @final since Symfony 6.4 */ #[\Attribute(\Attribute::TARGET_METHOD | \Attribute::TARGET_PROPERTY)] class SerializedPath From b57f39c5660e0f2a1696406d300afe03f9bd6aeb Mon Sep 17 00:00:00 2001 From: Christian Flothmann Date: Mon, 20 Nov 2023 23:02:28 +0100 Subject: [PATCH 170/297] fix tests --- Tests/SerializerTest.php | 9 --------- 1 file changed, 9 deletions(-) diff --git a/Tests/SerializerTest.php b/Tests/SerializerTest.php index c7bce9bc9..c6a35b621 100644 --- a/Tests/SerializerTest.php +++ b/Tests/SerializerTest.php @@ -1054,15 +1054,6 @@ public function testCollectDenormalizationErrors(?ClassMetadataFactory $classMet 'useMessageForUser' => false, 'message' => 'The type of the "something" attribute for class "Symfony\Component\Serializer\Tests\Fixtures\Php74FullWithTypedConstructor" must be one of "float" ("string" given).', ], - [ - 'currentType' => 'string', - 'expectedTypes' => [ - 'float', - ], - 'path' => 'php74FullWithTypedConstructor.something', - 'useMessageForUser' => false, - 'message' => 'The type of the "something" attribute for class "Symfony\Component\Serializer\Tests\Fixtures\Php74FullWithTypedConstructor" must be one of "float" ("string" given).', - ], [ 'currentType' => 'string', 'expectedTypes' => [ From 536ad5240a65695fabcb230f9d9556deb91a850c Mon Sep 17 00:00:00 2001 From: Christian Flothmann Date: Fri, 24 Nov 2023 14:23:54 +0100 Subject: [PATCH 171/297] do not detect the deserialization_path context value twice --- Normalizer/AbstractNormalizer.php | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/Normalizer/AbstractNormalizer.php b/Normalizer/AbstractNormalizer.php index 274a3cbe9..2e11136ba 100644 --- a/Normalizer/AbstractNormalizer.php +++ b/Normalizer/AbstractNormalizer.php @@ -342,15 +342,12 @@ protected function instantiateObject(array &$data, string $class, array &$contex $missingConstructorArguments = []; $params = []; $unsetKeys = []; - $objectDeserializationPath = $context['deserialization_path'] ?? null; foreach ($constructorParameters as $constructorParameter) { $paramName = $constructorParameter->name; $attributeContext = $this->getAttributeDenormalizationContext($class, $paramName, $context); $key = $this->nameConverter ? $this->nameConverter->normalize($paramName, $class, $format, $context) : $paramName; - $context['deserialization_path'] = $objectDeserializationPath ? $objectDeserializationPath.'.'.$paramName : $paramName; - $allowed = false === $allowedAttributes || \in_array($paramName, $allowedAttributes); $ignored = !$this->isAllowedAttribute($class, $paramName, $format, $context); if ($constructorParameter->isVariadic()) { @@ -406,15 +403,13 @@ protected function instantiateObject(array &$data, string $class, array &$contex sprintf('Failed to create object because the class misses the "%s" property.', $constructorParameter->name), $data, ['unknown'], - $objectDeserializationPath, + $context['deserialization_path'] ?? null, true ); $context['not_normalizable_value_exceptions'][] = $exception; } } - $context['deserialization_path'] = $objectDeserializationPath; - if ($missingConstructorArguments) { throw new MissingConstructorArgumentsException(sprintf('Cannot create an instance of "%s" from serialized data because its constructor requires the following parameters to be present : "$%s".', $class, implode('", "$', $missingConstructorArguments)), 0, null, $missingConstructorArguments, $class); } From 2f9f3021f3aa3df12e480a5e779995e15a6b62aa Mon Sep 17 00:00:00 2001 From: Christian Flothmann Date: Fri, 24 Nov 2023 16:10:40 +0100 Subject: [PATCH 172/297] fix merge --- Normalizer/AbstractObjectNormalizer.php | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/Normalizer/AbstractObjectNormalizer.php b/Normalizer/AbstractObjectNormalizer.php index 864544682..7a91de5c8 100644 --- a/Normalizer/AbstractObjectNormalizer.php +++ b/Normalizer/AbstractObjectNormalizer.php @@ -185,11 +185,10 @@ public function normalize(mixed $object, string $format = null, array $context = } $attributeContext = $this->getAttributeNormalizationContext($object, $attribute, $context); - $discriminatorMapping = $this->classDiscriminatorResolver?->getMappingForMappedObject($object); try { - $attributeValue = $attribute === $discriminatorMapping?->getTypeProperty() - ? $discriminatorMapping + $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) { From 5759be6fdeb091b7e0cb1676bd15b6ea2f38f216 Mon Sep 17 00:00:00 2001 From: Christian Flothmann Date: Fri, 24 Nov 2023 22:53:43 +0100 Subject: [PATCH 173/297] add return types to test fixtures --- Tests/Fixtures/DummyString.php | 2 +- Tests/Fixtures/NotNormalizableDummy.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Tests/Fixtures/DummyString.php b/Tests/Fixtures/DummyString.php index 056de3003..15bcc6e6b 100644 --- a/Tests/Fixtures/DummyString.php +++ b/Tests/Fixtures/DummyString.php @@ -22,7 +22,7 @@ class DummyString implements DenormalizableInterface /** @var string $value */ public $value; - public function denormalize(DenormalizerInterface $denormalizer, $data, string $format = null, array $context = []) + public function denormalize(DenormalizerInterface $denormalizer, $data, string $format = null, array $context = []): void { $this->value = $data; } diff --git a/Tests/Fixtures/NotNormalizableDummy.php b/Tests/Fixtures/NotNormalizableDummy.php index 8bb655db9..e8c64f577 100644 --- a/Tests/Fixtures/NotNormalizableDummy.php +++ b/Tests/Fixtures/NotNormalizableDummy.php @@ -24,7 +24,7 @@ public function __construct() { } - public function denormalize(DenormalizerInterface $denormalizer, $data, string $format = null, array $context = []) + public function denormalize(DenormalizerInterface $denormalizer, $data, string $format = null, array $context = []): void { throw new NotNormalizableValueException(); } From ff423e8e466cc0b0cc01add8e9494d11abfd906e Mon Sep 17 00:00:00 2001 From: Christian Flothmann Date: Sat, 25 Nov 2023 00:28:43 +0100 Subject: [PATCH 174/297] fix tests --- .../Normalizer/AbstractObjectNormalizerTest.php | 10 +++++----- Tests/Normalizer/GetSetMethodNormalizerTest.php | 16 +++++++--------- Tests/Normalizer/PropertyNormalizerTest.php | 16 +++++++--------- 3 files changed, 19 insertions(+), 23 deletions(-) diff --git a/Tests/Normalizer/AbstractObjectNormalizerTest.php b/Tests/Normalizer/AbstractObjectNormalizerTest.php index 2084d83b5..49f19666c 100644 --- a/Tests/Normalizer/AbstractObjectNormalizerTest.php +++ b/Tests/Normalizer/AbstractObjectNormalizerTest.php @@ -837,12 +837,12 @@ public function testDenormalizeWithCorrectOrderOfAttributeAndProperty() $this->assertSame('nested-id', $test->id); } - public function testNormalizeWithIgnoreAnnotationAndPrivateProperties() + public function testNormalizeWithIgnoreAttributeAndPrivateProperties() { - $classMetadataFactory = new ClassMetadataFactory(new AnnotationLoader(new AnnotationReader())); + $classMetadataFactory = new ClassMetadataFactory(new AttributeLoader()); $serializer = new Serializer([new ObjectNormalizer($classMetadataFactory)]); - $this->assertSame(['foo' => 'foo'], $serializer->normalize(new ObjectDummyWithIgnoreAnnotationAndPrivateProperty())); + $this->assertSame(['foo' => 'foo'], $serializer->normalize(new ObjectDummyWithIgnoreAttributeAndPrivateProperty())); } public function testDenormalizeUntypedFormat() @@ -1054,11 +1054,11 @@ class ObjectDummyWithContextAttributeSkipNullValues public ?string $propertyWithNullSkipNullValues = null; } -class ObjectDummyWithIgnoreAnnotationAndPrivateProperty +class ObjectDummyWithIgnoreAttributeAndPrivateProperty { public $foo = 'foo'; - /** @Ignore */ + #[Ignore] public $ignored = 'ignored'; private $private = 'private'; diff --git a/Tests/Normalizer/GetSetMethodNormalizerTest.php b/Tests/Normalizer/GetSetMethodNormalizerTest.php index 5e9be09bf..1d471981e 100644 --- a/Tests/Normalizer/GetSetMethodNormalizerTest.php +++ b/Tests/Normalizer/GetSetMethodNormalizerTest.php @@ -16,7 +16,7 @@ use Symfony\Component\PropertyInfo\Extractor\PhpDocExtractor; use Symfony\Component\PropertyInfo\Extractor\ReflectionExtractor; use Symfony\Component\PropertyInfo\PropertyInfoExtractor; -use Symfony\Component\Serializer\Annotation\DiscriminatorMap; +use Symfony\Component\Serializer\Attribute\DiscriminatorMap; use Symfony\Component\Serializer\Exception\LogicException; use Symfony\Component\Serializer\Mapping\ClassDiscriminatorFromClassMetadata; use Symfony\Component\Serializer\Mapping\Factory\ClassMetadataFactory; @@ -494,7 +494,7 @@ protected function getNormalizerForSkipUninitializedValues(): NormalizerInterfac public function testNormalizeWithDiscriminator() { - $classMetadataFactory = new ClassMetadataFactory(new AnnotationLoader(new AnnotationReader())); + $classMetadataFactory = new ClassMetadataFactory(new AttributeLoader()); $discriminator = new ClassDiscriminatorFromClassMetadata($classMetadataFactory); $normalizer = new GetSetMethodNormalizer($classMetadataFactory, null, null, $discriminator); @@ -503,7 +503,7 @@ public function testNormalizeWithDiscriminator() public function testDenormalizeWithDiscriminator() { - $classMetadataFactory = new ClassMetadataFactory(new AnnotationLoader(new AnnotationReader())); + $classMetadataFactory = new ClassMetadataFactory(new AttributeLoader()); $discriminator = new ClassDiscriminatorFromClassMetadata($classMetadataFactory); $normalizer = new GetSetMethodNormalizer($classMetadataFactory, null, null, $discriminator); @@ -777,12 +777,10 @@ public function __call($key, $value) } } -/** - * @DiscriminatorMap(typeProperty="type", mapping={ - * "one" = GetSetMethodDiscriminatedDummyOne::class, - * "two" = GetSetMethodDiscriminatedDummyTwo::class, - * }) - */ +#[DiscriminatorMap(typeProperty: 'type', mapping: [ + 'one' => GetSetMethodDiscriminatedDummyOne::class, + 'two' => GetSetMethodDiscriminatedDummyTwo::class, +])] interface GetSetMethodDummyInterface { } diff --git a/Tests/Normalizer/PropertyNormalizerTest.php b/Tests/Normalizer/PropertyNormalizerTest.php index 8461ecee5..631111d2a 100644 --- a/Tests/Normalizer/PropertyNormalizerTest.php +++ b/Tests/Normalizer/PropertyNormalizerTest.php @@ -15,7 +15,7 @@ use Symfony\Component\PropertyInfo\Extractor\PhpDocExtractor; use Symfony\Component\PropertyInfo\Extractor\ReflectionExtractor; use Symfony\Component\PropertyInfo\PropertyInfoExtractor; -use Symfony\Component\Serializer\Annotation\DiscriminatorMap; +use Symfony\Component\Serializer\Attribute\DiscriminatorMap; use Symfony\Component\Serializer\Exception\LogicException; use Symfony\Component\Serializer\Mapping\ClassDiscriminatorFromClassMetadata; use Symfony\Component\Serializer\Mapping\Factory\ClassMetadataFactory; @@ -499,7 +499,7 @@ protected function getNormalizerForSkipUninitializedValues(): NormalizerInterfac public function testNormalizeWithDiscriminator() { - $classMetadataFactory = new ClassMetadataFactory(new AnnotationLoader(new AnnotationReader())); + $classMetadataFactory = new ClassMetadataFactory(new AttributeLoader()); $discriminator = new ClassDiscriminatorFromClassMetadata($classMetadataFactory); $normalizer = new PropertyNormalizer($classMetadataFactory, null, null, $discriminator); @@ -508,7 +508,7 @@ public function testNormalizeWithDiscriminator() public function testDenormalizeWithDiscriminator() { - $classMetadataFactory = new ClassMetadataFactory(new AnnotationLoader(new AnnotationReader())); + $classMetadataFactory = new ClassMetadataFactory(new AttributeLoader()); $discriminator = new ClassDiscriminatorFromClassMetadata($classMetadataFactory); $normalizer = new PropertyNormalizer($classMetadataFactory, null, null, $discriminator); @@ -621,12 +621,10 @@ public function getIntMatrix(): array } } -/** - * @DiscriminatorMap(typeProperty="type", mapping={ - * "one" = PropertyDiscriminatedDummyOne::class, - * "two" = PropertyDiscriminatedDummyTwo::class, - * }) - */ +#[DiscriminatorMap(typeProperty: 'type', mapping: [ + 'one' => PropertyDiscriminatedDummyOne::class, + 'two' => PropertyDiscriminatedDummyTwo::class, +])] interface PropertyDummyInterface { } From 714bbe96c195d07757a6a33bbe558d3fefb6fd0c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Sch=C3=A4dlich?= Date: Sat, 25 Nov 2023 10:37:56 +0100 Subject: [PATCH 175/297] [Serializer] Consider SerializedPath in debug command output --- Command/DebugCommand.php | 1 + Tests/Command/DebugCommandTest.php | 2 ++ Tests/Dummy/DummyClassOne.php | 2 ++ 3 files changed, 5 insertions(+) diff --git a/Command/DebugCommand.php b/Command/DebugCommand.php index 13873dd1f..c85ee213e 100644 --- a/Command/DebugCommand.php +++ b/Command/DebugCommand.php @@ -102,6 +102,7 @@ private function getAttributesData(ClassMetadataInterface $classMetadata): array 'groups' => $attributeMetadata->getGroups(), 'maxDepth' => $attributeMetadata->getMaxDepth(), 'serializedName' => $attributeMetadata->getSerializedName(), + 'serializedPath' => $attributeMetadata->getSerializedPath() ? (string) $attributeMetadata->getSerializedPath() : null, 'ignore' => $attributeMetadata->isIgnored(), 'normalizationContexts' => $attributeMetadata->getNormalizationContexts(), 'denormalizationContexts' => $attributeMetadata->getDenormalizationContexts(), diff --git a/Tests/Command/DebugCommandTest.php b/Tests/Command/DebugCommandTest.php index 879231160..5b4f73c17 100644 --- a/Tests/Command/DebugCommandTest.php +++ b/Tests/Command/DebugCommandTest.php @@ -46,6 +46,7 @@ public function testOutputWithClassArgument() | | ], | | | "maxDepth" => 1, | | | "serializedName" => "identifier", | + | | "serializedPath" => null, | | | "ignore" => true, | | | "normalizationContexts" => [ | | | "*" => [ | @@ -66,6 +67,7 @@ public function testOutputWithClassArgument() | | "groups" => [], | | | "maxDepth" => null, | | | "serializedName" => null, | + | | "serializedPath" => [data][name], | | | "ignore" => false, | | | "normalizationContexts" => [], | | | "denormalizationContexts" => [] | diff --git a/Tests/Dummy/DummyClassOne.php b/Tests/Dummy/DummyClassOne.php index 2b3c94cb8..fc78db51c 100644 --- a/Tests/Dummy/DummyClassOne.php +++ b/Tests/Dummy/DummyClassOne.php @@ -16,6 +16,7 @@ use Symfony\Component\Serializer\Attribute\Ignore; use Symfony\Component\Serializer\Attribute\MaxDepth; use Symfony\Component\Serializer\Attribute\SerializedName; +use Symfony\Component\Serializer\Attribute\SerializedPath; class DummyClassOne { @@ -29,5 +30,6 @@ class DummyClassOne )] public string $code; + #[SerializedPath('[data][name]')] public string $name; } From 1a0e5c16759bd2917f6d4a8e74059e49bce1bbd1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Sch=C3=A4dlich?= Date: Sun, 26 Nov 2023 20:01:11 +0100 Subject: [PATCH 176/297] Fix DebugCommandTest --- Tests/Command/DebugCommandTest.php | 74 +++++++++++++++--------------- 1 file changed, 37 insertions(+), 37 deletions(-) diff --git a/Tests/Command/DebugCommandTest.php b/Tests/Command/DebugCommandTest.php index 5b4f73c17..7bfdf93dd 100644 --- a/Tests/Command/DebugCommandTest.php +++ b/Tests/Command/DebugCommandTest.php @@ -36,43 +36,43 @@ public function testOutputWithClassArgument() Symfony\Component\Serializer\Tests\Dummy\DummyClassOne ------------------------------------------------------ - +----------+-------------------------------------+ - | Property | Options | - +----------+-------------------------------------+ - | code | [ | - | | "groups" => [ | - | | "book:read", | - | | "book:write" | - | | ], | - | | "maxDepth" => 1, | - | | "serializedName" => "identifier", | - | | "serializedPath" => null, | - | | "ignore" => true, | - | | "normalizationContexts" => [ | - | | "*" => [ | - | | "groups" => [ | - | | "book:read" | - | | ] | - | | ] | - | | ], | - | | "denormalizationContexts" => [ | - | | "*" => [ | - | | "groups" => [ | - | | "book:write" | - | | ] | - | | ] | - | | ] | - | | ] | - | name | [ | - | | "groups" => [], | - | | "maxDepth" => null, | - | | "serializedName" => null, | - | | "serializedPath" => [data][name], | - | | "ignore" => false, | - | | "normalizationContexts" => [], | - | | "denormalizationContexts" => [] | - | | ] | - +----------+-------------------------------------+ + +----------+---------------------------------------+ + | Property | Options | + +----------+---------------------------------------+ + | code | [ | + | | "groups" => [ | + | | "book:read", | + | | "book:write" | + | | ], | + | | "maxDepth" => 1, | + | | "serializedName" => "identifier", | + | | "serializedPath" => null, | + | | "ignore" => true, | + | | "normalizationContexts" => [ | + | | "*" => [ | + | | "groups" => [ | + | | "book:read" | + | | ] | + | | ] | + | | ], | + | | "denormalizationContexts" => [ | + | | "*" => [ | + | | "groups" => [ | + | | "book:write" | + | | ] | + | | ] | + | | ] | + | | ] | + | name | [ | + | | "groups" => [], | + | | "maxDepth" => null, | + | | "serializedName" => null, | + | | "serializedPath" => "[data][name]", | + | | "ignore" => false, | + | | "normalizationContexts" => [], | + | | "denormalizationContexts" => [] | + | | ] | + +----------+---------------------------------------+ TXT, $tester->getDisplay(true), From 868bca198010047f4b8714e100810b3e6cd53c32 Mon Sep 17 00:00:00 2001 From: Alexandre Daubois Date: Wed, 29 Nov 2023 09:26:06 +0100 Subject: [PATCH 177/297] [Serializer] Fix anonymous test class --- Tests/Normalizer/AbstractObjectNormalizerTest.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Tests/Normalizer/AbstractObjectNormalizerTest.php b/Tests/Normalizer/AbstractObjectNormalizerTest.php index 259c1d8a4..60eaaad7f 100644 --- a/Tests/Normalizer/AbstractObjectNormalizerTest.php +++ b/Tests/Normalizer/AbstractObjectNormalizerTest.php @@ -855,7 +855,7 @@ public function testNormalizeWithIgnoreAnnotationAndPrivateProperties() public function testNormalizeBasedOnAllowedAttributes() { $normalizer = new class() extends AbstractObjectNormalizer { - protected function getAllowedAttributes($classOrObject, array $context, bool $attributesAsString = false) + protected function getAllowedAttributes($classOrObject, array $context, bool $attributesAsString = false): array { return ['foo']; } @@ -865,12 +865,12 @@ protected function extractAttributes(object $object, string $format = null, arra return []; } - protected function getAttributeValue(object $object, string $attribute, string $format = null, array $context = []) + protected function getAttributeValue(object $object, string $attribute, string $format = null, array $context = []): mixed { return $object->$attribute; } - protected function setAttributeValue(object $object, string $attribute, $value, string $format = null, array $context = []) + protected function setAttributeValue(object $object, string $attribute, $value, string $format = null, array $context = []): void { } }; From 39a8568daf6bb37be410fa0ebe772b8fd6cdcd35 Mon Sep 17 00:00:00 2001 From: Christian Flothmann Date: Wed, 29 Nov 2023 09:53:14 +0100 Subject: [PATCH 178/297] fix tests --- Tests/Normalizer/AbstractObjectNormalizerTest.php | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/Tests/Normalizer/AbstractObjectNormalizerTest.php b/Tests/Normalizer/AbstractObjectNormalizerTest.php index 90732c6a5..03ac7f991 100644 --- a/Tests/Normalizer/AbstractObjectNormalizerTest.php +++ b/Tests/Normalizer/AbstractObjectNormalizerTest.php @@ -848,6 +848,11 @@ public function testNormalizeWithIgnoreAttributeAndPrivateProperties() public function testNormalizeBasedOnAllowedAttributes() { $normalizer = new class() extends AbstractObjectNormalizer { + public function getSupportedTypes(?string $format): array + { + return ['*' => false]; + } + protected function getAllowedAttributes($classOrObject, array $context, bool $attributesAsString = false): array { return ['foo']; From fbbbb4144b5529c94ac0027edd842cfa5c0145fe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20Auswo=CC=88ger?= Date: Wed, 29 Nov 2023 14:15:38 +0100 Subject: [PATCH 179/297] Fix legacy class palceholder definitions for static analysis --- Mapping/Loader/AnnotationLoader.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Mapping/Loader/AnnotationLoader.php b/Mapping/Loader/AnnotationLoader.php index f5c8f9ccc..5ffbb3406 100644 --- a/Mapping/Loader/AnnotationLoader.php +++ b/Mapping/Loader/AnnotationLoader.php @@ -19,7 +19,7 @@ class_exists(AttributeLoader::class); /** * @deprecated since Symfony 6.4, to be removed in 7.0, use {@link AttributeLoader} instead */ - class AnnotationLoader + class AnnotationLoader extends AttributeLoader { } } From 467ed3ffdfc6c6fa6ad857a0bb61b705eccf1d96 Mon Sep 17 00:00:00 2001 From: Antoine Lamirault Date: Mon, 20 Nov 2023 19:32:45 +0100 Subject: [PATCH 180/297] [CssSelector][Serializer][Translation] [Command] Clean unused code --- Encoder/XmlEncoder.php | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/Encoder/XmlEncoder.php b/Encoder/XmlEncoder.php index 24d786e38..a3809bc84 100644 --- a/Encoder/XmlEncoder.php +++ b/Encoder/XmlEncoder.php @@ -200,13 +200,9 @@ final protected function appendCData(\DOMNode $node, string $val): bool final protected function appendDocumentFragment(\DOMNode $node, \DOMDocumentFragment $fragment): bool { - if ($fragment instanceof \DOMDocumentFragment) { - $node->appendChild($fragment); + $node->appendChild($fragment); - return true; - } - - return false; + return true; } final protected function appendComment(\DOMNode $node, string $data): bool From d61282e18457f007b79d9aad202253478992714b Mon Sep 17 00:00:00 2001 From: Farhad Safarov Date: Mon, 4 Dec 2023 02:41:30 +0300 Subject: [PATCH 181/297] [PropertyAccess][Serializer] Fix "type unknown" on denormalize --- Normalizer/AbstractObjectNormalizer.php | 3 ++- Tests/Normalizer/ObjectNormalizerTest.php | 20 ++++++++++++++++++++ 2 files changed, 22 insertions(+), 1 deletion(-) diff --git a/Normalizer/AbstractObjectNormalizer.php b/Normalizer/AbstractObjectNormalizer.php index 487cd4bda..1361115e2 100644 --- a/Normalizer/AbstractObjectNormalizer.php +++ b/Normalizer/AbstractObjectNormalizer.php @@ -12,6 +12,7 @@ namespace Symfony\Component\Serializer\Normalizer; use Symfony\Component\PropertyAccess\Exception\InvalidArgumentException as PropertyAccessInvalidArgumentException; +use Symfony\Component\PropertyAccess\Exception\InvalidTypeException; use Symfony\Component\PropertyAccess\Exception\NoSuchPropertyException; use Symfony\Component\PropertyAccess\Exception\UninitializedPropertyException; use Symfony\Component\PropertyAccess\PropertyAccess; @@ -374,7 +375,7 @@ public function denormalize(mixed $data, string $type, string $format = null, ar $exception = NotNormalizableValueException::createForUnexpectedDataType( sprintf('Failed to denormalize attribute "%s" value for class "%s": '.$e->getMessage(), $attribute, $type), $data, - ['unknown'], + $e instanceof InvalidTypeException ? [$e->expectedType] : ['unknown'], $context['deserialization_path'] ?? null, false, $e->getCode(), diff --git a/Tests/Normalizer/ObjectNormalizerTest.php b/Tests/Normalizer/ObjectNormalizerTest.php index fa9f5c396..39ab6c4cf 100644 --- a/Tests/Normalizer/ObjectNormalizerTest.php +++ b/Tests/Normalizer/ObjectNormalizerTest.php @@ -14,11 +14,13 @@ use PHPStan\PhpDocParser\Parser\PhpDocParser; use PHPUnit\Framework\MockObject\MockObject; use PHPUnit\Framework\TestCase; +use Symfony\Component\PropertyAccess\Exception\InvalidTypeException; use Symfony\Component\PropertyInfo\Extractor\PhpDocExtractor; use Symfony\Component\PropertyInfo\Extractor\PhpStanExtractor; use Symfony\Component\PropertyInfo\Extractor\ReflectionExtractor; use Symfony\Component\PropertyInfo\PropertyInfoExtractor; use Symfony\Component\Serializer\Exception\LogicException; +use Symfony\Component\Serializer\Exception\NotNormalizableValueException; use Symfony\Component\Serializer\Exception\RuntimeException; use Symfony\Component\Serializer\Exception\UnexpectedValueException; use Symfony\Component\Serializer\Mapping\Factory\ClassMetadataFactory; @@ -835,6 +837,24 @@ public function testNormalizeStdClass() $this->assertSame(['baz' => 'baz'], $this->normalizer->normalize($o2)); } + + public function testNotNormalizableValueInvalidType() + { + if (!class_exists(InvalidTypeException::class)) { + $this->markTestSkipped('Skipping test as the improvements on PropertyAccess are required.'); + } + + $this->expectException(NotNormalizableValueException::class); + $this->expectExceptionMessage('Expected argument of type "string", "array" given at property path "initialized"'); + + try { + $this->normalizer->denormalize(['initialized' => ['not a string']], TypedPropertiesObject::class, 'array'); + } catch (NotNormalizableValueException $e) { + $this->assertSame(['string'], $e->getExpectedTypes()); + + throw $e; + } + } } class ProxyObjectDummy extends ObjectDummy From d42b6d015a741ecb5bbcb9c2ca6f6002f7e24452 Mon Sep 17 00:00:00 2001 From: Alexandre Daubois Date: Thu, 14 Dec 2023 11:03:37 +0100 Subject: [PATCH 182/297] Set `strict` parameter of `in_array` to true where possible --- Mapping/AttributeMetadata.php | 2 +- NameConverter/CamelCaseToSnakeCaseNameConverter.php | 4 ++-- Normalizer/AbstractNormalizer.php | 4 ++-- Normalizer/AbstractObjectNormalizer.php | 2 +- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/Mapping/AttributeMetadata.php b/Mapping/AttributeMetadata.php index 9b04bb7e3..647d59309 100644 --- a/Mapping/AttributeMetadata.php +++ b/Mapping/AttributeMetadata.php @@ -90,7 +90,7 @@ public function getName(): string public function addGroup(string $group): void { - if (!\in_array($group, $this->groups)) { + if (!\in_array($group, $this->groups, true)) { $this->groups[] = $group; } } diff --git a/NameConverter/CamelCaseToSnakeCaseNameConverter.php b/NameConverter/CamelCaseToSnakeCaseNameConverter.php index ab6f99e13..a7b450fd2 100644 --- a/NameConverter/CamelCaseToSnakeCaseNameConverter.php +++ b/NameConverter/CamelCaseToSnakeCaseNameConverter.php @@ -30,7 +30,7 @@ public function __construct( public function normalize(string $propertyName): string { - if (null === $this->attributes || \in_array($propertyName, $this->attributes)) { + if (null === $this->attributes || \in_array($propertyName, $this->attributes, true)) { return strtolower(preg_replace('/[A-Z]/', '_\\0', lcfirst($propertyName))); } @@ -45,7 +45,7 @@ public function denormalize(string $propertyName): string $camelCasedName = lcfirst($camelCasedName); } - if (null === $this->attributes || \in_array($camelCasedName, $this->attributes)) { + if (null === $this->attributes || \in_array($camelCasedName, $this->attributes, true)) { return $camelCasedName; } diff --git a/Normalizer/AbstractNormalizer.php b/Normalizer/AbstractNormalizer.php index f53d4b139..40945fd2d 100644 --- a/Normalizer/AbstractNormalizer.php +++ b/Normalizer/AbstractNormalizer.php @@ -253,7 +253,7 @@ protected function getGroups(array $context): array protected function isAllowedAttribute(object|string $classOrObject, string $attribute, string $format = null, array $context = []): bool { $ignoredAttributes = $context[self::IGNORED_ATTRIBUTES] ?? $this->defaultContext[self::IGNORED_ATTRIBUTES]; - if (\in_array($attribute, $ignoredAttributes)) { + if (\in_array($attribute, $ignoredAttributes, true)) { return false; } @@ -326,7 +326,7 @@ protected function instantiateObject(array &$data, string $class, array &$contex $attributeContext = $this->getAttributeDenormalizationContext($class, $paramName, $context); $key = $this->nameConverter ? $this->nameConverter->normalize($paramName, $class, $format, $context) : $paramName; - $allowed = false === $allowedAttributes || \in_array($paramName, $allowedAttributes); + $allowed = false === $allowedAttributes || \in_array($paramName, $allowedAttributes, true); $ignored = !$this->isAllowedAttribute($class, $paramName, $format, $context); if ($constructorParameter->isVariadic()) { if ($allowed && !$ignored && (isset($data[$key]) || \array_key_exists($key, $data))) { diff --git a/Normalizer/AbstractObjectNormalizer.php b/Normalizer/AbstractObjectNormalizer.php index 7868ec10d..f27161cf1 100644 --- a/Normalizer/AbstractObjectNormalizer.php +++ b/Normalizer/AbstractObjectNormalizer.php @@ -334,7 +334,7 @@ public function denormalize(mixed $data, string $type, string $format = null, ar $attributeContext = $this->getAttributeDenormalizationContext($resolvedClass, $attribute, $context); - if ((false !== $allowedAttributes && !\in_array($attribute, $allowedAttributes)) || !$this->isAllowedAttribute($resolvedClass, $attribute, $format, $context)) { + if ((false !== $allowedAttributes && !\in_array($attribute, $allowedAttributes, true)) || !$this->isAllowedAttribute($resolvedClass, $attribute, $format, $context)) { if (!($context[self::ALLOW_EXTRA_ATTRIBUTES] ?? $this->defaultContext[self::ALLOW_EXTRA_ATTRIBUTES])) { $extraAttributes[] = $attribute; } From d2d2cc7f2e56c928864a843c1dae3159511e6ad4 Mon Sep 17 00:00:00 2001 From: Javier Eguiluz Date: Mon, 18 Dec 2023 08:46:12 +0100 Subject: [PATCH 183/297] Code updates --- Normalizer/DataUriNormalizer.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Normalizer/DataUriNormalizer.php b/Normalizer/DataUriNormalizer.php index c1aa9695b..3e0a61241 100644 --- a/Normalizer/DataUriNormalizer.php +++ b/Normalizer/DataUriNormalizer.php @@ -89,7 +89,7 @@ public function supportsNormalization(mixed $data, string $format = null, array */ public function denormalize(mixed $data, string $type, string $format = null, array $context = []): \SplFileInfo { - if (null === $data || !preg_match('/^data:([a-z0-9][a-z0-9\!\#\$\&\-\^\_\+\.]{0,126}\/[a-z0-9][a-z0-9\!\#\$\&\-\^\_\+\.]{0,126}(;[a-z0-9\-]+\=[a-z0-9\-]+)?)?(;base64)?,[a-z0-9\!\$\&\\\'\,\(\)\*\+\,\;\=\-\.\_\~\:\@\/\?\%\s]*\s*$/i', $data)) { + if (null === $data || !preg_match('/^data:([a-z0-9][a-z0-9\!\#\$\&\-\^\_\+\.]{0,126}\/[a-z0-9][a-z0-9\!\#\$\&\-\^\_\+\.]{0,126}(;[a-z0-9\-]+\=[a-z0-9\-]+)?)?(;base64)?,[a-z0-9\!\$\&\\\'\,\(\)\*\+\;\=\-\.\_\~\:\@\/\?\%\s]*\s*$/i', $data)) { throw NotNormalizableValueException::createForUnexpectedDataType('The provided "data:" URI is not valid.', $data, ['string'], $context['deserialization_path'] ?? null, true); } From 10940a765aea521b2cc8b415ce94444bf60c9581 Mon Sep 17 00:00:00 2001 From: Christian Flothmann Date: Tue, 19 Dec 2023 10:31:47 +0100 Subject: [PATCH 184/297] remove unneeded @requires PHP from tests --- Tests/SerializerTest.php | 3 --- 1 file changed, 3 deletions(-) diff --git a/Tests/SerializerTest.php b/Tests/SerializerTest.php index 93d910fda..daabf8e6c 100644 --- a/Tests/SerializerTest.php +++ b/Tests/SerializerTest.php @@ -755,9 +755,6 @@ public function testDeserializeWrappedScalar() $this->assertSame(42, $serializer->deserialize('{"wrapper": 42}', 'int', 'json', [UnwrappingDenormalizer::UNWRAP_PATH => '[wrapper]'])); } - /** - * @requires PHP 8 - */ public function testDeserializeNullableIntInXml() { $extractor = new PropertyInfoExtractor([], [new ReflectionExtractor()]); From 2620a67e4ecc1a6c25009417af44ea970fcd1799 Mon Sep 17 00:00:00 2001 From: Christian Flothmann Date: Fri, 22 Dec 2023 10:13:01 +0100 Subject: [PATCH 185/297] clean up @requires annotation --- Tests/Normalizer/AbstractNormalizerTest.php | 3 --- 1 file changed, 3 deletions(-) diff --git a/Tests/Normalizer/AbstractNormalizerTest.php b/Tests/Normalizer/AbstractNormalizerTest.php index a2f39206f..aabcad786 100644 --- a/Tests/Normalizer/AbstractNormalizerTest.php +++ b/Tests/Normalizer/AbstractNormalizerTest.php @@ -283,9 +283,6 @@ public function testIgnore() $this->assertSame([], $normalizer->normalize($dummy)); } - /** - * @requires PHP 8.1.2 - */ public function testDenormalizeWhenObjectNotInstantiable() { $this->expectException(NotNormalizableValueException::class); From 54f1106c09d0a9c3e2bba6b0349225f004b688b0 Mon Sep 17 00:00:00 2001 From: Oskar Stark Date: Wed, 1 Nov 2023 09:14:07 +0100 Subject: [PATCH 186/297] [Tests] Streamline --- Tests/Annotation/SerializedNameTest.php | 4 +- .../SerializerPassTest.php | 12 ++-- Tests/Encoder/JsonDecodeTest.php | 4 +- Tests/Encoder/JsonEncoderTest.php | 3 +- .../Factory/CacheMetadataFactoryTest.php | 3 +- .../CompiledClassMetadataFactoryTest.php | 6 +- Tests/Mapping/Loader/YamlFileLoaderTest.php | 4 +- .../AbstractObjectNormalizerTest.php | 41 +++++++++----- Tests/Normalizer/DataUriNormalizerTest.php | 4 +- .../Normalizer/GetSetMethodNormalizerTest.php | 9 ++- .../JsonSerializableNormalizerTest.php | 3 +- Tests/Normalizer/ObjectNormalizerTest.php | 10 ++-- Tests/Normalizer/PropertyNormalizerTest.php | 5 +- Tests/SerializerTest.php | 56 ++++++++++++++----- 14 files changed, 109 insertions(+), 55 deletions(-) diff --git a/Tests/Annotation/SerializedNameTest.php b/Tests/Annotation/SerializedNameTest.php index c2b5e5f2a..3a829aecf 100644 --- a/Tests/Annotation/SerializedNameTest.php +++ b/Tests/Annotation/SerializedNameTest.php @@ -30,7 +30,7 @@ public function testNotAStringSerializedNameParameter() public function testSerializedNameParameters() { - $maxDepth = new SerializedName('foo'); - $this->assertEquals('foo', $maxDepth->getSerializedName()); + $foo = new SerializedName('foo'); + $this->assertEquals('foo', $foo->getSerializedName()); } } diff --git a/Tests/DependencyInjection/SerializerPassTest.php b/Tests/DependencyInjection/SerializerPassTest.php index eb77263f4..037eafdb6 100644 --- a/Tests/DependencyInjection/SerializerPassTest.php +++ b/Tests/DependencyInjection/SerializerPassTest.php @@ -28,20 +28,20 @@ class SerializerPassTest extends TestCase { public function testThrowExceptionWhenNoNormalizers() { - $this->expectException(\RuntimeException::class); - $this->expectExceptionMessage('You must tag at least one service as "serializer.normalizer" to use the "serializer" service'); $container = new ContainerBuilder(); $container->setParameter('kernel.debug', false); $container->register('serializer'); $serializerPass = new SerializerPass(); + + $this->expectException(\RuntimeException::class); + $this->expectExceptionMessage('You must tag at least one service as "serializer.normalizer" to use the "serializer" service'); + $serializerPass->process($container); } public function testThrowExceptionWhenNoEncoders() { - $this->expectException(\RuntimeException::class); - $this->expectExceptionMessage('You must tag at least one service as "serializer.encoder" to use the "serializer" service'); $container = new ContainerBuilder(); $container->setParameter('kernel.debug', false); $container->register('serializer') @@ -50,6 +50,10 @@ public function testThrowExceptionWhenNoEncoders() $container->register('normalizer')->addTag('serializer.normalizer'); $serializerPass = new SerializerPass(); + + $this->expectException(\RuntimeException::class); + $this->expectExceptionMessage('You must tag at least one service as "serializer.encoder" to use the "serializer" service'); + $serializerPass->process($container); } diff --git a/Tests/Encoder/JsonDecodeTest.php b/Tests/Encoder/JsonDecodeTest.php index 66cd10114..f336bcd42 100644 --- a/Tests/Encoder/JsonDecodeTest.php +++ b/Tests/Encoder/JsonDecodeTest.php @@ -47,11 +47,9 @@ public static function decodeProvider() $stdClass = new \stdClass(); $stdClass->foo = 'bar'; - $assoc = ['foo' => 'bar']; - return [ ['{"foo": "bar"}', $stdClass, []], - ['{"foo": "bar"}', $assoc, ['json_decode_associative' => true]], + ['{"foo": "bar"}', ['foo' => 'bar'], ['json_decode_associative' => true]], ]; } diff --git a/Tests/Encoder/JsonEncoderTest.php b/Tests/Encoder/JsonEncoderTest.php index 1b47684ae..a34e82c6b 100644 --- a/Tests/Encoder/JsonEncoderTest.php +++ b/Tests/Encoder/JsonEncoderTest.php @@ -84,12 +84,13 @@ public function testWithDefaultContext() public function testEncodeNotUtf8WithoutPartialOnError() { - $this->expectException(UnexpectedValueException::class); $arr = [ 'utf8' => 'Hello World!', 'notUtf8' => "\xb0\xd0\xb5\xd0", ]; + $this->expectException(UnexpectedValueException::class); + $this->encoder->encode($arr, 'json'); } diff --git a/Tests/Mapping/Factory/CacheMetadataFactoryTest.php b/Tests/Mapping/Factory/CacheMetadataFactoryTest.php index 9525ca605..6db0b95ae 100644 --- a/Tests/Mapping/Factory/CacheMetadataFactoryTest.php +++ b/Tests/Mapping/Factory/CacheMetadataFactoryTest.php @@ -58,10 +58,11 @@ public function testHasMetadataFor() public function testInvalidClassThrowsException() { - $this->expectException(InvalidArgumentException::class); $decorated = $this->createMock(ClassMetadataFactoryInterface::class); $factory = new CacheClassMetadataFactory($decorated, new ArrayAdapter()); + $this->expectException(InvalidArgumentException::class); + $factory->getMetadataFor('Not\Exist'); } diff --git a/Tests/Mapping/Factory/CompiledClassMetadataFactoryTest.php b/Tests/Mapping/Factory/CompiledClassMetadataFactoryTest.php index 683f445df..ff54fb96b 100644 --- a/Tests/Mapping/Factory/CompiledClassMetadataFactoryTest.php +++ b/Tests/Mapping/Factory/CompiledClassMetadataFactoryTest.php @@ -34,19 +34,21 @@ public function testItImplementsClassMetadataFactoryInterface() public function testItThrowAnExceptionWhenCacheFileIsNotFound() { + $classMetadataFactory = $this->createMock(ClassMetadataFactoryInterface::class); + $this->expectException(\RuntimeException::class); $this->expectExceptionMessageMatches('#File ".*/Fixtures/not-found-serializer.class.metadata.php" could not be found.#'); - $classMetadataFactory = $this->createMock(ClassMetadataFactoryInterface::class); new CompiledClassMetadataFactory(__DIR__.'/../../Fixtures/not-found-serializer.class.metadata.php', $classMetadataFactory); } public function testItThrowAnExceptionWhenMetadataIsNotOfTypeArray() { + $classMetadataFactory = $this->createMock(ClassMetadataFactoryInterface::class); + $this->expectException(\RuntimeException::class); $this->expectExceptionMessage('Compiled metadata must be of the type array, object given.'); - $classMetadataFactory = $this->createMock(ClassMetadataFactoryInterface::class); new CompiledClassMetadataFactory(__DIR__.'/../../Fixtures/object-metadata.php', $classMetadataFactory); } diff --git a/Tests/Mapping/Loader/YamlFileLoaderTest.php b/Tests/Mapping/Loader/YamlFileLoaderTest.php index ea81a9d8a..48e95aecd 100644 --- a/Tests/Mapping/Loader/YamlFileLoaderTest.php +++ b/Tests/Mapping/Loader/YamlFileLoaderTest.php @@ -65,8 +65,10 @@ public function testLoadClassMetadataReturnsFalseWhenEmpty() public function testLoadClassMetadataReturnsThrowsInvalidMapping() { - $this->expectException(MappingException::class); $loader = new YamlFileLoader(__DIR__.'/../../Fixtures/invalid-mapping.yml'); + + $this->expectException(MappingException::class); + $loader->loadClassMetadata($this->metadata); } diff --git a/Tests/Normalizer/AbstractObjectNormalizerTest.php b/Tests/Normalizer/AbstractObjectNormalizerTest.php index 0b91fc0db..ca3c7579b 100644 --- a/Tests/Normalizer/AbstractObjectNormalizerTest.php +++ b/Tests/Normalizer/AbstractObjectNormalizerTest.php @@ -82,10 +82,12 @@ public function testInstantiateObjectDenormalizer() public function testDenormalizeWithExtraAttribute() { - $this->expectException(ExtraAttributesException::class); - $this->expectExceptionMessage('Extra attributes are not allowed ("fooFoo" is unknown).'); $factory = new ClassMetadataFactory(new AttributeLoader()); $normalizer = new AbstractObjectNormalizerDummy($factory); + + $this->expectException(ExtraAttributesException::class); + $this->expectExceptionMessage('Extra attributes are not allowed ("fooFoo" is unknown).'); + $normalizer->denormalize( ['fooFoo' => 'foo'], Dummy::class, @@ -96,10 +98,12 @@ public function testDenormalizeWithExtraAttribute() public function testDenormalizeWithExtraAttributes() { - $this->expectException(ExtraAttributesException::class); - $this->expectExceptionMessage('Extra attributes are not allowed ("fooFoo", "fooBar" are unknown).'); $factory = new ClassMetadataFactory(new AttributeLoader()); $normalizer = new AbstractObjectNormalizerDummy($factory); + + $this->expectException(ExtraAttributesException::class); + $this->expectExceptionMessage('Extra attributes are not allowed ("fooFoo", "fooBar" are unknown).'); + $normalizer->denormalize( ['fooFoo' => 'foo', 'fooBar' => 'bar'], Dummy::class, @@ -110,9 +114,11 @@ public function testDenormalizeWithExtraAttributes() public function testDenormalizeWithExtraAttributesAndNoGroupsWithMetadataFactory() { + $normalizer = new AbstractObjectNormalizerWithMetadata(); + $this->expectException(ExtraAttributesException::class); $this->expectExceptionMessage('Extra attributes are not allowed ("fooFoo", "fooBar" are unknown).'); - $normalizer = new AbstractObjectNormalizerWithMetadata(); + $normalizer->denormalize( ['fooFoo' => 'foo', 'fooBar' => 'bar', 'bar' => 'bar'], Dummy::class, @@ -134,9 +140,11 @@ public function testDenormalizePlainObject() public function testDenormalizeWithDuplicateNestedAttributes() { + $normalizer = new AbstractObjectNormalizerWithMetadata(); + $this->expectException(LogicException::class); $this->expectExceptionMessage('Duplicate serialized path: "one,two,three" used for properties "foo" and "bar".'); - $normalizer = new AbstractObjectNormalizerWithMetadata(); + $normalizer->denormalize([], DuplicateValueNestedDummy::class, 'any'); } @@ -204,8 +212,6 @@ public function testDenormalizeWithNestedAttributes() public function testDenormalizeWithNestedAttributesDuplicateKeys() { - $this->expectException(LogicException::class); - $this->expectExceptionMessage('Duplicate values for key "quux" found. One value is set via the SerializedPath attribute: "one->four", the other one is set via the SerializedName attribute: "notquux".'); $normalizer = new AbstractObjectNormalizerWithMetadata(); $data = [ 'one' => [ @@ -213,6 +219,10 @@ public function testDenormalizeWithNestedAttributesDuplicateKeys() ], 'quux' => 'notquux', ]; + + $this->expectException(LogicException::class); + $this->expectExceptionMessage('Duplicate values for key "quux" found. One value is set via the SerializedPath attribute: "one->four", the other one is set via the SerializedName attribute: "notquux".'); + $normalizer->denormalize($data, DuplicateKeyNestedDummy::class, 'any'); } @@ -265,25 +275,29 @@ public function testDenormalizeWithNestedAttributesInConstructorAndDiscriminator public function testNormalizeWithNestedAttributesMixingArrayTypes() { - $this->expectException(LogicException::class); - $this->expectExceptionMessage('The element you are trying to set is already populated: "[one][two]"'); $foobar = new AlreadyPopulatedNestedDummy(); $foobar->foo = 'foo'; $foobar->bar = 'bar'; $classMetadataFactory = new ClassMetadataFactory(new AttributeLoader()); $normalizer = new ObjectNormalizer($classMetadataFactory, new MetadataAwareNameConverter($classMetadataFactory)); + + $this->expectException(LogicException::class); + $this->expectExceptionMessage('The element you are trying to set is already populated: "[one][two]"'); + $normalizer->normalize($foobar, 'any'); } public function testNormalizeWithNestedAttributesElementAlreadySet() { - $this->expectException(LogicException::class); - $this->expectExceptionMessage('The element you are trying to set is already populated: "[one][two][three]"'); $foobar = new DuplicateValueNestedDummy(); $foobar->foo = 'foo'; $foobar->bar = 'bar'; $classMetadataFactory = new ClassMetadataFactory(new AttributeLoader()); $normalizer = new ObjectNormalizer($classMetadataFactory, new MetadataAwareNameConverter($classMetadataFactory)); + + $this->expectException(LogicException::class); + $this->expectExceptionMessage('The element you are trying to set is already populated: "[one][two][three]"'); + $normalizer->normalize($foobar, 'any'); } @@ -691,9 +705,10 @@ private function getDenormalizerForObjectWithBasicProperties() */ public function testExtraAttributesException() { + $normalizer = new ObjectNormalizer(); + $this->expectException(LogicException::class); $this->expectExceptionMessage('A class metadata factory must be provided in the constructor when setting "allow_extra_attributes" to false.'); - $normalizer = new ObjectNormalizer(); $normalizer->denormalize([], \stdClass::class, 'xml', [ 'allow_extra_attributes' => false, diff --git a/Tests/Normalizer/DataUriNormalizerTest.php b/Tests/Normalizer/DataUriNormalizerTest.php index 92e173fe0..7e9af4360 100644 --- a/Tests/Normalizer/DataUriNormalizerTest.php +++ b/Tests/Normalizer/DataUriNormalizerTest.php @@ -121,7 +121,7 @@ public function testGiveNotAccessToLocalFiles() /** * @dataProvider invalidUriProvider */ - public function testInvalidData($uri) + public function testInvalidData(?string $uri) { $this->expectException(UnexpectedValueException::class); $this->normalizer->denormalize($uri, 'SplFileObject'); @@ -148,7 +148,7 @@ public static function invalidUriProvider() /** * @dataProvider validUriProvider */ - public function testValidData($uri) + public function testValidData(string $uri) { $this->assertInstanceOf(\SplFileObject::class, $this->normalizer->denormalize($uri, 'SplFileObject')); } diff --git a/Tests/Normalizer/GetSetMethodNormalizerTest.php b/Tests/Normalizer/GetSetMethodNormalizerTest.php index 1d471981e..eb2b65306 100644 --- a/Tests/Normalizer/GetSetMethodNormalizerTest.php +++ b/Tests/Normalizer/GetSetMethodNormalizerTest.php @@ -374,8 +374,6 @@ protected function getDenormalizerForIgnoredAttributes(): GetSetMethodNormalizer public function testUnableToNormalizeObjectAttribute() { - $this->expectException(LogicException::class); - $this->expectExceptionMessage('Cannot normalize attribute "object" because the injected serializer is not a normalizer'); $serializer = $this->createMock(SerializerInterface::class); $this->normalizer->setSerializer($serializer); @@ -383,6 +381,9 @@ public function testUnableToNormalizeObjectAttribute() $object = new \stdClass(); $obj->setObject($object); + $this->expectException(LogicException::class); + $this->expectExceptionMessage('Cannot normalize attribute "object" because the injected serializer is not a normalizer'); + $this->normalizer->normalize($obj, 'any'); } @@ -391,14 +392,12 @@ public function testSiblingReference() $serializer = new Serializer([$this->normalizer]); $this->normalizer->setSerializer($serializer); - $siblingHolder = new SiblingHolder(); - $expected = [ 'sibling0' => ['coopTilleuls' => 'Les-Tilleuls.coop'], 'sibling1' => ['coopTilleuls' => 'Les-Tilleuls.coop'], 'sibling2' => ['coopTilleuls' => 'Les-Tilleuls.coop'], ]; - $this->assertEquals($expected, $this->normalizer->normalize($siblingHolder)); + $this->assertEquals($expected, $this->normalizer->normalize(new SiblingHolder())); } public function testDenormalizeNonExistingAttribute() diff --git a/Tests/Normalizer/JsonSerializableNormalizerTest.php b/Tests/Normalizer/JsonSerializableNormalizerTest.php index 54a977f55..f8f8546d7 100644 --- a/Tests/Normalizer/JsonSerializableNormalizerTest.php +++ b/Tests/Normalizer/JsonSerializableNormalizerTest.php @@ -68,9 +68,10 @@ public function testNormalize() public function testCircularNormalize() { - $this->expectException(CircularReferenceException::class); $this->createNormalizer([JsonSerializableNormalizer::CIRCULAR_REFERENCE_LIMIT => 1]); + $this->expectException(CircularReferenceException::class); + $this->serializer ->expects($this->once()) ->method('normalize') diff --git a/Tests/Normalizer/ObjectNormalizerTest.php b/Tests/Normalizer/ObjectNormalizerTest.php index f9f2e8ad0..05b1891f5 100644 --- a/Tests/Normalizer/ObjectNormalizerTest.php +++ b/Tests/Normalizer/ObjectNormalizerTest.php @@ -307,8 +307,6 @@ public function testConstructorWithUnconstructableNullableObjectTypeHintDenormal public function testConstructorWithUnknownObjectTypeHintDenormalize() { - $this->expectException(RuntimeException::class); - $this->expectExceptionMessage('Could not determine the class of the parameter "unknown".'); $data = [ 'id' => 10, 'unknown' => [ @@ -321,6 +319,9 @@ public function testConstructorWithUnknownObjectTypeHintDenormalize() $serializer = new Serializer([$normalizer]); $normalizer->setSerializer($serializer); + $this->expectException(RuntimeException::class); + $this->expectExceptionMessage('Could not determine the class of the parameter "unknown".'); + $normalizer->denormalize($data, DummyWithConstructorInexistingObject::class); } @@ -623,8 +624,6 @@ protected function getDenormalizerForTypeEnforcement(): ObjectNormalizer public function testUnableToNormalizeObjectAttribute() { - $this->expectException(LogicException::class); - $this->expectExceptionMessage('Cannot normalize attribute "object" because the injected serializer is not a normalizer'); $serializer = $this->createMock(SerializerInterface::class); $this->normalizer->setSerializer($serializer); @@ -632,6 +631,9 @@ public function testUnableToNormalizeObjectAttribute() $object = new \stdClass(); $obj->setObject($object); + $this->expectException(LogicException::class); + $this->expectExceptionMessage('Cannot normalize attribute "object" because the injected serializer is not a normalizer'); + $this->normalizer->normalize($obj, 'any'); } diff --git a/Tests/Normalizer/PropertyNormalizerTest.php b/Tests/Normalizer/PropertyNormalizerTest.php index 631111d2a..585c2068e 100644 --- a/Tests/Normalizer/PropertyNormalizerTest.php +++ b/Tests/Normalizer/PropertyNormalizerTest.php @@ -400,8 +400,6 @@ public function testDenormalizeShouldIgnoreStaticProperty() public function testUnableToNormalizeObjectAttribute() { - $this->expectException(LogicException::class); - $this->expectExceptionMessage('Cannot normalize attribute "bar" because the injected serializer is not a normalizer'); $serializer = $this->createMock(SerializerInterface::class); $this->normalizer->setSerializer($serializer); @@ -409,6 +407,9 @@ public function testUnableToNormalizeObjectAttribute() $object = new \stdClass(); $obj->setBar($object); + $this->expectException(LogicException::class); + $this->expectExceptionMessage('Cannot normalize attribute "bar" because the injected serializer is not a normalizer'); + $this->normalizer->normalize($obj, 'any'); } diff --git a/Tests/SerializerTest.php b/Tests/SerializerTest.php index 624853107..45d467064 100644 --- a/Tests/SerializerTest.php +++ b/Tests/SerializerTest.php @@ -97,8 +97,10 @@ public function testItThrowsExceptionOnInvalidEncoder() public function testNormalizeNoMatch() { - $this->expectException(UnexpectedValueException::class); $serializer = new Serializer([$this->createMock(NormalizerInterface::class)]); + + $this->expectException(UnexpectedValueException::class); + $serializer->normalize(new \stdClass(), 'xml'); } @@ -118,15 +120,19 @@ public function testNormalizeGivesPriorityToInterfaceOverTraversable() public function testNormalizeOnDenormalizer() { - $this->expectException(UnexpectedValueException::class); $serializer = new Serializer([new TestDenormalizer()], []); + + $this->expectException(UnexpectedValueException::class); + $this->assertTrue($serializer->normalize(new \stdClass(), 'json')); } public function testDenormalizeNoMatch() { - $this->expectException(UnexpectedValueException::class); $serializer = new Serializer([$this->createMock(NormalizerInterface::class)]); + + $this->expectException(UnexpectedValueException::class); + $serializer->denormalize('foo', 'stdClass'); } @@ -140,9 +146,11 @@ public function testDenormalizeOnObjectThatOnlySupportsDenormalization() public function testDenormalizeOnNormalizer() { - $this->expectException(UnexpectedValueException::class); $serializer = new Serializer([new TestNormalizer()], []); $data = ['title' => 'foo', 'numbers' => [5, 3]]; + + $this->expectException(UnexpectedValueException::class); + $this->assertTrue($serializer->denormalize(json_encode($data), 'stdClass', 'json')); } @@ -237,17 +245,21 @@ public function testSerializeEmpty() public function testSerializeNoEncoder() { - $this->expectException(UnexpectedValueException::class); $serializer = new Serializer([], []); $data = ['title' => 'foo', 'numbers' => [5, 3]]; + + $this->expectException(UnexpectedValueException::class); + $serializer->serialize($data, 'json'); } public function testSerializeNoNormalizer() { - $this->expectException(LogicException::class); $serializer = new Serializer([], ['json' => new JsonEncoder()]); $data = ['title' => 'foo', 'numbers' => [5, 3]]; + + $this->expectException(LogicException::class); + $serializer->serialize(Model::fromArray($data), 'json'); } @@ -271,25 +283,31 @@ public function testDeserializeUseCache() public function testDeserializeNoNormalizer() { - $this->expectException(LogicException::class); $serializer = new Serializer([], ['json' => new JsonEncoder()]); $data = ['title' => 'foo', 'numbers' => [5, 3]]; + + $this->expectException(LogicException::class); + $serializer->deserialize(json_encode($data), Model::class, 'json'); } public function testDeserializeWrongNormalizer() { - $this->expectException(UnexpectedValueException::class); $serializer = new Serializer([new CustomNormalizer()], ['json' => new JsonEncoder()]); $data = ['title' => 'foo', 'numbers' => [5, 3]]; + + $this->expectException(UnexpectedValueException::class); + $serializer->deserialize(json_encode($data), Model::class, 'json'); } public function testDeserializeNoEncoder() { - $this->expectException(UnexpectedValueException::class); $serializer = new Serializer([], []); $data = ['title' => 'foo', 'numbers' => [5, 3]]; + + $this->expectException(UnexpectedValueException::class); + $serializer->deserialize(json_encode($data), Model::class, 'json'); } @@ -689,29 +707,37 @@ public function testDeserializeScalar() public function testDeserializeLegacyScalarType() { - $this->expectException(LogicException::class); $serializer = new Serializer([], ['json' => new JsonEncoder()]); + + $this->expectException(LogicException::class); + $serializer->deserialize('42', 'integer', 'json'); } public function testDeserializeScalarTypeToCustomType() { - $this->expectException(LogicException::class); $serializer = new Serializer([], ['json' => new JsonEncoder()]); + + $this->expectException(LogicException::class); + $serializer->deserialize('"something"', Foo::class, 'json'); } public function testDeserializeNonscalarTypeToScalar() { - $this->expectException(NotNormalizableValueException::class); $serializer = new Serializer([], ['json' => new JsonEncoder()]); + + $this->expectException(NotNormalizableValueException::class); + $serializer->deserialize('{"foo":true}', 'string', 'json'); } public function testDeserializeInconsistentScalarType() { - $this->expectException(NotNormalizableValueException::class); $serializer = new Serializer([], ['json' => new JsonEncoder()]); + + $this->expectException(NotNormalizableValueException::class); + $serializer->deserialize('"42"', 'int', 'json'); } @@ -727,8 +753,10 @@ public function testDeserializeScalarArray() public function testDeserializeInconsistentScalarArray() { - $this->expectException(NotNormalizableValueException::class); $serializer = new Serializer([new ArrayDenormalizer()], ['json' => new JsonEncoder()]); + + $this->expectException(NotNormalizableValueException::class); + $serializer->deserialize('["42"]', 'int[]', 'json'); } From baa60530672793e078f8e61ff6822856a8f31a6a Mon Sep 17 00:00:00 2001 From: Alexandre Daubois Date: Tue, 10 Oct 2023 16:04:32 +0200 Subject: [PATCH 187/297] [Console][EventDispatcher][Security][Serializer][Workflow] Add PHPDoc to attribute classes and properties --- Attribute/Context.php | 5 ++++- Attribute/DiscriminatorMap.php | 6 ++++++ Attribute/Groups.php | 2 +- Attribute/MaxDepth.php | 3 +++ Attribute/SerializedName.php | 3 +++ Attribute/SerializedPath.php | 3 +++ 6 files changed, 20 insertions(+), 2 deletions(-) diff --git a/Attribute/Context.php b/Attribute/Context.php index 61ff1e79d..5ea2d2eb5 100644 --- a/Attribute/Context.php +++ b/Attribute/Context.php @@ -22,7 +22,10 @@ class Context private array $groups; /** - * @param string|string[] $groups + * @param array $context The common context to use when serializing or deserializing + * @param array $normalizationContext The context to use when serializing + * @param array $denormalizationContext The context to use when deserializing + * @param string|string[] $groups The groups to use when serializing or deserializing * * @throws InvalidArgumentException */ diff --git a/Attribute/DiscriminatorMap.php b/Attribute/DiscriminatorMap.php index 31b9eee7e..33a27db85 100644 --- a/Attribute/DiscriminatorMap.php +++ b/Attribute/DiscriminatorMap.php @@ -19,6 +19,12 @@ #[\Attribute(\Attribute::TARGET_CLASS)] class DiscriminatorMap { + /** + * @param string $typeProperty The property holding the type discriminator + * @param array $mapping The mapping between types and classes (i.e. ['admin_user' => AdminUser::class]) + * + * @throws InvalidArgumentException + */ public function __construct( private readonly string $typeProperty, private readonly array $mapping, diff --git a/Attribute/Groups.php b/Attribute/Groups.php index 1c9c9d025..39914f971 100644 --- a/Attribute/Groups.php +++ b/Attribute/Groups.php @@ -25,7 +25,7 @@ class Groups private readonly array $groups; /** - * @param string|string[] $groups + * @param string|string[] $groups The groups to define on the attribute target */ public function __construct(string|array $groups) { diff --git a/Attribute/MaxDepth.php b/Attribute/MaxDepth.php index f132c87a7..17562b6c2 100644 --- a/Attribute/MaxDepth.php +++ b/Attribute/MaxDepth.php @@ -19,6 +19,9 @@ #[\Attribute(\Attribute::TARGET_METHOD | \Attribute::TARGET_PROPERTY)] class MaxDepth { + /** + * @param int $maxDepth The maximum serialization depth + */ public function __construct(private readonly int $maxDepth) { if ($maxDepth <= 0) { diff --git a/Attribute/SerializedName.php b/Attribute/SerializedName.php index 265aea967..6acbebd03 100644 --- a/Attribute/SerializedName.php +++ b/Attribute/SerializedName.php @@ -19,6 +19,9 @@ #[\Attribute(\Attribute::TARGET_METHOD | \Attribute::TARGET_PROPERTY)] class SerializedName { + /** + * @param string $serializedName The name of the property as it will be serialized + */ public function __construct(private readonly string $serializedName) { if ('' === $serializedName) { diff --git a/Attribute/SerializedPath.php b/Attribute/SerializedPath.php index 58a35b6db..118e3adbe 100644 --- a/Attribute/SerializedPath.php +++ b/Attribute/SerializedPath.php @@ -23,6 +23,9 @@ class SerializedPath { private PropertyPath $serializedPath; + /** + * @param string $serializedPath A path using a valid PropertyAccess syntax where the value is stored in a normalized representation + */ public function __construct(string $serializedPath) { try { From ad53681596831f465f8985982fd2ee7e37893eb0 Mon Sep 17 00:00:00 2001 From: Christian Flothmann Date: Wed, 3 Jan 2024 09:08:31 +0100 Subject: [PATCH 188/297] fix merge --- Normalizer/AbstractObjectNormalizer.php | 1 - 1 file changed, 1 deletion(-) diff --git a/Normalizer/AbstractObjectNormalizer.php b/Normalizer/AbstractObjectNormalizer.php index bd75e990b..a2503e290 100644 --- a/Normalizer/AbstractObjectNormalizer.php +++ b/Normalizer/AbstractObjectNormalizer.php @@ -375,7 +375,6 @@ public function denormalize(mixed $data, string $type, string $format = null, ar sprintf('Failed to denormalize attribute "%s" value for class "%s": '.$e->getMessage(), $attribute, $type), $data, $e instanceof InvalidTypeException ? [$e->expectedType] : ['unknown'], - ['unknown'], $attributeContext['deserialization_path'] ?? null, false, $e->getCode(), From fdc4508632129b4d378d8a0743c078d856682535 Mon Sep 17 00:00:00 2001 From: Christian Flothmann Date: Wed, 3 Jan 2024 09:14:50 +0100 Subject: [PATCH 189/297] fix expected types --- Tests/SerializerTest.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Tests/SerializerTest.php b/Tests/SerializerTest.php index 60be7de93..85aad3f0b 100644 --- a/Tests/SerializerTest.php +++ b/Tests/SerializerTest.php @@ -1253,7 +1253,7 @@ public function testCollectDenormalizationErrorsWithoutTypeExtractor() [ 'currentType' => 'array', 'expectedTypes' => [ - 'unknown', + 'string', ], 'path' => 'string', 'useMessageForUser' => false, @@ -1262,7 +1262,7 @@ public function testCollectDenormalizationErrorsWithoutTypeExtractor() [ 'currentType' => 'array', 'expectedTypes' => [ - 'unknown', + 'int', ], 'path' => 'int', 'useMessageForUser' => false, @@ -1271,7 +1271,7 @@ public function testCollectDenormalizationErrorsWithoutTypeExtractor() [ 'currentType' => 'array', 'expectedTypes' => [ - 'unknown', + 'float', ], 'path' => 'float', 'useMessageForUser' => false, From 549f4f499bbca3819949458c71392acc8d2f2970 Mon Sep 17 00:00:00 2001 From: Christian Flothmann Date: Wed, 3 Jan 2024 10:29:17 +0100 Subject: [PATCH 190/297] fix tests --- Tests/SerializerTest.php | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/Tests/SerializerTest.php b/Tests/SerializerTest.php index 85aad3f0b..80f0de439 100644 --- a/Tests/SerializerTest.php +++ b/Tests/SerializerTest.php @@ -12,6 +12,7 @@ namespace Symfony\Component\Serializer\Tests; use PHPUnit\Framework\TestCase; +use Symfony\Component\PropertyAccess\Exception\InvalidTypeException; use Symfony\Component\PropertyAccess\PropertyAccessor; use Symfony\Component\PropertyInfo\Extractor\PhpDocExtractor; use Symfony\Component\PropertyInfo\Extractor\ReflectionExtractor; @@ -1253,7 +1254,7 @@ public function testCollectDenormalizationErrorsWithoutTypeExtractor() [ 'currentType' => 'array', 'expectedTypes' => [ - 'string', + class_exists(InvalidTypeException::class) ? 'string' : 'unknown', ], 'path' => 'string', 'useMessageForUser' => false, @@ -1262,7 +1263,7 @@ public function testCollectDenormalizationErrorsWithoutTypeExtractor() [ 'currentType' => 'array', 'expectedTypes' => [ - 'int', + class_exists(InvalidTypeException::class) ? 'int' : 'unknown', ], 'path' => 'int', 'useMessageForUser' => false, @@ -1271,7 +1272,7 @@ public function testCollectDenormalizationErrorsWithoutTypeExtractor() [ 'currentType' => 'array', 'expectedTypes' => [ - 'float', + class_exists(InvalidTypeException::class) ? 'float' : 'unknown', ], 'path' => 'float', 'useMessageForUser' => false, From ca1db1ae38394d9b05d062cb5bdf9aa52e439afa Mon Sep 17 00:00:00 2001 From: Dariusz Ruminski Date: Tue, 2 Jan 2024 15:49:33 +0100 Subject: [PATCH 191/297] CS: trailing commas --- Encoder/ChainDecoder.php | 2 +- Encoder/ChainEncoder.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Encoder/ChainDecoder.php b/Encoder/ChainDecoder.php index f1b0cd2e2..ad182a9a4 100644 --- a/Encoder/ChainDecoder.php +++ b/Encoder/ChainDecoder.php @@ -33,7 +33,7 @@ class ChainDecoder implements ContextAwareDecoderInterface * @param array $decoders */ public function __construct( - private readonly array $decoders = [] + private readonly array $decoders = [], ) { } diff --git a/Encoder/ChainEncoder.php b/Encoder/ChainEncoder.php index 731cfc601..5445761f3 100644 --- a/Encoder/ChainEncoder.php +++ b/Encoder/ChainEncoder.php @@ -34,7 +34,7 @@ class ChainEncoder implements ContextAwareEncoderInterface * @param array $encoders */ public function __construct( - private readonly array $encoders = [] + private readonly array $encoders = [], ) { } From 791534fbe79d8c306622baf01345ac2692f36204 Mon Sep 17 00:00:00 2001 From: Nicolas PHILIPPE Date: Thu, 4 Jan 2024 14:41:52 +0100 Subject: [PATCH 192/297] [Serializer] GetSetMethodNormalize: fix BC break with Ignore attribute --- Normalizer/GetSetMethodNormalizer.php | 3 ++- .../Attributes/ClassWithIgnoreAnnotation.php | 25 +++++++++++++++++++ .../Normalizer/GetSetMethodNormalizerTest.php | 18 +++++++++++-- 3 files changed, 43 insertions(+), 3 deletions(-) create mode 100644 Tests/Fixtures/Attributes/ClassWithIgnoreAnnotation.php diff --git a/Normalizer/GetSetMethodNormalizer.php b/Normalizer/GetSetMethodNormalizer.php index c3b9f06d4..234e64e79 100644 --- a/Normalizer/GetSetMethodNormalizer.php +++ b/Normalizer/GetSetMethodNormalizer.php @@ -11,6 +11,7 @@ namespace Symfony\Component\Serializer\Normalizer; +use Symfony\Component\Serializer\Annotation\Ignore as LegacyIgnore; use Symfony\Component\Serializer\Attribute\Ignore; /** @@ -97,7 +98,7 @@ private function supports(string $class): bool private function isGetMethod(\ReflectionMethod $method): bool { return !$method->isStatic() - && !$method->getAttributes(Ignore::class) + && !($method->getAttributes(Ignore::class) || $method->getAttributes(LegacyIgnore::class)) && !$method->getNumberOfRequiredParameters() && ((2 < ($methodLength = \strlen($method->name)) && str_starts_with($method->name, 'is')) || (3 < $methodLength && (str_starts_with($method->name, 'has') || str_starts_with($method->name, 'get'))) diff --git a/Tests/Fixtures/Attributes/ClassWithIgnoreAnnotation.php b/Tests/Fixtures/Attributes/ClassWithIgnoreAnnotation.php new file mode 100644 index 000000000..22df4f84a --- /dev/null +++ b/Tests/Fixtures/Attributes/ClassWithIgnoreAnnotation.php @@ -0,0 +1,25 @@ + + * + * 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\Attributes; + +use Symfony\Component\Serializer\Annotation\Ignore; + +class ClassWithIgnoreAnnotation +{ + public string $foo; + + #[Ignore] + public function isSomeIgnoredMethod(): bool + { + return true; + } +} diff --git a/Tests/Normalizer/GetSetMethodNormalizerTest.php b/Tests/Normalizer/GetSetMethodNormalizerTest.php index 7877a3c5e..e3e887379 100644 --- a/Tests/Normalizer/GetSetMethodNormalizerTest.php +++ b/Tests/Normalizer/GetSetMethodNormalizerTest.php @@ -30,6 +30,7 @@ use Symfony\Component\Serializer\Normalizer\NormalizerInterface; use Symfony\Component\Serializer\Serializer; use Symfony\Component\Serializer\SerializerInterface; +use Symfony\Component\Serializer\Tests\Fixtures\Attributes\ClassWithIgnoreAnnotation; use Symfony\Component\Serializer\Tests\Fixtures\Attributes\ClassWithIgnoreAttribute; use Symfony\Component\Serializer\Tests\Fixtures\Attributes\GroupDummy; use Symfony\Component\Serializer\Tests\Fixtures\CircularReferenceDummy; @@ -427,9 +428,22 @@ public function testNoStaticGetSetSupport() $this->assertFalse($this->normalizer->supportsNormalization(new ObjectWithJustStaticSetterDummy())); } - public function testNotIgnoredMethodSupport() + /** + * @param class-string $class + * + * @dataProvider provideNotIgnoredMethodSupport + */ + public function testNotIgnoredMethodSupport(string $class) { - $this->assertFalse($this->normalizer->supportsNormalization(new ClassWithIgnoreAttribute())); + $this->assertFalse($this->normalizer->supportsNormalization(new $class())); + } + + public static function provideNotIgnoredMethodSupport(): iterable + { + return [ + [ClassWithIgnoreAttribute::class], + [ClassWithIgnoreAnnotation::class], + ]; } public function testPrivateSetter() From 95767d59415e4f3cd1c06b81e2e72dda838711b7 Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Mon, 29 Jan 2024 16:15:02 +0100 Subject: [PATCH 193/297] Fix merge (bis) --- Tests/Normalizer/AbstractObjectNormalizerTest.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Tests/Normalizer/AbstractObjectNormalizerTest.php b/Tests/Normalizer/AbstractObjectNormalizerTest.php index 853aadb8d..a07fcd54e 100644 --- a/Tests/Normalizer/AbstractObjectNormalizerTest.php +++ b/Tests/Normalizer/AbstractObjectNormalizerTest.php @@ -938,7 +938,7 @@ protected function extractAttributes(object $object, string $format = null, arra return array_keys((array) $object); } - protected function getAttributeValue(object $object, string $attribute, string $format = null, array $context = []) + protected function getAttributeValue(object $object, string $attribute, string $format = null, array $context = []): mixed { return $object->{$attribute}; } @@ -978,7 +978,7 @@ protected function extractAttributes(object $object, string $format = null, arra return array_keys((array) $object); } - protected function getAttributeValue(object $object, string $attribute, string $format = null, array $context = []) + protected function getAttributeValue(object $object, string $attribute, string $format = null, array $context = []): mixed { return $object->{$attribute}; } @@ -1013,7 +1013,7 @@ protected function extractAttributes(object $object, string $format = null, arra return array_keys((array) $object); } - protected function getAttributeValue(object $object, string $attribute, string $format = null, array $context = []) + protected function getAttributeValue(object $object, string $attribute, string $format = null, array $context = []): mixed { return $object->{$attribute}; } From 51359502748e605e7ab755c8eb800c08d4ab75cc Mon Sep 17 00:00:00 2001 From: Tomas Date: Wed, 13 Dec 2023 12:51:12 +0200 Subject: [PATCH 194/297] [Serializer] Add `DateTimeNormalizer::CAST_KEY` context option --- CHANGELOG.md | 5 ++ .../DateTimeNormalizerContextBuilder.php | 8 +++ Normalizer/DateTimeNormalizer.php | 10 ++- .../DateTimeNormalizerContextBuilderTest.php | 3 + Tests/Normalizer/DateTimeNormalizerTest.php | 66 +++++++++++++++++++ 5 files changed, 90 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b329cf154..13de5123f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,11 @@ CHANGELOG ========= +7.1 +--- + + * Add `DateTimeNormalizer::CAST_KEY` context option + 7.0 --- diff --git a/Context/Normalizer/DateTimeNormalizerContextBuilder.php b/Context/Normalizer/DateTimeNormalizerContextBuilder.php index 99517afb1..e2d289e60 100644 --- a/Context/Normalizer/DateTimeNormalizerContextBuilder.php +++ b/Context/Normalizer/DateTimeNormalizerContextBuilder.php @@ -61,4 +61,12 @@ public function withTimezone(\DateTimeZone|string|null $timezone): static return $this->with(DateTimeNormalizer::TIMEZONE_KEY, $timezone); } + + /** + * @param 'int'|'float'|null $cast + */ + public function withCast(?string $cast): static + { + return $this->with(DateTimeNormalizer::CAST_KEY, $cast); + } } diff --git a/Normalizer/DateTimeNormalizer.php b/Normalizer/DateTimeNormalizer.php index 4e36aa63b..527a104e7 100644 --- a/Normalizer/DateTimeNormalizer.php +++ b/Normalizer/DateTimeNormalizer.php @@ -25,10 +25,12 @@ final class DateTimeNormalizer implements NormalizerInterface, DenormalizerInter { public const FORMAT_KEY = 'datetime_format'; public const TIMEZONE_KEY = 'datetime_timezone'; + public const CAST_KEY = 'datetime_cast'; private array $defaultContext = [ self::FORMAT_KEY => \DateTimeInterface::RFC3339, self::TIMEZONE_KEY => null, + self::CAST_KEY => null, ]; private const SUPPORTED_TYPES = [ @@ -59,7 +61,7 @@ public function getSupportedTypes(?string $format): array /** * @throws InvalidArgumentException */ - public function normalize(mixed $object, ?string $format = null, array $context = []): string + public function normalize(mixed $object, ?string $format = null, array $context = []): int|float|string { if (!$object instanceof \DateTimeInterface) { throw new InvalidArgumentException('The object must implement the "\DateTimeInterface".'); @@ -73,7 +75,11 @@ public function normalize(mixed $object, ?string $format = null, array $context $object = $object->setTimezone($timezone); } - return $object->format($dateTimeFormat); + return match ($context[self::CAST_KEY] ?? $this->defaultContext[self::CAST_KEY] ?? false) { + 'int' => (int) $object->format($dateTimeFormat), + 'float' => (float) $object->format($dateTimeFormat), + default => $object->format($dateTimeFormat), + }; } public function supportsNormalization(mixed $data, ?string $format = null, array $context = []): bool diff --git a/Tests/Context/Normalizer/DateTimeNormalizerContextBuilderTest.php b/Tests/Context/Normalizer/DateTimeNormalizerContextBuilderTest.php index 8ab41f949..ac4badc19 100644 --- a/Tests/Context/Normalizer/DateTimeNormalizerContextBuilderTest.php +++ b/Tests/Context/Normalizer/DateTimeNormalizerContextBuilderTest.php @@ -38,6 +38,7 @@ public function testWithers(array $values) $context = $this->contextBuilder ->withFormat($values[DateTimeNormalizer::FORMAT_KEY]) ->withTimezone($values[DateTimeNormalizer::TIMEZONE_KEY]) + ->withCast($values[DateTimeNormalizer::CAST_KEY]) ->toArray(); $this->assertEquals($values, $context); @@ -51,11 +52,13 @@ public static function withersDataProvider(): iterable yield 'With values' => [[ DateTimeNormalizer::FORMAT_KEY => 'format', DateTimeNormalizer::TIMEZONE_KEY => new \DateTimeZone('GMT'), + DateTimeNormalizer::CAST_KEY => 'int', ]]; yield 'With null values' => [[ DateTimeNormalizer::FORMAT_KEY => null, DateTimeNormalizer::TIMEZONE_KEY => null, + DateTimeNormalizer::CAST_KEY => null, ]]; } diff --git a/Tests/Normalizer/DateTimeNormalizerTest.php b/Tests/Normalizer/DateTimeNormalizerTest.php index e65b6f67d..5dbf36fbe 100644 --- a/Tests/Normalizer/DateTimeNormalizerTest.php +++ b/Tests/Normalizer/DateTimeNormalizerTest.php @@ -154,6 +154,72 @@ public static function normalizeUsingTimeZonePassedInContextAndExpectedFormatWit ]; } + /** + * @dataProvider provideNormalizeUsingCastCases + */ + public function testNormalizeUsingCastPassedInConstructor(\DateTimeInterface $value, string $format, ?string $cast, string|int|float $expectedResult) + { + $normalizer = new DateTimeNormalizer([DateTimeNormalizer::CAST_KEY => $cast]); + + $this->assertSame($normalizer->normalize($value, null, [DateTimeNormalizer::FORMAT_KEY => $format]), $expectedResult); + } + + /** + * @dataProvider provideNormalizeUsingCastCases + */ + public function testNormalizeUsingCastPassedInContext(\DateTimeInterface $value, string $format, ?string $cast, string|int|float $expectedResult) + { + $this->assertSame($this->normalizer->normalize($value, null, [DateTimeNormalizer::FORMAT_KEY => $format, DateTimeNormalizer::CAST_KEY => $cast]), $expectedResult); + } + + /** + * @return iterable + */ + public static function provideNormalizeUsingCastCases(): iterable + { + yield [ + \DateTimeImmutable::createFromFormat('U', '1703071202'), + 'Y', + null, + '2023', + ]; + + yield [ + \DateTimeImmutable::createFromFormat('U', '1703071202'), + 'Y', + 'int', + 2023, + ]; + + yield [ + \DateTimeImmutable::createFromFormat('U', '1703071202'), + 'Ymd', + 'int', + 20231220, + ]; + + yield [ + \DateTimeImmutable::createFromFormat('U', '1703071202'), + 'Y', + 'int', + 2023, + ]; + + yield [ + \DateTimeImmutable::createFromFormat('U.v', '1703071202.388'), + 'U.v', + 'float', + 1703071202.388, + ]; + + yield [ + \DateTimeImmutable::createFromFormat('U.u', '1703071202.388811'), + 'U.u', + 'float', + 1703071202.388811, + ]; + } + public function testNormalizeInvalidObjectThrowsException() { $this->expectException(InvalidArgumentException::class); From 38b8e259a542e7c56aeea59ef7af38c483f3fadb Mon Sep 17 00:00:00 2001 From: Mathias Arlaud Date: Tue, 29 Aug 2023 16:10:08 +0200 Subject: [PATCH 195/297] [Serializer] Add default groups --- CHANGELOG.md | 1 + NameConverter/MetadataAwareNameConverter.php | 7 +- Normalizer/AbstractNormalizer.php | 12 +++- Tests/Fixtures/Attributes/GroupDummy.php | 24 +++++++ Tests/Mapping/TestClassMetadataFactory.php | 8 +++ Tests/Normalizer/Features/GroupsTestTrait.php | 65 +++++++++++++++++-- .../Normalizer/GetSetMethodNormalizerTest.php | 2 + Tests/Normalizer/ObjectNormalizerTest.php | 2 + Tests/Normalizer/PropertyNormalizerTest.php | 15 ++++- 9 files changed, 123 insertions(+), 13 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 13de5123f..a5cb2e777 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,7 @@ CHANGELOG --- * Add `DateTimeNormalizer::CAST_KEY` context option + * Add `Default` and "class name" default groups 7.0 --- diff --git a/NameConverter/MetadataAwareNameConverter.php b/NameConverter/MetadataAwareNameConverter.php index 445ad7422..327d92dc1 100644 --- a/NameConverter/MetadataAwareNameConverter.php +++ b/NameConverter/MetadataAwareNameConverter.php @@ -128,13 +128,16 @@ private function getCacheValueForAttributesMetadata(string $class, array $contex } $metadataGroups = $metadata->getGroups(); + $contextGroups = (array) ($context[AbstractNormalizer::GROUPS] ?? []); + $contextGroupsHasBeenDefined = [] !== $contextGroups; + $contextGroups = array_merge($contextGroups, ['Default', (false !== $nsSep = strrpos($class, '\\')) ? substr($class, $nsSep + 1) : $class]); - if ($contextGroups && !$metadataGroups) { + if ($contextGroupsHasBeenDefined && !$metadataGroups) { continue; } - if ($metadataGroups && !array_intersect($metadataGroups, $contextGroups) && !\in_array('*', $contextGroups, true)) { + if ($metadataGroups && !array_intersect(array_merge($metadataGroups, ['*']), $contextGroups)) { continue; } diff --git a/Normalizer/AbstractNormalizer.php b/Normalizer/AbstractNormalizer.php index 776b00fc0..8fbc85042 100644 --- a/Normalizer/AbstractNormalizer.php +++ b/Normalizer/AbstractNormalizer.php @@ -213,11 +213,17 @@ protected function getAllowedAttributes(string|object $classOrObject, array $con return false; } + $classMetadata = $this->classMetadataFactory->getMetadataFor($classOrObject); + $class = $classMetadata->getName(); + $groups = $this->getGroups($context); + $groupsHasBeenDefined = [] !== $groups; + $groups = array_merge($groups, ['Default', (false !== $nsSep = strrpos($class, '\\')) ? substr($class, $nsSep + 1) : $class]); $allowedAttributes = []; $ignoreUsed = false; - foreach ($this->classMetadataFactory->getMetadataFor($classOrObject)->getAttributesMetadata() as $attributeMetadata) { + + foreach ($classMetadata->getAttributesMetadata() as $attributeMetadata) { if ($ignore = $attributeMetadata->isIgnored()) { $ignoreUsed = true; } @@ -225,14 +231,14 @@ protected function getAllowedAttributes(string|object $classOrObject, array $con // If you update this check, update accordingly the one in Symfony\Component\PropertyInfo\Extractor\SerializerExtractor::getProperties() if ( !$ignore - && ([] === $groups || array_intersect(array_merge($attributeMetadata->getGroups(), ['*']), $groups)) + && (!$groupsHasBeenDefined || array_intersect(array_merge($attributeMetadata->getGroups(), ['*']), $groups)) && $this->isAllowedAttribute($classOrObject, $name = $attributeMetadata->getName(), null, $context) ) { $allowedAttributes[] = $attributesAsString ? $name : $attributeMetadata; } } - if (!$ignoreUsed && [] === $groups && $allowExtraAttributes) { + if (!$ignoreUsed && !$groupsHasBeenDefined && $allowExtraAttributes) { // Backward Compatibility with the code using this method written before the introduction of @Ignore return false; } diff --git a/Tests/Fixtures/Attributes/GroupDummy.php b/Tests/Fixtures/Attributes/GroupDummy.php index 749e841a5..5c34c95a4 100644 --- a/Tests/Fixtures/Attributes/GroupDummy.php +++ b/Tests/Fixtures/Attributes/GroupDummy.php @@ -27,6 +27,10 @@ class GroupDummy extends GroupDummyParent implements GroupDummyInterface protected $quux; private $fooBar; private $symfony; + #[Groups(['Default'])] + private $default; + #[Groups(['GroupDummy'])] + private $className; #[Groups(['b'])] public function setBar($bar) @@ -80,4 +84,24 @@ public function setQuux($quux): void { $this->quux = $quux; } + + public function setDefault($default) + { + $this->default = $default; + } + + public function getDefault() + { + return $this->default; + } + + public function setClassName($className) + { + $this->className = $className; + } + + public function getClassName() + { + return $this->className; + } } diff --git a/Tests/Mapping/TestClassMetadataFactory.php b/Tests/Mapping/TestClassMetadataFactory.php index 61147316a..d617ffaeb 100644 --- a/Tests/Mapping/TestClassMetadataFactory.php +++ b/Tests/Mapping/TestClassMetadataFactory.php @@ -63,6 +63,14 @@ public static function createClassMetadata(string $namespace, bool $withParent = $symfony->addGroup('name_converter'); } + $default = new AttributeMetadata('default'); + $default->addGroup('Default'); + $expected->addAttributeMetadata($default); + + $className = new AttributeMetadata('className'); + $className->addGroup('GroupDummy'); + $expected->addAttributeMetadata($className); + // load reflection class so that the comparison passes $expected->getReflectionClass(); diff --git a/Tests/Normalizer/Features/GroupsTestTrait.php b/Tests/Normalizer/Features/GroupsTestTrait.php index 08d5e065a..ba4d76323 100644 --- a/Tests/Normalizer/Features/GroupsTestTrait.php +++ b/Tests/Normalizer/Features/GroupsTestTrait.php @@ -31,13 +31,18 @@ public function testGroupsNormalize() $obj = new GroupDummy(); $obj->setFoo('foo'); $obj->setBar('bar'); + $obj->setQuux('quux'); $obj->setFooBar('fooBar'); $obj->setSymfony('symfony'); $obj->setKevin('kevin'); $obj->setCoopTilleuls('coopTilleuls'); + $obj->setDefault('default'); + $obj->setClassName('className'); $this->assertEquals([ 'bar' => 'bar', + 'default' => 'default', + 'className' => 'className', ], $normalizer->normalize($obj, null, ['groups' => ['c']])); $this->assertEquals([ @@ -47,7 +52,26 @@ public function testGroupsNormalize() 'bar' => 'bar', 'kevin' => 'kevin', 'coopTilleuls' => 'coopTilleuls', + 'default' => 'default', + 'className' => 'className', ], $normalizer->normalize($obj, null, ['groups' => ['a', 'c']])); + + $this->assertEquals([ + 'default' => 'default', + 'className' => 'className', + ], $normalizer->normalize($obj, null, ['groups' => ['unknown']])); + + $this->assertEquals([ + 'quux' => 'quux', + 'symfony' => 'symfony', + 'foo' => 'foo', + 'fooBar' => 'fooBar', + 'bar' => 'bar', + 'kevin' => 'kevin', + 'coopTilleuls' => 'coopTilleuls', + 'default' => 'default', + 'className' => 'className', + ], $normalizer->normalize($obj)); } public function testGroupsDenormalize() @@ -55,27 +79,49 @@ public function testGroupsDenormalize() $normalizer = $this->getDenormalizerForGroups(); $obj = new GroupDummy(); - $obj->setFoo('foo'); + $obj->setDefault('default'); + $obj->setClassName('className'); - $data = ['foo' => 'foo', 'bar' => 'bar']; + $data = [ + 'foo' => 'foo', + 'bar' => 'bar', + 'quux' => 'quux', + 'default' => 'default', + 'className' => 'className', + ]; - $normalized = $normalizer->denormalize( + $denormalized = $normalizer->denormalize( + $data, + GroupDummy::class, + null, + ['groups' => ['unknown']] + ); + $this->assertEquals($obj, $denormalized); + + $obj->setFoo('foo'); + + $denormalized = $normalizer->denormalize( $data, GroupDummy::class, null, ['groups' => ['a']] ); - $this->assertEquals($obj, $normalized); + $this->assertEquals($obj, $denormalized); $obj->setBar('bar'); - $normalized = $normalizer->denormalize( + $denormalized = $normalizer->denormalize( $data, GroupDummy::class, null, ['groups' => ['a', 'b']] ); - $this->assertEquals($obj, $normalized); + $this->assertEquals($obj, $denormalized); + + $obj->setQuux('quux'); + + $denormalized = $normalizer->denormalize($data, GroupDummy::class); + $this->assertEquals($obj, $denormalized); } public function testNormalizeNoPropertyInGroup() @@ -84,7 +130,12 @@ public function testNormalizeNoPropertyInGroup() $obj = new GroupDummy(); $obj->setFoo('foo'); + $obj->setDefault('default'); + $obj->setClassName('className'); - $this->assertEquals([], $normalizer->normalize($obj, null, ['groups' => ['notExist']])); + $this->assertEquals([ + 'default' => 'default', + 'className' => 'className', + ], $normalizer->normalize($obj, null, ['groups' => ['notExist']])); } } diff --git a/Tests/Normalizer/GetSetMethodNormalizerTest.php b/Tests/Normalizer/GetSetMethodNormalizerTest.php index 8f28999fe..8000dea19 100644 --- a/Tests/Normalizer/GetSetMethodNormalizerTest.php +++ b/Tests/Normalizer/GetSetMethodNormalizerTest.php @@ -295,6 +295,8 @@ public function testGroupsNormalizeWithNameConverter() 'bar' => null, 'foo_bar' => '@dunglas', 'symfony' => '@coopTilleuls', + 'default' => null, + 'class_name' => null, ], $this->normalizer->normalize($obj, null, [GetSetMethodNormalizer::GROUPS => ['name_converter']]) ); diff --git a/Tests/Normalizer/ObjectNormalizerTest.php b/Tests/Normalizer/ObjectNormalizerTest.php index 7350e71bf..fa47995da 100644 --- a/Tests/Normalizer/ObjectNormalizerTest.php +++ b/Tests/Normalizer/ObjectNormalizerTest.php @@ -482,6 +482,8 @@ public function testGroupsNormalizeWithNameConverter() 'bar' => null, 'foo_bar' => '@dunglas', 'symfony' => '@coopTilleuls', + 'default' => null, + 'class_name' => null, ], $this->normalizer->normalize($obj, null, [ObjectNormalizer::GROUPS => ['name_converter']]) ); diff --git a/Tests/Normalizer/PropertyNormalizerTest.php b/Tests/Normalizer/PropertyNormalizerTest.php index 7ba3d95eb..04a9afaf8 100644 --- a/Tests/Normalizer/PropertyNormalizerTest.php +++ b/Tests/Normalizer/PropertyNormalizerTest.php @@ -184,7 +184,18 @@ public function testNormalizeWithParentClass() $group->setKevin('Kevin'); $group->setCoopTilleuls('coop'); $this->assertEquals( - ['foo' => 'foo', 'bar' => 'bar', 'quux' => 'quux', 'kevin' => 'Kevin', 'coopTilleuls' => 'coop', 'fooBar' => null, 'symfony' => null, 'baz' => 'baz'], + [ + 'foo' => 'foo', + 'bar' => 'bar', + 'quux' => 'quux', + 'kevin' => 'Kevin', + 'coopTilleuls' => 'coop', + 'fooBar' => null, + 'symfony' => null, + 'baz' => 'baz', + 'default' => null, + 'className' => null, + ], $this->normalizer->normalize($group, 'any') ); } @@ -303,6 +314,8 @@ public function testGroupsNormalizeWithNameConverter() 'bar' => null, 'foo_bar' => '@dunglas', 'symfony' => '@coopTilleuls', + 'default' => null, + 'class_name' => null, ], $this->normalizer->normalize($obj, null, [PropertyNormalizer::GROUPS => ['name_converter']]) ); From a9853ac96f9716fb70c62957f877c09ca3f1b362 Mon Sep 17 00:00:00 2001 From: "hubert.lenoir" Date: Mon, 4 Mar 2024 15:55:09 +0100 Subject: [PATCH 196/297] [HttpKernel] allow boolean argument support for MapQueryString --- CHANGELOG.md | 1 + Normalizer/AbstractNormalizer.php | 34 +++++++++++--- Normalizer/AbstractObjectNormalizer.php | 4 +- .../Normalizer/Features/FilterBoolObject.php | 19 ++++++++ .../Features/FilterBoolTestTrait.php | 47 +++++++++++++++++++ .../Normalizer/GetSetMethodNormalizerTest.php | 7 +++ Tests/Normalizer/ObjectNormalizerTest.php | 7 +++ Tests/Normalizer/PropertyNormalizerTest.php | 7 +++ 8 files changed, 118 insertions(+), 8 deletions(-) create mode 100644 Tests/Normalizer/Features/FilterBoolObject.php create mode 100644 Tests/Normalizer/Features/FilterBoolTestTrait.php diff --git a/CHANGELOG.md b/CHANGELOG.md index a5cb2e777..88a42d4e8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,7 @@ CHANGELOG * Add `DateTimeNormalizer::CAST_KEY` context option * Add `Default` and "class name" default groups + * Add `AbstractNormalizer::FILTER_BOOL` context option 7.0 --- diff --git a/Normalizer/AbstractNormalizer.php b/Normalizer/AbstractNormalizer.php index bd3171b17..93c367b7d 100644 --- a/Normalizer/AbstractNormalizer.php +++ b/Normalizer/AbstractNormalizer.php @@ -118,6 +118,16 @@ abstract class AbstractNormalizer implements NormalizerInterface, DenormalizerIn */ public const REQUIRE_ALL_PROPERTIES = 'require_all_properties'; + /** + * Flag to control whether a non-boolean value should be filtered using the + * filter_var function with the {@see https://www.php.net/manual/fr/filter.filters.validate.php} + * \FILTER_VALIDATE_BOOL filter before casting it to a boolean. + * + * "0", "false", "off", "no" and "" will be cast to false. + * "1", "true", "on" and "yes" will be cast to true. + */ + public const FILTER_BOOL = 'filter_bool'; + /** * @internal */ @@ -436,12 +446,7 @@ protected function instantiateObject(array &$data, string $class, array &$contex unset($context['has_constructor']); if (!$reflectionClass->isInstantiable()) { - throw NotNormalizableValueException::createForUnexpectedDataType( - sprintf('Failed to create object because the class "%s" is not instantiable.', $class), - $data, - ['unknown'], - $context['deserialization_path'] ?? null - ); + throw NotNormalizableValueException::createForUnexpectedDataType(sprintf('Failed to create object because the class "%s" is not instantiable.', $class), $data, ['unknown'], $context['deserialization_path'] ?? null); } return new $class(); @@ -473,7 +478,9 @@ protected function denormalizeParameter(\ReflectionClass $class, \ReflectionPara return null; } - return $this->applyCallbacks($parameterData, $class->getName(), $parameterName, $format, $context); + $parameterData = $this->applyCallbacks($parameterData, $class->getName(), $parameterName, $format, $context); + + return $this->applyFilterBool($parameter, $parameterData, $context); } /** @@ -524,6 +531,19 @@ final protected function applyCallbacks(mixed $value, object|string $object, str return $callback ? $callback($value, $object, $attribute, $format, $context) : $value; } + final protected function applyFilterBool(\ReflectionParameter $parameter, mixed $value, array $context): mixed + { + if (!($context[self::FILTER_BOOL] ?? false)) { + return $value; + } + + if (!($parameterType = $parameter->getType()) instanceof \ReflectionNamedType || 'bool' !== $parameterType->getName()) { + return $value; + } + + return filter_var($value, \FILTER_VALIDATE_BOOL, \FILTER_NULL_ON_FAILURE) ?? $value; + } + /** * Computes the normalization context merged with current one. Metadata always wins over global context, as more specific. * diff --git a/Normalizer/AbstractObjectNormalizer.php b/Normalizer/AbstractObjectNormalizer.php index 4ca328d59..1c2f52fcd 100644 --- a/Normalizer/AbstractObjectNormalizer.php +++ b/Normalizer/AbstractObjectNormalizer.php @@ -601,7 +601,9 @@ protected function denormalizeParameter(\ReflectionClass $class, \ReflectionPara $parameterData = $this->validateAndDenormalize($types, $class->getName(), $parameterName, $parameterData, $format, $context); - return $this->applyCallbacks($parameterData, $class->getName(), $parameterName, $format, $context); + $parameterData = $this->applyCallbacks($parameterData, $class->getName(), $parameterName, $format, $context); + + return $this->applyFilterBool($parameter, $parameterData, $context); } /** diff --git a/Tests/Normalizer/Features/FilterBoolObject.php b/Tests/Normalizer/Features/FilterBoolObject.php new file mode 100644 index 000000000..2d9828b91 --- /dev/null +++ b/Tests/Normalizer/Features/FilterBoolObject.php @@ -0,0 +1,19 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Serializer\Tests\Normalizer\Features; + +class FilterBoolObject +{ + public function __construct(public ?bool $value) + { + } +} diff --git a/Tests/Normalizer/Features/FilterBoolTestTrait.php b/Tests/Normalizer/Features/FilterBoolTestTrait.php new file mode 100644 index 000000000..ceb80dc3b --- /dev/null +++ b/Tests/Normalizer/Features/FilterBoolTestTrait.php @@ -0,0 +1,47 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Serializer\Tests\Normalizer\Features; + +use Symfony\Component\Serializer\Normalizer\DenormalizerInterface; + +/** + * Test AbstractNormalizer::FILTER_BOOL. + */ +trait FilterBoolTestTrait +{ + abstract protected function getNormalizerForFilterBool(): DenormalizerInterface; + + /** + * @dataProvider provideObjectWithBoolArguments + */ + public function testObjectWithBoolArguments(?bool $expectedValue, ?string $parameterValue) + { + $normalizer = $this->getNormalizerForFilterBool(); + + $dummy = $normalizer->denormalize(['value' => $parameterValue], FilterBoolObject::class, context: ['filter_bool' => true]); + + $this->assertSame($expectedValue, $dummy->value); + } + + public static function provideObjectWithBoolArguments() + { + yield 'default value' => [null, null]; + yield '0' => [false, '0']; + yield 'false' => [false, 'false']; + yield 'no' => [false, 'no']; + yield 'off' => [false, 'off']; + yield '1' => [true, '1']; + yield 'true' => [true, 'true']; + yield 'yes' => [true, 'yes']; + yield 'on' => [true, 'on']; + } +} diff --git a/Tests/Normalizer/GetSetMethodNormalizerTest.php b/Tests/Normalizer/GetSetMethodNormalizerTest.php index 8000dea19..c5fb73100 100644 --- a/Tests/Normalizer/GetSetMethodNormalizerTest.php +++ b/Tests/Normalizer/GetSetMethodNormalizerTest.php @@ -39,6 +39,7 @@ use Symfony\Component\Serializer\Tests\Normalizer\Features\CallbacksTestTrait; use Symfony\Component\Serializer\Tests\Normalizer\Features\CircularReferenceTestTrait; use Symfony\Component\Serializer\Tests\Normalizer\Features\ConstructorArgumentsTestTrait; +use Symfony\Component\Serializer\Tests\Normalizer\Features\FilterBoolTestTrait; use Symfony\Component\Serializer\Tests\Normalizer\Features\GroupsTestTrait; use Symfony\Component\Serializer\Tests\Normalizer\Features\IgnoredAttributesTestTrait; use Symfony\Component\Serializer\Tests\Normalizer\Features\MaxDepthTestTrait; @@ -53,6 +54,7 @@ class GetSetMethodNormalizerTest extends TestCase use CallbacksTestTrait; use CircularReferenceTestTrait; use ConstructorArgumentsTestTrait; + use FilterBoolTestTrait; use GroupsTestTrait; use IgnoredAttributesTestTrait; use MaxDepthTestTrait; @@ -279,6 +281,11 @@ protected function getDenormalizerForGroups(): GetSetMethodNormalizer return new GetSetMethodNormalizer($classMetadataFactory); } + protected function getNormalizerForFilterBool(): GetSetMethodNormalizer + { + return new GetSetMethodNormalizer(); + } + public function testGroupsNormalizeWithNameConverter() { $classMetadataFactory = new ClassMetadataFactory(new AttributeLoader()); diff --git a/Tests/Normalizer/ObjectNormalizerTest.php b/Tests/Normalizer/ObjectNormalizerTest.php index fa47995da..9994a27e5 100644 --- a/Tests/Normalizer/ObjectNormalizerTest.php +++ b/Tests/Normalizer/ObjectNormalizerTest.php @@ -50,6 +50,7 @@ use Symfony\Component\Serializer\Tests\Normalizer\Features\CircularReferenceTestTrait; use Symfony\Component\Serializer\Tests\Normalizer\Features\ConstructorArgumentsTestTrait; use Symfony\Component\Serializer\Tests\Normalizer\Features\ContextMetadataTestTrait; +use Symfony\Component\Serializer\Tests\Normalizer\Features\FilterBoolTestTrait; use Symfony\Component\Serializer\Tests\Normalizer\Features\GroupsTestTrait; use Symfony\Component\Serializer\Tests\Normalizer\Features\IgnoredAttributesTestTrait; use Symfony\Component\Serializer\Tests\Normalizer\Features\MaxDepthTestTrait; @@ -72,6 +73,7 @@ class ObjectNormalizerTest extends TestCase use CircularReferenceTestTrait; use ConstructorArgumentsTestTrait; use ContextMetadataTestTrait; + use FilterBoolTestTrait; use GroupsTestTrait; use IgnoredAttributesTestTrait; use MaxDepthTestTrait; @@ -345,6 +347,11 @@ protected function getDenormalizerForAttributes(): ObjectNormalizer return $normalizer; } + protected function getNormalizerForFilterBool(): ObjectNormalizer + { + return new ObjectNormalizer(); + } + public function testAttributesContextDenormalizeConstructor() { $normalizer = new ObjectNormalizer(null, null, null, new ReflectionExtractor()); diff --git a/Tests/Normalizer/PropertyNormalizerTest.php b/Tests/Normalizer/PropertyNormalizerTest.php index 04a9afaf8..b93a7bb9f 100644 --- a/Tests/Normalizer/PropertyNormalizerTest.php +++ b/Tests/Normalizer/PropertyNormalizerTest.php @@ -38,6 +38,7 @@ use Symfony\Component\Serializer\Tests\Normalizer\Features\CallbacksTestTrait; use Symfony\Component\Serializer\Tests\Normalizer\Features\CircularReferenceTestTrait; use Symfony\Component\Serializer\Tests\Normalizer\Features\ConstructorArgumentsTestTrait; +use Symfony\Component\Serializer\Tests\Normalizer\Features\FilterBoolTestTrait; use Symfony\Component\Serializer\Tests\Normalizer\Features\GroupsTestTrait; use Symfony\Component\Serializer\Tests\Normalizer\Features\IgnoredAttributesTestTrait; use Symfony\Component\Serializer\Tests\Normalizer\Features\MaxDepthTestTrait; @@ -52,6 +53,7 @@ class PropertyNormalizerTest extends TestCase use CallbacksTestTrait; use CircularReferenceTestTrait; use ConstructorArgumentsTestTrait; + use FilterBoolTestTrait; use GroupsTestTrait; use IgnoredAttributesTestTrait; use MaxDepthTestTrait; @@ -259,6 +261,11 @@ protected function getSelfReferencingModel() return new PropertyCircularReferenceDummy(); } + protected function getNormalizerForFilterBool(): PropertyNormalizer + { + return new PropertyNormalizer(); + } + public function testSiblingReference() { $serializer = new Serializer([$this->normalizer]); From ad2dc5abfc3abd2c9b3087cc9c4d93aa271173bb Mon Sep 17 00:00:00 2001 From: Aurelien Pillevesse Date: Sat, 10 Feb 2024 20:13:52 +0100 Subject: [PATCH 197/297] add context to force snake_case --- Exception/UnexpectedPropertyException.php | 29 +++++++++++++++++++ .../CamelCaseToSnakeCaseNameConverter.php | 18 ++++++++++-- .../CamelCaseToSnakeCaseNameConverterTest.php | 18 ++++++++++++ 3 files changed, 62 insertions(+), 3 deletions(-) create mode 100644 Exception/UnexpectedPropertyException.php diff --git a/Exception/UnexpectedPropertyException.php b/Exception/UnexpectedPropertyException.php new file mode 100644 index 000000000..4f9ead9a6 --- /dev/null +++ b/Exception/UnexpectedPropertyException.php @@ -0,0 +1,29 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Serializer\Exception; + +/** + * UnexpectedPropertyException. + * + * @author Aurélien Pillevesse + */ +class UnexpectedPropertyException extends \UnexpectedValueException implements ExceptionInterface +{ + public function __construct( + public readonly string $property, + ?\Throwable $previous = null, + ) { + $msg = sprintf('Property is not allowed ("%s" is unknown).', $this->property); + + parent::__construct($msg, 0, $previous); + } +} diff --git a/NameConverter/CamelCaseToSnakeCaseNameConverter.php b/NameConverter/CamelCaseToSnakeCaseNameConverter.php index a7b450fd2..8c0b53157 100644 --- a/NameConverter/CamelCaseToSnakeCaseNameConverter.php +++ b/NameConverter/CamelCaseToSnakeCaseNameConverter.php @@ -11,13 +11,21 @@ namespace Symfony\Component\Serializer\NameConverter; +use Symfony\Component\Serializer\Exception\UnexpectedPropertyException; + /** * CamelCase to Underscore name converter. * * @author Kévin Dunglas + * @author Aurélien Pillevesse */ -class CamelCaseToSnakeCaseNameConverter implements NameConverterInterface +class CamelCaseToSnakeCaseNameConverter implements AdvancedNameConverterInterface { + /** + * Require all properties to be written in snake_case. + */ + public const REQUIRE_SNAKE_CASE_PROPERTIES = 'require_snake_case_properties'; + /** * @param array|null $attributes The list of attributes to rename or null for all attributes * @param bool $lowerCamelCase Use lowerCamelCase style @@ -28,7 +36,7 @@ public function __construct( ) { } - public function normalize(string $propertyName): string + public function normalize(string $propertyName, ?string $class = null, ?string $format = null, array $context = []): string { if (null === $this->attributes || \in_array($propertyName, $this->attributes, true)) { return strtolower(preg_replace('/[A-Z]/', '_\\0', lcfirst($propertyName))); @@ -37,8 +45,12 @@ public function normalize(string $propertyName): string return $propertyName; } - public function denormalize(string $propertyName): string + public function denormalize(string $propertyName, ?string $class = null, ?string $format = null, array $context = []): string { + if (($context[self::REQUIRE_SNAKE_CASE_PROPERTIES] ?? false) && $propertyName !== $this->normalize($propertyName, $class, $format, $context)) { + throw new UnexpectedPropertyException($propertyName); + } + $camelCasedName = preg_replace_callback('/(^|_|\.)+(.)/', fn ($match) => ('.' === $match[1] ? '_' : '').strtoupper($match[2]), $propertyName); if ($this->lowerCamelCase) { diff --git a/Tests/NameConverter/CamelCaseToSnakeCaseNameConverterTest.php b/Tests/NameConverter/CamelCaseToSnakeCaseNameConverterTest.php index e4d419e45..f9d941890 100644 --- a/Tests/NameConverter/CamelCaseToSnakeCaseNameConverterTest.php +++ b/Tests/NameConverter/CamelCaseToSnakeCaseNameConverterTest.php @@ -12,11 +12,13 @@ namespace Symfony\Component\Serializer\Tests\NameConverter; use PHPUnit\Framework\TestCase; +use Symfony\Component\Serializer\Exception\UnexpectedPropertyException; use Symfony\Component\Serializer\NameConverter\CamelCaseToSnakeCaseNameConverter; use Symfony\Component\Serializer\NameConverter\NameConverterInterface; /** * @author Kévin Dunglas + * @author Aurélien Pillevesse */ class CamelCaseToSnakeCaseNameConverterTest extends TestCase { @@ -55,4 +57,20 @@ public static function attributeProvider() ['this_is_a_test', 'ThisIsATest', false], ]; } + + public function testDenormalizeWithContext() + { + $nameConverter = new CamelCaseToSnakeCaseNameConverter(null, true); + $denormalizedValue = $nameConverter->denormalize('last_name', context: [CamelCaseToSnakeCaseNameConverter::REQUIRE_SNAKE_CASE_PROPERTIES]); + + $this->assertSame($denormalizedValue, 'lastName'); + } + + public function testErrorDenormalizeWithContext() + { + $nameConverter = new CamelCaseToSnakeCaseNameConverter(null, true); + + $this->expectException(UnexpectedPropertyException::class); + $nameConverter->denormalize('lastName', context: [CamelCaseToSnakeCaseNameConverter::REQUIRE_SNAKE_CASE_PROPERTIES => true]); + } } From d594563498f583b928d5047d9f5ad721623e5aff Mon Sep 17 00:00:00 2001 From: Aurelien Pillevesse Date: Sun, 17 Mar 2024 18:46:53 +0100 Subject: [PATCH 198/297] add missing changelog for PR 53898 --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 88a42d4e8..9e495db1c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,7 @@ CHANGELOG * Add `DateTimeNormalizer::CAST_KEY` context option * Add `Default` and "class name" default groups * Add `AbstractNormalizer::FILTER_BOOL` context option + * Add `CamelCaseToSnakeCaseNameConverter::REQUIRE_SNAKE_CASE_PROPERTIES` context option 7.0 --- From 50b490aa88cbcf1300bf78f639b2837cac9898b9 Mon Sep 17 00:00:00 2001 From: Christian Flothmann Date: Tue, 19 Mar 2024 10:40:14 +0100 Subject: [PATCH 199/297] fix merge --- Tests/Normalizer/ObjectNormalizerTest.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Tests/Normalizer/ObjectNormalizerTest.php b/Tests/Normalizer/ObjectNormalizerTest.php index 21e913103..8285dd88e 100644 --- a/Tests/Normalizer/ObjectNormalizerTest.php +++ b/Tests/Normalizer/ObjectNormalizerTest.php @@ -854,7 +854,7 @@ public function testSamePropertyAsMethod() public function testSamePropertyAsMethodWithPropertySerializedName() { - $classMetadataFactory = new ClassMetadataFactory(new AnnotationLoader(new AnnotationReader())); + $classMetadataFactory = new ClassMetadataFactory(new AttributeLoader()); $this->normalizer = new ObjectNormalizer($classMetadataFactory, new MetadataAwareNameConverter($classMetadataFactory)); $this->normalizer->setSerializer($this->serializer); @@ -871,7 +871,7 @@ public function testSamePropertyAsMethodWithPropertySerializedName() public function testSamePropertyAsMethodWithMethodSerializedName() { - $classMetadataFactory = new ClassMetadataFactory(new AnnotationLoader(new AnnotationReader())); + $classMetadataFactory = new ClassMetadataFactory(new AttributeLoader()); $this->normalizer = new ObjectNormalizer($classMetadataFactory, new MetadataAwareNameConverter($classMetadataFactory)); $this->normalizer->setSerializer($this->serializer); From 8b8394541f730ea2ba6c969e6a1b20a8eedb8612 Mon Sep 17 00:00:00 2001 From: "Alexander M. Turek" Date: Tue, 19 Mar 2024 13:22:59 +0100 Subject: [PATCH 200/297] [Serializer] Fix merge --- ...ertyAsMethodWithMethodSerializedNameDummy.php | 16 ++++------------ ...tyAsMethodWithPropertySerializedNameDummy.php | 16 ++++------------ Tests/Normalizer/ObjectNormalizerTest.php | 4 ++-- 3 files changed, 10 insertions(+), 26 deletions(-) diff --git a/Tests/Fixtures/SamePropertyAsMethodWithMethodSerializedNameDummy.php b/Tests/Fixtures/SamePropertyAsMethodWithMethodSerializedNameDummy.php index b4cf205fd..203118885 100644 --- a/Tests/Fixtures/SamePropertyAsMethodWithMethodSerializedNameDummy.php +++ b/Tests/Fixtures/SamePropertyAsMethodWithMethodSerializedNameDummy.php @@ -28,33 +28,25 @@ public function __construct($freeTrial, $hasSubscribe, $getReady, $isActive) $this->isActive = $isActive; } - /** - * @SerializedName("free_trial_method") - */ + #[SerializedName('free_trial_method')] public function getFreeTrial() { return $this->freeTrial; } - /** - * @SerializedName("has_subscribe_method") - */ + #[SerializedName('has_subscribe_method')] public function hasSubscribe() { return $this->hasSubscribe; } - /** - * @SerializedName("get_ready_method") - */ + #[SerializedName('get_ready_method')] public function getReady() { return $this->getReady; } - /** - * @SerializedName("is_active_method") - */ + #[SerializedName('is_active_method')] public function isActive() { return $this->isActive; diff --git a/Tests/Fixtures/SamePropertyAsMethodWithPropertySerializedNameDummy.php b/Tests/Fixtures/SamePropertyAsMethodWithPropertySerializedNameDummy.php index 04dc64a3c..0b681934f 100644 --- a/Tests/Fixtures/SamePropertyAsMethodWithPropertySerializedNameDummy.php +++ b/Tests/Fixtures/SamePropertyAsMethodWithPropertySerializedNameDummy.php @@ -15,24 +15,16 @@ class SamePropertyAsMethodWithPropertySerializedNameDummy { - /** - * @SerializedName("free_trial_property") - */ + #[SerializedName('free_trial_property')] private $freeTrial; - /** - * @SerializedName("has_subscribe_property") - */ + #[SerializedName('has_subscribe_property')] private $hasSubscribe; - /** - * @SerializedName("get_ready_property") - */ + #[SerializedName('get_ready_property')] private $getReady; - /** - * @SerializedName("is_active_property") - */ + #[SerializedName('is_active_property')] private $isActive; public function __construct($freeTrial, $hasSubscribe, $getReady, $isActive) diff --git a/Tests/Normalizer/ObjectNormalizerTest.php b/Tests/Normalizer/ObjectNormalizerTest.php index 496d76a2a..10b8b3c96 100644 --- a/Tests/Normalizer/ObjectNormalizerTest.php +++ b/Tests/Normalizer/ObjectNormalizerTest.php @@ -869,7 +869,7 @@ public function testSamePropertyAsMethod() public function testSamePropertyAsMethodWithPropertySerializedName() { - $classMetadataFactory = new ClassMetadataFactory(new AnnotationLoader(new AnnotationReader())); + $classMetadataFactory = new ClassMetadataFactory(new AttributeLoader()); $this->normalizer = new ObjectNormalizer($classMetadataFactory, new MetadataAwareNameConverter($classMetadataFactory)); $this->normalizer->setSerializer($this->serializer); @@ -886,7 +886,7 @@ public function testSamePropertyAsMethodWithPropertySerializedName() public function testSamePropertyAsMethodWithMethodSerializedName() { - $classMetadataFactory = new ClassMetadataFactory(new AnnotationLoader(new AnnotationReader())); + $classMetadataFactory = new ClassMetadataFactory(new AttributeLoader()); $this->normalizer = new ObjectNormalizer($classMetadataFactory, new MetadataAwareNameConverter($classMetadataFactory)); $this->normalizer->setSerializer($this->serializer); From 9e3063cd774a7518a5558f8bdab99a37369062b1 Mon Sep 17 00:00:00 2001 From: Dariusz Ruminski Date: Mon, 18 Mar 2024 20:27:13 +0100 Subject: [PATCH 201/297] chore: CS fixes --- Normalizer/DateTimeNormalizer.php | 8 ++++++-- Tests/Normalizer/AbstractObjectNormalizerTest.php | 12 ++++++------ 2 files changed, 12 insertions(+), 8 deletions(-) diff --git a/Normalizer/DateTimeNormalizer.php b/Normalizer/DateTimeNormalizer.php index 527a104e7..46dff93f7 100644 --- a/Normalizer/DateTimeNormalizer.php +++ b/Normalizer/DateTimeNormalizer.php @@ -94,8 +94,12 @@ public function denormalize(mixed $data, string $type, ?string $format = null, a { if (\is_int($data) || \is_float($data)) { switch ($context[self::FORMAT_KEY] ?? $this->defaultContext[self::FORMAT_KEY] ?? null) { - case 'U': $data = sprintf('%d', $data); break; - case 'U.u': $data = sprintf('%.6F', $data); break; + case 'U': + $data = sprintf('%d', $data); + break; + case 'U.u': + $data = sprintf('%.6F', $data); + break; } } diff --git a/Tests/Normalizer/AbstractObjectNormalizerTest.php b/Tests/Normalizer/AbstractObjectNormalizerTest.php index 4ebe11d91..a8e0b6eb1 100644 --- a/Tests/Normalizer/AbstractObjectNormalizerTest.php +++ b/Tests/Normalizer/AbstractObjectNormalizerTest.php @@ -946,12 +946,12 @@ public function testProvidingContextCacheKeyGeneratesSameChildContextCacheKey() $normalizer = new class() extends AbstractObjectNormalizerDummy { public $childContextCacheKey; - protected function extractAttributes(object $object, string $format = null, array $context = []): array + protected function extractAttributes(object $object, ?string $format = null, array $context = []): array { return array_keys((array) $object); } - protected function getAttributeValue(object $object, string $attribute, string $format = null, array $context = []): mixed + protected function getAttributeValue(object $object, string $attribute, ?string $format = null, array $context = []): mixed { return $object->{$attribute}; } @@ -986,12 +986,12 @@ public function testChildContextKeepsOriginalContextCacheKey() $normalizer = new class() extends AbstractObjectNormalizerDummy { public $childContextCacheKey; - protected function extractAttributes(object $object, string $format = null, array $context = []): array + protected function extractAttributes(object $object, ?string $format = null, array $context = []): array { return array_keys((array) $object); } - protected function getAttributeValue(object $object, string $attribute, string $format = null, array $context = []): mixed + protected function getAttributeValue(object $object, string $attribute, ?string $format = null, array $context = []): mixed { return $object->{$attribute}; } @@ -1021,12 +1021,12 @@ public function testChildContextCacheKeyStaysFalseWhenOriginalCacheKeyIsFalse() $normalizer = new class() extends AbstractObjectNormalizerDummy { public $childContextCacheKey; - protected function extractAttributes(object $object, string $format = null, array $context = []): array + protected function extractAttributes(object $object, ?string $format = null, array $context = []): array { return array_keys((array) $object); } - protected function getAttributeValue(object $object, string $attribute, string $format = null, array $context = []): mixed + protected function getAttributeValue(object $object, string $attribute, ?string $format = null, array $context = []): mixed { return $object->{$attribute}; } From 3697adf91f83516c86b4912c08c28084711ed560 Mon Sep 17 00:00:00 2001 From: Christian Flothmann Date: Wed, 27 Mar 2024 22:24:38 +0100 Subject: [PATCH 202/297] stop marking parameters implicitly as nullable --- Tests/Fixtures/FooInterfaceDummyDenormalizer.php | 4 ++-- Tests/Fixtures/FormatAndContextAwareNormalizer.php | 2 +- Tests/Fixtures/StaticConstructorNormalizer.php | 6 +++--- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/Tests/Fixtures/FooInterfaceDummyDenormalizer.php b/Tests/Fixtures/FooInterfaceDummyDenormalizer.php index a8c45373b..0fa3c8202 100644 --- a/Tests/Fixtures/FooInterfaceDummyDenormalizer.php +++ b/Tests/Fixtures/FooInterfaceDummyDenormalizer.php @@ -15,7 +15,7 @@ final class FooInterfaceDummyDenormalizer implements DenormalizerInterface { - public function denormalize(mixed $data, string $type, string $format = null, array $context = []): array + public function denormalize(mixed $data, string $type, ?string $format = null, array $context = []): array { $result = []; foreach ($data as $foo) { @@ -27,7 +27,7 @@ public function denormalize(mixed $data, string $type, string $format = null, ar return $result; } - public function supportsDenormalization(mixed $data, string $type, string $format = null, array $context = []): bool + public function supportsDenormalization(mixed $data, string $type, ?string $format = null, array $context = []): bool { if (str_ends_with($type, '[]')) { $className = substr($type, 0, -2); diff --git a/Tests/Fixtures/FormatAndContextAwareNormalizer.php b/Tests/Fixtures/FormatAndContextAwareNormalizer.php index 404228845..428c618a9 100644 --- a/Tests/Fixtures/FormatAndContextAwareNormalizer.php +++ b/Tests/Fixtures/FormatAndContextAwareNormalizer.php @@ -15,7 +15,7 @@ class FormatAndContextAwareNormalizer extends ObjectNormalizer { - protected function isAllowedAttribute($classOrObject, string $attribute, string $format = null, array $context = []): bool + protected function isAllowedAttribute($classOrObject, string $attribute, ?string $format = null, array $context = []): bool { return \in_array($attribute, ['foo', 'bar']) && 'foo_and_bar_included' === $format; } diff --git a/Tests/Fixtures/StaticConstructorNormalizer.php b/Tests/Fixtures/StaticConstructorNormalizer.php index c2e77aa4b..1ba6884bd 100644 --- a/Tests/Fixtures/StaticConstructorNormalizer.php +++ b/Tests/Fixtures/StaticConstructorNormalizer.php @@ -23,17 +23,17 @@ public function getSupportedTypes(?string $format): array return [StaticConstructorDummy::class]; } - protected function extractAttributes(object $object, string $format = null, array $context = []): array + protected function extractAttributes(object $object, ?string $format = null, array $context = []): array { return get_object_vars($object); } - protected function getAttributeValue(object $object, string $attribute, string $format = null, array $context = []): mixed + protected function getAttributeValue(object $object, string $attribute, ?string $format = null, array $context = []): mixed { return $object->$attribute; } - protected function setAttributeValue(object $object, string $attribute, mixed $value, string $format = null, array $context = []): void + protected function setAttributeValue(object $object, string $attribute, mixed $value, ?string $format = null, array $context = []): void { $object->$attribute = $value; } From bdcae583f759131672372222d9a468c4b810c1d2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gabriel=20Ostroluck=C3=BD?= Date: Sun, 31 Mar 2024 15:15:18 +0200 Subject: [PATCH 203/297] Remove unnecessary empty usages --- Attribute/DiscriminatorMap.php | 4 ++-- Encoder/CsvEncoder.php | 4 ++-- Mapping/Loader/YamlFileLoader.php | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/Attribute/DiscriminatorMap.php b/Attribute/DiscriminatorMap.php index 33a27db85..a77fc2984 100644 --- a/Attribute/DiscriminatorMap.php +++ b/Attribute/DiscriminatorMap.php @@ -29,11 +29,11 @@ public function __construct( private readonly string $typeProperty, private readonly array $mapping, ) { - if (empty($typeProperty)) { + if (!$typeProperty) { throw new InvalidArgumentException(sprintf('Parameter "typeProperty" given to "%s" cannot be empty.', static::class)); } - if (empty($mapping)) { + if (!$mapping) { throw new InvalidArgumentException(sprintf('Parameter "mapping" given to "%s" cannot be empty.', static::class)); } } diff --git a/Encoder/CsvEncoder.php b/Encoder/CsvEncoder.php index dc952b2e0..613fad509 100644 --- a/Encoder/CsvEncoder.php +++ b/Encoder/CsvEncoder.php @@ -62,7 +62,7 @@ public function encode(mixed $data, string $format, array $context = []): string if (!is_iterable($data)) { $data = [[$data]]; - } elseif (empty($data)) { + } elseif (!$data) { $data = [[]]; } else { // Sequential arrays of arrays are considered as collections @@ -191,7 +191,7 @@ public function decode(string $data, string $format, array $context = []): mixed return $result; } - if (empty($result) || isset($result[1])) { + if (!$result || isset($result[1])) { return $result; } diff --git a/Mapping/Loader/YamlFileLoader.php b/Mapping/Loader/YamlFileLoader.php index d47ad7430..b0398355a 100644 --- a/Mapping/Loader/YamlFileLoader.php +++ b/Mapping/Loader/YamlFileLoader.php @@ -160,7 +160,7 @@ private function getClassesFromYaml(): array $classes = $this->yamlParser->parseFile($this->file, Yaml::PARSE_CONSTANT); - if (empty($classes)) { + if (!$classes) { return []; } From b8d7dfe6f48ae0cabe4859e9048468bc7e17660e Mon Sep 17 00:00:00 2001 From: Mathias Arlaud Date: Thu, 26 Oct 2023 10:56:04 +0200 Subject: [PATCH 204/297] [PropertyInfo] Deprecate PropertyInfo Type Co-authored-by: Baptiste Leduc --- Exception/NotNormalizableValueException.php | 10 +- Normalizer/AbstractObjectNormalizer.php | 316 +++++++++++++++--- Normalizer/ArrayDenormalizer.php | 32 +- Normalizer/BackedEnumNormalizer.php | 3 +- Normalizer/DateTimeNormalizer.php | 7 +- Normalizer/DateTimeZoneNormalizer.php | 5 +- Normalizer/UidNormalizer.php | 3 +- .../AbstractObjectNormalizerTest.php | 90 +++-- composer.json | 1 + 9 files changed, 373 insertions(+), 94 deletions(-) diff --git a/Exception/NotNormalizableValueException.php b/Exception/NotNormalizableValueException.php index 279be4127..4b956f1b0 100644 --- a/Exception/NotNormalizableValueException.php +++ b/Exception/NotNormalizableValueException.php @@ -22,17 +22,17 @@ class NotNormalizableValueException extends UnexpectedValueException private bool $useMessageForUser = false; /** - * @param string[] $expectedTypes - * @param bool $useMessageForUser If the message passed to this exception is something that can be shown - * safely to your user. In other words, avoid catching other exceptions and - * passing their message directly to this class. + * @param list $expectedTypes + * @param bool $useMessageForUser If the message passed to this exception is something that can be shown + * safely to your user. In other words, avoid catching other exceptions and + * passing their message directly to this class. */ public static function createForUnexpectedDataType(string $message, mixed $data, array $expectedTypes, ?string $path = null, bool $useMessageForUser = false, int $code = 0, ?\Throwable $previous = null): self { $self = new self($message, $code, $previous); $self->currentType = get_debug_type($data); - $self->expectedTypes = $expectedTypes; + $self->expectedTypes = array_map(strval(...), $expectedTypes); $self->path = $path; $self->useMessageForUser = $useMessageForUser; diff --git a/Normalizer/AbstractObjectNormalizer.php b/Normalizer/AbstractObjectNormalizer.php index 1c2f52fcd..c89b20047 100644 --- a/Normalizer/AbstractObjectNormalizer.php +++ b/Normalizer/AbstractObjectNormalizer.php @@ -17,7 +17,7 @@ use Symfony\Component\PropertyAccess\Exception\UninitializedPropertyException; use Symfony\Component\PropertyAccess\PropertyAccess; use Symfony\Component\PropertyInfo\PropertyTypeExtractorInterface; -use Symfony\Component\PropertyInfo\Type; +use Symfony\Component\PropertyInfo\Type as LegacyType; use Symfony\Component\Serializer\Encoder\CsvEncoder; use Symfony\Component\Serializer\Encoder\JsonEncoder; use Symfony\Component\Serializer\Encoder\XmlEncoder; @@ -32,6 +32,12 @@ use Symfony\Component\Serializer\Mapping\ClassMetadataInterface; use Symfony\Component\Serializer\Mapping\Factory\ClassMetadataFactoryInterface; use Symfony\Component\Serializer\NameConverter\NameConverterInterface; +use Symfony\Component\TypeInfo\Type; +use Symfony\Component\TypeInfo\Type\CollectionType; +use Symfony\Component\TypeInfo\Type\IntersectionType; +use Symfony\Component\TypeInfo\Type\ObjectType; +use Symfony\Component\TypeInfo\Type\UnionType; +use Symfony\Component\TypeInfo\TypeIdentifier; /** * Base class for a normalizer dealing with objects. @@ -51,7 +57,7 @@ abstract class AbstractObjectNormalizer extends AbstractNormalizer public const DEPTH_KEY_PATTERN = 'depth_%s::%s'; /** - * While denormalizing, we can verify that types match. + * While denormalizing, we can verify that type matches. * * You can disable this by setting this flag to true. */ @@ -109,7 +115,10 @@ abstract class AbstractObjectNormalizer extends AbstractNormalizer protected ?ClassDiscriminatorResolverInterface $classDiscriminatorResolver; - private array $typesCache = []; + /** + * @var array|false> + */ + private array $typeCache = []; private array $attributesCache = []; private readonly \Closure $objectClassResolver; @@ -290,7 +299,7 @@ public function denormalize(mixed $data, string $type, ?string $format = null, a $this->validateCallbackContext($context); - if (null === $data && isset($context['value_type']) && $context['value_type'] instanceof Type && $context['value_type']->isNullable()) { + if (null === $data && isset($context['value_type']) && ($context['value_type'] instanceof Type || $context['value_type'] instanceof LegacyType) && $context['value_type']->isNullable()) { return null; } @@ -352,11 +361,15 @@ public function denormalize(mixed $data, string $type, ?string $format = null, a } } - $types = $this->getTypes($resolvedClass, $attribute); - - if (null !== $types) { + if (null !== $type = $this->getType($resolvedClass, $attribute)) { try { - $value = $this->validateAndDenormalize($types, $resolvedClass, $attribute, $value, $format, $attributeContext); + // BC layer for PropertyTypeExtractorInterface::getTypes(). + // Can be removed as soon as PropertyTypeExtractorInterface::getTypes() is removed (8.0). + if (\is_array($type)) { + $value = $this->validateAndDenormalizeLegacy($type, $resolvedClass, $attribute, $value, $format, $attributeContext); + } else { + $value = $this->validateAndDenormalize($type, $resolvedClass, $attribute, $value, $format, $attributeContext); + } } catch (NotNormalizableValueException $exception) { if (isset($context['not_normalizable_value_exceptions'])) { $context['not_normalizable_value_exceptions'][] = $exception; @@ -372,7 +385,7 @@ public function denormalize(mixed $data, string $type, ?string $format = null, a $this->setAttributeValue($object, $attribute, $value, $format, $attributeContext); } catch (PropertyAccessInvalidArgumentException $e) { $exception = NotNormalizableValueException::createForUnexpectedDataType( - sprintf('Failed to denormalize attribute "%s" value for class "%s": '.$e->getMessage(), $attribute, $type), + sprintf('Failed to denormalize attribute "%s" value for class "%s": '.$e->getMessage(), $attribute, $resolvedClass), $data, $e instanceof InvalidTypeException ? [$e->expectedType] : ['unknown'], $attributeContext['deserialization_path'] ?? null, @@ -400,14 +413,17 @@ abstract protected function setAttributeValue(object $object, string $attribute, /** * Validates the submitted data and denormalizes it. * - * @param Type[] $types + * BC layer for PropertyTypeExtractorInterface::getTypes(). + * Can be removed as soon as PropertyTypeExtractorInterface::getTypes() is removed (8.0). + * + * @param LegacyType[] $types * * @throws NotNormalizableValueException * @throws ExtraAttributesException * @throws MissingConstructorArgumentsException * @throws LogicException */ - private function validateAndDenormalize(array $types, string $currentClass, string $attribute, mixed $data, ?string $format, array $context): mixed + private function validateAndDenormalizeLegacy(array $types, string $currentClass, string $attribute, mixed $data, ?string $format, array $context): mixed { $expectedTypes = []; $isUnionType = \count($types) > 1; @@ -440,11 +456,11 @@ private function validateAndDenormalize(array $types, string $currentClass, stri $builtinType = $type->getBuiltinType(); if (\is_string($data) && (XmlEncoder::FORMAT === $format || CsvEncoder::FORMAT === $format)) { if ('' === $data) { - if (Type::BUILTIN_TYPE_ARRAY === $builtinType) { + if (LegacyType::BUILTIN_TYPE_ARRAY === $builtinType) { return []; } - if (Type::BUILTIN_TYPE_STRING === $builtinType) { + if (LegacyType::BUILTIN_TYPE_STRING === $builtinType) { return ''; } @@ -453,24 +469,24 @@ private function validateAndDenormalize(array $types, string $currentClass, stri } switch ($builtinType) { - case Type::BUILTIN_TYPE_BOOL: + case LegacyType::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) { $data = false; } elseif ('true' === $data || '1' === $data) { $data = true; } else { - throw NotNormalizableValueException::createForUnexpectedDataType(sprintf('The type of the "%s" attribute for class "%s" must be bool ("%s" given).', $attribute, $currentClass, $data), $data, [Type::BUILTIN_TYPE_BOOL], $context['deserialization_path'] ?? null); + throw NotNormalizableValueException::createForUnexpectedDataType(sprintf('The type of the "%s" attribute for class "%s" must be bool ("%s" given).', $attribute, $currentClass, $data), $data, [LegacyType::BUILTIN_TYPE_BOOL], $context['deserialization_path'] ?? null); } break; - case Type::BUILTIN_TYPE_INT: + case LegacyType::BUILTIN_TYPE_INT: if (ctype_digit(isset($data[0]) && '-' === $data[0] ? substr($data, 1) : $data)) { $data = (int) $data; } else { - throw NotNormalizableValueException::createForUnexpectedDataType(sprintf('The type of the "%s" attribute for class "%s" must be int ("%s" given).', $attribute, $currentClass, $data), $data, [Type::BUILTIN_TYPE_INT], $context['deserialization_path'] ?? null); + throw NotNormalizableValueException::createForUnexpectedDataType(sprintf('The type of the "%s" attribute for class "%s" must be int ("%s" given).', $attribute, $currentClass, $data), $data, [LegacyType::BUILTIN_TYPE_INT], $context['deserialization_path'] ?? null); } break; - case Type::BUILTIN_TYPE_FLOAT: + case LegacyType::BUILTIN_TYPE_FLOAT: if (is_numeric($data)) { return (float) $data; } @@ -479,13 +495,13 @@ private function validateAndDenormalize(array $types, string $currentClass, stri 'NaN' => \NAN, 'INF' => \INF, '-INF' => -\INF, - default => throw NotNormalizableValueException::createForUnexpectedDataType(sprintf('The type of the "%s" attribute for class "%s" must be float ("%s" given).', $attribute, $currentClass, $data), $data, [Type::BUILTIN_TYPE_FLOAT], $context['deserialization_path'] ?? null), + default => throw NotNormalizableValueException::createForUnexpectedDataType(sprintf('The type of the "%s" attribute for class "%s" must be float ("%s" given).', $attribute, $currentClass, $data), $data, [LegacyType::BUILTIN_TYPE_FLOAT], $context['deserialization_path'] ?? null), }; } } - if (null !== $collectionValueType && Type::BUILTIN_TYPE_OBJECT === $collectionValueType->getBuiltinType()) { - $builtinType = Type::BUILTIN_TYPE_OBJECT; + if (null !== $collectionValueType && LegacyType::BUILTIN_TYPE_OBJECT === $collectionValueType->getBuiltinType()) { + $builtinType = LegacyType::BUILTIN_TYPE_OBJECT; $class = $collectionValueType->getClassName().'[]'; if (\count($collectionKeyType = $type->getCollectionKeyTypes()) > 0) { @@ -493,13 +509,13 @@ private function validateAndDenormalize(array $types, string $currentClass, stri } $context['value_type'] = $collectionValueType; - } elseif ($type->isCollection() && \count($collectionValueType = $type->getCollectionValueTypes()) > 0 && Type::BUILTIN_TYPE_ARRAY === $collectionValueType[0]->getBuiltinType()) { + } elseif ($type->isCollection() && \count($collectionValueType = $type->getCollectionValueTypes()) > 0 && LegacyType::BUILTIN_TYPE_ARRAY === $collectionValueType[0]->getBuiltinType()) { // get inner type for any nested array [$innerType] = $collectionValueType; // note that it will break for any other builtinType $dimensions = '[]'; - while (\count($innerType->getCollectionValueTypes()) > 0 && Type::BUILTIN_TYPE_ARRAY === $innerType->getBuiltinType()) { + while (\count($innerType->getCollectionValueTypes()) > 0 && LegacyType::BUILTIN_TYPE_ARRAY === $innerType->getBuiltinType()) { $dimensions .= '[]'; [$innerType] = $innerType->getCollectionValueTypes(); } @@ -518,9 +534,9 @@ private function validateAndDenormalize(array $types, string $currentClass, stri $class = $type->getClassName(); } - $expectedTypes[Type::BUILTIN_TYPE_OBJECT === $builtinType && $class ? $class : $builtinType] = true; + $expectedTypes[LegacyType::BUILTIN_TYPE_OBJECT === $builtinType && $class ? $class : $builtinType] = true; - if (Type::BUILTIN_TYPE_OBJECT === $builtinType && null !== $class) { + if (LegacyType::BUILTIN_TYPE_OBJECT === $builtinType && null !== $class) { if (!$this->serializer instanceof DenormalizerInterface) { throw new LogicException(sprintf('Cannot denormalize attribute "%s" for class "%s" because injected serializer is not a denormalizer.', $attribute, $class)); } @@ -537,11 +553,11 @@ private function validateAndDenormalize(array $types, string $currentClass, stri // PHP's json_decode automatically converts Numbers without a decimal part to integers. // To circumvent this behavior, integers are converted to floats when denormalizing JSON based formats and when // a float is expected. - if (Type::BUILTIN_TYPE_FLOAT === $builtinType && \is_int($data) && null !== $format && str_contains($format, JsonEncoder::FORMAT)) { + if (LegacyType::BUILTIN_TYPE_FLOAT === $builtinType && \is_int($data) && null !== $format && str_contains($format, JsonEncoder::FORMAT)) { return (float) $data; } - if ((Type::BUILTIN_TYPE_FALSE === $builtinType && false === $data) || (Type::BUILTIN_TYPE_TRUE === $builtinType && true === $data)) { + if ((LegacyType::BUILTIN_TYPE_FALSE === $builtinType && false === $data) || (LegacyType::BUILTIN_TYPE_TRUE === $builtinType && true === $data)) { return $data; } @@ -590,16 +606,221 @@ private function validateAndDenormalize(array $types, string $currentClass, stri throw NotNormalizableValueException::createForUnexpectedDataType(sprintf('The type of the "%s" attribute for class "%s" must be one of "%s" ("%s" given).', $attribute, $currentClass, implode('", "', array_keys($expectedTypes)), get_debug_type($data)), $data, array_keys($expectedTypes), $context['deserialization_path'] ?? $attribute); } + /** + * Validates the submitted data and denormalizes it. + * + * @throws NotNormalizableValueException + * @throws ExtraAttributesException + * @throws MissingConstructorArgumentsException + * @throws LogicException + */ + private function validateAndDenormalize(Type $type, string $currentClass, string $attribute, mixed $data, ?string $format, array $context): mixed + { + $expectedTypes = []; + $extraAttributesException = null; + $missingConstructorArgumentsException = null; + + $types = match (true) { + $type instanceof IntersectionType => throw new LogicException('Unable to handle intersection type.'), + $type instanceof UnionType => $type->getTypes(), + default => [$type], + }; + + foreach ($types as $t) { + if (null === $data && $type->isNullable()) { + return null; + } + + $collectionKeyType = $collectionValueType = null; + if ($t instanceof CollectionType) { + $collectionKeyType = $t->getCollectionKeyType(); + $collectionValueType = $t->getCollectionValueType(); + } + + $t = $t->getBaseType(); + + // Fix a collection that contains the only one element + // This is special to xml format only + if ('xml' === $format && $collectionValueType && !$collectionValueType->isA(TypeIdentifier::MIXED) && (!\is_array($data) || !\is_int(key($data)))) { + $data = [$data]; + } + + // This try-catch should cover all NotNormalizableValueException (and all return branches after the first + // exception) so we could try denormalizing all types of an union type. If the target type is not an union + // type, we will just re-throw the catched exception. + // In the case of no denormalization succeeds with an union type, it will fall back to the default exception + // with the acceptable types list. + try { + // 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. + $typeIdentifier = $t->getTypeIdentifier(); + if (\is_string($data) && (XmlEncoder::FORMAT === $format || CsvEncoder::FORMAT === $format)) { + switch ($typeIdentifier) { + case TypeIdentifier::ARRAY: + if ('' === $data) { + return []; + } + break; + case TypeIdentifier::BOOL: + // according to https://www.w3.org/TR/xmlschema-2/#boolean, valid representations are "false", "true", "0" and "1" + if ('false' === $data || '0' === $data) { + $data = false; + } elseif ('true' === $data || '1' === $data) { + $data = true; + } else { + throw NotNormalizableValueException::createForUnexpectedDataType(sprintf('The type of the "%s" attribute for class "%s" must be bool ("%s" given).', $attribute, $currentClass, $data), $data, [Type::bool()], $context['deserialization_path'] ?? null); + } + break; + case TypeIdentifier::INT: + if (ctype_digit(isset($data[0]) && '-' === $data[0] ? substr($data, 1) : $data)) { + $data = (int) $data; + } else { + throw NotNormalizableValueException::createForUnexpectedDataType(sprintf('The type of the "%s" attribute for class "%s" must be int ("%s" given).', $attribute, $currentClass, $data), $data, [Type::int()], $context['deserialization_path'] ?? null); + } + break; + case TypeIdentifier::FLOAT: + if (is_numeric($data)) { + return (float) $data; + } + + return match ($data) { + 'NaN' => \NAN, + 'INF' => \INF, + '-INF' => -\INF, + default => throw NotNormalizableValueException::createForUnexpectedDataType(sprintf('The type of the "%s" attribute for class "%s" must be float ("%s" given).', $attribute, $currentClass, $data), $data, [Type::float()], $context['deserialization_path'] ?? null), + }; + } + } + + if ($collectionValueType) { + $collectionValueBaseType = $collectionValueType instanceof UnionType ? $collectionValueType->asNonNullable()->getBaseType() : $collectionValueType->getBaseType(); + + if ($collectionValueBaseType instanceof ObjectType) { + $typeIdentifier = TypeIdentifier::OBJECT; + $class = $collectionValueBaseType->getClassName().'[]'; + $context['key_type'] = $collectionKeyType; + $context['value_type'] = $collectionValueType; + } elseif (TypeIdentifier::ARRAY === $collectionValueBaseType->getTypeIdentifier()) { + // get inner type for any nested array + $innerType = $collectionValueType; + + // note that it will break for any other builtinType + $dimensions = '[]'; + while ($innerType instanceof CollectionType) { + $dimensions .= '[]'; + $innerType = $innerType->getCollectionValueType(); + } + + if ($innerType instanceof ObjectType) { + // the builtinType is the inner one and the class is the class followed by []...[] + $typeIdentifier = TypeIdentifier::OBJECT; + $class = $innerType->getClassName().$dimensions; + } else { + // default fallback (keep it as array) + if ($t instanceof ObjectType) { + $typeIdentifier = TypeIdentifier::OBJECT; + $class = $t->getClassName(); + } else { + $typeIdentifier = $t->getTypeIdentifier()->value; + $class = null; + } + } + } + } else { + if ($t instanceof ObjectType) { + $typeIdentifier = TypeIdentifier::OBJECT; + $class = $t->getClassName(); + } else { + $typeIdentifier = $t->getTypeIdentifier(); + $class = null; + } + } + + $expectedTypes[TypeIdentifier::OBJECT === $typeIdentifier && $class ? $class : $typeIdentifier->value] = true; + + if (TypeIdentifier::OBJECT === $typeIdentifier && null !== $class) { + if (!$this->serializer instanceof DenormalizerInterface) { + throw new LogicException(sprintf('Cannot denormalize attribute "%s" for class "%s" because injected serializer is not a denormalizer.', $attribute, $class)); + } + + $childContext = $this->createChildContext($context, $attribute, $format); + if ($this->serializer->supportsDenormalization($data, $class, $format, $childContext)) { + return $this->serializer->denormalize($data, $class, $format, $childContext); + } + } + + // JSON only has a Number type corresponding to both int and float PHP types. + // PHP's json_encode, JavaScript's JSON.stringify, Go's json.Marshal as well as most other JSON encoders convert + // floating-point numbers like 12.0 to 12 (the decimal part is dropped when possible). + // PHP's json_decode automatically converts Numbers without a decimal part to integers. + // To circumvent this behavior, integers are converted to floats when denormalizing JSON based formats and when + // a float is expected. + if (TypeIdentifier::FLOAT === $typeIdentifier && \is_int($data) && null !== $format && str_contains($format, JsonEncoder::FORMAT)) { + return (float) $data; + } + + if ((TypeIdentifier::FALSE === $typeIdentifier && false === $data) || (TypeIdentifier::TRUE === $typeIdentifier && true === $data)) { + return $data; + } + + if (('is_'.$typeIdentifier->value)($data)) { + return $data; + } + } catch (NotNormalizableValueException|InvalidArgumentException $e) { + if (!$type instanceof UnionType) { + throw $e; + } + } catch (ExtraAttributesException $e) { + if (!$type instanceof UnionType) { + throw $e; + } + + $extraAttributesException ??= $e; + } catch (MissingConstructorArgumentsException $e) { + if (!$type instanceof UnionType) { + throw $e; + } + + $missingConstructorArgumentsException ??= $e; + } + } + + if ('' === $data && (XmlEncoder::FORMAT === $format || CsvEncoder::FORMAT === $format) && $type->isNullable()) { + return null; + } + + if ($extraAttributesException) { + throw $extraAttributesException; + } + + if ($missingConstructorArgumentsException) { + throw $missingConstructorArgumentsException; + } + + if ($context[self::DISABLE_TYPE_ENFORCEMENT] ?? $this->defaultContext[self::DISABLE_TYPE_ENFORCEMENT] ?? false) { + return $data; + } + + throw NotNormalizableValueException::createForUnexpectedDataType(sprintf('The type of the "%s" attribute for class "%s" must be one of "%s" ("%s" given).', $attribute, $currentClass, implode('", "', array_keys($expectedTypes)), get_debug_type($data)), $data, array_keys($expectedTypes), $context['deserialization_path'] ?? $attribute); + } + /** * @internal */ protected function denormalizeParameter(\ReflectionClass $class, \ReflectionParameter $parameter, string $parameterName, mixed $parameterData, array $context, ?string $format = null): mixed { - if ($parameter->isVariadic() || null === $this->propertyTypeExtractor || null === $types = $this->getTypes($class->getName(), $parameterName)) { + if ($parameter->isVariadic() || null === $this->propertyTypeExtractor || null === $type = $this->getType($class->getName(), $parameterName)) { return parent::denormalizeParameter($class, $parameter, $parameterName, $parameterData, $context, $format); } - $parameterData = $this->validateAndDenormalize($types, $class->getName(), $parameterName, $parameterData, $format, $context); + // BC layer for PropertyTypeExtractorInterface::getTypes(). + // Can be removed as soon as PropertyTypeExtractorInterface::getTypes() is removed (8.0). + if (\is_array($type)) { + $parameterData = $this->validateAndDenormalizeLegacy($type, $class->getName(), $parameterName, $parameterData, $format, $context); + } else { + $parameterData = $this->validateAndDenormalize($type, $class->getName(), $parameterName, $parameterData, $format, $context); + } $parameterData = $this->applyCallbacks($parameterData, $class->getName(), $parameterName, $format, $context); @@ -607,42 +828,55 @@ protected function denormalizeParameter(\ReflectionClass $class, \ReflectionPara } /** - * @return Type[]|null + * @return Type|list|null */ - private function getTypes(string $currentClass, string $attribute): ?array + private function getType(string $currentClass, string $attribute): Type|array|null { if (null === $this->propertyTypeExtractor) { return null; } $key = $currentClass.'::'.$attribute; - if (isset($this->typesCache[$key])) { - return false === $this->typesCache[$key] ? null : $this->typesCache[$key]; + if (isset($this->typeCache[$key])) { + return false === $this->typeCache[$key] ? null : $this->typeCache[$key]; } - if (null !== $types = $this->propertyTypeExtractor->getTypes($currentClass, $attribute)) { - return $this->typesCache[$key] = $types; + if (null !== $type = $this->getPropertyType($currentClass, $attribute)) { + return $this->typeCache[$key] = $type; } if ($discriminatorMapping = $this->classDiscriminatorResolver?->getMappingForClass($currentClass)) { if ($discriminatorMapping->getTypeProperty() === $attribute) { - return $this->typesCache[$key] = [ - new Type(Type::BUILTIN_TYPE_STRING), - ]; + return $this->typeCache[$key] = Type::string(); } foreach ($discriminatorMapping->getTypesMapping() as $mappedClass) { - if (null !== $types = $this->propertyTypeExtractor->getTypes($mappedClass, $attribute)) { - return $this->typesCache[$key] = $types; + if (null !== $type = $this->getPropertyType($mappedClass, $attribute)) { + return $this->typeCache[$key] = $type; } } } - $this->typesCache[$key] = false; + $this->typeCache[$key] = false; return null; } + /** + * BC layer for PropertyTypeExtractorInterface::getTypes(). + * Can be removed as soon as PropertyTypeExtractorInterface::getTypes() is removed (8.0). + * + * @return Type|list|null + */ + private function getPropertyType(string $className, string $property): Type|array|null + { + if (method_exists($this->propertyTypeExtractor, 'getType')) { + return $this->propertyTypeExtractor->getType($className, $property); + } + + return $this->propertyTypeExtractor->getTypes($className, $property); + } + /** * Sets an attribute and apply the name converter if necessary. */ diff --git a/Normalizer/ArrayDenormalizer.php b/Normalizer/ArrayDenormalizer.php index a9c64a1e7..1bd6c54b3 100644 --- a/Normalizer/ArrayDenormalizer.php +++ b/Normalizer/ArrayDenormalizer.php @@ -11,10 +11,12 @@ namespace Symfony\Component\Serializer\Normalizer; -use Symfony\Component\PropertyInfo\Type; +use Symfony\Component\PropertyInfo\Type as LegacyType; use Symfony\Component\Serializer\Exception\BadMethodCallException; use Symfony\Component\Serializer\Exception\InvalidArgumentException; use Symfony\Component\Serializer\Exception\NotNormalizableValueException; +use Symfony\Component\TypeInfo\Type; +use Symfony\Component\TypeInfo\Type\UnionType; /** * Denormalizes arrays of objects. @@ -46,7 +48,7 @@ public function denormalize(mixed $data, string $type, ?string $format = null, a throw new BadMethodCallException('Please set a denormalizer before calling denormalize()!'); } if (!\is_array($data)) { - throw NotNormalizableValueException::createForUnexpectedDataType(sprintf('Data expected to be "%s", "%s" given.', $type, get_debug_type($data)), $data, [Type::BUILTIN_TYPE_ARRAY], $context['deserialization_path'] ?? null); + throw NotNormalizableValueException::createForUnexpectedDataType(sprintf('Data expected to be "%s", "%s" given.', $type, get_debug_type($data)), $data, ['array'], $context['deserialization_path'] ?? null); } if (!str_ends_with($type, '[]')) { throw new InvalidArgumentException('Unsupported class: '.$type); @@ -54,15 +56,20 @@ public function denormalize(mixed $data, string $type, ?string $format = null, a $type = substr($type, 0, -2); - $builtinTypes = array_map(static function (Type $keyType) { - return $keyType->getBuiltinType(); - }, \is_array($keyType = $context['key_type'] ?? []) ? $keyType : [$keyType]); + $typeIdentifiers = []; + if (null !== $keyType = ($context['key_type'] ?? null)) { + if ($keyType instanceof Type) { + $typeIdentifiers = array_map(fn (Type $t): string => $t->getBaseType()->getTypeIdentifier()->value, $keyType instanceof UnionType ? $keyType->getTypes() : [$keyType]); + } else { + $typeIdentifiers = array_map(fn (LegacyType $t): string => $t->getBuiltinType(), \is_array($keyType) ? $keyType : [$keyType]); + } + } foreach ($data as $key => $value) { $subContext = $context; $subContext['deserialization_path'] = ($context['deserialization_path'] ?? false) ? sprintf('%s[%s]', $context['deserialization_path'], $key) : "[$key]"; - $this->validateKeyType($builtinTypes, $key, $subContext['deserialization_path']); + $this->validateKeyType($typeIdentifiers, $key, $subContext['deserialization_path']); $data[$key] = $this->denormalizer->denormalize($value, $type, $format, $subContext); } @@ -80,18 +87,21 @@ public function supportsDenormalization(mixed $data, string $type, ?string $form && $this->denormalizer->supportsDenormalization($data, substr($type, 0, -2), $format, $context); } - private function validateKeyType(array $builtinTypes, mixed $key, string $path): void + /** + * @param list $typeIdentifiers + */ + private function validateKeyType(array $typeIdentifiers, mixed $key, string $path): void { - if (!$builtinTypes) { + if (!$typeIdentifiers) { return; } - foreach ($builtinTypes as $builtinType) { - if (('is_'.$builtinType)($key)) { + foreach ($typeIdentifiers as $typeIdentifier) { + if (('is_'.$typeIdentifier)($key)) { return; } } - throw NotNormalizableValueException::createForUnexpectedDataType(sprintf('The type of the key "%s" must be "%s" ("%s" given).', $key, implode('", "', $builtinTypes), get_debug_type($key)), $key, $builtinTypes, $path, true); + throw NotNormalizableValueException::createForUnexpectedDataType(sprintf('The type of the key "%s" must be "%s" ("%s" given).', $key, implode('", "', $typeIdentifiers), get_debug_type($key)), $key, $typeIdentifiers, $path, true); } } diff --git a/Normalizer/BackedEnumNormalizer.php b/Normalizer/BackedEnumNormalizer.php index f6cefed87..504047bc1 100644 --- a/Normalizer/BackedEnumNormalizer.php +++ b/Normalizer/BackedEnumNormalizer.php @@ -11,7 +11,6 @@ namespace Symfony\Component\Serializer\Normalizer; -use Symfony\Component\PropertyInfo\Type; use Symfony\Component\Serializer\Exception\InvalidArgumentException; use Symfony\Component\Serializer\Exception\NotNormalizableValueException; @@ -70,7 +69,7 @@ public function denormalize(mixed $data, string $type, ?string $format = null, a } if (!\is_int($data) && !\is_string($data)) { - throw NotNormalizableValueException::createForUnexpectedDataType('The data is neither an integer nor a string, you should pass an integer or a string that can be parsed as an enumeration case of type '.$type.'.', $data, [Type::BUILTIN_TYPE_INT, Type::BUILTIN_TYPE_STRING], $context['deserialization_path'] ?? null, true); + throw NotNormalizableValueException::createForUnexpectedDataType('The data is neither an integer nor a string, you should pass an integer or a string that can be parsed as an enumeration case of type '.$type.'.', $data, ['int', 'string'], $context['deserialization_path'] ?? null, true); } try { diff --git a/Normalizer/DateTimeNormalizer.php b/Normalizer/DateTimeNormalizer.php index 46dff93f7..71ce26496 100644 --- a/Normalizer/DateTimeNormalizer.php +++ b/Normalizer/DateTimeNormalizer.php @@ -11,7 +11,6 @@ namespace Symfony\Component\Serializer\Normalizer; -use Symfony\Component\PropertyInfo\Type; use Symfony\Component\Serializer\Exception\InvalidArgumentException; use Symfony\Component\Serializer\Exception\NotNormalizableValueException; @@ -104,7 +103,7 @@ public function denormalize(mixed $data, string $type, ?string $format = null, a } if (!\is_string($data) || '' === trim($data)) { - throw NotNormalizableValueException::createForUnexpectedDataType('The data is either not an string, an empty string, or null; you should pass a string that can be parsed with the passed format or a valid DateTime string.', $data, [Type::BUILTIN_TYPE_STRING], $context['deserialization_path'] ?? null, true); + throw NotNormalizableValueException::createForUnexpectedDataType('The data is either not an string, an empty string, or null; you should pass a string that can be parsed with the passed format or a valid DateTime string.', $data, ['string'], $context['deserialization_path'] ?? null, true); } try { @@ -122,7 +121,7 @@ public function denormalize(mixed $data, string $type, ?string $format = null, a $dateTimeErrors = $type::getLastErrors(); - throw NotNormalizableValueException::createForUnexpectedDataType(sprintf('Parsing datetime string "%s" using format "%s" resulted in %d errors: ', $data, $dateTimeFormat, $dateTimeErrors['error_count'])."\n".implode("\n", $this->formatDateTimeErrors($dateTimeErrors['errors'])), $data, [Type::BUILTIN_TYPE_STRING], $context['deserialization_path'] ?? null, true); + throw NotNormalizableValueException::createForUnexpectedDataType(sprintf('Parsing datetime string "%s" using format "%s" resulted in %d errors: ', $data, $dateTimeFormat, $dateTimeErrors['error_count'])."\n".implode("\n", $this->formatDateTimeErrors($dateTimeErrors['errors'])), $data, ['string'], $context['deserialization_path'] ?? null, true); } $defaultDateTimeFormat = $this->defaultContext[self::FORMAT_KEY] ?? null; @@ -137,7 +136,7 @@ public function denormalize(mixed $data, string $type, ?string $format = null, a } catch (NotNormalizableValueException $e) { throw $e; } catch (\Exception $e) { - throw NotNormalizableValueException::createForUnexpectedDataType($e->getMessage(), $data, [Type::BUILTIN_TYPE_STRING], $context['deserialization_path'] ?? null, false, $e->getCode(), $e); + throw NotNormalizableValueException::createForUnexpectedDataType($e->getMessage(), $data, ['string'], $context['deserialization_path'] ?? null, false, $e->getCode(), $e); } } diff --git a/Normalizer/DateTimeZoneNormalizer.php b/Normalizer/DateTimeZoneNormalizer.php index 437e40bfc..f4528a03d 100644 --- a/Normalizer/DateTimeZoneNormalizer.php +++ b/Normalizer/DateTimeZoneNormalizer.php @@ -11,7 +11,6 @@ namespace Symfony\Component\Serializer\Normalizer; -use Symfony\Component\PropertyInfo\Type; use Symfony\Component\Serializer\Exception\InvalidArgumentException; use Symfony\Component\Serializer\Exception\NotNormalizableValueException; @@ -52,13 +51,13 @@ public function supportsNormalization(mixed $data, ?string $format = null, array public function denormalize(mixed $data, string $type, ?string $format = null, array $context = []): \DateTimeZone { if ('' === $data || null === $data) { - throw NotNormalizableValueException::createForUnexpectedDataType('The data is either an empty string or null, you should pass a string that can be parsed as a DateTimeZone.', $data, [Type::BUILTIN_TYPE_STRING], $context['deserialization_path'] ?? null, true); + throw NotNormalizableValueException::createForUnexpectedDataType('The data is either an empty string or null, you should pass a string that can be parsed as a DateTimeZone.', $data, ['string'], $context['deserialization_path'] ?? null, true); } try { return new \DateTimeZone($data); } catch (\Exception $e) { - throw NotNormalizableValueException::createForUnexpectedDataType($e->getMessage(), $data, [Type::BUILTIN_TYPE_STRING], $context['deserialization_path'] ?? null, true, $e->getCode(), $e); + throw NotNormalizableValueException::createForUnexpectedDataType($e->getMessage(), $data, ['string'], $context['deserialization_path'] ?? null, true, $e->getCode(), $e); } } diff --git a/Normalizer/UidNormalizer.php b/Normalizer/UidNormalizer.php index 732f802be..a6cc190a9 100644 --- a/Normalizer/UidNormalizer.php +++ b/Normalizer/UidNormalizer.php @@ -11,7 +11,6 @@ namespace Symfony\Component\Serializer\Normalizer; -use Symfony\Component\PropertyInfo\Type; use Symfony\Component\Serializer\Exception\LogicException; use Symfony\Component\Serializer\Exception\NotNormalizableValueException; use Symfony\Component\Uid\AbstractUid; @@ -71,7 +70,7 @@ public function denormalize(mixed $data, string $type, ?string $format = null, a try { return $type::fromString($data); } catch (\InvalidArgumentException|\TypeError) { - throw NotNormalizableValueException::createForUnexpectedDataType(sprintf('The data is not a valid "%s" string representation.', $type), $data, [Type::BUILTIN_TYPE_STRING], $context['deserialization_path'] ?? null, true); + throw NotNormalizableValueException::createForUnexpectedDataType(sprintf('The data is not a valid "%s" string representation.', $type), $data, ['string'], $context['deserialization_path'] ?? null, true); } } diff --git a/Tests/Normalizer/AbstractObjectNormalizerTest.php b/Tests/Normalizer/AbstractObjectNormalizerTest.php index a8e0b6eb1..1f5a556ce 100644 --- a/Tests/Normalizer/AbstractObjectNormalizerTest.php +++ b/Tests/Normalizer/AbstractObjectNormalizerTest.php @@ -15,7 +15,7 @@ use Symfony\Component\PropertyInfo\Extractor\PhpDocExtractor; use Symfony\Component\PropertyInfo\Extractor\ReflectionExtractor; use Symfony\Component\PropertyInfo\PropertyInfoExtractor; -use Symfony\Component\PropertyInfo\Type; +use Symfony\Component\PropertyInfo\Type as LegacyType; use Symfony\Component\Serializer\Attribute\Context; use Symfony\Component\Serializer\Attribute\DiscriminatorMap; use Symfony\Component\Serializer\Attribute\SerializedName; @@ -56,6 +56,7 @@ use Symfony\Component\Serializer\Tests\Fixtures\DummyWithObjectOrNull; use Symfony\Component\Serializer\Tests\Fixtures\DummyWithStringObject; use Symfony\Component\Serializer\Tests\Normalizer\Features\ObjectDummyWithContextAttribute; +use Symfony\Component\TypeInfo\Type; class AbstractObjectNormalizerTest extends TestCase { @@ -433,11 +434,20 @@ public function testDenormalizeCollectionDecodedFromXmlWithTwoChildren() private function getDenormalizerForDummyCollection() { $extractor = $this->createMock(PhpDocExtractor::class); - $extractor->method('getTypes') - ->will($this->onConsecutiveCalls( - [new Type('array', false, null, true, new Type('int'), new Type('object', false, DummyChild::class))], - null - )); + + if (method_exists(PhpDocExtractor::class, 'getType')) { + $extractor->method('getType') + ->will($this->onConsecutiveCalls( + Type::list(Type::object(DummyChild::class)), + null, + )); + } else { + $extractor->method('getTypes') + ->will($this->onConsecutiveCalls( + [new LegacyType('array', false, null, true, new LegacyType('int'), new LegacyType('object', false, DummyChild::class))], + null + )); + } $denormalizer = new AbstractObjectNormalizerCollectionDummy(null, null, $extractor); $arrayDenormalizer = new ArrayDenormalizerDummy(); @@ -488,11 +498,20 @@ public function testDenormalizeNotSerializableObjectToPopulate() private function getDenormalizerForStringCollection() { $extractor = $this->createMock(PhpDocExtractor::class); - $extractor->method('getTypes') - ->will($this->onConsecutiveCalls( - [new Type('array', false, null, true, new Type('int'), new Type('string'))], - null - )); + + if (method_exists(PhpDocExtractor::class, 'getType')) { + $extractor->method('getType') + ->will($this->onConsecutiveCalls( + Type::list(Type::string()), + null, + )); + } else { + $extractor->method('getTypes') + ->will($this->onConsecutiveCalls( + [new LegacyType('array', false, null, true, new LegacyType('int'), new LegacyType('string'))], + null + )); + } $denormalizer = new AbstractObjectNormalizerCollectionDummy(null, null, $extractor); $arrayDenormalizer = new ArrayDenormalizerDummy(); @@ -675,21 +694,40 @@ public function testDenormalizeBasicTypePropertiesFromXml() private function getDenormalizerForObjectWithBasicProperties() { $extractor = $this->createMock(PhpDocExtractor::class); - $extractor->method('getTypes') - ->will($this->onConsecutiveCalls( - [new Type('bool')], - [new Type('bool')], - [new Type('bool')], - [new Type('bool')], - [new Type('int')], - [new Type('int')], - [new Type('float')], - [new Type('float')], - [new Type('float')], - [new Type('float')], - [new Type('float')], - [new Type('float')] - )); + + if (method_exists(PhpDocExtractor::class, 'getType')) { + $extractor->method('getType') + ->will($this->onConsecutiveCalls( + Type::bool(), + Type::bool(), + Type::bool(), + Type::bool(), + Type::int(), + Type::int(), + Type::float(), + Type::float(), + Type::float(), + Type::float(), + Type::float(), + Type::float(), + )); + } else { + $extractor->method('getTypes') + ->will($this->onConsecutiveCalls( + [new LegacyType('bool')], + [new LegacyType('bool')], + [new LegacyType('bool')], + [new LegacyType('bool')], + [new LegacyType('int')], + [new LegacyType('int')], + [new LegacyType('float')], + [new LegacyType('float')], + [new LegacyType('float')], + [new LegacyType('float')], + [new LegacyType('float')], + [new LegacyType('float')] + )); + } $denormalizer = new AbstractObjectNormalizerCollectionDummy(null, null, $extractor); $arrayDenormalizer = new ArrayDenormalizerDummy(); diff --git a/composer.json b/composer.json index 627bfccaf..452dcc0a8 100644 --- a/composer.json +++ b/composer.json @@ -36,6 +36,7 @@ "symfony/property-access": "^6.4|^7.0", "symfony/property-info": "^6.4|^7.0", "symfony/translation-contracts": "^2.5|^3", + "symfony/type-info": "^7.1", "symfony/uid": "^6.4|^7.0", "symfony/validator": "^6.4|^7.0", "symfony/var-dumper": "^6.4|^7.0", From 76696bcaee6c09f2f9fcd5c7135537fd1b43aaa2 Mon Sep 17 00:00:00 2001 From: Vincent Langlet Date: Wed, 20 Mar 2024 21:34:40 +0100 Subject: [PATCH 205/297] [Serializer] Fix: Report Xml warning/error instead of silently returning a wrong xml --- Encoder/XmlEncoder.php | 23 +++++++++++++++++++++-- Tests/Encoder/XmlEncoderTest.php | 6 ++++++ 2 files changed, 27 insertions(+), 2 deletions(-) diff --git a/Encoder/XmlEncoder.php b/Encoder/XmlEncoder.php index c33a35650..ad4d87c20 100644 --- a/Encoder/XmlEncoder.php +++ b/Encoder/XmlEncoder.php @@ -82,7 +82,7 @@ public function encode(mixed $data, string $format, array $context = []): string $encoderIgnoredNodeTypes = $context[self::ENCODER_IGNORED_NODE_TYPES] ?? $this->defaultContext[self::ENCODER_IGNORED_NODE_TYPES]; $ignorePiNode = \in_array(\XML_PI_NODE, $encoderIgnoredNodeTypes, true); if ($data instanceof \DOMDocument) { - return $data->saveXML($ignorePiNode ? $data->documentElement : null); + return $this->saveXml($data, $ignorePiNode ? $data->documentElement : null); } $xmlRootNodeName = $context[self::ROOT_NODE_NAME] ?? $this->defaultContext[self::ROOT_NODE_NAME]; @@ -97,7 +97,7 @@ public function encode(mixed $data, string $format, array $context = []): string $this->appendNode($dom, $data, $format, $context, $xmlRootNodeName); } - return $dom->saveXML($ignorePiNode ? $dom->documentElement : null, $context[self::SAVE_OPTIONS] ?? $this->defaultContext[self::SAVE_OPTIONS]); + return $this->saveXml($dom, $ignorePiNode ? $dom->documentElement : null, $context[self::SAVE_OPTIONS] ?? $this->defaultContext[self::SAVE_OPTIONS]); } public function decode(string $data, string $format, array $context = []): mixed @@ -498,4 +498,23 @@ private function createDomDocument(array $context): \DOMDocument return $document; } + + /** + * @throws NotEncodableValueException + */ + private function saveXml(\DOMDocument $document, ?\DOMNode $node = null, ?int $options = null): string + { + $prevErrorHandler = set_error_handler(static function ($type, $message, $file, $line, $context = []) use (&$prevErrorHandler) { + if (\E_ERROR === $type || \E_WARNING === $type) { + throw new NotEncodableValueException($message); + } + + return $prevErrorHandler ? $prevErrorHandler($type, $message, $file, $line, $context) : false; + }); + try { + return $document->saveXML($node, $options); + } finally { + restore_error_handler(); + } + } } diff --git a/Tests/Encoder/XmlEncoderTest.php b/Tests/Encoder/XmlEncoderTest.php index 97de86abf..f0ff1c5b9 100644 --- a/Tests/Encoder/XmlEncoderTest.php +++ b/Tests/Encoder/XmlEncoderTest.php @@ -433,6 +433,12 @@ public function testEncodeTraversableWhenNormalizable() $this->assertEquals($expected, $serializer->serialize(new NormalizableTraversableDummy(), 'xml')); } + public function testEncodeException() + { + $this->expectException(NotEncodableValueException::class); + $this->encoder->encode('Invalid character: '.\chr(7), 'xml'); + } + public function testDecode() { $source = $this->getXmlSource(); From 69a2ca82a64e03cec79c133df7d9f8c9348665cd Mon Sep 17 00:00:00 2001 From: Christian Flothmann Date: Fri, 5 Apr 2024 21:45:29 +0200 Subject: [PATCH 206/297] fix merge --- Tests/Normalizer/AbstractObjectNormalizerTest.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Tests/Normalizer/AbstractObjectNormalizerTest.php b/Tests/Normalizer/AbstractObjectNormalizerTest.php index b21fc5a1f..ab9191eca 100644 --- a/Tests/Normalizer/AbstractObjectNormalizerTest.php +++ b/Tests/Normalizer/AbstractObjectNormalizerTest.php @@ -1040,12 +1040,12 @@ protected function extractAttributes(object $object, string $format = null, arra return []; } - protected function getAttributeValue(object $object, string $attribute, string $format = null, array $context = []) + protected function getAttributeValue(object $object, string $attribute, string $format = null, array $context = []): mixed { return null; } - protected function setAttributeValue(object $object, string $attribute, $value, string $format = null, array $context = []) + protected function setAttributeValue(object $object, string $attribute, $value, string $format = null, array $context = []): void { $object->$attribute = $value; } From 36ec3968e08e189a588b6f43e0087f18fdcc340a Mon Sep 17 00:00:00 2001 From: Christian Flothmann Date: Fri, 5 Apr 2024 21:58:03 +0200 Subject: [PATCH 207/297] fix merge --- Tests/Normalizer/AbstractObjectNormalizerTest.php | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/Tests/Normalizer/AbstractObjectNormalizerTest.php b/Tests/Normalizer/AbstractObjectNormalizerTest.php index dbbd5438e..d902df2b5 100644 --- a/Tests/Normalizer/AbstractObjectNormalizerTest.php +++ b/Tests/Normalizer/AbstractObjectNormalizerTest.php @@ -1054,6 +1054,11 @@ protected function setAttributeValue(object $object, string $attribute, $value, { $object->$attribute = $value; } + + public function getSupportedTypes(?string $format): array + { + return ['*' => false]; + } }; $this->assertSame('scalar', $normalizer->denormalize('scalar', XmlScalarDummy::class, 'xml')->value); From 591de5c9480029afdbd7b3d13d99aa6713a935c0 Mon Sep 17 00:00:00 2001 From: Christian Flothmann Date: Fri, 5 Apr 2024 23:47:04 +0200 Subject: [PATCH 208/297] initialize serializer in trait with null --- SerializerAwareTrait.php | 2 +- Tests/Normalizer/ObjectNormalizerTest.php | 10 ++++++++++ 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/SerializerAwareTrait.php b/SerializerAwareTrait.php index e4ba0ecb9..495e5889c 100644 --- a/SerializerAwareTrait.php +++ b/SerializerAwareTrait.php @@ -16,7 +16,7 @@ */ trait SerializerAwareTrait { - protected SerializerInterface $serializer; + protected ?SerializerInterface $serializer = null; public function setSerializer(SerializerInterface $serializer): void { diff --git a/Tests/Normalizer/ObjectNormalizerTest.php b/Tests/Normalizer/ObjectNormalizerTest.php index 8285dd88e..740accd8f 100644 --- a/Tests/Normalizer/ObjectNormalizerTest.php +++ b/Tests/Normalizer/ObjectNormalizerTest.php @@ -885,6 +885,16 @@ public function testSamePropertyAsMethodWithMethodSerializedName() $this->assertSame($expected, $this->normalizer->normalize($object)); } + + public function testNormalizeWithoutSerializerSet() + { + $normalizer = new ObjectNormalizer(new ClassMetadataFactory(new AttributeLoader())); + + $this->expectException(LogicException::class); + $this->expectExceptionMessage('Cannot normalize attribute "foo" because the injected serializer is not a normalizer.'); + + $normalizer->normalize(new ObjectConstructorDummy([], [], [])); + } } class ProxyObjectDummy extends ObjectDummy From 6c2d321573e35842b4e214c281abbbc8fbeeae59 Mon Sep 17 00:00:00 2001 From: Alexandre Daubois Date: Thu, 11 Apr 2024 15:20:20 +0200 Subject: [PATCH 209/297] [Serializer] Use explicit nullable type --- Tests/Normalizer/AbstractObjectNormalizerTest.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Tests/Normalizer/AbstractObjectNormalizerTest.php b/Tests/Normalizer/AbstractObjectNormalizerTest.php index ab9191eca..44c3f9b3e 100644 --- a/Tests/Normalizer/AbstractObjectNormalizerTest.php +++ b/Tests/Normalizer/AbstractObjectNormalizerTest.php @@ -1035,17 +1035,17 @@ public function __construct() parent::__construct(null, new MetadataAwareNameConverter(new ClassMetadataFactory(new AttributeLoader()))); } - protected function extractAttributes(object $object, string $format = null, array $context = []): array + protected function extractAttributes(object $object, ?string $format = null, array $context = []): array { return []; } - protected function getAttributeValue(object $object, string $attribute, string $format = null, array $context = []): mixed + protected function getAttributeValue(object $object, string $attribute, ?string $format = null, array $context = []): mixed { return null; } - protected function setAttributeValue(object $object, string $attribute, $value, string $format = null, array $context = []): void + protected function setAttributeValue(object $object, string $attribute, $value, ?string $format = null, array $context = []): void { $object->$attribute = $value; } From 4f1fd90b2795c8f9a77833cee5b06034db8704d5 Mon Sep 17 00:00:00 2001 From: Jay Klehr Date: Mon, 25 Mar 2024 11:38:47 -0600 Subject: [PATCH 210/297] [Serializer] Fixing PHP warning in the ObjectNormalizer with MaxDepth enabled --- Normalizer/AbstractObjectNormalizer.php | 2 +- Tests/Normalizer/AbstractObjectNormalizerTest.php | 14 ++++++++++++++ 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/Normalizer/AbstractObjectNormalizer.php b/Normalizer/AbstractObjectNormalizer.php index 70b702930..1e4797ee7 100644 --- a/Normalizer/AbstractObjectNormalizer.php +++ b/Normalizer/AbstractObjectNormalizer.php @@ -707,7 +707,7 @@ private function updateData(array $data, string $attribute, mixed $attributeValu private function isMaxDepthReached(array $attributesMetadata, string $class, string $attribute, array &$context): bool { if (!($enableMaxDepth = $context[self::ENABLE_MAX_DEPTH] ?? $this->defaultContext[self::ENABLE_MAX_DEPTH] ?? false) - || null === $maxDepth = $attributesMetadata[$attribute]?->getMaxDepth() + || !isset($attributesMetadata[$attribute]) || null === $maxDepth = $attributesMetadata[$attribute]?->getMaxDepth() ) { return false; } diff --git a/Tests/Normalizer/AbstractObjectNormalizerTest.php b/Tests/Normalizer/AbstractObjectNormalizerTest.php index 44c3f9b3e..b2d13024c 100644 --- a/Tests/Normalizer/AbstractObjectNormalizerTest.php +++ b/Tests/Normalizer/AbstractObjectNormalizerTest.php @@ -1053,6 +1053,20 @@ protected function setAttributeValue(object $object, string $attribute, $value, $this->assertSame('scalar', $normalizer->denormalize('scalar', XmlScalarDummy::class, 'xml')->value); } + + public function testNormalizationWithMaxDepthOnStdclassObjectDoesNotThrowWarning() + { + $object = new \stdClass(); + $object->string = 'yes'; + + $classMetadataFactory = new ClassMetadataFactory(new AttributeLoader()); + $normalizer = new ObjectNormalizer($classMetadataFactory); + $normalized = $normalizer->normalize($object, context: [ + AbstractObjectNormalizer::ENABLE_MAX_DEPTH => true, + ]); + + $this->assertSame(['string' => 'yes'], $normalized); + } } class AbstractObjectNormalizerDummy extends AbstractObjectNormalizer From 4f5b995ddc4ca7823fe7d8a12bd35f6f14b0557a Mon Sep 17 00:00:00 2001 From: Christian Flothmann Date: Sat, 13 Apr 2024 00:48:09 +0200 Subject: [PATCH 211/297] fix merge --- Tests/Normalizer/ObjectNormalizerTest.php | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/Tests/Normalizer/ObjectNormalizerTest.php b/Tests/Normalizer/ObjectNormalizerTest.php index c282d15a0..78dd780f8 100644 --- a/Tests/Normalizer/ObjectNormalizerTest.php +++ b/Tests/Normalizer/ObjectNormalizerTest.php @@ -18,7 +18,7 @@ use Symfony\Component\PropertyInfo\Extractor\PhpStanExtractor; use Symfony\Component\PropertyInfo\Extractor\ReflectionExtractor; use Symfony\Component\PropertyInfo\PropertyInfoExtractor; -use Symfony\Component\Serializer\Annotation\Ignore; +use Symfony\Component\Serializer\Attribute\Ignore; use Symfony\Component\Serializer\Exception\LogicException; use Symfony\Component\Serializer\Exception\RuntimeException; use Symfony\Component\Serializer\Exception\UnexpectedValueException; @@ -902,26 +902,26 @@ public function testSamePropertyAsMethodWithMethodSerializedName() $this->assertSame($expected, $this->normalizer->normalize($object)); } - public function testNormalizeWithIgnoreAnnotationAndPrivateProperties() + public function testNormalizeWithIgnoreAttributeAndPrivateProperties() { - $classMetadataFactory = new ClassMetadataFactory(new AnnotationLoader(new AnnotationReader())); + $classMetadataFactory = new ClassMetadataFactory(new AttributeLoader()); $normalizer = new ObjectNormalizer($classMetadataFactory); - $this->assertSame(['foo' => 'foo'], $normalizer->normalize(new ObjectDummyWithIgnoreAnnotationAndPrivateProperty())); + $this->assertSame(['foo' => 'foo'], $normalizer->normalize(new ObjectDummyWithIgnoreAttributeAndPrivateProperty())); } - public function testDenormalizeWithIgnoreAnnotationAndPrivateProperties() + public function testDenormalizeWithIgnoreAttributeAndPrivateProperties() { - $classMetadataFactory = new ClassMetadataFactory(new AnnotationLoader(new AnnotationReader())); + $classMetadataFactory = new ClassMetadataFactory(new AttributeLoader()); $normalizer = new ObjectNormalizer($classMetadataFactory); $obj = $normalizer->denormalize([ 'foo' => 'set', 'ignore' => 'set', 'private' => 'set', - ], ObjectDummyWithIgnoreAnnotationAndPrivateProperty::class); + ], ObjectDummyWithIgnoreAttributeAndPrivateProperty::class); - $expected = new ObjectDummyWithIgnoreAnnotationAndPrivateProperty(); + $expected = new ObjectDummyWithIgnoreAttributeAndPrivateProperty(); $expected->foo = 'set'; $this->assertEquals($expected, $obj); @@ -1199,11 +1199,11 @@ public function getInner() } } -class ObjectDummyWithIgnoreAnnotationAndPrivateProperty +class ObjectDummyWithIgnoreAttributeAndPrivateProperty { public $foo = 'foo'; - /** @Ignore */ + #[Ignore] public $ignored = 'ignored'; private $private = 'private'; From ef541952b3d1db93c254b56e2fd4b43188d80daa Mon Sep 17 00:00:00 2001 From: Christian Flothmann Date: Sat, 13 Apr 2024 00:51:45 +0200 Subject: [PATCH 212/297] add missing return type-hints --- Normalizer/AbstractNormalizer.php | 4 +--- Normalizer/GetSetMethodNormalizer.php | 2 +- Normalizer/ObjectNormalizer.php | 2 +- 3 files changed, 3 insertions(+), 5 deletions(-) diff --git a/Normalizer/AbstractNormalizer.php b/Normalizer/AbstractNormalizer.php index 3cd4be838..836b1ae08 100644 --- a/Normalizer/AbstractNormalizer.php +++ b/Normalizer/AbstractNormalizer.php @@ -267,10 +267,8 @@ protected function getGroups(array $context): array /** * Is this attribute allowed? - * - * @return bool */ - protected function isAllowedAttribute(object|string $classOrObject, string $attribute, ?string $format = null, array $context = []) + protected function isAllowedAttribute(object|string $classOrObject, string $attribute, ?string $format = null, array $context = []): bool { $ignoredAttributes = $context[self::IGNORED_ATTRIBUTES] ?? $this->defaultContext[self::IGNORED_ATTRIBUTES]; if (\in_array($attribute, $ignoredAttributes)) { diff --git a/Normalizer/GetSetMethodNormalizer.php b/Normalizer/GetSetMethodNormalizer.php index ba5feb744..683eb04ce 100644 --- a/Normalizer/GetSetMethodNormalizer.php +++ b/Normalizer/GetSetMethodNormalizer.php @@ -179,7 +179,7 @@ protected function setAttributeValue(object $object, string $attribute, mixed $v } } - protected function isAllowedAttribute($classOrObject, string $attribute, ?string $format = null, array $context = []) + protected function isAllowedAttribute($classOrObject, string $attribute, ?string $format = null, array $context = []): bool { if (!parent::isAllowedAttribute($classOrObject, $attribute, $format, $context)) { return false; diff --git a/Normalizer/ObjectNormalizer.php b/Normalizer/ObjectNormalizer.php index 7814e769b..213e830ba 100644 --- a/Normalizer/ObjectNormalizer.php +++ b/Normalizer/ObjectNormalizer.php @@ -186,7 +186,7 @@ protected function getAllowedAttributes(string|object $classOrObject, array $con return $allowedAttributes; } - protected function isAllowedAttribute($classOrObject, string $attribute, ?string $format = null, array $context = []) + protected function isAllowedAttribute($classOrObject, string $attribute, ?string $format = null, array $context = []): bool { if (!parent::isAllowedAttribute($classOrObject, $attribute, $format, $context)) { return false; From 03a98bcd8f6c3898f38f9df3b84e74805c82f3de Mon Sep 17 00:00:00 2001 From: Christian Flothmann Date: Sat, 13 Apr 2024 08:24:47 +0200 Subject: [PATCH 213/297] clean up PHP 8.0 version checks --- Normalizer/GetSetMethodNormalizer.php | 2 +- Normalizer/ObjectNormalizer.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Normalizer/GetSetMethodNormalizer.php b/Normalizer/GetSetMethodNormalizer.php index 683eb04ce..5916202f3 100644 --- a/Normalizer/GetSetMethodNormalizer.php +++ b/Normalizer/GetSetMethodNormalizer.php @@ -116,7 +116,7 @@ private function isGetMethod(\ReflectionMethod $method): bool private function isSetMethod(\ReflectionMethod $method): bool { return !$method->isStatic() - && (\PHP_VERSION_ID < 80000 || !$method->getAttributes(Ignore::class)) + && !$method->getAttributes(Ignore::class) && 1 === $method->getNumberOfRequiredParameters() && str_starts_with($method->name, 'set'); } diff --git a/Normalizer/ObjectNormalizer.php b/Normalizer/ObjectNormalizer.php index 213e830ba..0bac8eecb 100644 --- a/Normalizer/ObjectNormalizer.php +++ b/Normalizer/ObjectNormalizer.php @@ -216,7 +216,7 @@ private function hasAttributeAccessorMethod(string $class, string $attribute): b $method = $reflection->getMethod($attribute); return !$method->isStatic() - && (\PHP_VERSION_ID < 80000 || !$method->getAttributes(Ignore::class)) + && !$method->getAttributes(Ignore::class) && !$method->getNumberOfRequiredParameters(); } } From 260e68552160e6dd283276144a93f51de107c5a6 Mon Sep 17 00:00:00 2001 From: Christian Flothmann Date: Tue, 9 Apr 2024 10:33:09 +0200 Subject: [PATCH 214/297] add $class, $format and $context arguments to NameConverterInterface methods --- CHANGELOG.md | 1 + .../CamelCaseToSnakeCaseNameConverter.php | 10 +++++++--- NameConverter/MetadataAwareNameConverter.php | 14 +++++++++++--- NameConverter/NameConverterInterface.php | 12 ++++++++++-- 4 files changed, 29 insertions(+), 8 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9e495db1c..4a84fa2c7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,7 @@ CHANGELOG 7.1 --- + * Add arguments `$class`, `$format` and `$context` to `NameConverterInterface::normalize()` and `NameConverterInterface::denormalize()` * Add `DateTimeNormalizer::CAST_KEY` context option * Add `Default` and "class name" default groups * Add `AbstractNormalizer::FILTER_BOOL` context option diff --git a/NameConverter/CamelCaseToSnakeCaseNameConverter.php b/NameConverter/CamelCaseToSnakeCaseNameConverter.php index 8c0b53157..ad1e3cc9d 100644 --- a/NameConverter/CamelCaseToSnakeCaseNameConverter.php +++ b/NameConverter/CamelCaseToSnakeCaseNameConverter.php @@ -19,7 +19,7 @@ * @author Kévin Dunglas * @author Aurélien Pillevesse */ -class CamelCaseToSnakeCaseNameConverter implements AdvancedNameConverterInterface +class CamelCaseToSnakeCaseNameConverter implements NameConverterInterface { /** * Require all properties to be written in snake_case. @@ -36,7 +36,7 @@ public function __construct( ) { } - public function normalize(string $propertyName, ?string $class = null, ?string $format = null, array $context = []): string + public function normalize(string $propertyName/* , ?string $class = null, ?string $format = null, array $context = [] */): string { if (null === $this->attributes || \in_array($propertyName, $this->attributes, true)) { return strtolower(preg_replace('/[A-Z]/', '_\\0', lcfirst($propertyName))); @@ -45,8 +45,12 @@ public function normalize(string $propertyName, ?string $class = null, ?string $ return $propertyName; } - public function denormalize(string $propertyName, ?string $class = null, ?string $format = null, array $context = []): string + public function denormalize(string $propertyName/* , ?string $class = null, ?string $format = null, array $context = [] */): string { + $class = 1 < \func_num_args() ? func_get_arg(1) : null; + $format = 2 < \func_num_args() ? func_get_arg(2) : null; + $context = 3 < \func_num_args() ? func_get_arg(3) : []; + if (($context[self::REQUIRE_SNAKE_CASE_PROPERTIES] ?? false) && $propertyName !== $this->normalize($propertyName, $class, $format, $context)) { throw new UnexpectedPropertyException($propertyName); } diff --git a/NameConverter/MetadataAwareNameConverter.php b/NameConverter/MetadataAwareNameConverter.php index 327d92dc1..5366b57b9 100644 --- a/NameConverter/MetadataAwareNameConverter.php +++ b/NameConverter/MetadataAwareNameConverter.php @@ -18,7 +18,7 @@ /** * @author Fabien Bourigault */ -final class MetadataAwareNameConverter implements AdvancedNameConverterInterface +final class MetadataAwareNameConverter implements NameConverterInterface { /** * @var array> @@ -41,8 +41,12 @@ public function __construct( ) { } - public function normalize(string $propertyName, ?string $class = null, ?string $format = null, array $context = []): string + public function normalize(string $propertyName/* , ?string $class = null, ?string $format = null, array $context = [] */): string { + $class = 1 < \func_num_args() ? func_get_arg(1) : null; + $format = 2 < \func_num_args() ? func_get_arg(2) : null; + $context = 3 < \func_num_args() ? func_get_arg(3) : []; + if (null === $class) { return $this->normalizeFallback($propertyName, $class, $format, $context); } @@ -54,8 +58,12 @@ public function normalize(string $propertyName, ?string $class = null, ?string $ return self::$normalizeCache[$class][$propertyName] ?? $this->normalizeFallback($propertyName, $class, $format, $context); } - public function denormalize(string $propertyName, ?string $class = null, ?string $format = null, array $context = []): string + public function denormalize(string $propertyName/* , ?string $class = null, ?string $format = null, array $context = [] */): string { + $class = 1 < \func_num_args() ? func_get_arg(1) : null; + $format = 2 < \func_num_args() ? func_get_arg(2) : null; + $context = 3 < \func_num_args() ? func_get_arg(3) : []; + if (null === $class) { return $this->denormalizeFallback($propertyName, $class, $format, $context); } diff --git a/NameConverter/NameConverterInterface.php b/NameConverter/NameConverterInterface.php index aba69a49e..d6bfeceb4 100644 --- a/NameConverter/NameConverterInterface.php +++ b/NameConverter/NameConverterInterface.php @@ -20,11 +20,19 @@ interface NameConverterInterface { /** * Converts a property name to its normalized value. + * + * @param class-string|null $class + * @param string|null $format + * @param array $context */ - public function normalize(string $propertyName): string; + public function normalize(string $propertyName/* , ?string $class = null, ?string $format = null, array $context = [] */): string; /** * Converts a property name to its denormalized value. + * + * @param class-string|null $class + * @param string|null $format + * @param array $context */ - public function denormalize(string $propertyName): string; + public function denormalize(string $propertyName/* , ?string $class = null, ?string $format = null, array $context = [] */): string; } From 7c9e0461fba85494d0cb3b55686c609653032135 Mon Sep 17 00:00:00 2001 From: Christian Flothmann Date: Sat, 13 Apr 2024 21:26:18 +0200 Subject: [PATCH 215/297] sync .github/expected-missing-return-types.diff --- Normalizer/AbstractNormalizer.php | 2 +- Normalizer/GetSetMethodNormalizer.php | 2 +- Normalizer/ObjectNormalizer.php | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Normalizer/AbstractNormalizer.php b/Normalizer/AbstractNormalizer.php index 836b1ae08..9df372d17 100644 --- a/Normalizer/AbstractNormalizer.php +++ b/Normalizer/AbstractNormalizer.php @@ -268,7 +268,7 @@ protected function getGroups(array $context): array /** * Is this attribute allowed? */ - protected function isAllowedAttribute(object|string $classOrObject, string $attribute, ?string $format = null, array $context = []): bool + protected function isAllowedAttribute(object|string $classOrObject, string $attribute, ?string $format = null, array $context = []) { $ignoredAttributes = $context[self::IGNORED_ATTRIBUTES] ?? $this->defaultContext[self::IGNORED_ATTRIBUTES]; if (\in_array($attribute, $ignoredAttributes)) { diff --git a/Normalizer/GetSetMethodNormalizer.php b/Normalizer/GetSetMethodNormalizer.php index 5916202f3..0306ad1a1 100644 --- a/Normalizer/GetSetMethodNormalizer.php +++ b/Normalizer/GetSetMethodNormalizer.php @@ -179,7 +179,7 @@ protected function setAttributeValue(object $object, string $attribute, mixed $v } } - protected function isAllowedAttribute($classOrObject, string $attribute, ?string $format = null, array $context = []): bool + protected function isAllowedAttribute($classOrObject, string $attribute, ?string $format = null, array $context = []) { if (!parent::isAllowedAttribute($classOrObject, $attribute, $format, $context)) { return false; diff --git a/Normalizer/ObjectNormalizer.php b/Normalizer/ObjectNormalizer.php index 0bac8eecb..6e7d9d7b8 100644 --- a/Normalizer/ObjectNormalizer.php +++ b/Normalizer/ObjectNormalizer.php @@ -186,7 +186,7 @@ protected function getAllowedAttributes(string|object $classOrObject, array $con return $allowedAttributes; } - protected function isAllowedAttribute($classOrObject, string $attribute, ?string $format = null, array $context = []): bool + protected function isAllowedAttribute($classOrObject, string $attribute, ?string $format = null, array $context = []) { if (!parent::isAllowedAttribute($classOrObject, $attribute, $format, $context)) { return false; From a3ea83c0e08e86fc0150b1da897b267a6b806b42 Mon Sep 17 00:00:00 2001 From: Christian Flothmann Date: Tue, 16 Apr 2024 22:27:56 +0200 Subject: [PATCH 216/297] add missing return type-hints --- Normalizer/AbstractNormalizer.php | 2 +- Normalizer/GetSetMethodNormalizer.php | 2 +- Normalizer/ObjectNormalizer.php | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Normalizer/AbstractNormalizer.php b/Normalizer/AbstractNormalizer.php index 5d6c26701..ef9de38b5 100644 --- a/Normalizer/AbstractNormalizer.php +++ b/Normalizer/AbstractNormalizer.php @@ -250,7 +250,7 @@ protected function getGroups(array $context): array /** * Is this attribute allowed? */ - protected function isAllowedAttribute(object|string $classOrObject, string $attribute, ?string $format = null, array $context = []) + protected function isAllowedAttribute(object|string $classOrObject, string $attribute, ?string $format = null, array $context = []): bool { $ignoredAttributes = $context[self::IGNORED_ATTRIBUTES] ?? $this->defaultContext[self::IGNORED_ATTRIBUTES]; if (\in_array($attribute, $ignoredAttributes)) { diff --git a/Normalizer/GetSetMethodNormalizer.php b/Normalizer/GetSetMethodNormalizer.php index fbe5c77c2..33ca1effc 100644 --- a/Normalizer/GetSetMethodNormalizer.php +++ b/Normalizer/GetSetMethodNormalizer.php @@ -158,7 +158,7 @@ protected function setAttributeValue(object $object, string $attribute, mixed $v } } - protected function isAllowedAttribute($classOrObject, string $attribute, ?string $format = null, array $context = []) + protected function isAllowedAttribute($classOrObject, string $attribute, ?string $format = null, array $context = []): bool { if (!parent::isAllowedAttribute($classOrObject, $attribute, $format, $context)) { return false; diff --git a/Normalizer/ObjectNormalizer.php b/Normalizer/ObjectNormalizer.php index e6747240e..80ca3ffce 100644 --- a/Normalizer/ObjectNormalizer.php +++ b/Normalizer/ObjectNormalizer.php @@ -171,7 +171,7 @@ protected function getAllowedAttributes(string|object $classOrObject, array $con return $allowedAttributes; } - protected function isAllowedAttribute($classOrObject, string $attribute, ?string $format = null, array $context = []) + protected function isAllowedAttribute($classOrObject, string $attribute, ?string $format = null, array $context = []): bool { if (!parent::isAllowedAttribute($classOrObject, $attribute, $format, $context)) { return false; From 7714934f959ca63e33419016e045b84f04e117ae Mon Sep 17 00:00:00 2001 From: Christian Flothmann Date: Thu, 18 Apr 2024 08:05:33 +0200 Subject: [PATCH 217/297] add method parameters in final class --- NameConverter/CamelCaseToSnakeCaseNameConverter.php | 10 ++++++++++ NameConverter/MetadataAwareNameConverter.php | 4 ++-- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/NameConverter/CamelCaseToSnakeCaseNameConverter.php b/NameConverter/CamelCaseToSnakeCaseNameConverter.php index ad1e3cc9d..47d69d3ab 100644 --- a/NameConverter/CamelCaseToSnakeCaseNameConverter.php +++ b/NameConverter/CamelCaseToSnakeCaseNameConverter.php @@ -36,6 +36,11 @@ public function __construct( ) { } + /** + * @param class-string|null $class + * @param string|null $format + * @param array $context + */ public function normalize(string $propertyName/* , ?string $class = null, ?string $format = null, array $context = [] */): string { if (null === $this->attributes || \in_array($propertyName, $this->attributes, true)) { @@ -45,6 +50,11 @@ public function normalize(string $propertyName/* , ?string $class = null, ?strin return $propertyName; } + /** + * @param class-string|null $class + * @param string|null $format + * @param array $context + */ public function denormalize(string $propertyName/* , ?string $class = null, ?string $format = null, array $context = [] */): string { $class = 1 < \func_num_args() ? func_get_arg(1) : null; diff --git a/NameConverter/MetadataAwareNameConverter.php b/NameConverter/MetadataAwareNameConverter.php index 5366b57b9..bc693bd90 100644 --- a/NameConverter/MetadataAwareNameConverter.php +++ b/NameConverter/MetadataAwareNameConverter.php @@ -41,7 +41,7 @@ public function __construct( ) { } - public function normalize(string $propertyName/* , ?string $class = null, ?string $format = null, array $context = [] */): string + public function normalize(string $propertyName, ?string $class = null, ?string $format = null, array $context = []): string { $class = 1 < \func_num_args() ? func_get_arg(1) : null; $format = 2 < \func_num_args() ? func_get_arg(2) : null; @@ -58,7 +58,7 @@ public function normalize(string $propertyName/* , ?string $class = null, ?strin return self::$normalizeCache[$class][$propertyName] ?? $this->normalizeFallback($propertyName, $class, $format, $context); } - public function denormalize(string $propertyName/* , ?string $class = null, ?string $format = null, array $context = [] */): string + public function denormalize(string $propertyName, ?string $class = null, ?string $format = null, array $context = []): string { $class = 1 < \func_num_args() ? func_get_arg(1) : null; $format = 2 < \func_num_args() ? func_get_arg(2) : null; From df7e418c75bb8baaee5357bb097b744fde273bf1 Mon Sep 17 00:00:00 2001 From: Christian Flothmann Date: Thu, 18 Apr 2024 08:27:37 +0200 Subject: [PATCH 218/297] fix tests --- Tests/NameConverter/CamelCaseToSnakeCaseNameConverterTest.php | 4 ++-- Tests/Normalizer/AbstractNormalizerTest.php | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Tests/NameConverter/CamelCaseToSnakeCaseNameConverterTest.php b/Tests/NameConverter/CamelCaseToSnakeCaseNameConverterTest.php index f9d941890..fc9967f0c 100644 --- a/Tests/NameConverter/CamelCaseToSnakeCaseNameConverterTest.php +++ b/Tests/NameConverter/CamelCaseToSnakeCaseNameConverterTest.php @@ -61,7 +61,7 @@ public static function attributeProvider() public function testDenormalizeWithContext() { $nameConverter = new CamelCaseToSnakeCaseNameConverter(null, true); - $denormalizedValue = $nameConverter->denormalize('last_name', context: [CamelCaseToSnakeCaseNameConverter::REQUIRE_SNAKE_CASE_PROPERTIES]); + $denormalizedValue = $nameConverter->denormalize('last_name', null, null, [CamelCaseToSnakeCaseNameConverter::REQUIRE_SNAKE_CASE_PROPERTIES]); $this->assertSame($denormalizedValue, 'lastName'); } @@ -71,6 +71,6 @@ public function testErrorDenormalizeWithContext() $nameConverter = new CamelCaseToSnakeCaseNameConverter(null, true); $this->expectException(UnexpectedPropertyException::class); - $nameConverter->denormalize('lastName', context: [CamelCaseToSnakeCaseNameConverter::REQUIRE_SNAKE_CASE_PROPERTIES => true]); + $nameConverter->denormalize('lastName', null, null, [CamelCaseToSnakeCaseNameConverter::REQUIRE_SNAKE_CASE_PROPERTIES => true]); } } diff --git a/Tests/Normalizer/AbstractNormalizerTest.php b/Tests/Normalizer/AbstractNormalizerTest.php index a32574878..3108fe3c6 100644 --- a/Tests/Normalizer/AbstractNormalizerTest.php +++ b/Tests/Normalizer/AbstractNormalizerTest.php @@ -272,12 +272,12 @@ public static function getNormalizerWithCustomNameConverter() { $extractor = new PhpDocExtractor(); $nameConverter = new class() implements NameConverterInterface { - public function normalize(string $propertyName): string + public function normalize(string $propertyName, ?string $class = null, ?string $format = null, array $context = []): string { return ucfirst($propertyName); } - public function denormalize(string $propertyName): string + public function denormalize(string $propertyName, ?string $class = null, ?string $format = null, array $context = []): string { return lcfirst($propertyName); } From 72d87ea1624fee8b9f1aa927a0619c367296defd Mon Sep 17 00:00:00 2001 From: javaDeveloperKid Date: Wed, 17 Apr 2024 23:53:56 +0200 Subject: [PATCH 219/297] fix typo in AbstractNormalizerContextBuilder::withDefaultContructorArguments() --- CHANGELOG.md | 1 + .../AbstractNormalizerContextBuilder.php | 20 +++++++++++++++---- .../AbstractNormalizerContextBuilderTest.php | 2 +- 3 files changed, 18 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4a84fa2c7..4f456f3f8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,7 @@ CHANGELOG * Add `Default` and "class name" default groups * Add `AbstractNormalizer::FILTER_BOOL` context option * Add `CamelCaseToSnakeCaseNameConverter::REQUIRE_SNAKE_CASE_PROPERTIES` context option + * Deprecate `AbstractNormalizerContextBuilder::withDefaultContructorArguments(?array $defaultContructorArguments)`, use `withDefaultConstructorArguments(?array $defaultConstructorArguments)` instead (note the missing `s` character in Contructor word in deprecated method) 7.0 --- diff --git a/Context/Normalizer/AbstractNormalizerContextBuilder.php b/Context/Normalizer/AbstractNormalizerContextBuilder.php index ecb328dd6..cb5b0544d 100644 --- a/Context/Normalizer/AbstractNormalizerContextBuilder.php +++ b/Context/Normalizer/AbstractNormalizerContextBuilder.php @@ -104,17 +104,29 @@ public function withAllowExtraAttributes(?bool $allowExtraAttributes): static } /** - * Configures an hashmap of classes containing hashmaps of constructor argument => default value. + * @deprecated since Symfony 7.1, use withDefaultConstructorArguments(?array $defaultConstructorArguments)" instead + * + * @param array>|null $defaultContructorArguments + */ + public function withDefaultContructorArguments(?array $defaultContructorArguments): static + { + trigger_deprecation('symfony/serializer', '7.1', 'The "%s()" method is deprecated, use "withDefaultConstructorArguments(?array $defaultConstructorArguments)" instead.', __METHOD__); + + return self::withDefaultConstructorArguments($defaultContructorArguments); + } + + /** + * Configures a hashmap of classes containing hashmaps of constructor argument => default value. * * The names need to match the parameter names in the constructor arguments. * * Eg: [Foo::class => ['foo' => true, 'bar' => 0]] * - * @param array>|null $defaultContructorArguments + * @param array>|null $defaultConstructorArguments */ - public function withDefaultContructorArguments(?array $defaultContructorArguments): static + public function withDefaultConstructorArguments(?array $defaultConstructorArguments): static { - return $this->with(AbstractNormalizer::DEFAULT_CONSTRUCTOR_ARGUMENTS, $defaultContructorArguments); + return $this->with(AbstractNormalizer::DEFAULT_CONSTRUCTOR_ARGUMENTS, $defaultConstructorArguments); } /** diff --git a/Tests/Context/Normalizer/AbstractNormalizerContextBuilderTest.php b/Tests/Context/Normalizer/AbstractNormalizerContextBuilderTest.php index 158fa8fea..4b8f0cc3f 100644 --- a/Tests/Context/Normalizer/AbstractNormalizerContextBuilderTest.php +++ b/Tests/Context/Normalizer/AbstractNormalizerContextBuilderTest.php @@ -41,7 +41,7 @@ public function testWithers(array $values) ->withGroups($values[AbstractNormalizer::GROUPS]) ->withAttributes($values[AbstractNormalizer::ATTRIBUTES]) ->withAllowExtraAttributes($values[AbstractNormalizer::ALLOW_EXTRA_ATTRIBUTES]) - ->withDefaultContructorArguments($values[AbstractNormalizer::DEFAULT_CONSTRUCTOR_ARGUMENTS]) + ->withDefaultConstructorArguments($values[AbstractNormalizer::DEFAULT_CONSTRUCTOR_ARGUMENTS]) ->withCallbacks($values[AbstractNormalizer::CALLBACKS]) ->withCircularReferenceHandler($values[AbstractNormalizer::CIRCULAR_REFERENCE_HANDLER]) ->withIgnoredAttributes($values[AbstractNormalizer::IGNORED_ATTRIBUTES]) From 73820ab43d12c2f29445080004054b0066082bf1 Mon Sep 17 00:00:00 2001 From: javaDeveloperKid Date: Wed, 17 Apr 2024 23:53:56 +0200 Subject: [PATCH 220/297] [Serializer] Add AbstractNormalizerContextBuilder::defaultConstructorArguments() --- .../AbstractNormalizerContextBuilder.php | 14 +++++++++++--- .../AbstractNormalizerContextBuilderTest.php | 2 +- 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/Context/Normalizer/AbstractNormalizerContextBuilder.php b/Context/Normalizer/AbstractNormalizerContextBuilder.php index ecb328dd6..f365ac8df 100644 --- a/Context/Normalizer/AbstractNormalizerContextBuilder.php +++ b/Context/Normalizer/AbstractNormalizerContextBuilder.php @@ -104,17 +104,25 @@ public function withAllowExtraAttributes(?bool $allowExtraAttributes): static } /** - * Configures an hashmap of classes containing hashmaps of constructor argument => default value. + * Configures a hashmap of classes containing hashmaps of constructor argument => default value. * * The names need to match the parameter names in the constructor arguments. * * Eg: [Foo::class => ['foo' => true, 'bar' => 0]] * - * @param array>|null $defaultContructorArguments + * @param array>|null $defaultConstructorArguments + */ + public function withDefaultConstructorArguments(?array $defaultConstructorArguments): static + { + return $this->with(AbstractNormalizer::DEFAULT_CONSTRUCTOR_ARGUMENTS, $defaultConstructorArguments); + } + + /** + * Deprecated in Symfony 7.1, use withDefaultConstructorArguments() instead. */ public function withDefaultContructorArguments(?array $defaultContructorArguments): static { - return $this->with(AbstractNormalizer::DEFAULT_CONSTRUCTOR_ARGUMENTS, $defaultContructorArguments); + return self::withDefaultConstructorArguments($defaultContructorArguments); } /** diff --git a/Tests/Context/Normalizer/AbstractNormalizerContextBuilderTest.php b/Tests/Context/Normalizer/AbstractNormalizerContextBuilderTest.php index 158fa8fea..4b8f0cc3f 100644 --- a/Tests/Context/Normalizer/AbstractNormalizerContextBuilderTest.php +++ b/Tests/Context/Normalizer/AbstractNormalizerContextBuilderTest.php @@ -41,7 +41,7 @@ public function testWithers(array $values) ->withGroups($values[AbstractNormalizer::GROUPS]) ->withAttributes($values[AbstractNormalizer::ATTRIBUTES]) ->withAllowExtraAttributes($values[AbstractNormalizer::ALLOW_EXTRA_ATTRIBUTES]) - ->withDefaultContructorArguments($values[AbstractNormalizer::DEFAULT_CONSTRUCTOR_ARGUMENTS]) + ->withDefaultConstructorArguments($values[AbstractNormalizer::DEFAULT_CONSTRUCTOR_ARGUMENTS]) ->withCallbacks($values[AbstractNormalizer::CALLBACKS]) ->withCircularReferenceHandler($values[AbstractNormalizer::CIRCULAR_REFERENCE_HANDLER]) ->withIgnoredAttributes($values[AbstractNormalizer::IGNORED_ATTRIBUTES]) From 529d35861cb1b96a8b1bf68b07e37477260c85db Mon Sep 17 00:00:00 2001 From: alexpozzi Date: Thu, 18 Apr 2024 16:39:43 +0200 Subject: [PATCH 221/297] [Serializer] Add XmlEncoder::CDATA_WRAPPING_PATTERN context option --- CHANGELOG.md | 1 + Context/Encoder/XmlEncoderContextBuilder.php | 8 +++ Encoder/XmlEncoder.php | 4 +- .../Encoder/XmlEncoderContextBuilderTest.php | 3 ++ Tests/Encoder/XmlEncoderTest.php | 52 ++++++++++++++++--- 5 files changed, 61 insertions(+), 7 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4f456f3f8..3118834d8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ CHANGELOG * Add `AbstractNormalizer::FILTER_BOOL` context option * Add `CamelCaseToSnakeCaseNameConverter::REQUIRE_SNAKE_CASE_PROPERTIES` context option * Deprecate `AbstractNormalizerContextBuilder::withDefaultContructorArguments(?array $defaultContructorArguments)`, use `withDefaultConstructorArguments(?array $defaultConstructorArguments)` instead (note the missing `s` character in Contructor word in deprecated method) + * Add `XmlEncoder::CDATA_WRAPPING_PATTERN` context option 7.0 --- diff --git a/Context/Encoder/XmlEncoderContextBuilder.php b/Context/Encoder/XmlEncoderContextBuilder.php index 34cf78198..0fd1f2f44 100644 --- a/Context/Encoder/XmlEncoderContextBuilder.php +++ b/Context/Encoder/XmlEncoderContextBuilder.php @@ -152,4 +152,12 @@ public function withCdataWrapping(?bool $cdataWrapping): static { return $this->with(XmlEncoder::CDATA_WRAPPING, $cdataWrapping); } + + /** + * Configures the pattern used to evaluate if a CDATA section should be added. + */ + public function withCdataWrappingPattern(?string $cdataWrappingPattern): static + { + return $this->with(XmlEncoder::CDATA_WRAPPING_PATTERN, $cdataWrappingPattern); + } } diff --git a/Encoder/XmlEncoder.php b/Encoder/XmlEncoder.php index ad4d87c20..5dcb2ba7e 100644 --- a/Encoder/XmlEncoder.php +++ b/Encoder/XmlEncoder.php @@ -59,6 +59,7 @@ class XmlEncoder implements EncoderInterface, DecoderInterface, NormalizationAwa public const TYPE_CAST_ATTRIBUTES = 'xml_type_cast_attributes'; public const VERSION = 'xml_version'; public const CDATA_WRAPPING = 'cdata_wrapping'; + public const CDATA_WRAPPING_PATTERN = 'cdata_wrapping_pattern'; private array $defaultContext = [ self::AS_COLLECTION => false, @@ -70,6 +71,7 @@ class XmlEncoder implements EncoderInterface, DecoderInterface, NormalizationAwa self::ROOT_NODE_NAME => 'response', self::TYPE_CAST_ATTRIBUTES => true, self::CDATA_WRAPPING => true, + self::CDATA_WRAPPING_PATTERN => '/[<>&]/', ]; public function __construct(array $defaultContext = []) @@ -433,7 +435,7 @@ private function appendNode(\DOMNode $parentNode, mixed $data, string $format, a */ private function needsCdataWrapping(string $val, array $context): bool { - return ($context[self::CDATA_WRAPPING] ?? $this->defaultContext[self::CDATA_WRAPPING]) && preg_match('/[<>&]/', $val); + return ($context[self::CDATA_WRAPPING] ?? $this->defaultContext[self::CDATA_WRAPPING]) && preg_match($context[self::CDATA_WRAPPING_PATTERN] ?? $this->defaultContext[self::CDATA_WRAPPING_PATTERN], $val); } /** diff --git a/Tests/Context/Encoder/XmlEncoderContextBuilderTest.php b/Tests/Context/Encoder/XmlEncoderContextBuilderTest.php index d1ea307a9..2f71c6012 100644 --- a/Tests/Context/Encoder/XmlEncoderContextBuilderTest.php +++ b/Tests/Context/Encoder/XmlEncoderContextBuilderTest.php @@ -46,6 +46,7 @@ public function testWithers(array $values) ->withTypeCastAttributes($values[XmlEncoder::TYPE_CAST_ATTRIBUTES]) ->withVersion($values[XmlEncoder::VERSION]) ->withCdataWrapping($values[XmlEncoder::CDATA_WRAPPING]) + ->withCdataWrappingPattern($values[XmlEncoder::CDATA_WRAPPING_PATTERN]) ->toArray(); $this->assertSame($values, $context); @@ -67,6 +68,7 @@ public static function withersDataProvider(): iterable XmlEncoder::TYPE_CAST_ATTRIBUTES => true, XmlEncoder::VERSION => '1.0', XmlEncoder::CDATA_WRAPPING => false, + XmlEncoder::CDATA_WRAPPING_PATTERN => '/[<>&"\']/', ]]; yield 'With null values' => [[ @@ -83,6 +85,7 @@ public static function withersDataProvider(): iterable XmlEncoder::TYPE_CAST_ATTRIBUTES => null, XmlEncoder::VERSION => null, XmlEncoder::CDATA_WRAPPING => null, + XmlEncoder::CDATA_WRAPPING_PATTERN => null, ]]; } } diff --git a/Tests/Encoder/XmlEncoderTest.php b/Tests/Encoder/XmlEncoderTest.php index f0ff1c5b9..5be6be232 100644 --- a/Tests/Encoder/XmlEncoderTest.php +++ b/Tests/Encoder/XmlEncoderTest.php @@ -231,16 +231,56 @@ public function testEncodeRootAttributes() $this->assertEquals($expected, $this->encoder->encode($array, 'xml')); } - public function testEncodeCdataWrapping() + /** + * @dataProvider encodeCdataWrappingWithDefaultPattern + */ + public function testEncodeCdataWrappingWithDefaultPattern($input, $expected) { - $array = [ - 'firstname' => 'Paul & Martha ', + $this->assertEquals($expected, $this->encoder->encode($input, 'xml')); + } + + public static function encodeCdataWrappingWithDefaultPattern() + { + return [ + [ + ['firstname' => 'Paul and Martha'], + ''."\n".'Paul and Martha'."\n", + ], + [ + ['lastname' => 'O\'Donnel'], + ''."\n".'O\'Donnel'."\n", + ], + [ + ['firstname' => 'Paul & Martha '], + ''."\n".']]>'."\n", + ], ]; + } - $expected = ''."\n". - ']]>'."\n"; + /** + * @dataProvider encodeCdataWrappingWithCustomPattern + */ + public function testEncodeCdataWrappingWithCustomPattern($input, $expected) + { + $this->assertEquals($expected, $this->encoder->encode($input, 'xml', ['cdata_wrapping_pattern' => '/[<>&"\']/'])); + } - $this->assertEquals($expected, $this->encoder->encode($array, 'xml')); + public static function encodeCdataWrappingWithCustomPattern() + { + return [ + [ + ['firstname' => 'Paul and Martha'], + ''."\n".'Paul and Martha'."\n", + ], + [ + ['lastname' => 'O\'Donnel'], + ''."\n".''."\n", + ], + [ + ['firstname' => 'Paul & Martha '], + ''."\n".']]>'."\n", + ], + ]; } public function testEnableCdataWrapping() From b8aa793e695b2ae3d8a35a9ed2d43b1ec423eaac Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Jusi=C4=99ga?= Date: Mon, 6 May 2024 19:21:10 +0200 Subject: [PATCH 222/297] Fixed "Warning: Attempt to read property "value" on string" --- Normalizer/AbstractObjectNormalizer.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Normalizer/AbstractObjectNormalizer.php b/Normalizer/AbstractObjectNormalizer.php index d1f565cea..e766e3245 100644 --- a/Normalizer/AbstractObjectNormalizer.php +++ b/Normalizer/AbstractObjectNormalizer.php @@ -730,7 +730,7 @@ private function validateAndDenormalize(Type $type, string $currentClass, string $typeIdentifier = TypeIdentifier::OBJECT; $class = $t->getClassName(); } else { - $typeIdentifier = $t->getTypeIdentifier()->value; + $typeIdentifier = $t->getTypeIdentifier(); $class = null; } } From d62457270ee38c12a80d612ed9915885dc82e987 Mon Sep 17 00:00:00 2001 From: Maximilian Beckers Date: Fri, 17 May 2024 07:31:11 +0200 Subject: [PATCH 223/297] [Serializer] rename the first parameter of ``NormalizerInterface::normalize`` from object to data because of type mixed --- Normalizer/NormalizerInterface.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Normalizer/NormalizerInterface.php b/Normalizer/NormalizerInterface.php index 562f87c28..bbc8a94e7 100644 --- a/Normalizer/NormalizerInterface.php +++ b/Normalizer/NormalizerInterface.php @@ -22,9 +22,9 @@ interface NormalizerInterface { /** - * Normalizes an object into a set of arrays/scalars. + * Normalizes data into a set of arrays/scalars. * - * @param mixed $object Object to normalize + * @param mixed $data Data to normalize * @param string|null $format Format the normalization result will be encoded as * @param array $context Context options for the normalizer * @@ -36,7 +36,7 @@ interface NormalizerInterface * @throws LogicException Occurs when the normalizer is not called in an expected context * @throws ExceptionInterface Occurs for all the other cases of errors */ - public function normalize(mixed $object, ?string $format = null, array $context = []): array|string|int|float|bool|\ArrayObject|null; + public function normalize(mixed $data, ?string $format = null, array $context = []): array|string|int|float|bool|\ArrayObject|null; /** * Checks whether the given class is supported for normalization by this normalizer. From af03803b2d5e001948ae792d85f3feb9a2f85178 Mon Sep 17 00:00:00 2001 From: Christian Flothmann Date: Fri, 17 May 2024 09:05:25 +0200 Subject: [PATCH 224/297] add missing deprecation contracts dependency --- composer.json | 1 + 1 file changed, 1 insertion(+) diff --git a/composer.json b/composer.json index 452dcc0a8..0092a9643 100644 --- a/composer.json +++ b/composer.json @@ -17,6 +17,7 @@ ], "require": { "php": ">=8.2", + "symfony/deprecation-contracts": "^2.5|^3", "symfony/polyfill-ctype": "~1.8" }, "require-dev": { From 8030de88a41354aeb7659539211d1e9be8ce70e9 Mon Sep 17 00:00:00 2001 From: Christian Flothmann Date: Fri, 17 May 2024 08:37:35 +0200 Subject: [PATCH 225/297] add test --- .../AbstractObjectNormalizerTest.php | 65 +++++++++++++++++++ 1 file changed, 65 insertions(+) diff --git a/Tests/Normalizer/AbstractObjectNormalizerTest.php b/Tests/Normalizer/AbstractObjectNormalizerTest.php index b878ca5c0..fea4f6066 100644 --- a/Tests/Normalizer/AbstractObjectNormalizerTest.php +++ b/Tests/Normalizer/AbstractObjectNormalizerTest.php @@ -1129,6 +1129,24 @@ public function testNormalizationWithMaxDepthOnStdclassObjectDoesNotThrowWarning $this->assertSame(['string' => 'yes'], $normalized); } + + public function testDenormalizeCollectionOfScalarTypesPropertyWithPhpDocExtractor() + { + $normalizer = new AbstractObjectNormalizerWithMetadataAndPhpDocExtractor(); + $data = [ + 'type' => 'foo', + 'values' => [ + ['1'], + ['2'], + ['3'], + ['4'], + ['5'], + ], + ]; + $expected = new ScalarCollectionDocBlockDummy([[1], [2], [3], [4], [5]]); + + $this->assertEquals($expected, $normalizer->denormalize($data, ScalarCollectionDocBlockDummy::class)); + } } class AbstractObjectNormalizerDummy extends AbstractObjectNormalizer @@ -1540,3 +1558,50 @@ public function __construct( ) { } } + +#[DiscriminatorMap('type', ['foo' => ScalarCollectionDocBlockDummy::class])] +class ScalarCollectionDocBlockDummy +{ + /** + * @param array>|null $values + */ + public function __construct( + private readonly ?array $values = null, + ) { + } + + /** @return array>|null */ + public function getValues(): ?array + { + return $this->values; + } +} + +class AbstractObjectNormalizerWithMetadataAndPhpDocExtractor extends AbstractObjectNormalizer +{ + public function __construct() + { + parent::__construct(new ClassMetadataFactory(new AttributeLoader()), null, new PropertyInfoExtractor([], [new PhpDocExtractor()])); + } + + protected function extractAttributes(object $object, ?string $format = null, array $context = []): array + { + return []; + } + + protected function getAttributeValue(object $object, string $attribute, ?string $format = null, array $context = []): mixed + { + return null; + } + + protected function setAttributeValue(object $object, string $attribute, mixed $value, ?string $format = null, array $context = []): void + { + } + + public function getSupportedTypes(?string $format): array + { + return [ + '*' => false, + ]; + } +} From 06cd11ff947d9d88ea2f86d1e7ab9f9f9facc7e6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Anderson=20M=C3=BCller?= Date: Wed, 1 Feb 2023 13:19:44 +0100 Subject: [PATCH 226/297] [Serializer] Improve exception message in UnwrappingDenormalizer --- Context/Normalizer/UnwrappingDenormalizerContextBuilder.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Context/Normalizer/UnwrappingDenormalizerContextBuilder.php b/Context/Normalizer/UnwrappingDenormalizerContextBuilder.php index f06f6ac77..5beb4e984 100644 --- a/Context/Normalizer/UnwrappingDenormalizerContextBuilder.php +++ b/Context/Normalizer/UnwrappingDenormalizerContextBuilder.php @@ -45,7 +45,7 @@ public function withUnwrapPath(?string $unwrapPath): static try { new PropertyPath($unwrapPath); } catch (InvalidPropertyPathException $e) { - throw new InvalidArgumentException('The "%s" property path is not valid.', previous: $e); + throw new InvalidArgumentException(sprintf('The "%s" property path is not valid.', $unwrapPath), previous: $e); } return $this->with(UnwrappingDenormalizer::UNWRAP_PATH, $unwrapPath); From cd0cd9c148d3bdd3f7d8a91fc88d2cff998d2484 Mon Sep 17 00:00:00 2001 From: Mathias Arlaud Date: Fri, 17 May 2024 15:02:50 +0200 Subject: [PATCH 227/297] [Serializer] Cache readability/writability computation --- Normalizer/ObjectNormalizer.php | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/Normalizer/ObjectNormalizer.php b/Normalizer/ObjectNormalizer.php index 45f272031..f8fd93f30 100644 --- a/Normalizer/ObjectNormalizer.php +++ b/Normalizer/ObjectNormalizer.php @@ -35,6 +35,8 @@ class ObjectNormalizer extends AbstractObjectNormalizer { private static $reflectionCache = []; + private static $isReadableCache = []; + private static $isWritableCache = []; protected $propertyAccessor; protected $propertyInfoExtractor; @@ -185,21 +187,23 @@ protected function isAllowedAttribute($classOrObject, string $attribute, ?string if (!parent::isAllowedAttribute($classOrObject, $attribute, $format, $context)) { return false; } + $class = \is_object($classOrObject) ? \get_class($classOrObject) : $classOrObject; if ($context['_read_attributes'] ?? true) { - return $this->propertyInfoExtractor->isReadable($class, $attribute) || $this->hasAttributeAccessorMethod($class, $attribute); - } + if (!isset(self::$isReadableCache[$class.$attribute])) { + self::$isReadableCache[$class.$attribute] = $this->propertyInfoExtractor->isReadable($class, $attribute) || $this->hasAttributeAccessorMethod($class, $attribute); + } - if ($this->propertyInfoExtractor->isWritable($class, $attribute)) { - return true; + return self::$isReadableCache[$class.$attribute]; } - if (($writeInfo = $this->writeInfoExtractor->getWriteInfo($class, $attribute)) && PropertyWriteInfo::TYPE_NONE !== $writeInfo->getType()) { - return true; + if (!isset(self::$isWritableCache[$class.$attribute])) { + self::$isWritableCache[$class.$attribute] = $this->propertyInfoExtractor->isWritable($class, $attribute) + || (($writeInfo = $this->writeInfoExtractor->getWriteInfo($class, $attribute)) && PropertyWriteInfo::TYPE_NONE !== $writeInfo->getType()); } - return false; + return self::$isWritableCache[$class.$attribute]; } private function hasAttributeAccessorMethod(string $class, string $attribute): bool From d7e6907e0c9c7e606e0a057d03ec6a78b12bb32c Mon Sep 17 00:00:00 2001 From: HypeMC Date: Tue, 28 May 2024 01:30:34 +0200 Subject: [PATCH 228/297] [Serializer] Fix denormalizing a collection of union types --- Normalizer/AbstractObjectNormalizer.php | 7 +++- .../AbstractObjectNormalizerTest.php | 38 +++++++++++++++++++ 2 files changed, 44 insertions(+), 1 deletion(-) diff --git a/Normalizer/AbstractObjectNormalizer.php b/Normalizer/AbstractObjectNormalizer.php index e766e3245..df3d693f2 100644 --- a/Normalizer/AbstractObjectNormalizer.php +++ b/Normalizer/AbstractObjectNormalizer.php @@ -32,6 +32,7 @@ use Symfony\Component\Serializer\Mapping\ClassMetadataInterface; use Symfony\Component\Serializer\Mapping\Factory\ClassMetadataFactoryInterface; use Symfony\Component\Serializer\NameConverter\NameConverterInterface; +use Symfony\Component\TypeInfo\Exception\LogicException as TypeInfoLogicException; use Symfony\Component\TypeInfo\Type; use Symfony\Component\TypeInfo\Type\CollectionType; use Symfony\Component\TypeInfo\Type\IntersectionType; @@ -702,7 +703,11 @@ private function validateAndDenormalize(Type $type, string $currentClass, string } if ($collectionValueType) { - $collectionValueBaseType = $collectionValueType instanceof UnionType ? $collectionValueType->asNonNullable()->getBaseType() : $collectionValueType->getBaseType(); + try { + $collectionValueBaseType = $collectionValueType->getBaseType(); + } catch (TypeInfoLogicException) { + $collectionValueBaseType = Type::mixed(); + } if ($collectionValueBaseType instanceof ObjectType) { $typeIdentifier = TypeIdentifier::OBJECT; diff --git a/Tests/Normalizer/AbstractObjectNormalizerTest.php b/Tests/Normalizer/AbstractObjectNormalizerTest.php index fea4f6066..f41c0fdf3 100644 --- a/Tests/Normalizer/AbstractObjectNormalizerTest.php +++ b/Tests/Normalizer/AbstractObjectNormalizerTest.php @@ -1147,6 +1147,25 @@ public function testDenormalizeCollectionOfScalarTypesPropertyWithPhpDocExtracto $this->assertEquals($expected, $normalizer->denormalize($data, ScalarCollectionDocBlockDummy::class)); } + + public function testDenormalizeCollectionOfUnionTypesPropertyWithPhpDocExtractor() + { + $normalizer = new AbstractObjectNormalizerWithMetadataAndPhpDocExtractor(); + $data = [ + 'values1' => [ + 'foo' => 'foo', + 'bar' => 222, + ], + 'values2' => [ + 'baz' => 'baz', + 'qux' => 333, + ], + ]; + $expected = new UnionCollectionDocBlockDummy($data['values1']); + $expected->values2 = $data['values2']; + + $this->assertEquals($expected, $normalizer->denormalize($data, UnionCollectionDocBlockDummy::class)); + } } class AbstractObjectNormalizerDummy extends AbstractObjectNormalizer @@ -1577,6 +1596,22 @@ public function getValues(): ?array } } +class UnionCollectionDocBlockDummy +{ + /** + * @param array $values1 + */ + public function __construct( + public array $values1, + ) { + } + + /** + * @var array + */ + public array $values2; +} + class AbstractObjectNormalizerWithMetadataAndPhpDocExtractor extends AbstractObjectNormalizer { public function __construct() @@ -1596,6 +1631,9 @@ protected function getAttributeValue(object $object, string $attribute, ?string protected function setAttributeValue(object $object, string $attribute, mixed $value, ?string $format = null, array $context = []): void { + if (property_exists($object, $attribute)) { + $object->$attribute = $value; + } } public function getSupportedTypes(?string $format): array From 0128e9f9c91ac12bad70bfa82eb0dbdb9021b57f Mon Sep 17 00:00:00 2001 From: Christian Flothmann Date: Mon, 3 Jun 2024 15:27:28 +0200 Subject: [PATCH 229/297] use constructor property promotion --- Extractor/ObjectPropertyListExtractor.php | 8 ++++---- Mapping/Loader/FileLoader.php | 9 +++------ Normalizer/AbstractNormalizer.php | 11 +++++------ 3 files changed, 12 insertions(+), 16 deletions(-) diff --git a/Extractor/ObjectPropertyListExtractor.php b/Extractor/ObjectPropertyListExtractor.php index 8422b0786..42a546805 100644 --- a/Extractor/ObjectPropertyListExtractor.php +++ b/Extractor/ObjectPropertyListExtractor.php @@ -18,12 +18,12 @@ */ final class ObjectPropertyListExtractor implements ObjectPropertyListExtractorInterface { - private PropertyListExtractorInterface $propertyListExtractor; private \Closure $objectClassResolver; - public function __construct(PropertyListExtractorInterface $propertyListExtractor, ?callable $objectClassResolver = null) - { - $this->propertyListExtractor = $propertyListExtractor; + public function __construct( + private PropertyListExtractorInterface $propertyListExtractor, + ?callable $objectClassResolver = null, + ) { $this->objectClassResolver = ($objectClassResolver ?? 'get_class')(...); } diff --git a/Mapping/Loader/FileLoader.php b/Mapping/Loader/FileLoader.php index 7fda4ebd5..ccd443b04 100644 --- a/Mapping/Loader/FileLoader.php +++ b/Mapping/Loader/FileLoader.php @@ -20,15 +20,14 @@ */ abstract class FileLoader implements LoaderInterface { - protected string $file; - /** * @param string $file The mapping file to load * * @throws MappingException if the mapping file does not exist or is not readable */ - public function __construct(string $file) - { + public function __construct( + protected string $file, + ) { if (!is_file($file)) { throw new MappingException(sprintf('The mapping file "%s" does not exist.', $file)); } @@ -36,7 +35,5 @@ public function __construct(string $file) if (!is_readable($file)) { throw new MappingException(sprintf('The mapping file "%s" is not readable.', $file)); } - - $this->file = $file; } } diff --git a/Normalizer/AbstractNormalizer.php b/Normalizer/AbstractNormalizer.php index 004581399..70714ac24 100644 --- a/Normalizer/AbstractNormalizer.php +++ b/Normalizer/AbstractNormalizer.php @@ -139,16 +139,15 @@ abstract class AbstractNormalizer implements NormalizerInterface, DenormalizerIn self::CIRCULAR_REFERENCE_LIMIT => 1, self::IGNORED_ATTRIBUTES => [], ]; - protected ?ClassMetadataFactoryInterface $classMetadataFactory; - protected ?NameConverterInterface $nameConverter; /** * Sets the {@link ClassMetadataFactoryInterface} to use. */ - public function __construct(?ClassMetadataFactoryInterface $classMetadataFactory = null, ?NameConverterInterface $nameConverter = null, array $defaultContext = []) - { - $this->classMetadataFactory = $classMetadataFactory; - $this->nameConverter = $nameConverter; + public function __construct( + protected ?ClassMetadataFactoryInterface $classMetadataFactory = null, + protected ?NameConverterInterface $nameConverter = null, + array $defaultContext = [], + ) { $this->defaultContext = array_merge($this->defaultContext, $defaultContext); $this->validateCallbackContext($this->defaultContext, 'default'); From 9a3acd7fa878f64de2dbdb20c39af4b954ebf1c2 Mon Sep 17 00:00:00 2001 From: Christian Flothmann Date: Wed, 5 Jun 2024 09:30:29 +0200 Subject: [PATCH 230/297] avoid calling undefined built-in is_*() functions --- Normalizer/AbstractObjectNormalizer.php | 23 +++++++++++++++---- .../AbstractObjectNormalizerTest.php | 22 ++++++++++++++---- 2 files changed, 36 insertions(+), 9 deletions(-) diff --git a/Normalizer/AbstractObjectNormalizer.php b/Normalizer/AbstractObjectNormalizer.php index df3d693f2..27c383177 100644 --- a/Normalizer/AbstractObjectNormalizer.php +++ b/Normalizer/AbstractObjectNormalizer.php @@ -773,11 +773,24 @@ private function validateAndDenormalize(Type $type, string $currentClass, string return (float) $data; } - if ((TypeIdentifier::FALSE === $typeIdentifier && false === $data) || (TypeIdentifier::TRUE === $typeIdentifier && true === $data)) { - return $data; - } - - if (('is_'.$typeIdentifier->value)($data)) { + $dataMatchesExpectedType = match ($typeIdentifier) { + TypeIdentifier::ARRAY => \is_array($data), + TypeIdentifier::BOOL => \is_bool($data), + TypeIdentifier::CALLABLE => \is_callable($data), + TypeIdentifier::FALSE => false === $data, + TypeIdentifier::FLOAT => \is_float($data), + TypeIdentifier::INT => \is_int($data), + TypeIdentifier::ITERABLE => is_iterable($data), + TypeIdentifier::MIXED => true, + TypeIdentifier::NULL => null === $data, + TypeIdentifier::OBJECT => \is_object($data), + TypeIdentifier::RESOURCE => \is_resource($data), + TypeIdentifier::STRING => \is_string($data), + TypeIdentifier::TRUE => true === $data, + default => false, + }; + + if ($dataMatchesExpectedType) { return $data; } } catch (NotNormalizableValueException|InvalidArgumentException $e) { diff --git a/Tests/Normalizer/AbstractObjectNormalizerTest.php b/Tests/Normalizer/AbstractObjectNormalizerTest.php index f41c0fdf3..21ce5d768 100644 --- a/Tests/Normalizer/AbstractObjectNormalizerTest.php +++ b/Tests/Normalizer/AbstractObjectNormalizerTest.php @@ -1132,7 +1132,7 @@ public function testNormalizationWithMaxDepthOnStdclassObjectDoesNotThrowWarning public function testDenormalizeCollectionOfScalarTypesPropertyWithPhpDocExtractor() { - $normalizer = new AbstractObjectNormalizerWithMetadataAndPhpDocExtractor(); + $normalizer = new AbstractObjectNormalizerWithMetadataAndPropertyTypeExtractors(); $data = [ 'type' => 'foo', 'values' => [ @@ -1150,7 +1150,7 @@ public function testDenormalizeCollectionOfScalarTypesPropertyWithPhpDocExtracto public function testDenormalizeCollectionOfUnionTypesPropertyWithPhpDocExtractor() { - $normalizer = new AbstractObjectNormalizerWithMetadataAndPhpDocExtractor(); + $normalizer = new AbstractObjectNormalizerWithMetadataAndPropertyTypeExtractors(); $data = [ 'values1' => [ 'foo' => 'foo', @@ -1166,6 +1166,15 @@ public function testDenormalizeCollectionOfUnionTypesPropertyWithPhpDocExtractor $this->assertEquals($expected, $normalizer->denormalize($data, UnionCollectionDocBlockDummy::class)); } + + public function testDenormalizeMixedProperty() + { + $normalizer = new AbstractObjectNormalizerWithMetadataAndPropertyTypeExtractors(); + $expected = new MixedPropertyDummy(); + $expected->foo = 'bar'; + + $this->assertEquals($expected, $normalizer->denormalize(['foo' => 'bar'], MixedPropertyDummy::class)); + } } class AbstractObjectNormalizerDummy extends AbstractObjectNormalizer @@ -1268,6 +1277,11 @@ class SnakeCaseNestedDummy public $fooBar; } +class MixedPropertyDummy +{ + public mixed $foo; +} + #[DiscriminatorMap(typeProperty: 'type', mapping: [ 'first' => FirstNestedDummyWithConstructorAndDiscriminator::class, 'second' => SecondNestedDummyWithConstructorAndDiscriminator::class, @@ -1612,11 +1626,11 @@ public function __construct( public array $values2; } -class AbstractObjectNormalizerWithMetadataAndPhpDocExtractor extends AbstractObjectNormalizer +class AbstractObjectNormalizerWithMetadataAndPropertyTypeExtractors extends AbstractObjectNormalizer { public function __construct() { - parent::__construct(new ClassMetadataFactory(new AttributeLoader()), null, new PropertyInfoExtractor([], [new PhpDocExtractor()])); + parent::__construct(new ClassMetadataFactory(new AttributeLoader()), null, new PropertyInfoExtractor([], [new PhpDocExtractor(), new ReflectionExtractor()])); } protected function extractAttributes(object $object, ?string $format = null, array $context = []): array From 4b41b4b5e0e33aa69921416b77658f6f7a65b489 Mon Sep 17 00:00:00 2001 From: "Alexander M. Turek" Date: Thu, 20 Jun 2024 17:52:34 +0200 Subject: [PATCH 231/297] Prefix all sprintf() calls --- Attribute/Context.php | 4 +- Attribute/DiscriminatorMap.php | 4 +- Attribute/Groups.php | 4 +- Attribute/MaxDepth.php | 2 +- Attribute/SerializedName.php | 2 +- Attribute/SerializedPath.php | 2 +- Command/DebugCommand.php | 4 +- Context/Encoder/CsvEncoderContextBuilder.php | 6 +-- .../AbstractNormalizerContextBuilder.php | 2 +- ...AbstractObjectNormalizerContextBuilder.php | 2 +- .../DateTimeNormalizerContextBuilder.php | 2 +- .../UidNormalizerContextBuilder.php | 2 +- .../UnwrappingDenormalizerContextBuilder.php | 2 +- Debug/TraceableEncoder.php | 4 +- Debug/TraceableNormalizer.php | 4 +- Encoder/ChainDecoder.php | 2 +- Encoder/ChainEncoder.php | 2 +- Encoder/CsvEncoder.php | 2 +- Encoder/JsonDecode.php | 2 +- Encoder/XmlEncoder.php | 6 +-- Exception/ExtraAttributesException.php | 2 +- Exception/UnexpectedPropertyException.php | 2 +- .../Factory/ClassMetadataFactoryCompiler.php | 2 +- Mapping/Factory/ClassResolverTrait.php | 2 +- .../Factory/CompiledClassMetadataFactory.php | 2 +- Mapping/Loader/AttributeLoader.php | 16 ++++---- Mapping/Loader/FileLoader.php | 4 +- Mapping/Loader/LoaderChain.php | 2 +- Mapping/Loader/XmlFileLoader.php | 2 +- Mapping/Loader/YamlFileLoader.php | 20 +++++----- NameConverter/MetadataAwareNameConverter.php | 4 +- Normalizer/AbstractNormalizer.php | 22 +++++----- Normalizer/AbstractObjectNormalizer.php | 40 +++++++++---------- Normalizer/ArrayDenormalizer.php | 8 ++-- .../ConstraintViolationListNormalizer.php | 4 +- Normalizer/DataUriNormalizer.php | 8 ++-- Normalizer/DateIntervalNormalizer.php | 2 +- Normalizer/DateTimeNormalizer.php | 8 ++-- Normalizer/JsonSerializableNormalizer.php | 4 +- Normalizer/MimeMessageNormalizer.php | 2 +- Normalizer/ProblemNormalizer.php | 2 +- Normalizer/PropertyNormalizer.php | 2 +- Normalizer/TranslatableNormalizer.php | 2 +- Normalizer/UidNormalizer.php | 4 +- Serializer.php | 16 ++++---- Tests/Annotation/ContextTest.php | 2 +- Tests/Encoder/XmlEncoderTest.php | 4 +- Tests/Mapping/Loader/AttributeLoaderTest.php | 2 +- .../Features/CircularReferenceTestTrait.php | 2 +- .../ConstructorArgumentsTestTrait.php | 4 +- Tests/Normalizer/ObjectNormalizerTest.php | 4 +- 51 files changed, 130 insertions(+), 130 deletions(-) diff --git a/Attribute/Context.php b/Attribute/Context.php index 5ea2d2eb5..892af481a 100644 --- a/Attribute/Context.php +++ b/Attribute/Context.php @@ -36,14 +36,14 @@ public function __construct( string|array $groups = [], ) { if (!$context && !$normalizationContext && !$denormalizationContext) { - throw new InvalidArgumentException(sprintf('At least one of the "context", "normalizationContext", or "denormalizationContext" options must be provided as a non-empty array to "%s".', static::class)); + throw new InvalidArgumentException(\sprintf('At least one of the "context", "normalizationContext", or "denormalizationContext" options must be provided as a non-empty array to "%s".', static::class)); } $this->groups = (array) $groups; foreach ($this->groups as $group) { if (!\is_string($group)) { - throw new InvalidArgumentException(sprintf('Parameter "groups" given to "%s" must be a string or an array of strings, "%s" given.', static::class, get_debug_type($group))); + throw new InvalidArgumentException(\sprintf('Parameter "groups" given to "%s" must be a string or an array of strings, "%s" given.', static::class, get_debug_type($group))); } } } diff --git a/Attribute/DiscriminatorMap.php b/Attribute/DiscriminatorMap.php index a77fc2984..48d0842aa 100644 --- a/Attribute/DiscriminatorMap.php +++ b/Attribute/DiscriminatorMap.php @@ -30,11 +30,11 @@ public function __construct( private readonly array $mapping, ) { if (!$typeProperty) { - throw new InvalidArgumentException(sprintf('Parameter "typeProperty" given to "%s" cannot be empty.', static::class)); + throw new InvalidArgumentException(\sprintf('Parameter "typeProperty" given to "%s" cannot be empty.', static::class)); } if (!$mapping) { - throw new InvalidArgumentException(sprintf('Parameter "mapping" given to "%s" cannot be empty.', static::class)); + throw new InvalidArgumentException(\sprintf('Parameter "mapping" given to "%s" cannot be empty.', static::class)); } } diff --git a/Attribute/Groups.php b/Attribute/Groups.php index 39914f971..8747949a4 100644 --- a/Attribute/Groups.php +++ b/Attribute/Groups.php @@ -32,12 +32,12 @@ public function __construct(string|array $groups) $this->groups = (array) $groups; if (!$this->groups) { - throw new InvalidArgumentException(sprintf('Parameter given to "%s" cannot be empty.', static::class)); + throw new InvalidArgumentException(\sprintf('Parameter given to "%s" cannot be empty.', static::class)); } foreach ($this->groups as $group) { if (!\is_string($group) || '' === $group) { - throw new InvalidArgumentException(sprintf('Parameter given to "%s" must be a string or an array of non-empty strings.', static::class)); + throw new InvalidArgumentException(\sprintf('Parameter given to "%s" must be a string or an array of non-empty strings.', static::class)); } } } diff --git a/Attribute/MaxDepth.php b/Attribute/MaxDepth.php index 17562b6c2..16d6a8120 100644 --- a/Attribute/MaxDepth.php +++ b/Attribute/MaxDepth.php @@ -25,7 +25,7 @@ class MaxDepth public function __construct(private readonly int $maxDepth) { if ($maxDepth <= 0) { - throw new InvalidArgumentException(sprintf('Parameter given to "%s" must be a positive integer.', static::class)); + throw new InvalidArgumentException(\sprintf('Parameter given to "%s" must be a positive integer.', static::class)); } } diff --git a/Attribute/SerializedName.php b/Attribute/SerializedName.php index 6acbebd03..f1c6cefe2 100644 --- a/Attribute/SerializedName.php +++ b/Attribute/SerializedName.php @@ -25,7 +25,7 @@ class SerializedName public function __construct(private readonly string $serializedName) { if ('' === $serializedName) { - throw new InvalidArgumentException(sprintf('Parameter given to "%s" must be a non-empty string.', self::class)); + throw new InvalidArgumentException(\sprintf('Parameter given to "%s" must be a non-empty string.', self::class)); } } diff --git a/Attribute/SerializedPath.php b/Attribute/SerializedPath.php index 118e3adbe..71254bfb9 100644 --- a/Attribute/SerializedPath.php +++ b/Attribute/SerializedPath.php @@ -31,7 +31,7 @@ public function __construct(string $serializedPath) try { $this->serializedPath = new PropertyPath($serializedPath); } catch (InvalidPropertyPathException $pathException) { - throw new InvalidArgumentException(sprintf('Parameter given to "%s" must be a valid property path.', self::class)); + throw new InvalidArgumentException(\sprintf('Parameter given to "%s" must be a valid property path.', self::class)); } } diff --git a/Command/DebugCommand.php b/Command/DebugCommand.php index c85ee213e..3e70c93a5 100644 --- a/Command/DebugCommand.php +++ b/Command/DebugCommand.php @@ -49,7 +49,7 @@ protected function execute(InputInterface $input, OutputInterface $output): int if (!class_exists($class)) { $io = new SymfonyStyle($input, $output); - $io->error(sprintf('Class "%s" was not found.', $class)); + $io->error(\sprintf('Class "%s" was not found.', $class)); return Command::FAILURE; } @@ -62,7 +62,7 @@ protected function execute(InputInterface $input, OutputInterface $output): int private function dumpSerializerDataForClass(InputInterface $input, OutputInterface $output, string $class): void { $io = new SymfonyStyle($input, $output); - $title = sprintf('%s', $class); + $title = \sprintf('%s', $class); $rows = []; $dump = new Dumper($output); diff --git a/Context/Encoder/CsvEncoderContextBuilder.php b/Context/Encoder/CsvEncoderContextBuilder.php index f75759031..f449bc314 100644 --- a/Context/Encoder/CsvEncoderContextBuilder.php +++ b/Context/Encoder/CsvEncoderContextBuilder.php @@ -35,7 +35,7 @@ final class CsvEncoderContextBuilder implements ContextBuilderInterface public function withDelimiter(?string $delimiter): static { if (null !== $delimiter && 1 !== \strlen($delimiter)) { - throw new InvalidArgumentException(sprintf('The "%s" delimiter must be a single character.', $delimiter)); + throw new InvalidArgumentException(\sprintf('The "%s" delimiter must be a single character.', $delimiter)); } return $this->with(CsvEncoder::DELIMITER_KEY, $delimiter); @@ -51,7 +51,7 @@ public function withDelimiter(?string $delimiter): static public function withEnclosure(?string $enclosure): static { if (null !== $enclosure && 1 !== \strlen($enclosure)) { - throw new InvalidArgumentException(sprintf('The "%s" enclosure must be a single character.', $enclosure)); + throw new InvalidArgumentException(\sprintf('The "%s" enclosure must be a single character.', $enclosure)); } return $this->with(CsvEncoder::ENCLOSURE_KEY, $enclosure); @@ -67,7 +67,7 @@ public function withEnclosure(?string $enclosure): static public function withEscapeChar(?string $escapeChar): static { if (null !== $escapeChar && \strlen($escapeChar) > 1) { - throw new InvalidArgumentException(sprintf('The "%s" escape character must be empty or a single character.', $escapeChar)); + throw new InvalidArgumentException(\sprintf('The "%s" escape character must be empty or a single character.', $escapeChar)); } return $this->with(CsvEncoder::ESCAPE_CHAR_KEY, $escapeChar); diff --git a/Context/Normalizer/AbstractNormalizerContextBuilder.php b/Context/Normalizer/AbstractNormalizerContextBuilder.php index cb5b0544d..a63e1a507 100644 --- a/Context/Normalizer/AbstractNormalizerContextBuilder.php +++ b/Context/Normalizer/AbstractNormalizerContextBuilder.php @@ -87,7 +87,7 @@ public function withAttributes(?array $attributes): static foreach ($it as $attribute) { if (!\is_string($attribute)) { - throw new InvalidArgumentException(sprintf('Each attribute must be a string, "%s" given.', get_debug_type($attribute))); + throw new InvalidArgumentException(\sprintf('Each attribute must be a string, "%s" given.', get_debug_type($attribute))); } } diff --git a/Context/Normalizer/AbstractObjectNormalizerContextBuilder.php b/Context/Normalizer/AbstractObjectNormalizerContextBuilder.php index a27f00c5b..45b47bc1b 100644 --- a/Context/Normalizer/AbstractObjectNormalizerContextBuilder.php +++ b/Context/Normalizer/AbstractObjectNormalizerContextBuilder.php @@ -47,7 +47,7 @@ public function withDepthKeyPattern(?string $depthKeyPattern): static preg_match_all('/(?[a-z])/', $depthKeyPattern, $matches); if (2 !== \count($matches['specifier']) || 's' !== $matches['specifier'][0] || 's' !== $matches['specifier'][1]) { - throw new InvalidArgumentException(sprintf('The depth key pattern "%s" is not valid. You must set exactly two string placeholders.', $depthKeyPattern)); + throw new InvalidArgumentException(\sprintf('The depth key pattern "%s" is not valid. You must set exactly two string placeholders.', $depthKeyPattern)); } return $this->with(AbstractObjectNormalizer::DEPTH_KEY_PATTERN, $depthKeyPattern); diff --git a/Context/Normalizer/DateTimeNormalizerContextBuilder.php b/Context/Normalizer/DateTimeNormalizerContextBuilder.php index e2d289e60..de83b1245 100644 --- a/Context/Normalizer/DateTimeNormalizerContextBuilder.php +++ b/Context/Normalizer/DateTimeNormalizerContextBuilder.php @@ -55,7 +55,7 @@ public function withTimezone(\DateTimeZone|string|null $timezone): static try { $timezone = new \DateTimeZone($timezone); } catch (\Exception $e) { - throw new InvalidArgumentException(sprintf('The "%s" timezone is invalid.', $timezone), previous: $e); + throw new InvalidArgumentException(\sprintf('The "%s" timezone is invalid.', $timezone), previous: $e); } } diff --git a/Context/Normalizer/UidNormalizerContextBuilder.php b/Context/Normalizer/UidNormalizerContextBuilder.php index 1d889e502..b809fe3eb 100644 --- a/Context/Normalizer/UidNormalizerContextBuilder.php +++ b/Context/Normalizer/UidNormalizerContextBuilder.php @@ -33,7 +33,7 @@ final class UidNormalizerContextBuilder implements ContextBuilderInterface public function withNormalizationFormat(?string $normalizationFormat): static { if (null !== $normalizationFormat && !\in_array($normalizationFormat, UidNormalizer::NORMALIZATION_FORMATS, true)) { - throw new InvalidArgumentException(sprintf('The "%s" normalization format is not valid.', $normalizationFormat)); + throw new InvalidArgumentException(\sprintf('The "%s" normalization format is not valid.', $normalizationFormat)); } return $this->with(UidNormalizer::NORMALIZATION_FORMAT_KEY, $normalizationFormat); diff --git a/Context/Normalizer/UnwrappingDenormalizerContextBuilder.php b/Context/Normalizer/UnwrappingDenormalizerContextBuilder.php index 5beb4e984..2945cb961 100644 --- a/Context/Normalizer/UnwrappingDenormalizerContextBuilder.php +++ b/Context/Normalizer/UnwrappingDenormalizerContextBuilder.php @@ -45,7 +45,7 @@ public function withUnwrapPath(?string $unwrapPath): static try { new PropertyPath($unwrapPath); } catch (InvalidPropertyPathException $e) { - throw new InvalidArgumentException(sprintf('The "%s" property path is not valid.', $unwrapPath), previous: $e); + throw new InvalidArgumentException(\sprintf('The "%s" property path is not valid.', $unwrapPath), previous: $e); } return $this->with(UnwrappingDenormalizer::UNWRAP_PATH, $unwrapPath); diff --git a/Debug/TraceableEncoder.php b/Debug/TraceableEncoder.php index 0795d14ca..2d1ca62a5 100644 --- a/Debug/TraceableEncoder.php +++ b/Debug/TraceableEncoder.php @@ -36,7 +36,7 @@ public function __construct( public function encode(mixed $data, string $format, array $context = []): string { if (!$this->encoder instanceof EncoderInterface) { - throw new \BadMethodCallException(sprintf('The "%s()" method cannot be called as nested encoder doesn\'t implements "%s".', __METHOD__, EncoderInterface::class)); + throw new \BadMethodCallException(\sprintf('The "%s()" method cannot be called as nested encoder doesn\'t implements "%s".', __METHOD__, EncoderInterface::class)); } $startTime = microtime(true); @@ -62,7 +62,7 @@ public function supportsEncoding(string $format, array $context = []): bool public function decode(string $data, string $format, array $context = []): mixed { if (!$this->encoder instanceof DecoderInterface) { - throw new \BadMethodCallException(sprintf('The "%s()" method cannot be called as nested encoder doesn\'t implements "%s".', __METHOD__, DecoderInterface::class)); + throw new \BadMethodCallException(\sprintf('The "%s()" method cannot be called as nested encoder doesn\'t implements "%s".', __METHOD__, DecoderInterface::class)); } $startTime = microtime(true); diff --git a/Debug/TraceableNormalizer.php b/Debug/TraceableNormalizer.php index 50842a7b9..62e6663da 100644 --- a/Debug/TraceableNormalizer.php +++ b/Debug/TraceableNormalizer.php @@ -42,7 +42,7 @@ public function getSupportedTypes(?string $format): array public function normalize(mixed $object, ?string $format = null, array $context = []): array|string|int|float|bool|\ArrayObject|null { if (!$this->normalizer instanceof NormalizerInterface) { - throw new \BadMethodCallException(sprintf('The "%s()" method cannot be called as nested normalizer doesn\'t implements "%s".', __METHOD__, NormalizerInterface::class)); + throw new \BadMethodCallException(\sprintf('The "%s()" method cannot be called as nested normalizer doesn\'t implements "%s".', __METHOD__, NormalizerInterface::class)); } $startTime = microtime(true); @@ -68,7 +68,7 @@ public function supportsNormalization(mixed $data, ?string $format = null, array public function denormalize(mixed $data, string $type, ?string $format = null, array $context = []): mixed { if (!$this->normalizer instanceof DenormalizerInterface) { - throw new \BadMethodCallException(sprintf('The "%s()" method cannot be called as nested normalizer doesn\'t implements "%s".', __METHOD__, DenormalizerInterface::class)); + throw new \BadMethodCallException(\sprintf('The "%s()" method cannot be called as nested normalizer doesn\'t implements "%s".', __METHOD__, DenormalizerInterface::class)); } $startTime = microtime(true); diff --git a/Encoder/ChainDecoder.php b/Encoder/ChainDecoder.php index ad182a9a4..8df5cd8b8 100644 --- a/Encoder/ChainDecoder.php +++ b/Encoder/ChainDecoder.php @@ -78,6 +78,6 @@ private function getDecoder(string $format, array $context): DecoderInterface } } - throw new RuntimeException(sprintf('No decoder found for format "%s".', $format)); + throw new RuntimeException(\sprintf('No decoder found for format "%s".', $format)); } } diff --git a/Encoder/ChainEncoder.php b/Encoder/ChainEncoder.php index 5445761f3..3621471b4 100644 --- a/Encoder/ChainEncoder.php +++ b/Encoder/ChainEncoder.php @@ -101,6 +101,6 @@ private function getEncoder(string $format, array $context): EncoderInterface } } - throw new RuntimeException(sprintf('No encoder found for format "%s".', $format)); + throw new RuntimeException(\sprintf('No encoder found for format "%s".', $format)); } } diff --git a/Encoder/CsvEncoder.php b/Encoder/CsvEncoder.php index a3112ae36..462bd663b 100644 --- a/Encoder/CsvEncoder.php +++ b/Encoder/CsvEncoder.php @@ -241,7 +241,7 @@ private function getCsvOptions(array $context): array $asCollection = $context[self::AS_COLLECTION_KEY] ?? $this->defaultContext[self::AS_COLLECTION_KEY]; if (!\is_array($headers)) { - throw new InvalidArgumentException(sprintf('The "%s" context variable must be an array or null, given "%s".', self::HEADERS_KEY, get_debug_type($headers))); + throw new InvalidArgumentException(\sprintf('The "%s" context variable must be an array or null, given "%s".', self::HEADERS_KEY, get_debug_type($headers))); } return [$delimiter, $enclosure, $escapeChar, $keySeparator, $headers, $escapeFormulas, $outputBom, $asCollection]; diff --git a/Encoder/JsonDecode.php b/Encoder/JsonDecode.php index 0dbc9f6df..b50538175 100644 --- a/Encoder/JsonDecode.php +++ b/Encoder/JsonDecode.php @@ -106,7 +106,7 @@ public function decode(string $data, string $format, array $context = []): mixed } if (!class_exists(JsonParser::class)) { - throw new UnsupportedException(sprintf('Enabling "%s" serializer option requires seld/jsonlint. Try running "composer require seld/jsonlint".', self::DETAILED_ERROR_MESSAGES)); + throw new UnsupportedException(\sprintf('Enabling "%s" serializer option requires seld/jsonlint. Try running "composer require seld/jsonlint".', self::DETAILED_ERROR_MESSAGES)); } throw new NotEncodableValueException((new JsonParser())->lint($data)?->getMessage() ?: $errorMessage); diff --git a/Encoder/XmlEncoder.php b/Encoder/XmlEncoder.php index 5dcb2ba7e..a57030b10 100644 --- a/Encoder/XmlEncoder.php +++ b/Encoder/XmlEncoder.php @@ -389,7 +389,7 @@ private function buildXml(\DOMNode $parentNode, mixed $data, string $format, arr if (\is_object($data)) { if (null === $this->serializer) { - throw new BadMethodCallException(sprintf('The serializer needs to be set to allow "%s()" to be used with object data.', __METHOD__)); + throw new BadMethodCallException(\sprintf('The serializer needs to be set to allow "%s()" to be used with object data.', __METHOD__)); } $data = $this->serializer->normalize($data, $format, $context); @@ -408,7 +408,7 @@ private function buildXml(\DOMNode $parentNode, mixed $data, string $format, arr return $this->appendNode($parentNode, $data, $format, $context, 'data'); } - throw new NotEncodableValueException('An unexpected value could not be serialized: '.(!\is_resource($data) ? var_export($data, true) : sprintf('%s resource', get_resource_type($data)))); + throw new NotEncodableValueException('An unexpected value could not be serialized: '.(!\is_resource($data) ? var_export($data, true) : \sprintf('%s resource', get_resource_type($data)))); } /** @@ -457,7 +457,7 @@ private function selectNodeType(\DOMNode $node, mixed $val, string $format, arra $node->appendChild($child); } elseif (\is_object($val)) { if (null === $this->serializer) { - throw new BadMethodCallException(sprintf('The serializer needs to be set to allow "%s()" to be used with object data.', __METHOD__)); + throw new BadMethodCallException(\sprintf('The serializer needs to be set to allow "%s()" to be used with object data.', __METHOD__)); } return $this->selectNodeType($node, $this->serializer->normalize($val, $format, $context), $format, $context); diff --git a/Exception/ExtraAttributesException.php b/Exception/ExtraAttributesException.php index 24b031ae3..639808b10 100644 --- a/Exception/ExtraAttributesException.php +++ b/Exception/ExtraAttributesException.php @@ -22,7 +22,7 @@ public function __construct( private readonly array $extraAttributes, ?\Throwable $previous = null, ) { - $msg = sprintf('Extra attributes are not allowed ("%s" %s unknown).', implode('", "', $extraAttributes), \count($extraAttributes) > 1 ? 'are' : 'is'); + $msg = \sprintf('Extra attributes are not allowed ("%s" %s unknown).', implode('", "', $extraAttributes), \count($extraAttributes) > 1 ? 'are' : 'is'); parent::__construct($msg, 0, $previous); } diff --git a/Exception/UnexpectedPropertyException.php b/Exception/UnexpectedPropertyException.php index 4f9ead9a6..03e42fca5 100644 --- a/Exception/UnexpectedPropertyException.php +++ b/Exception/UnexpectedPropertyException.php @@ -22,7 +22,7 @@ public function __construct( public readonly string $property, ?\Throwable $previous = null, ) { - $msg = sprintf('Property is not allowed ("%s" is unknown).', $this->property); + $msg = \sprintf('Property is not allowed ("%s" is unknown).', $this->property); parent::__construct($msg, 0, $previous); } diff --git a/Mapping/Factory/ClassMetadataFactoryCompiler.php b/Mapping/Factory/ClassMetadataFactoryCompiler.php index f01fe9ce2..1e9202b7d 100644 --- a/Mapping/Factory/ClassMetadataFactoryCompiler.php +++ b/Mapping/Factory/ClassMetadataFactoryCompiler.php @@ -57,7 +57,7 @@ private function generateDeclaredClassMetadata(array $classMetadatas): string $classMetadata->getClassDiscriminatorMapping()->getTypesMapping(), ] : null; - $compiled .= sprintf("\n'%s' => %s,", $classMetadata->getName(), VarExporter::export([ + $compiled .= \sprintf("\n'%s' => %s,", $classMetadata->getName(), VarExporter::export([ $attributesMetadata, $classDiscriminatorMapping, ])); diff --git a/Mapping/Factory/ClassResolverTrait.php b/Mapping/Factory/ClassResolverTrait.php index 7af722d55..d7037f894 100644 --- a/Mapping/Factory/ClassResolverTrait.php +++ b/Mapping/Factory/ClassResolverTrait.php @@ -31,7 +31,7 @@ private function getClass(object|string $value): string { if (\is_string($value)) { if (!class_exists($value) && !interface_exists($value, false)) { - throw new InvalidArgumentException(sprintf('The class or interface "%s" does not exist.', $value)); + throw new InvalidArgumentException(\sprintf('The class or interface "%s" does not exist.', $value)); } return ltrim($value, '\\'); diff --git a/Mapping/Factory/CompiledClassMetadataFactory.php b/Mapping/Factory/CompiledClassMetadataFactory.php index f4d41c1e6..ec25d7440 100644 --- a/Mapping/Factory/CompiledClassMetadataFactory.php +++ b/Mapping/Factory/CompiledClassMetadataFactory.php @@ -35,7 +35,7 @@ public function __construct( $compiledClassMetadata = require $compiledClassMetadataFile; if (!\is_array($compiledClassMetadata)) { - throw new \RuntimeException(sprintf('Compiled metadata must be of the type array, %s given.', \gettype($compiledClassMetadata))); + throw new \RuntimeException(\sprintf('Compiled metadata must be of the type array, %s given.', \gettype($compiledClassMetadata))); } $this->compiledClassMetadata = $compiledClassMetadata; diff --git a/Mapping/Loader/AttributeLoader.php b/Mapping/Loader/AttributeLoader.php index ddb467164..272e236b6 100644 --- a/Mapping/Loader/AttributeLoader.php +++ b/Mapping/Loader/AttributeLoader.php @@ -138,7 +138,7 @@ public function loadClassMetadata(ClassMetadataInterface $classMetadata): bool foreach ($this->loadAttributes($method) as $annotation) { if ($annotation instanceof Groups) { if (!$accessorOrMutator) { - throw new MappingException(sprintf('Groups on "%s::%s()" cannot be added. Groups can only be added on methods beginning with "get", "is", "has" or "set".', $className, $method->name)); + throw new MappingException(\sprintf('Groups on "%s::%s()" cannot be added. Groups can only be added on methods beginning with "get", "is", "has" or "set".', $className, $method->name)); } foreach ($annotation->getGroups() as $group) { @@ -146,19 +146,19 @@ public function loadClassMetadata(ClassMetadataInterface $classMetadata): bool } } elseif ($annotation instanceof MaxDepth) { if (!$accessorOrMutator) { - throw new MappingException(sprintf('MaxDepth on "%s::%s()" cannot be added. MaxDepth can only be added on methods beginning with "get", "is", "has" or "set".', $className, $method->name)); + throw new MappingException(\sprintf('MaxDepth on "%s::%s()" cannot be added. MaxDepth can only be added on methods beginning with "get", "is", "has" or "set".', $className, $method->name)); } $attributeMetadata->setMaxDepth($annotation->getMaxDepth()); } elseif ($annotation instanceof SerializedName) { if (!$accessorOrMutator) { - throw new MappingException(sprintf('SerializedName on "%s::%s()" cannot be added. SerializedName can only be added on methods beginning with "get", "is", "has" or "set".', $className, $method->name)); + throw new MappingException(\sprintf('SerializedName on "%s::%s()" cannot be added. SerializedName can only be added on methods beginning with "get", "is", "has" or "set".', $className, $method->name)); } $attributeMetadata->setSerializedName($annotation->getSerializedName()); } elseif ($annotation instanceof SerializedPath) { if (!$accessorOrMutator) { - throw new MappingException(sprintf('SerializedPath on "%s::%s()" cannot be added. SerializedPath can only be added on methods beginning with "get", "is", "has" or "set".', $className, $method->name)); + throw new MappingException(\sprintf('SerializedPath on "%s::%s()" cannot be added. SerializedPath can only be added on methods beginning with "get", "is", "has" or "set".', $className, $method->name)); } $attributeMetadata->setSerializedPath($annotation->getSerializedPath()); @@ -168,7 +168,7 @@ public function loadClassMetadata(ClassMetadataInterface $classMetadata): bool } } elseif ($annotation instanceof Context) { if (!$accessorOrMutator) { - throw new MappingException(sprintf('Context on "%s::%s()" cannot be added. Context can only be added on methods beginning with "get", "is", "has" or "set".', $className, $method->name)); + throw new MappingException(\sprintf('Context on "%s::%s()" cannot be added. Context can only be added on methods beginning with "get", "is", "has" or "set".', $className, $method->name)); } $this->setAttributeContextsForGroups($annotation, $attributeMetadata); @@ -193,12 +193,12 @@ private function loadAttributes(\ReflectionMethod|\ReflectionClass|\ReflectionPr } $on = match (true) { $reflector instanceof \ReflectionClass => ' on class '.$reflector->name, - $reflector instanceof \ReflectionMethod => sprintf(' on "%s::%s()"', $reflector->getDeclaringClass()->name, $reflector->name), - $reflector instanceof \ReflectionProperty => sprintf(' on "%s::$%s"', $reflector->getDeclaringClass()->name, $reflector->name), + $reflector instanceof \ReflectionMethod => \sprintf(' on "%s::%s()"', $reflector->getDeclaringClass()->name, $reflector->name), + $reflector instanceof \ReflectionProperty => \sprintf(' on "%s::$%s"', $reflector->getDeclaringClass()->name, $reflector->name), default => '', }; - throw new MappingException(sprintf('Could not instantiate attribute "%s"%s.', $attribute->getName(), $on), 0, $e); + throw new MappingException(\sprintf('Could not instantiate attribute "%s"%s.', $attribute->getName(), $on), 0, $e); } } } diff --git a/Mapping/Loader/FileLoader.php b/Mapping/Loader/FileLoader.php index ccd443b04..ac68d21e8 100644 --- a/Mapping/Loader/FileLoader.php +++ b/Mapping/Loader/FileLoader.php @@ -29,11 +29,11 @@ public function __construct( protected string $file, ) { if (!is_file($file)) { - throw new MappingException(sprintf('The mapping file "%s" does not exist.', $file)); + throw new MappingException(\sprintf('The mapping file "%s" does not exist.', $file)); } if (!is_readable($file)) { - throw new MappingException(sprintf('The mapping file "%s" is not readable.', $file)); + throw new MappingException(\sprintf('The mapping file "%s" is not readable.', $file)); } } } diff --git a/Mapping/Loader/LoaderChain.php b/Mapping/Loader/LoaderChain.php index e93a0a7b0..41abf8cec 100644 --- a/Mapping/Loader/LoaderChain.php +++ b/Mapping/Loader/LoaderChain.php @@ -38,7 +38,7 @@ public function __construct(private readonly array $loaders) { foreach ($loaders as $loader) { if (!$loader instanceof LoaderInterface) { - throw new MappingException(sprintf('Class "%s" is expected to implement LoaderInterface.', get_debug_type($loader))); + throw new MappingException(\sprintf('Class "%s" is expected to implement LoaderInterface.', get_debug_type($loader))); } } } diff --git a/Mapping/Loader/XmlFileLoader.php b/Mapping/Loader/XmlFileLoader.php index b36fb871d..44ba89df1 100644 --- a/Mapping/Loader/XmlFileLoader.php +++ b/Mapping/Loader/XmlFileLoader.php @@ -70,7 +70,7 @@ public function loadClassMetadata(ClassMetadataInterface $classMetadata): bool try { $attributeMetadata->setSerializedPath(new PropertyPath((string) $attribute['serialized-path'])); } catch (InvalidPropertyPathException) { - throw new MappingException(sprintf('The "serialized-path" value must be a valid property path for the attribute "%s" of the class "%s".', $attributeName, $classMetadata->getName())); + throw new MappingException(\sprintf('The "serialized-path" value must be a valid property path for the attribute "%s" of the class "%s".', $attributeName, $classMetadata->getName())); } } diff --git a/Mapping/Loader/YamlFileLoader.php b/Mapping/Loader/YamlFileLoader.php index b0398355a..ca71cbcba 100644 --- a/Mapping/Loader/YamlFileLoader.php +++ b/Mapping/Loader/YamlFileLoader.php @@ -59,12 +59,12 @@ public function loadClassMetadata(ClassMetadataInterface $classMetadata): bool if (isset($data['groups'])) { if (!\is_array($data['groups'])) { - throw new MappingException(sprintf('The "groups" key must be an array of strings in "%s" for the attribute "%s" of the class "%s".', $this->file, $attribute, $classMetadata->getName())); + throw new MappingException(\sprintf('The "groups" key must be an array of strings in "%s" for the attribute "%s" of the class "%s".', $this->file, $attribute, $classMetadata->getName())); } foreach ($data['groups'] as $group) { if (!\is_string($group)) { - throw new MappingException(sprintf('Group names must be strings in "%s" for the attribute "%s" of the class "%s".', $this->file, $attribute, $classMetadata->getName())); + throw new MappingException(\sprintf('Group names must be strings in "%s" for the attribute "%s" of the class "%s".', $this->file, $attribute, $classMetadata->getName())); } $attributeMetadata->addGroup($group); @@ -73,7 +73,7 @@ public function loadClassMetadata(ClassMetadataInterface $classMetadata): bool if (isset($data['max_depth'])) { if (!\is_int($data['max_depth'])) { - throw new MappingException(sprintf('The "max_depth" value must be an integer in "%s" for the attribute "%s" of the class "%s".', $this->file, $attribute, $classMetadata->getName())); + throw new MappingException(\sprintf('The "max_depth" value must be an integer in "%s" for the attribute "%s" of the class "%s".', $this->file, $attribute, $classMetadata->getName())); } $attributeMetadata->setMaxDepth($data['max_depth']); @@ -81,7 +81,7 @@ public function loadClassMetadata(ClassMetadataInterface $classMetadata): bool if (isset($data['serialized_name'])) { if (!\is_string($data['serialized_name']) || '' === $data['serialized_name']) { - throw new MappingException(sprintf('The "serialized_name" value must be a non-empty string in "%s" for the attribute "%s" of the class "%s".', $this->file, $attribute, $classMetadata->getName())); + throw new MappingException(\sprintf('The "serialized_name" value must be a non-empty string in "%s" for the attribute "%s" of the class "%s".', $this->file, $attribute, $classMetadata->getName())); } $attributeMetadata->setSerializedName($data['serialized_name']); @@ -91,13 +91,13 @@ public function loadClassMetadata(ClassMetadataInterface $classMetadata): bool try { $attributeMetadata->setSerializedPath(new PropertyPath((string) $data['serialized_path'])); } catch (InvalidPropertyPathException) { - throw new MappingException(sprintf('The "serialized_path" value must be a valid property path in "%s" for the attribute "%s" of the class "%s".', $this->file, $attribute, $classMetadata->getName())); + throw new MappingException(\sprintf('The "serialized_path" value must be a valid property path in "%s" for the attribute "%s" of the class "%s".', $this->file, $attribute, $classMetadata->getName())); } } if (isset($data['ignore'])) { if (!\is_bool($data['ignore'])) { - throw new MappingException(sprintf('The "ignore" value must be a boolean in "%s" for the attribute "%s" of the class "%s".', $this->file, $attribute, $classMetadata->getName())); + throw new MappingException(\sprintf('The "ignore" value must be a boolean in "%s" for the attribute "%s" of the class "%s".', $this->file, $attribute, $classMetadata->getName())); } $attributeMetadata->setIgnore($data['ignore']); @@ -124,11 +124,11 @@ public function loadClassMetadata(ClassMetadataInterface $classMetadata): bool if (isset($yaml['discriminator_map'])) { if (!isset($yaml['discriminator_map']['type_property'])) { - throw new MappingException(sprintf('The "type_property" key must be set for the discriminator map of the class "%s" in "%s".', $classMetadata->getName(), $this->file)); + throw new MappingException(\sprintf('The "type_property" key must be set for the discriminator map of the class "%s" in "%s".', $classMetadata->getName(), $this->file)); } if (!isset($yaml['discriminator_map']['mapping'])) { - throw new MappingException(sprintf('The "mapping" key must be set for the discriminator map of the class "%s" in "%s".', $classMetadata->getName(), $this->file)); + throw new MappingException(\sprintf('The "mapping" key must be set for the discriminator map of the class "%s" in "%s".', $classMetadata->getName(), $this->file)); } $classMetadata->setClassDiscriminatorMapping(new ClassDiscriminatorMapping( @@ -153,7 +153,7 @@ public function getMappedClasses(): array private function getClassesFromYaml(): array { if (!stream_is_local($this->file)) { - throw new MappingException(sprintf('This is not a local file "%s".', $this->file)); + throw new MappingException(\sprintf('This is not a local file "%s".', $this->file)); } $this->yamlParser ??= new Parser(); @@ -165,7 +165,7 @@ private function getClassesFromYaml(): array } if (!\is_array($classes)) { - throw new MappingException(sprintf('The file "%s" must contain a YAML array.', $this->file)); + throw new MappingException(\sprintf('The file "%s" must contain a YAML array.', $this->file)); } return $classes; diff --git a/NameConverter/MetadataAwareNameConverter.php b/NameConverter/MetadataAwareNameConverter.php index bc693bd90..341e6353b 100644 --- a/NameConverter/MetadataAwareNameConverter.php +++ b/NameConverter/MetadataAwareNameConverter.php @@ -88,7 +88,7 @@ private function getCacheValueForNormalization(string $propertyName, string $cla } if (null !== $attributesMetadata[$propertyName]->getSerializedName() && null !== $attributesMetadata[$propertyName]->getSerializedPath()) { - throw new LogicException(sprintf('Found SerializedName and SerializedPath attributes on property "%s" of class "%s".', $propertyName, $class)); + throw new LogicException(\sprintf('Found SerializedName and SerializedPath attributes on property "%s" of class "%s".', $propertyName, $class)); } return $attributesMetadata[$propertyName]->getSerializedName() ?? null; @@ -132,7 +132,7 @@ private function getCacheValueForAttributesMetadata(string $class, array $contex } if (null !== $metadata->getSerializedName() && null !== $metadata->getSerializedPath()) { - throw new LogicException(sprintf('Found SerializedName and SerializedPath attributes on property "%s" of class "%s".', $name, $class)); + throw new LogicException(\sprintf('Found SerializedName and SerializedPath attributes on property "%s" of class "%s".', $name, $class)); } $metadataGroups = $metadata->getGroups(); diff --git a/Normalizer/AbstractNormalizer.php b/Normalizer/AbstractNormalizer.php index 70714ac24..d87bc0597 100644 --- a/Normalizer/AbstractNormalizer.php +++ b/Normalizer/AbstractNormalizer.php @@ -153,7 +153,7 @@ public function __construct( $this->validateCallbackContext($this->defaultContext, 'default'); if (isset($this->defaultContext[self::CIRCULAR_REFERENCE_HANDLER]) && !\is_callable($this->defaultContext[self::CIRCULAR_REFERENCE_HANDLER])) { - throw new InvalidArgumentException(sprintf('Invalid callback found in the "%s" default context option.', self::CIRCULAR_REFERENCE_HANDLER)); + throw new InvalidArgumentException(\sprintf('Invalid callback found in the "%s" default context option.', self::CIRCULAR_REFERENCE_HANDLER)); } } @@ -199,7 +199,7 @@ protected function handleCircularReference(object $object, ?string $format = nul return $circularReferenceHandler($object, $format, $context); } - throw new CircularReferenceException(sprintf('A circular reference has been detected when serializing the object of class "%s" (configured limit: %d).', get_debug_type($object), $context[self::CIRCULAR_REFERENCE_LIMIT] ?? $this->defaultContext[self::CIRCULAR_REFERENCE_LIMIT])); + throw new CircularReferenceException(\sprintf('A circular reference has been detected when serializing the object of class "%s" (configured limit: %d).', get_debug_type($object), $context[self::CIRCULAR_REFERENCE_LIMIT] ?? $this->defaultContext[self::CIRCULAR_REFERENCE_LIMIT])); } /** @@ -216,7 +216,7 @@ protected function getAllowedAttributes(string|object $classOrObject, array $con $allowExtraAttributes = $context[self::ALLOW_EXTRA_ATTRIBUTES] ?? $this->defaultContext[self::ALLOW_EXTRA_ATTRIBUTES]; if (!$this->classMetadataFactory) { if (!$allowExtraAttributes) { - throw new LogicException(sprintf('A class metadata factory must be provided in the constructor when setting "%s" to false.', self::ALLOW_EXTRA_ATTRIBUTES)); + throw new LogicException(\sprintf('A class metadata factory must be provided in the constructor when setting "%s" to false.', self::ALLOW_EXTRA_ATTRIBUTES)); } return false; @@ -346,7 +346,7 @@ protected function instantiateObject(array &$data, string $class, array &$contex if ($constructorParameter->isVariadic()) { if ($allowed && !$ignored && (isset($data[$key]) || \array_key_exists($key, $data))) { if (!\is_array($data[$key])) { - 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)); + 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)); } $variadicParameters = []; @@ -399,7 +399,7 @@ protected function instantiateObject(array &$data, string $class, array &$contex } $exception = NotNormalizableValueException::createForUnexpectedDataType( - sprintf('Failed to create object because the class misses the "%s" property.', $constructorParameter->name), + \sprintf('Failed to create object because the class misses the "%s" property.', $constructorParameter->name), null, [$constructorParameterType], $attributeContext['deserialization_path'] ?? null, @@ -410,7 +410,7 @@ protected function instantiateObject(array &$data, string $class, array &$contex } if ($missingConstructorArguments) { - throw new MissingConstructorArgumentsException(sprintf('Cannot create an instance of "%s" from serialized data because its constructor requires the following parameters to be present : "$%s".', $class, implode('", "$', $missingConstructorArguments)), 0, null, $missingConstructorArguments, $class); + throw new MissingConstructorArgumentsException(\sprintf('Cannot create an instance of "%s" from serialized data because its constructor requires the following parameters to be present : "$%s".', $class, implode('", "$', $missingConstructorArguments)), 0, null, $missingConstructorArguments, $class); } if (!$constructor->isConstructor()) { @@ -445,7 +445,7 @@ protected function instantiateObject(array &$data, string $class, array &$contex unset($context['has_constructor']); if (!$reflectionClass->isInstantiable()) { - throw NotNormalizableValueException::createForUnexpectedDataType(sprintf('Failed to create object because the class "%s" is not instantiable.', $class), $data, ['unknown'], $context['deserialization_path'] ?? null); + throw NotNormalizableValueException::createForUnexpectedDataType(\sprintf('Failed to create object because the class "%s" is not instantiable.', $class), $data, ['unknown'], $context['deserialization_path'] ?? null); } return new $class(); @@ -462,13 +462,13 @@ protected function denormalizeParameter(\ReflectionClass $class, \ReflectionPara new \ReflectionClass($parameterClass); // throws a \ReflectionException if the class doesn't exist if (!$this->serializer instanceof DenormalizerInterface) { - throw new LogicException(sprintf('Cannot create an instance of "%s" from serialized data because the serializer inject in "%s" is not a denormalizer.', $parameterClass, static::class)); + throw new LogicException(\sprintf('Cannot create an instance of "%s" from serialized data because the serializer inject in "%s" is not a denormalizer.', $parameterClass, static::class)); } $parameterData = $this->serializer->denormalize($parameterData, $parameterClass, $format, $this->createChildContext($context, $parameterName, $format)); } } catch (\ReflectionException $e) { - throw new RuntimeException(sprintf('Could not determine the class of the parameter "%s".', $parameterName), 0, $e); + throw new RuntimeException(\sprintf('Could not determine the class of the parameter "%s".', $parameterName), 0, $e); } catch (MissingConstructorArgumentsException $e) { if (!$parameter->getType()->allowsNull()) { throw $e; @@ -510,12 +510,12 @@ final protected function validateCallbackContext(array $context, string $context } if (!\is_array($context[self::CALLBACKS])) { - throw new InvalidArgumentException(sprintf('The "%s"%s context option must be an array of callables.', self::CALLBACKS, '' !== $contextType ? " $contextType" : '')); + throw new InvalidArgumentException(\sprintf('The "%s"%s context option must be an array of callables.', self::CALLBACKS, '' !== $contextType ? " $contextType" : '')); } foreach ($context[self::CALLBACKS] as $attribute => $callback) { if (!\is_callable($callback)) { - throw new InvalidArgumentException(sprintf('Invalid callback found for attribute "%s" in the "%s"%s context option.', $attribute, self::CALLBACKS, '' !== $contextType ? " $contextType" : '')); + throw new InvalidArgumentException(\sprintf('Invalid callback found for attribute "%s" in the "%s"%s context option.', $attribute, self::CALLBACKS, '' !== $contextType ? " $contextType" : '')); } } } diff --git a/Normalizer/AbstractObjectNormalizer.php b/Normalizer/AbstractObjectNormalizer.php index 27c383177..4a0a84b9c 100644 --- a/Normalizer/AbstractObjectNormalizer.php +++ b/Normalizer/AbstractObjectNormalizer.php @@ -134,7 +134,7 @@ public function __construct( parent::__construct($classMetadataFactory, $nameConverter, $defaultContext); if (isset($this->defaultContext[self::MAX_DEPTH_HANDLER]) && !\is_callable($this->defaultContext[self::MAX_DEPTH_HANDLER])) { - throw new InvalidArgumentException(sprintf('The "%s" given in the default context is not callable.', self::MAX_DEPTH_HANDLER)); + throw new InvalidArgumentException(\sprintf('The "%s" given in the default context is not callable.', self::MAX_DEPTH_HANDLER)); } $this->defaultContext[self::EXCLUDE_FROM_CACHE_KEY] = array_merge($this->defaultContext[self::EXCLUDE_FROM_CACHE_KEY] ?? [], [self::CIRCULAR_REFERENCE_LIMIT_COUNTERS]); @@ -174,7 +174,7 @@ public function normalize(mixed $object, ?string $format = null, array $context if (isset($context[self::MAX_DEPTH_HANDLER])) { $maxDepthHandler = $context[self::MAX_DEPTH_HANDLER]; if (!\is_callable($maxDepthHandler)) { - throw new InvalidArgumentException(sprintf('The "%s" given in the context is not callable.', self::MAX_DEPTH_HANDLER)); + throw new InvalidArgumentException(\sprintf('The "%s" given in the context is not callable.', self::MAX_DEPTH_HANDLER)); } } else { $maxDepthHandler = null; @@ -215,7 +215,7 @@ public function normalize(mixed $object, ?string $format = null, array $context } if (!$this->serializer instanceof NormalizerInterface) { - throw new LogicException(sprintf('Cannot normalize attribute "%s" because the injected serializer is not a normalizer.', $attribute)); + throw new LogicException(\sprintf('Cannot normalize attribute "%s" because the injected serializer is not a normalizer.', $attribute)); } $childContext = $this->createChildContext($attributeContext, $attribute, $format); @@ -341,7 +341,7 @@ public function denormalize(mixed $data, string $type, ?string $format = null, a $notConverted = $attribute; $attribute = $this->nameConverter->denormalize($attribute, $resolvedClass, $format, $context); if (isset($nestedData[$notConverted]) && !isset($originalNestedData[$attribute])) { - throw new LogicException(sprintf('Duplicate values for key "%s" found. One value is set via the SerializedPath attribute: "%s", the other one is set via the SerializedName attribute: "%s".', $notConverted, implode('->', $nestedAttributes[$notConverted]->getElements()), $attribute)); + throw new LogicException(\sprintf('Duplicate values for key "%s" found. One value is set via the SerializedPath attribute: "%s", the other one is set via the SerializedName attribute: "%s".', $notConverted, implode('->', $nestedAttributes[$notConverted]->getElements()), $attribute)); } } @@ -394,7 +394,7 @@ public function denormalize(mixed $data, string $type, ?string $format = null, a $this->setAttributeValue($object, $attribute, $value, $format, $attributeContext); } catch (PropertyAccessInvalidArgumentException $e) { $exception = NotNormalizableValueException::createForUnexpectedDataType( - sprintf('Failed to denormalize attribute "%s" value for class "%s": '.$e->getMessage(), $attribute, $resolvedClass), + \sprintf('Failed to denormalize attribute "%s" value for class "%s": '.$e->getMessage(), $attribute, $resolvedClass), $data, $e instanceof InvalidTypeException ? [$e->expectedType] : ['unknown'], $attributeContext['deserialization_path'] ?? null, @@ -485,14 +485,14 @@ private function validateAndDenormalizeLegacy(array $types, string $currentClass } elseif ('true' === $data || '1' === $data) { $data = true; } else { - throw NotNormalizableValueException::createForUnexpectedDataType(sprintf('The type of the "%s" attribute for class "%s" must be bool ("%s" given).', $attribute, $currentClass, $data), $data, [LegacyType::BUILTIN_TYPE_BOOL], $context['deserialization_path'] ?? null); + throw NotNormalizableValueException::createForUnexpectedDataType(\sprintf('The type of the "%s" attribute for class "%s" must be bool ("%s" given).', $attribute, $currentClass, $data), $data, [LegacyType::BUILTIN_TYPE_BOOL], $context['deserialization_path'] ?? null); } break; case LegacyType::BUILTIN_TYPE_INT: if (ctype_digit(isset($data[0]) && '-' === $data[0] ? substr($data, 1) : $data)) { $data = (int) $data; } else { - throw NotNormalizableValueException::createForUnexpectedDataType(sprintf('The type of the "%s" attribute for class "%s" must be int ("%s" given).', $attribute, $currentClass, $data), $data, [LegacyType::BUILTIN_TYPE_INT], $context['deserialization_path'] ?? null); + throw NotNormalizableValueException::createForUnexpectedDataType(\sprintf('The type of the "%s" attribute for class "%s" must be int ("%s" given).', $attribute, $currentClass, $data), $data, [LegacyType::BUILTIN_TYPE_INT], $context['deserialization_path'] ?? null); } break; case LegacyType::BUILTIN_TYPE_FLOAT: @@ -504,7 +504,7 @@ private function validateAndDenormalizeLegacy(array $types, string $currentClass 'NaN' => \NAN, 'INF' => \INF, '-INF' => -\INF, - default => throw NotNormalizableValueException::createForUnexpectedDataType(sprintf('The type of the "%s" attribute for class "%s" must be float ("%s" given).', $attribute, $currentClass, $data), $data, [LegacyType::BUILTIN_TYPE_FLOAT], $context['deserialization_path'] ?? null), + default => throw NotNormalizableValueException::createForUnexpectedDataType(\sprintf('The type of the "%s" attribute for class "%s" must be float ("%s" given).', $attribute, $currentClass, $data), $data, [LegacyType::BUILTIN_TYPE_FLOAT], $context['deserialization_path'] ?? null), }; } } @@ -547,7 +547,7 @@ private function validateAndDenormalizeLegacy(array $types, string $currentClass if (LegacyType::BUILTIN_TYPE_OBJECT === $builtinType && null !== $class) { if (!$this->serializer instanceof DenormalizerInterface) { - throw new LogicException(sprintf('Cannot denormalize attribute "%s" for class "%s" because injected serializer is not a denormalizer.', $attribute, $class)); + throw new LogicException(\sprintf('Cannot denormalize attribute "%s" for class "%s" because injected serializer is not a denormalizer.', $attribute, $class)); } $childContext = $this->createChildContext($context, $attribute, $format); @@ -612,7 +612,7 @@ private function validateAndDenormalizeLegacy(array $types, string $currentClass return $data; } - throw NotNormalizableValueException::createForUnexpectedDataType(sprintf('The type of the "%s" attribute for class "%s" must be one of "%s" ("%s" given).', $attribute, $currentClass, implode('", "', array_keys($expectedTypes)), get_debug_type($data)), $data, array_keys($expectedTypes), $context['deserialization_path'] ?? $attribute); + throw NotNormalizableValueException::createForUnexpectedDataType(\sprintf('The type of the "%s" attribute for class "%s" must be one of "%s" ("%s" given).', $attribute, $currentClass, implode('", "', array_keys($expectedTypes)), get_debug_type($data)), $data, array_keys($expectedTypes), $context['deserialization_path'] ?? $attribute); } /** @@ -678,14 +678,14 @@ private function validateAndDenormalize(Type $type, string $currentClass, string } elseif ('true' === $data || '1' === $data) { $data = true; } else { - throw NotNormalizableValueException::createForUnexpectedDataType(sprintf('The type of the "%s" attribute for class "%s" must be bool ("%s" given).', $attribute, $currentClass, $data), $data, [Type::bool()], $context['deserialization_path'] ?? null); + throw NotNormalizableValueException::createForUnexpectedDataType(\sprintf('The type of the "%s" attribute for class "%s" must be bool ("%s" given).', $attribute, $currentClass, $data), $data, [Type::bool()], $context['deserialization_path'] ?? null); } break; case TypeIdentifier::INT: if (ctype_digit(isset($data[0]) && '-' === $data[0] ? substr($data, 1) : $data)) { $data = (int) $data; } else { - throw NotNormalizableValueException::createForUnexpectedDataType(sprintf('The type of the "%s" attribute for class "%s" must be int ("%s" given).', $attribute, $currentClass, $data), $data, [Type::int()], $context['deserialization_path'] ?? null); + throw NotNormalizableValueException::createForUnexpectedDataType(\sprintf('The type of the "%s" attribute for class "%s" must be int ("%s" given).', $attribute, $currentClass, $data), $data, [Type::int()], $context['deserialization_path'] ?? null); } break; case TypeIdentifier::FLOAT: @@ -697,7 +697,7 @@ private function validateAndDenormalize(Type $type, string $currentClass, string 'NaN' => \NAN, 'INF' => \INF, '-INF' => -\INF, - default => throw NotNormalizableValueException::createForUnexpectedDataType(sprintf('The type of the "%s" attribute for class "%s" must be float ("%s" given).', $attribute, $currentClass, $data), $data, [Type::float()], $context['deserialization_path'] ?? null), + default => throw NotNormalizableValueException::createForUnexpectedDataType(\sprintf('The type of the "%s" attribute for class "%s" must be float ("%s" given).', $attribute, $currentClass, $data), $data, [Type::float()], $context['deserialization_path'] ?? null), }; } } @@ -754,7 +754,7 @@ private function validateAndDenormalize(Type $type, string $currentClass, string if (TypeIdentifier::OBJECT === $typeIdentifier && null !== $class) { if (!$this->serializer instanceof DenormalizerInterface) { - throw new LogicException(sprintf('Cannot denormalize attribute "%s" for class "%s" because injected serializer is not a denormalizer.', $attribute, $class)); + throw new LogicException(\sprintf('Cannot denormalize attribute "%s" for class "%s" because injected serializer is not a denormalizer.', $attribute, $class)); } $childContext = $this->createChildContext($context, $attribute, $format); @@ -828,7 +828,7 @@ private function validateAndDenormalize(Type $type, string $currentClass, string return $data; } - throw NotNormalizableValueException::createForUnexpectedDataType(sprintf('The type of the "%s" attribute for class "%s" must be one of "%s" ("%s" given).', $attribute, $currentClass, implode('", "', array_keys($expectedTypes)), get_debug_type($data)), $data, array_keys($expectedTypes), $context['deserialization_path'] ?? $attribute); + throw NotNormalizableValueException::createForUnexpectedDataType(\sprintf('The type of the "%s" attribute for class "%s" must be one of "%s" ("%s" given).', $attribute, $currentClass, implode('", "', array_keys($expectedTypes)), get_debug_type($data)), $data, array_keys($expectedTypes), $context['deserialization_path'] ?? $attribute); } /** @@ -915,7 +915,7 @@ private function updateData(array $data, string $attribute, mixed $attributeValu if (null !== $classMetadata && null !== $serializedPath = ($attributesMetadata[$attribute] ?? null)?->getSerializedPath()) { $propertyAccessor = PropertyAccess::createPropertyAccessor(); if ($propertyAccessor->isReadable($data, $serializedPath) && null !== $propertyAccessor->getValue($data, $serializedPath)) { - throw new LogicException(sprintf('The element you are trying to set is already populated: "%s".', (string) $serializedPath)); + throw new LogicException(\sprintf('The element you are trying to set is already populated: "%s".', (string) $serializedPath)); } $propertyAccessor->setValue($data, $serializedPath, $attributeValue); @@ -944,7 +944,7 @@ private function isMaxDepthReached(array $attributesMetadata, string $class, str return false; } - $key = sprintf(self::DEPTH_KEY_PATTERN, $class, $attribute); + $key = \sprintf(self::DEPTH_KEY_PATTERN, $class, $attribute); if (!isset($context[$key])) { $context[$key] = 1; @@ -1033,7 +1033,7 @@ private function getNestedAttributes(string $class): array } $pathIdentifier = implode(',', $serializedPath->getElements()); if (isset($serializedPaths[$pathIdentifier])) { - throw new LogicException(sprintf('Duplicate serialized path: "%s" used for properties "%s" and "%s".', $pathIdentifier, $serializedPaths[$pathIdentifier], $name)); + throw new LogicException(\sprintf('Duplicate serialized path: "%s" used for properties "%s" and "%s".', $pathIdentifier, $serializedPaths[$pathIdentifier], $name)); } $serializedPaths[$pathIdentifier] = $name; $properties[$name] = $serializedPath; @@ -1066,11 +1066,11 @@ private function getMappedClass(array $data, string $class, array $context): str } if (null === $type = $data[$mapping->getTypeProperty()] ?? null) { - 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(), false); + 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(), false); } if (null === $mappedClass = $mapping->getClassForType($type)) { - 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); + 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); } return $mappedClass; diff --git a/Normalizer/ArrayDenormalizer.php b/Normalizer/ArrayDenormalizer.php index 1bd6c54b3..af5ffb6aa 100644 --- a/Normalizer/ArrayDenormalizer.php +++ b/Normalizer/ArrayDenormalizer.php @@ -48,7 +48,7 @@ public function denormalize(mixed $data, string $type, ?string $format = null, a throw new BadMethodCallException('Please set a denormalizer before calling denormalize()!'); } if (!\is_array($data)) { - throw NotNormalizableValueException::createForUnexpectedDataType(sprintf('Data expected to be "%s", "%s" given.', $type, get_debug_type($data)), $data, ['array'], $context['deserialization_path'] ?? null); + throw NotNormalizableValueException::createForUnexpectedDataType(\sprintf('Data expected to be "%s", "%s" given.', $type, get_debug_type($data)), $data, ['array'], $context['deserialization_path'] ?? null); } if (!str_ends_with($type, '[]')) { throw new InvalidArgumentException('Unsupported class: '.$type); @@ -67,7 +67,7 @@ public function denormalize(mixed $data, string $type, ?string $format = null, a foreach ($data as $key => $value) { $subContext = $context; - $subContext['deserialization_path'] = ($context['deserialization_path'] ?? false) ? sprintf('%s[%s]', $context['deserialization_path'], $key) : "[$key]"; + $subContext['deserialization_path'] = ($context['deserialization_path'] ?? false) ? \sprintf('%s[%s]', $context['deserialization_path'], $key) : "[$key]"; $this->validateKeyType($typeIdentifiers, $key, $subContext['deserialization_path']); @@ -80,7 +80,7 @@ public function denormalize(mixed $data, string $type, ?string $format = null, a public function supportsDenormalization(mixed $data, string $type, ?string $format = null, array $context = []): bool { if (null === $this->denormalizer) { - throw new BadMethodCallException(sprintf('The nested denormalizer needs to be set to allow "%s()" to be used.', __METHOD__)); + throw new BadMethodCallException(\sprintf('The nested denormalizer needs to be set to allow "%s()" to be used.', __METHOD__)); } return str_ends_with($type, '[]') @@ -102,6 +102,6 @@ private function validateKeyType(array $typeIdentifiers, mixed $key, string $pat } } - throw NotNormalizableValueException::createForUnexpectedDataType(sprintf('The type of the key "%s" must be "%s" ("%s" given).', $key, implode('", "', $typeIdentifiers), get_debug_type($key)), $key, $typeIdentifiers, $path, true); + throw NotNormalizableValueException::createForUnexpectedDataType(\sprintf('The type of the key "%s" must be "%s" ("%s" given).', $key, implode('", "', $typeIdentifiers), get_debug_type($key)), $key, $typeIdentifiers, $path, true); } } diff --git a/Normalizer/ConstraintViolationListNormalizer.php b/Normalizer/ConstraintViolationListNormalizer.php index eeb6ab462..eda3b758e 100644 --- a/Normalizer/ConstraintViolationListNormalizer.php +++ b/Normalizer/ConstraintViolationListNormalizer.php @@ -69,7 +69,7 @@ public function normalize(mixed $object, ?string $format = null, array $context 'parameters' => $violation->getParameters(), ]; if (null !== $code = $violation->getCode()) { - $violationEntry['type'] = sprintf('urn:uuid:%s', $code); + $violationEntry['type'] = \sprintf('urn:uuid:%s', $code); } $constraint = $violation->getConstraint(); @@ -85,7 +85,7 @@ public function normalize(mixed $object, ?string $format = null, array $context $violations[] = $violationEntry; - $prefix = $propertyPath ? sprintf('%s: ', $propertyPath) : ''; + $prefix = $propertyPath ? \sprintf('%s: ', $propertyPath) : ''; $messages[] = $prefix.$violation->getMessage(); } diff --git a/Normalizer/DataUriNormalizer.php b/Normalizer/DataUriNormalizer.php index bd1dc822b..f8577f84f 100644 --- a/Normalizer/DataUriNormalizer.php +++ b/Normalizer/DataUriNormalizer.php @@ -68,10 +68,10 @@ public function normalize(mixed $object, ?string $format = null, array $context } if ('text' === explode('/', $mimeType, 2)[0]) { - return sprintf('data:%s,%s', $mimeType, rawurlencode($data)); + return \sprintf('data:%s,%s', $mimeType, rawurlencode($data)); } - return sprintf('data:%s;base64,%s', $mimeType, base64_encode($data)); + return \sprintf('data:%s;base64,%s', $mimeType, base64_encode($data)); } public function supportsNormalization(mixed $data, ?string $format = null, array $context = []): bool @@ -97,7 +97,7 @@ public function denormalize(mixed $data, string $type, ?string $format = null, a switch ($type) { case File::class: if (!class_exists(File::class)) { - throw new InvalidArgumentException(sprintf('Cannot denormalize to a "%s" without the HttpFoundation component installed. Try running "composer require symfony/http-foundation".', File::class)); + throw new InvalidArgumentException(\sprintf('Cannot denormalize to a "%s" without the HttpFoundation component installed. Try running "composer require symfony/http-foundation".', File::class)); } return new File($data, false); @@ -110,7 +110,7 @@ public function denormalize(mixed $data, string $type, ?string $format = null, a throw NotNormalizableValueException::createForUnexpectedDataType($exception->getMessage(), $data, ['string'], $context['deserialization_path'] ?? null, false, $exception->getCode(), $exception); } - throw new InvalidArgumentException(sprintf('The class parameter "%s" is not supported. It must be one of "SplFileInfo", "SplFileObject" or "Symfony\Component\HttpFoundation\File\File".', $type)); + throw new InvalidArgumentException(\sprintf('The class parameter "%s" is not supported. It must be one of "SplFileInfo", "SplFileObject" or "Symfony\Component\HttpFoundation\File\File".', $type)); } public function supportsDenormalization(mixed $data, string $type, ?string $format = null, array $context = []): bool diff --git a/Normalizer/DateIntervalNormalizer.php b/Normalizer/DateIntervalNormalizer.php index 995d4f6a3..05d1a8529 100644 --- a/Normalizer/DateIntervalNormalizer.php +++ b/Normalizer/DateIntervalNormalizer.php @@ -85,7 +85,7 @@ public function denormalize(mixed $data, string $type, ?string $format = null, a } $valuePattern = '/^'.$signPattern.preg_replace('/%([yYmMdDhHiIsSwW])(\w)/', '(?:(?P<$1>\d+)$2)?', preg_replace('/(T.*)$/', '($1)?', $dateIntervalFormat)).'$/'; if (!preg_match($valuePattern, $data)) { - throw NotNormalizableValueException::createForUnexpectedDataType(sprintf('Value "%s" contains intervals not accepted by format "%s".', $data, $dateIntervalFormat), $data, ['string'], $context['deserialization_path'] ?? null, false); + throw NotNormalizableValueException::createForUnexpectedDataType(\sprintf('Value "%s" contains intervals not accepted by format "%s".', $data, $dateIntervalFormat), $data, ['string'], $context['deserialization_path'] ?? null, false); } try { diff --git a/Normalizer/DateTimeNormalizer.php b/Normalizer/DateTimeNormalizer.php index 71ce26496..fc32f6f50 100644 --- a/Normalizer/DateTimeNormalizer.php +++ b/Normalizer/DateTimeNormalizer.php @@ -94,10 +94,10 @@ public function denormalize(mixed $data, string $type, ?string $format = null, a if (\is_int($data) || \is_float($data)) { switch ($context[self::FORMAT_KEY] ?? $this->defaultContext[self::FORMAT_KEY] ?? null) { case 'U': - $data = sprintf('%d', $data); + $data = \sprintf('%d', $data); break; case 'U.u': - $data = sprintf('%.6F', $data); + $data = \sprintf('%.6F', $data); break; } } @@ -121,7 +121,7 @@ public function denormalize(mixed $data, string $type, ?string $format = null, a $dateTimeErrors = $type::getLastErrors(); - throw NotNormalizableValueException::createForUnexpectedDataType(sprintf('Parsing datetime string "%s" using format "%s" resulted in %d errors: ', $data, $dateTimeFormat, $dateTimeErrors['error_count'])."\n".implode("\n", $this->formatDateTimeErrors($dateTimeErrors['errors'])), $data, ['string'], $context['deserialization_path'] ?? null, true); + throw NotNormalizableValueException::createForUnexpectedDataType(\sprintf('Parsing datetime string "%s" using format "%s" resulted in %d errors: ', $data, $dateTimeFormat, $dateTimeErrors['error_count'])."\n".implode("\n", $this->formatDateTimeErrors($dateTimeErrors['errors'])), $data, ['string'], $context['deserialization_path'] ?? null, true); } $defaultDateTimeFormat = $this->defaultContext[self::FORMAT_KEY] ?? null; @@ -155,7 +155,7 @@ private function formatDateTimeErrors(array $errors): array $formattedErrors = []; foreach ($errors as $pos => $message) { - $formattedErrors[] = sprintf('at position %d: %s', $pos, $message); + $formattedErrors[] = \sprintf('at position %d: %s', $pos, $message); } return $formattedErrors; diff --git a/Normalizer/JsonSerializableNormalizer.php b/Normalizer/JsonSerializableNormalizer.php index 1287627fb..31c224175 100644 --- a/Normalizer/JsonSerializableNormalizer.php +++ b/Normalizer/JsonSerializableNormalizer.php @@ -28,7 +28,7 @@ public function normalize(mixed $object, ?string $format = null, array $context } if (!$object instanceof \JsonSerializable) { - throw new InvalidArgumentException(sprintf('The object must implement "%s".', \JsonSerializable::class)); + throw new InvalidArgumentException(\sprintf('The object must implement "%s".', \JsonSerializable::class)); } if (!$this->serializer instanceof NormalizerInterface) { @@ -57,6 +57,6 @@ public function supportsDenormalization(mixed $data, string $type, ?string $form public function denormalize(mixed $data, string $type, ?string $format = null, array $context = []): mixed { - throw new LogicException(sprintf('Cannot denormalize with "%s".', \JsonSerializable::class)); + throw new LogicException(\sprintf('Cannot denormalize with "%s".', \JsonSerializable::class)); } } diff --git a/Normalizer/MimeMessageNormalizer.php b/Normalizer/MimeMessageNormalizer.php index 7006ab321..633edf369 100644 --- a/Normalizer/MimeMessageNormalizer.php +++ b/Normalizer/MimeMessageNormalizer.php @@ -56,7 +56,7 @@ public function getSupportedTypes(?string $format): array public function setSerializer(SerializerInterface $serializer): void { if (!$serializer instanceof NormalizerInterface || !$serializer instanceof DenormalizerInterface) { - throw new LogicException(sprintf('The passed serializer should implement both NormalizerInterface and DenormalizerInterface, "%s" given.', get_debug_type($serializer))); + throw new LogicException(\sprintf('The passed serializer should implement both NormalizerInterface and DenormalizerInterface, "%s" given.', get_debug_type($serializer))); } $this->serializer = $serializer; $this->normalizer->setSerializer($serializer); diff --git a/Normalizer/ProblemNormalizer.php b/Normalizer/ProblemNormalizer.php index 04c680647..08aca6796 100644 --- a/Normalizer/ProblemNormalizer.php +++ b/Normalizer/ProblemNormalizer.php @@ -54,7 +54,7 @@ public function getSupportedTypes(?string $format): array public function normalize(mixed $object, ?string $format = null, array $context = []): array { if (!$object instanceof FlattenException) { - throw new InvalidArgumentException(sprintf('The object must implement "%s".', FlattenException::class)); + throw new InvalidArgumentException(\sprintf('The object must implement "%s".', FlattenException::class)); } $data = []; diff --git a/Normalizer/PropertyNormalizer.php b/Normalizer/PropertyNormalizer.php index e1d893be8..1619f35bf 100644 --- a/Normalizer/PropertyNormalizer.php +++ b/Normalizer/PropertyNormalizer.php @@ -162,7 +162,7 @@ protected function getAttributeValue(object $object, string $attribute, ?string || ($reflectionProperty->isProtected() && !\array_key_exists("\0*\0{$reflectionProperty->name}", $propertyValues)) || ($reflectionProperty->isPrivate() && !\array_key_exists("\0{$reflectionProperty->class}\0{$reflectionProperty->name}", $propertyValues)) ) { - throw new UninitializedPropertyException(sprintf('The property "%s::$%s" is not initialized.', $object::class, $reflectionProperty->name)); + throw new UninitializedPropertyException(\sprintf('The property "%s::$%s" is not initialized.', $object::class, $reflectionProperty->name)); } } diff --git a/Normalizer/TranslatableNormalizer.php b/Normalizer/TranslatableNormalizer.php index 398e00b79..463616e72 100644 --- a/Normalizer/TranslatableNormalizer.php +++ b/Normalizer/TranslatableNormalizer.php @@ -37,7 +37,7 @@ public function __construct( public function normalize(mixed $object, ?string $format = null, array $context = []): string { if (!$object instanceof TranslatableInterface) { - throw NotNormalizableValueException::createForUnexpectedDataType(sprintf('The object must implement the "%s".', TranslatableInterface::class), $object, [TranslatableInterface::class]); + throw NotNormalizableValueException::createForUnexpectedDataType(\sprintf('The object must implement the "%s".', TranslatableInterface::class), $object, [TranslatableInterface::class]); } return $object->trans($this->translator, $context[self::NORMALIZATION_LOCALE_KEY] ?? $this->defaultContext[self::NORMALIZATION_LOCALE_KEY]); diff --git a/Normalizer/UidNormalizer.php b/Normalizer/UidNormalizer.php index a6cc190a9..b107c9d36 100644 --- a/Normalizer/UidNormalizer.php +++ b/Normalizer/UidNormalizer.php @@ -56,7 +56,7 @@ public function normalize(mixed $object, ?string $format = null, array $context self::NORMALIZATION_FORMAT_BASE58 => $object->toBase58(), self::NORMALIZATION_FORMAT_BASE32 => $object->toBase32(), self::NORMALIZATION_FORMAT_RFC4122 => $object->toRfc4122(), - default => throw new LogicException(sprintf('The "%s" format is not valid.', $context[self::NORMALIZATION_FORMAT_KEY] ?? $this->defaultContext[self::NORMALIZATION_FORMAT_KEY])), + default => throw new LogicException(\sprintf('The "%s" format is not valid.', $context[self::NORMALIZATION_FORMAT_KEY] ?? $this->defaultContext[self::NORMALIZATION_FORMAT_KEY])), }; } @@ -70,7 +70,7 @@ public function denormalize(mixed $data, string $type, ?string $format = null, a try { return $type::fromString($data); } catch (\InvalidArgumentException|\TypeError) { - throw NotNormalizableValueException::createForUnexpectedDataType(sprintf('The data is not a valid "%s" string representation.', $type), $data, ['string'], $context['deserialization_path'] ?? null, true); + throw NotNormalizableValueException::createForUnexpectedDataType(\sprintf('The data is not a valid "%s" string representation.', $type), $data, ['string'], $context['deserialization_path'] ?? null, true); } } diff --git a/Serializer.php b/Serializer.php index 9fba20f14..b907a5186 100644 --- a/Serializer.php +++ b/Serializer.php @@ -94,7 +94,7 @@ public function __construct( } if (!($normalizer instanceof NormalizerInterface || $normalizer instanceof DenormalizerInterface)) { - throw new InvalidArgumentException(sprintf('The class "%s" neither implements "%s" nor "%s".', get_debug_type($normalizer), NormalizerInterface::class, DenormalizerInterface::class)); + throw new InvalidArgumentException(\sprintf('The class "%s" neither implements "%s" nor "%s".', get_debug_type($normalizer), NormalizerInterface::class, DenormalizerInterface::class)); } } @@ -112,7 +112,7 @@ public function __construct( } if (!($encoder instanceof EncoderInterface || $encoder instanceof DecoderInterface)) { - throw new InvalidArgumentException(sprintf('The class "%s" neither implements "%s" nor "%s".', get_debug_type($encoder), EncoderInterface::class, DecoderInterface::class)); + throw new InvalidArgumentException(\sprintf('The class "%s" neither implements "%s" nor "%s".', get_debug_type($encoder), EncoderInterface::class, DecoderInterface::class)); } } $this->encoder = new ChainEncoder($realEncoders); @@ -122,7 +122,7 @@ public function __construct( final public function serialize(mixed $data, string $format, array $context = []): string { if (!$this->supportsEncoding($format, $context)) { - throw new UnsupportedFormatException(sprintf('Serialization for the format "%s" is not supported.', $format)); + throw new UnsupportedFormatException(\sprintf('Serialization for the format "%s" is not supported.', $format)); } if ($this->encoder->needsNormalization($format, $context)) { @@ -135,7 +135,7 @@ final public function serialize(mixed $data, string $format, array $context = [] final public function deserialize(mixed $data, string $type, string $format, array $context = []): mixed { if (!$this->supportsDecoding($format, $context)) { - throw new UnsupportedFormatException(sprintf('Deserialization for the format "%s" is not supported.', $format)); + throw new UnsupportedFormatException(\sprintf('Deserialization for the format "%s" is not supported.', $format)); } $data = $this->decode($data, $format, $context); @@ -176,10 +176,10 @@ public function normalize(mixed $data, ?string $format = null, array $context = throw new LogicException('You must register at least one normalizer to be able to normalize objects.'); } - throw new NotNormalizableValueException(sprintf('Could not normalize object of type "%s", no supporting normalizer found.', get_debug_type($data))); + throw new NotNormalizableValueException(\sprintf('Could not normalize object of type "%s", no supporting normalizer found.', get_debug_type($data))); } - throw new NotNormalizableValueException('An unexpected value could not be normalized: '.(!\is_resource($data) ? var_export($data, true) : sprintf('"%s" resource', get_resource_type($data)))); + throw new NotNormalizableValueException('An unexpected value could not be normalized: '.(!\is_resource($data) ? var_export($data, true) : \sprintf('"%s" resource', get_resource_type($data)))); } /** @@ -197,7 +197,7 @@ public function denormalize(mixed $data, string $type, ?string $format = null, a // Check for a denormalizer first, e.g. the data is wrapped if (!$normalizer && isset(self::SCALAR_TYPES[$type])) { if (!('is_'.$type)($data)) { - throw NotNormalizableValueException::createForUnexpectedDataType(sprintf('Data expected to be of type "%s" ("%s" given).', $type, get_debug_type($data)), $data, [$type], $context['deserialization_path'] ?? null, true); + throw NotNormalizableValueException::createForUnexpectedDataType(\sprintf('Data expected to be of type "%s" ("%s" given).', $type, get_debug_type($data)), $data, [$type], $context['deserialization_path'] ?? null, true); } return $data; @@ -208,7 +208,7 @@ public function denormalize(mixed $data, string $type, ?string $format = null, a } if (!$normalizer) { - throw new NotNormalizableValueException(sprintf('Could not denormalize object of type "%s", no supporting normalizer found.', $type)); + throw new NotNormalizableValueException(\sprintf('Could not denormalize object of type "%s", no supporting normalizer found.', $type)); } if (isset($context[DenormalizerInterface::COLLECT_DENORMALIZATION_ERRORS])) { diff --git a/Tests/Annotation/ContextTest.php b/Tests/Annotation/ContextTest.php index 9584d6f1b..84ff41b8a 100644 --- a/Tests/Annotation/ContextTest.php +++ b/Tests/Annotation/ContextTest.php @@ -40,7 +40,7 @@ public function testThrowsOnEmptyContext() public function testInvalidGroupOption() { $this->expectException(InvalidArgumentException::class); - $this->expectExceptionMessage(sprintf('Parameter "groups" given to "%s" must be a string or an array of strings, "stdClass" given', Context::class)); + $this->expectExceptionMessage(\sprintf('Parameter "groups" given to "%s" must be a string or an array of strings, "stdClass" given', Context::class)); new Context(context: ['foo' => 'bar'], groups: ['fine', new \stdClass()]); } diff --git a/Tests/Encoder/XmlEncoderTest.php b/Tests/Encoder/XmlEncoderTest.php index 5be6be232..911637620 100644 --- a/Tests/Encoder/XmlEncoderTest.php +++ b/Tests/Encoder/XmlEncoderTest.php @@ -1050,14 +1050,14 @@ private function createMockDateTimeNormalizer(): MockObject&NormalizerInterface private function createXmlWithDateTime(): string { - return sprintf(' + return \sprintf(' %s ', $this->exampleDateTimeString); } private function createXmlWithDateTimeField(): string { - return sprintf(' + return \sprintf(' ', $this->exampleDateTimeString); } diff --git a/Tests/Mapping/Loader/AttributeLoaderTest.php b/Tests/Mapping/Loader/AttributeLoaderTest.php index a76595506..5b6ef3de7 100644 --- a/Tests/Mapping/Loader/AttributeLoaderTest.php +++ b/Tests/Mapping/Loader/AttributeLoaderTest.php @@ -162,7 +162,7 @@ public function testLoadContextsPropertiesPromoted() public function testThrowsOnContextOnInvalidMethod() { $this->expectException(MappingException::class); - $this->expectExceptionMessage(sprintf('Context on "%s::badMethod()" cannot be added', BadMethodContextDummy::class)); + $this->expectExceptionMessage(\sprintf('Context on "%s::badMethod()" cannot be added', BadMethodContextDummy::class)); $loader = $this->getLoaderForContextMapping(); diff --git a/Tests/Normalizer/Features/CircularReferenceTestTrait.php b/Tests/Normalizer/Features/CircularReferenceTestTrait.php index d02e245f7..85720bcfe 100644 --- a/Tests/Normalizer/Features/CircularReferenceTestTrait.php +++ b/Tests/Normalizer/Features/CircularReferenceTestTrait.php @@ -42,7 +42,7 @@ public function testUnableToNormalizeCircularReference(array $defaultContext, ar $obj = $this->getSelfReferencingModel(); $this->expectException(CircularReferenceException::class); - $this->expectExceptionMessage(sprintf('A circular reference has been detected when serializing the object of class "%s" (configured limit: %d).', $obj::class, $expectedLimit)); + $this->expectExceptionMessage(\sprintf('A circular reference has been detected when serializing the object of class "%s" (configured limit: %d).', $obj::class, $expectedLimit)); $normalizer->normalize($obj, null, $context); } diff --git a/Tests/Normalizer/Features/ConstructorArgumentsTestTrait.php b/Tests/Normalizer/Features/ConstructorArgumentsTestTrait.php index 72652f340..0a5f6f249 100644 --- a/Tests/Normalizer/Features/ConstructorArgumentsTestTrait.php +++ b/Tests/Normalizer/Features/ConstructorArgumentsTestTrait.php @@ -64,10 +64,10 @@ public function testConstructorWithMissingData() $normalizer = $this->getDenormalizerForConstructArguments(); try { $normalizer->denormalize($data, ConstructorArgumentsObject::class); - self::fail(sprintf('Failed asserting that exception of type "%s" is thrown.', MissingConstructorArgumentsException::class)); + self::fail(\sprintf('Failed asserting that exception of type "%s" is thrown.', MissingConstructorArgumentsException::class)); } catch (MissingConstructorArgumentsException $e) { self::assertSame(ConstructorArgumentsObject::class, $e->getClass()); - self::assertSame(sprintf('Cannot create an instance of "%s" from serialized data because its constructor requires the following parameters to be present : "$foo", "$baz".', ConstructorArgumentsObject::class), $e->getMessage()); + self::assertSame(\sprintf('Cannot create an instance of "%s" from serialized data because its constructor requires the following parameters to be present : "$foo", "$baz".', ConstructorArgumentsObject::class), $e->getMessage()); self::assertSame(['foo', 'baz'], $e->getMissingConstructorArguments()); } } diff --git a/Tests/Normalizer/ObjectNormalizerTest.php b/Tests/Normalizer/ObjectNormalizerTest.php index 19bbcde2c..8c564f003 100644 --- a/Tests/Normalizer/ObjectNormalizerTest.php +++ b/Tests/Normalizer/ObjectNormalizerTest.php @@ -786,12 +786,12 @@ public function testAdvancedNameConverter() $nameConverter = new class() implements AdvancedNameConverterInterface { public function normalize(string $propertyName, ?string $class = null, ?string $format = null, array $context = []): string { - return sprintf('%s-%s-%s-%s', $propertyName, $class, $format, $context['foo']); + return \sprintf('%s-%s-%s-%s', $propertyName, $class, $format, $context['foo']); } public function denormalize(string $propertyName, ?string $class = null, ?string $format = null, array $context = []): string { - return sprintf('%s-%s-%s-%s', $propertyName, $class, $format, $context['foo']); + return \sprintf('%s-%s-%s-%s', $propertyName, $class, $format, $context['foo']); } }; From 289be0b7bf80214234d4f2d804d6c037e7b4ca50 Mon Sep 17 00:00:00 2001 From: hbgamra Date: Fri, 21 Jun 2024 14:38:03 +0200 Subject: [PATCH 232/297] Update ClassMetadata.php Optimize the getReflectionClass() function --- Mapping/ClassMetadata.php | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/Mapping/ClassMetadata.php b/Mapping/ClassMetadata.php index 5cec14c91..179e6050d 100644 --- a/Mapping/ClassMetadata.php +++ b/Mapping/ClassMetadata.php @@ -78,11 +78,7 @@ public function merge(ClassMetadataInterface $classMetadata): void public function getReflectionClass(): \ReflectionClass { - if (!$this->reflClass) { - $this->reflClass = new \ReflectionClass($this->getName()); - } - - return $this->reflClass; + return $this->reflClass ??= new \ReflectionClass($this->getName()); } public function getClassDiscriminatorMapping(): ?ClassDiscriminatorMapping From 898e452d5d5d8a0c3175151e244b30c5b4fd6f4a Mon Sep 17 00:00:00 2001 From: Alexandre Daubois Date: Fri, 21 Jun 2024 16:02:45 +0200 Subject: [PATCH 233/297] [Serializer] Use `SUPPORTED_TYPES` in Normalizers when available --- Normalizer/DataUriNormalizer.php | 6 +----- Normalizer/DateTimeNormalizer.php | 6 +----- 2 files changed, 2 insertions(+), 10 deletions(-) diff --git a/Normalizer/DataUriNormalizer.php b/Normalizer/DataUriNormalizer.php index f8577f84f..5ee076be6 100644 --- a/Normalizer/DataUriNormalizer.php +++ b/Normalizer/DataUriNormalizer.php @@ -44,11 +44,7 @@ public function __construct(?MimeTypeGuesserInterface $mimeTypeGuesser = null) public function getSupportedTypes(?string $format): array { - return [ - \SplFileInfo::class => true, - \SplFileObject::class => true, - File::class => true, - ]; + return self::SUPPORTED_TYPES; } public function normalize(mixed $object, ?string $format = null, array $context = []): string diff --git a/Normalizer/DateTimeNormalizer.php b/Normalizer/DateTimeNormalizer.php index fc32f6f50..55b2e130e 100644 --- a/Normalizer/DateTimeNormalizer.php +++ b/Normalizer/DateTimeNormalizer.php @@ -50,11 +50,7 @@ public function setDefaultContext(array $defaultContext): void public function getSupportedTypes(?string $format): array { - return [ - \DateTimeInterface::class => true, - \DateTimeImmutable::class => true, - \DateTime::class => true, - ]; + return self::SUPPORTED_TYPES; } /** From 27859b61ea53fdfae97242f3d9338405402be9dc Mon Sep 17 00:00:00 2001 From: Christian Flothmann Date: Sat, 22 Jun 2024 09:48:46 +0200 Subject: [PATCH 234/297] fix merge --- Tests/Normalizer/AbstractObjectNormalizerTest.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Tests/Normalizer/AbstractObjectNormalizerTest.php b/Tests/Normalizer/AbstractObjectNormalizerTest.php index 67f7e3d1d..f5c0b0753 100644 --- a/Tests/Normalizer/AbstractObjectNormalizerTest.php +++ b/Tests/Normalizer/AbstractObjectNormalizerTest.php @@ -1522,7 +1522,7 @@ protected function extractAttributes(object $object, ?string $format = null, arr return []; } - protected function getAttributeValue(object $object, string $attribute, ?string $format = null, array $context = []) + protected function getAttributeValue(object $object, string $attribute, ?string $format = null, array $context = []): mixed { return null; } From 7dfc0daec63386d56dd3f80c5a139c8859d55132 Mon Sep 17 00:00:00 2001 From: Christian Flothmann Date: Sat, 22 Jun 2024 10:46:19 +0200 Subject: [PATCH 235/297] fix merge --- Normalizer/ObjectNormalizer.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Normalizer/ObjectNormalizer.php b/Normalizer/ObjectNormalizer.php index 4f6d5f3b9..a663083db 100644 --- a/Normalizer/ObjectNormalizer.php +++ b/Normalizer/ObjectNormalizer.php @@ -192,7 +192,7 @@ protected function isAllowedAttribute($classOrObject, string $attribute, ?string if ($context['_read_attributes'] ?? true) { if (!isset(self::$isReadableCache[$class.$attribute])) { - self::$isReadableCache[$class.$attribute] = (\is_object($classOrObject) && $this->propertyAccessor->isReadable($classOrObject, $attribute)) || $this->hasAttributeAccessorMethod($class, $attribute); + self::$isReadableCache[$class.$attribute] = (\is_object($classOrObject) && $this->propertyAccessor->isReadable($classOrObject, $attribute)) || $this->propertyInfoExtractor->isReadable($class, $attribute) || $this->hasAttributeAccessorMethod($class, $attribute); } return self::$isReadableCache[$class.$attribute]; From 240a7bb5b31ce68f8f2152a1c3638e6fe1b13f77 Mon Sep 17 00:00:00 2001 From: Christian Flothmann Date: Sat, 22 Jun 2024 10:53:28 +0200 Subject: [PATCH 236/297] add missing method --- Tests/Normalizer/AbstractObjectNormalizerTest.php | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/Tests/Normalizer/AbstractObjectNormalizerTest.php b/Tests/Normalizer/AbstractObjectNormalizerTest.php index f5c0b0753..c8eab2a08 100644 --- a/Tests/Normalizer/AbstractObjectNormalizerTest.php +++ b/Tests/Normalizer/AbstractObjectNormalizerTest.php @@ -1517,6 +1517,11 @@ public function __construct() parent::__construct(new ClassMetadataFactory(new AttributeLoader()), null, new PropertyInfoExtractor([], [new PhpDocExtractor(), new ReflectionExtractor()])); } + public function getSupportedTypes(?string $format): array + { + return ['*' => false]; + } + protected function extractAttributes(object $object, ?string $format = null, array $context = []): array { return []; From f78926de1d52c57a27e72f4c3ebd1fe0ef7efc70 Mon Sep 17 00:00:00 2001 From: Dariusz Ruminski Date: Sun, 16 Jun 2024 17:17:26 +0200 Subject: [PATCH 237/297] chore: CS fixes --- Normalizer/BackedEnumNormalizer.php | 2 +- Normalizer/GetSetMethodNormalizer.php | 2 +- Normalizer/ObjectNormalizer.php | 2 +- Tests/Encoder/XmlEncoderTest.php | 12 +++--- .../ConstraintViolationListNormalizerTest.php | 30 +++++++-------- Tests/Normalizer/PropertyNormalizerTest.php | 37 ++++++++++++++----- Tests/Normalizer/UidNormalizerTest.php | 4 +- Tests/SerializerTest.php | 6 +-- 8 files changed, 56 insertions(+), 39 deletions(-) diff --git a/Normalizer/BackedEnumNormalizer.php b/Normalizer/BackedEnumNormalizer.php index 504047bc1..3d8e7e7c5 100644 --- a/Normalizer/BackedEnumNormalizer.php +++ b/Normalizer/BackedEnumNormalizer.php @@ -29,7 +29,7 @@ final class BackedEnumNormalizer implements NormalizerInterface, DenormalizerInt public function getSupportedTypes(?string $format): array { return [ - \BackedEnum::class => true, + \BackedEnum::class => true, ]; } diff --git a/Normalizer/GetSetMethodNormalizer.php b/Normalizer/GetSetMethodNormalizer.php index 3a398ef33..61fdc3b4d 100644 --- a/Normalizer/GetSetMethodNormalizer.php +++ b/Normalizer/GetSetMethodNormalizer.php @@ -164,7 +164,7 @@ protected function isAllowedAttribute($classOrObject, string $attribute, ?string return false; } - $class = \is_object($classOrObject) ? \get_class($classOrObject) : $classOrObject; + $class = \is_object($classOrObject) ? $classOrObject::class : $classOrObject; if (!isset(self::$reflectionCache[$class])) { self::$reflectionCache[$class] = new \ReflectionClass($class); diff --git a/Normalizer/ObjectNormalizer.php b/Normalizer/ObjectNormalizer.php index 1b51b729c..10fe403eb 100644 --- a/Normalizer/ObjectNormalizer.php +++ b/Normalizer/ObjectNormalizer.php @@ -173,7 +173,7 @@ protected function isAllowedAttribute($classOrObject, string $attribute, ?string return false; } - $class = \is_object($classOrObject) ? \get_class($classOrObject) : $classOrObject; + $class = \is_object($classOrObject) ? $classOrObject::class : $classOrObject; if ($context['_read_attributes'] ?? true) { if (!isset(self::$isReadableCache[$class.$attribute])) { diff --git a/Tests/Encoder/XmlEncoderTest.php b/Tests/Encoder/XmlEncoderTest.php index 911637620..5fc8fe983 100644 --- a/Tests/Encoder/XmlEncoderTest.php +++ b/Tests/Encoder/XmlEncoderTest.php @@ -671,8 +671,8 @@ public function testDecodeIgnoreComments() XML; $expected = ['person' => [ - ['firstname' => 'Benjamin', 'lastname' => 'Alexandre'], - ['firstname' => 'Damien', 'lastname' => 'Clay'], + ['firstname' => 'Benjamin', 'lastname' => 'Alexandre'], + ['firstname' => 'Damien', 'lastname' => 'Clay'], ]]; $this->assertEquals($expected, $this->encoder->decode($source, 'xml')); @@ -695,8 +695,8 @@ public function testDecodeIgnoreDocumentType() XML; $expected = ['person' => [ - ['firstname' => 'Benjamin', 'lastname' => 'Alexandre'], - ['firstname' => 'Damien', 'lastname' => 'Clay'], + ['firstname' => 'Benjamin', 'lastname' => 'Alexandre'], + ['firstname' => 'Damien', 'lastname' => 'Clay'], ]]; $this->assertEquals($expected, $this->encoder->decode( $source, @@ -730,8 +730,8 @@ public function testDecodePreserveComments() $this->encoder->setSerializer($serializer); $expected = ['person' => [ - ['firstname' => 'Benjamin', 'lastname' => 'Alexandre', '#comment' => ' This comment should be decoded. '], - ['firstname' => 'Damien', 'lastname' => 'Clay'], + ['firstname' => 'Benjamin', 'lastname' => 'Alexandre', '#comment' => ' This comment should be decoded. '], + ['firstname' => 'Damien', 'lastname' => 'Clay'], ]]; $this->assertEquals($expected, $this->encoder->decode($source, 'xml')); diff --git a/Tests/Normalizer/ConstraintViolationListNormalizerTest.php b/Tests/Normalizer/ConstraintViolationListNormalizerTest.php index bb69392f5..1d0afb3cb 100644 --- a/Tests/Normalizer/ConstraintViolationListNormalizerTest.php +++ b/Tests/Normalizer/ConstraintViolationListNormalizerTest.php @@ -50,23 +50,23 @@ public function testNormalize() 'detail' => 'd: a 4: 1', 'violations' => [ - [ - 'propertyPath' => 'd', - 'title' => 'a', - 'template' => 'b', - 'type' => 'urn:uuid:f', - 'parameters' => [ - 'value' => 'foo', - ], - ], - [ - 'propertyPath' => '4', - 'title' => '1', - 'template' => '2', - 'type' => 'urn:uuid:6', - 'parameters' => [], + [ + 'propertyPath' => 'd', + 'title' => 'a', + 'template' => 'b', + 'type' => 'urn:uuid:f', + 'parameters' => [ + 'value' => 'foo', ], ], + [ + 'propertyPath' => '4', + 'title' => '1', + 'template' => '2', + 'type' => 'urn:uuid:6', + 'parameters' => [], + ], + ], ]; $this->assertEquals($expected, $this->normalizer->normalize($list)); diff --git a/Tests/Normalizer/PropertyNormalizerTest.php b/Tests/Normalizer/PropertyNormalizerTest.php index b93a7bb9f..9773e65b7 100644 --- a/Tests/Normalizer/PropertyNormalizerTest.php +++ b/Tests/Normalizer/PropertyNormalizerTest.php @@ -340,12 +340,16 @@ public function testGroupsDenormalizeWithNameConverter() $this->assertEquals( $obj, - $this->normalizer->denormalize([ - 'bar' => null, - 'foo_bar' => '@dunglas', - 'symfony' => '@coopTilleuls', - 'coop_tilleuls' => 'les-tilleuls.coop', - ], GroupDummy::class, null, [PropertyNormalizer::GROUPS => ['name_converter']]) + $this->normalizer->denormalize( + [ + 'bar' => null, + 'foo_bar' => '@dunglas', + 'symfony' => '@coopTilleuls', + 'coop_tilleuls' => 'les-tilleuls.coop', + ], + GroupDummy::class, null, + [PropertyNormalizer::GROUPS => ['name_converter']] + ) ); } @@ -405,13 +409,19 @@ public function testDenormalizeNonExistingAttribute() { $this->assertEquals( new PropertyDummy(), - $this->normalizer->denormalize(['non_existing' => true], PropertyDummy::class) + $this->normalizer->denormalize( + ['non_existing' => true], + PropertyDummy::class + ) ); } public function testDenormalizeShouldIgnoreStaticProperty() { - $obj = $this->normalizer->denormalize(['outOfScope' => true], PropertyDummy::class); + $obj = $this->normalizer->denormalize( + ['outOfScope' => true], + PropertyDummy::class + ); $this->assertEquals(new PropertyDummy(), $obj); $this->assertEquals('out_of_scope', PropertyDummy::$outOfScope); @@ -450,7 +460,8 @@ public function testInheritedPropertiesSupport() public function testMultiDimensionObject() { $normalizer = $this->getDenormalizerForTypeEnforcement(); - $root = $normalizer->denormalize([ + $root = $normalizer->denormalize( + [ 'children' => [[ ['foo' => 'one', 'bar' => 'two'], ['foo' => 'three', 'bar' => 'four'], @@ -535,7 +546,13 @@ public function testDenormalizeWithDiscriminator() $denormalized = new PropertyDiscriminatedDummyTwo(); $denormalized->url = 'url'; - $this->assertEquals($denormalized, $normalizer->denormalize(['type' => 'two', 'url' => 'url'], PropertyDummyInterface::class)); + $this->assertEquals( + $denormalized, + $normalizer->denormalize( + ['type' => 'two', 'url' => 'url'], + PropertyDummyInterface::class + ) + ); } } diff --git a/Tests/Normalizer/UidNormalizerTest.php b/Tests/Normalizer/UidNormalizerTest.php index 1471074a0..734b15b48 100644 --- a/Tests/Normalizer/UidNormalizerTest.php +++ b/Tests/Normalizer/UidNormalizerTest.php @@ -47,8 +47,8 @@ public static function normalizeProvider() { $uidFormats = [null, 'canonical', 'base58', 'base32', 'rfc4122']; $data = [ - [ - UuidV1::fromString('9b7541de-6f87-11ea-ab3c-9da9a81562fc'), + [ + UuidV1::fromString('9b7541de-6f87-11ea-ab3c-9da9a81562fc'), '9b7541de-6f87-11ea-ab3c-9da9a81562fc', '9b7541de-6f87-11ea-ab3c-9da9a81562fc', 'LCQS8f2p5SDSiAt9V7ZYnF', diff --git a/Tests/SerializerTest.php b/Tests/SerializerTest.php index 1bf5905ad..7d4cbc30d 100644 --- a/Tests/SerializerTest.php +++ b/Tests/SerializerTest.php @@ -1214,7 +1214,7 @@ public function testCollectDenormalizationErrors2(?ClassMetadataFactory $classMe 'useMessageForUser' => false, 'message' => 'The type of the "string" attribute for class "Symfony\\Component\\Serializer\\Tests\\Fixtures\\Php74Full" must be one of "string" ("null" given).', ], - ]; + ]; $this->assertSame($expected, $exceptionsAsArray); } @@ -1464,8 +1464,8 @@ public function testCollectDenormalizationErrorsWithWrongPropertyWithoutConstruc try { $serializer->deserialize('{"get": "POST"}', DummyObjectWithEnumProperty::class, 'json', [ - DenormalizerInterface::COLLECT_DENORMALIZATION_ERRORS => true, - ]); + DenormalizerInterface::COLLECT_DENORMALIZATION_ERRORS => true, + ]); } catch (\Throwable $e) { $this->assertInstanceOf(PartialDenormalizationException::class, $e); } From 62660267ad33e7cd9677a20cec7acc39937a58a2 Mon Sep 17 00:00:00 2001 From: Mathias Arlaud Date: Wed, 26 Jun 2024 09:40:14 +0200 Subject: [PATCH 238/297] [Serializer] Fix access to wrong Type class --- Normalizer/AbstractObjectNormalizer.php | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/Normalizer/AbstractObjectNormalizer.php b/Normalizer/AbstractObjectNormalizer.php index 2d0671359..16cf59e8b 100644 --- a/Normalizer/AbstractObjectNormalizer.php +++ b/Normalizer/AbstractObjectNormalizer.php @@ -571,16 +571,16 @@ private function validateAndDenormalizeLegacy(array $types, string $currentClass } switch ($builtinType) { - case Type::BUILTIN_TYPE_ARRAY: - case Type::BUILTIN_TYPE_BOOL: - case Type::BUILTIN_TYPE_CALLABLE: - case Type::BUILTIN_TYPE_FLOAT: - case Type::BUILTIN_TYPE_INT: - case Type::BUILTIN_TYPE_ITERABLE: - case Type::BUILTIN_TYPE_NULL: - case Type::BUILTIN_TYPE_OBJECT: - case Type::BUILTIN_TYPE_RESOURCE: - case Type::BUILTIN_TYPE_STRING: + case LegacyType::BUILTIN_TYPE_ARRAY: + case LegacyType::BUILTIN_TYPE_BOOL: + case LegacyType::BUILTIN_TYPE_CALLABLE: + case LegacyType::BUILTIN_TYPE_FLOAT: + case LegacyType::BUILTIN_TYPE_INT: + case LegacyType::BUILTIN_TYPE_ITERABLE: + case LegacyType::BUILTIN_TYPE_NULL: + case LegacyType::BUILTIN_TYPE_OBJECT: + case LegacyType::BUILTIN_TYPE_RESOURCE: + case LegacyType::BUILTIN_TYPE_STRING: if (('is_'.$builtinType)($data)) { return $data; } From 7d12e1a685349c87ae095461a92fe95ec3eed781 Mon Sep 17 00:00:00 2001 From: HypeMC Date: Thu, 27 Jun 2024 18:09:31 +0200 Subject: [PATCH 239/297] [Serializer] Check if exception message in test is correct --- Tests/Fixtures/NotNormalizableDummy.php | 2 +- Tests/Normalizer/AbstractObjectNormalizerTest.php | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/Tests/Fixtures/NotNormalizableDummy.php b/Tests/Fixtures/NotNormalizableDummy.php index 41da0eac8..ef7a8c906 100644 --- a/Tests/Fixtures/NotNormalizableDummy.php +++ b/Tests/Fixtures/NotNormalizableDummy.php @@ -26,6 +26,6 @@ public function __construct() public function denormalize(DenormalizerInterface $denormalizer, $data, ?string $format = null, array $context = []): void { - throw new NotNormalizableValueException(); + throw new NotNormalizableValueException('Custom exception message'); } } diff --git a/Tests/Normalizer/AbstractObjectNormalizerTest.php b/Tests/Normalizer/AbstractObjectNormalizerTest.php index c5c1f6f0b..6f6256506 100644 --- a/Tests/Normalizer/AbstractObjectNormalizerTest.php +++ b/Tests/Normalizer/AbstractObjectNormalizerTest.php @@ -946,6 +946,7 @@ public function testDenormalizeUntypedFormat() public function testDenormalizeUntypedFormatNotNormalizable() { $this->expectException(NotNormalizableValueException::class); + $this->expectExceptionMessage('Custom exception message'); $serializer = new Serializer([new CustomNormalizer(), new ObjectNormalizer(null, null, null, new PropertyInfoExtractor([], [new PhpDocExtractor(), new ReflectionExtractor()]))]); $serializer->denormalize(['value' => 'test'], DummyWithNotNormalizable::class, 'xml'); } From a61c583ee08b7e4d219cb0c8dc6b623fb56182bd Mon Sep 17 00:00:00 2001 From: Christian Flothmann Date: Mon, 17 Jun 2024 23:31:30 +0200 Subject: [PATCH 240/297] forward exceptions caught in the AbstractObjectNormalizer --- Normalizer/AbstractObjectNormalizer.php | 30 ++++++++++++++++++------- 1 file changed, 22 insertions(+), 8 deletions(-) diff --git a/Normalizer/AbstractObjectNormalizer.php b/Normalizer/AbstractObjectNormalizer.php index 16cf59e8b..e15c89e82 100644 --- a/Normalizer/AbstractObjectNormalizer.php +++ b/Normalizer/AbstractObjectNormalizer.php @@ -640,8 +640,11 @@ private function validateAndDenormalizeLegacy(array $types, string $currentClass private function validateAndDenormalize(Type $type, string $currentClass, string $attribute, mixed $data, ?string $format, array $context): mixed { $expectedTypes = []; + $isUnionType = $type->asNonNullable() instanceof UnionType; + $e = null; $extraAttributesException = null; $missingConstructorArgumentsException = null; + $isNullable = false; $types = match (true) { $type instanceof IntersectionType => throw new LogicException('Unable to handle intersection type.'), @@ -679,12 +682,19 @@ private function validateAndDenormalize(Type $type, string $currentClass, string // That's why we have to transform the values, if one of these non-string basic datatypes is expected. $typeIdentifier = $t->getTypeIdentifier(); if (\is_string($data) && (XmlEncoder::FORMAT === $format || CsvEncoder::FORMAT === $format)) { + if ('' === $data) { + if (TypeIdentifier::ARRAY === $typeIdentifier) { + return []; + } + + if (TypeIdentifier::STRING === $typeIdentifier) { + return ''; + } + + $isNullable = $isNullable ?: $type->isNullable(); + } + switch ($typeIdentifier) { - case TypeIdentifier::ARRAY: - if ('' === $data) { - return []; - } - break; case TypeIdentifier::BOOL: // according to https://www.w3.org/TR/xmlschema-2/#boolean, valid representations are "false", "true", "0" and "1" if ('false' === $data || '0' === $data) { @@ -808,17 +818,17 @@ private function validateAndDenormalize(Type $type, string $currentClass, string return $data; } } catch (NotNormalizableValueException|InvalidArgumentException $e) { - if (!$type instanceof UnionType) { + if (!$isUnionType && !$isNullable) { throw $e; } } catch (ExtraAttributesException $e) { - if (!$type instanceof UnionType) { + if (!$isUnionType && !$isNullable) { throw $e; } $extraAttributesException ??= $e; } catch (MissingConstructorArgumentsException $e) { - if (!$type instanceof UnionType) { + if (!$isUnionType && !$isNullable) { throw $e; } @@ -838,6 +848,10 @@ private function validateAndDenormalize(Type $type, string $currentClass, string throw $missingConstructorArgumentsException; } + if (!$isUnionType && $e) { + throw $e; + } + if ($context[self::DISABLE_TYPE_ENFORCEMENT] ?? $this->defaultContext[self::DISABLE_TYPE_ENFORCEMENT] ?? false) { return $data; } From 59699189f2136f6fdd9b485ad2d1f1b28c12adf6 Mon Sep 17 00:00:00 2001 From: Maximilian Zumbansen Date: Wed, 26 Jun 2024 14:46:59 +0200 Subject: [PATCH 241/297] [Serializer] [ObjectNormalizer] Use bool filter when FILTER_BOOL is set --- Normalizer/AbstractObjectNormalizer.php | 8 +++++ .../AbstractObjectNormalizerTest.php | 34 +++++++++++++++++++ 2 files changed, 42 insertions(+) diff --git a/Normalizer/AbstractObjectNormalizer.php b/Normalizer/AbstractObjectNormalizer.php index 2d0671359..ab9568457 100644 --- a/Normalizer/AbstractObjectNormalizer.php +++ b/Normalizer/AbstractObjectNormalizer.php @@ -566,6 +566,10 @@ private function validateAndDenormalizeLegacy(array $types, string $currentClass return (float) $data; } + if (LegacyType::BUILTIN_TYPE_BOOL === $builtinType && \is_string($data) && ($context[self::FILTER_BOOL] ?? false)) { + return filter_var($data, \FILTER_VALIDATE_BOOL, \FILTER_NULL_ON_FAILURE); + } + if ((LegacyType::BUILTIN_TYPE_FALSE === $builtinType && false === $data) || (LegacyType::BUILTIN_TYPE_TRUE === $builtinType && true === $data)) { return $data; } @@ -787,6 +791,10 @@ private function validateAndDenormalize(Type $type, string $currentClass, string return (float) $data; } + if (TypeIdentifier::BOOL === $typeIdentifier && \is_string($data) && ($context[self::FILTER_BOOL] ?? false)) { + return filter_var($data, \FILTER_VALIDATE_BOOL, \FILTER_NULL_ON_FAILURE); + } + $dataMatchesExpectedType = match ($typeIdentifier) { TypeIdentifier::ARRAY => \is_array($data), TypeIdentifier::BOOL => \is_bool($data), diff --git a/Tests/Normalizer/AbstractObjectNormalizerTest.php b/Tests/Normalizer/AbstractObjectNormalizerTest.php index c5c1f6f0b..90e6be952 100644 --- a/Tests/Normalizer/AbstractObjectNormalizerTest.php +++ b/Tests/Normalizer/AbstractObjectNormalizerTest.php @@ -1195,6 +1195,34 @@ public function provideBooleanTypesData() [['foo' => false], TruePropertyDummy::class], ]; } + + /** + * @dataProvider provideDenormalizeWithFilterBoolData + */ + public function testDenormalizeBooleanTypeWithFilterBool(array $data, ?bool $expectedFoo) + { + $normalizer = new AbstractObjectNormalizerWithMetadataAndPropertyTypeExtractors(); + + $dummy = $normalizer->denormalize($data, BoolPropertyDummy::class, null, [AbstractNormalizer::FILTER_BOOL => true]); + + $this->assertSame($expectedFoo, $dummy->foo); + } + + public function provideDenormalizeWithFilterBoolData(): array + { + return [ + [['foo' => 'true'], true], + [['foo' => '1'], true], + [['foo' => 'yes'], true], + [['foo' => 'false'], false], + [['foo' => '0'], false], + [['foo' => 'no'], false], + [['foo' => ''], false], + [['foo' => null], null], + [['foo' => 'null'], null], + [['foo' => 'something'], null], + ]; + } } class AbstractObjectNormalizerDummy extends AbstractObjectNormalizer @@ -1480,6 +1508,12 @@ class TruePropertyDummy public $foo; } +class BoolPropertyDummy +{ + /** @var null|bool */ + public $foo; +} + class SerializerCollectionDummy implements SerializerInterface, DenormalizerInterface { private array $normalizers; From 0ae173cc0ebdd73f15fad4005151e1eeac101df4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Tamarelle?= Date: Mon, 1 Jul 2024 02:16:34 +0200 Subject: [PATCH 242/297] Remove useless uniqid in tempnam calls --- Tests/Mapping/Factory/ClassMetadataFactoryCompilerTest.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Tests/Mapping/Factory/ClassMetadataFactoryCompilerTest.php b/Tests/Mapping/Factory/ClassMetadataFactoryCompilerTest.php index 1826d3dc4..40dcb5015 100644 --- a/Tests/Mapping/Factory/ClassMetadataFactoryCompilerTest.php +++ b/Tests/Mapping/Factory/ClassMetadataFactoryCompilerTest.php @@ -27,7 +27,7 @@ final class ClassMetadataFactoryCompilerTest extends TestCase protected function setUp(): void { - $this->dumpPath = sys_get_temp_dir().\DIRECTORY_SEPARATOR.'php_serializer_metadata.'.uniqid('CompiledClassMetadataFactory').'.php'; + $this->dumpPath = tempnam(sys_get_temp_dir(), 'sf_serializer_metadata_'); } protected function tearDown(): void From 3bfc744bd038f16bd9d976c7d3af64e12d14288f Mon Sep 17 00:00:00 2001 From: "Alexander M. Turek" Date: Thu, 4 Jul 2024 11:36:30 +0200 Subject: [PATCH 243/297] [Serializer] Raise correct exception in ArrayDenormalizer MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit … when it's called without a nested denormalizer --- Normalizer/ArrayDenormalizer.php | 4 ++-- Tests/Normalizer/ArrayDenormalizerTest.php | 16 ++++++++++++++++ 2 files changed, 18 insertions(+), 2 deletions(-) diff --git a/Normalizer/ArrayDenormalizer.php b/Normalizer/ArrayDenormalizer.php index a9c64a1e7..a52c0de46 100644 --- a/Normalizer/ArrayDenormalizer.php +++ b/Normalizer/ArrayDenormalizer.php @@ -42,7 +42,7 @@ public function getSupportedTypes(?string $format): array */ public function denormalize(mixed $data, string $type, ?string $format = null, array $context = []): array { - if (null === $this->denormalizer) { + if (!isset($this->denormalizer)) { throw new BadMethodCallException('Please set a denormalizer before calling denormalize()!'); } if (!\is_array($data)) { @@ -72,7 +72,7 @@ public function denormalize(mixed $data, string $type, ?string $format = null, a public function supportsDenormalization(mixed $data, string $type, ?string $format = null, array $context = []): bool { - if (null === $this->denormalizer) { + if (!isset($this->denormalizer)) { throw new BadMethodCallException(sprintf('The nested denormalizer needs to be set to allow "%s()" to be used.', __METHOD__)); } diff --git a/Tests/Normalizer/ArrayDenormalizerTest.php b/Tests/Normalizer/ArrayDenormalizerTest.php index be4a30d86..b60f57bac 100644 --- a/Tests/Normalizer/ArrayDenormalizerTest.php +++ b/Tests/Normalizer/ArrayDenormalizerTest.php @@ -108,6 +108,22 @@ public function testSupportsNoArray() ) ); } + + public function testDenormalizeWithoutDenormalizer() + { + $arrayDenormalizer = new ArrayDenormalizer(); + + $this->expectException(\BadMethodCallException::class); + $arrayDenormalizer->denormalize([], 'string[]'); + } + + public function testSupportsDenormalizationWithoutDenormalizer() + { + $arrayDenormalizer = new ArrayDenormalizer(); + + $this->expectException(\BadMethodCallException::class); + $arrayDenormalizer->supportsDenormalization([], 'string[]'); + } } class ArrayDummy From faaab8b87521de97b2471db04569a99d3ff1f466 Mon Sep 17 00:00:00 2001 From: "Alexander M. Turek" Date: Thu, 4 Jul 2024 11:48:29 +0200 Subject: [PATCH 244/297] [Serializer] Remove ArrayDenormalizer::setDenormalizer() --- Normalizer/ArrayDenormalizer.php | 5 ----- 1 file changed, 5 deletions(-) diff --git a/Normalizer/ArrayDenormalizer.php b/Normalizer/ArrayDenormalizer.php index af5ffb6aa..23df0a1e2 100644 --- a/Normalizer/ArrayDenormalizer.php +++ b/Normalizer/ArrayDenormalizer.php @@ -29,11 +29,6 @@ class ArrayDenormalizer implements DenormalizerInterface, DenormalizerAwareInter { use DenormalizerAwareTrait; - public function setDenormalizer(DenormalizerInterface $denormalizer): void - { - $this->denormalizer = $denormalizer; - } - public function getSupportedTypes(?string $format): array { return ['object' => null, '*' => false]; From e439ec653b32cd8b75faaf639d1b3d1952ef2c95 Mon Sep 17 00:00:00 2001 From: Fabien Potencier Date: Sat, 6 Jul 2024 09:57:16 +0200 Subject: [PATCH 245/297] Update .gitattributes --- .gitattributes | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.gitattributes b/.gitattributes index 84c7add05..14c3c3594 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1,4 +1,3 @@ /Tests export-ignore /phpunit.xml.dist export-ignore -/.gitattributes export-ignore -/.gitignore export-ignore +/.git* export-ignore From 430a73df11875ec5aead1c147d4ad9ef2493acd5 Mon Sep 17 00:00:00 2001 From: Christian Flothmann Date: Mon, 15 Jul 2024 11:35:34 +0200 Subject: [PATCH 246/297] use more entropy with uniqid() --- Debug/TraceableSerializer.php | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/Debug/TraceableSerializer.php b/Debug/TraceableSerializer.php index 2e375e08d..789ae65ca 100644 --- a/Debug/TraceableSerializer.php +++ b/Debug/TraceableSerializer.php @@ -41,7 +41,7 @@ public function __construct( public function serialize(mixed $data, string $format, array $context = []): string { - $context[self::DEBUG_TRACE_ID] = $traceId = uniqid(); + $context[self::DEBUG_TRACE_ID] = $traceId = uniqid('', true); $startTime = microtime(true); $result = $this->serializer->serialize($data, $format, $context); @@ -56,7 +56,7 @@ public function serialize(mixed $data, string $format, array $context = []): str public function deserialize(mixed $data, string $type, string $format, array $context = []): mixed { - $context[self::DEBUG_TRACE_ID] = $traceId = uniqid(); + $context[self::DEBUG_TRACE_ID] = $traceId = uniqid('', true); $startTime = microtime(true); $result = $this->serializer->deserialize($data, $type, $format, $context); @@ -71,7 +71,7 @@ public function deserialize(mixed $data, string $type, string $format, array $co public function normalize(mixed $object, ?string $format = null, array $context = []): array|string|int|float|bool|\ArrayObject|null { - $context[self::DEBUG_TRACE_ID] = $traceId = uniqid(); + $context[self::DEBUG_TRACE_ID] = $traceId = uniqid('', true); $startTime = microtime(true); $result = $this->serializer->normalize($object, $format, $context); @@ -86,7 +86,7 @@ public function normalize(mixed $object, ?string $format = null, array $context public function denormalize(mixed $data, string $type, ?string $format = null, array $context = []): mixed { - $context[self::DEBUG_TRACE_ID] = $traceId = uniqid(); + $context[self::DEBUG_TRACE_ID] = $traceId = uniqid('', true); $startTime = microtime(true); $result = $this->serializer->denormalize($data, $type, $format, $context); @@ -101,7 +101,7 @@ public function denormalize(mixed $data, string $type, ?string $format = null, a public function encode(mixed $data, string $format, array $context = []): string { - $context[self::DEBUG_TRACE_ID] = $traceId = uniqid(); + $context[self::DEBUG_TRACE_ID] = $traceId = uniqid('', true); $startTime = microtime(true); $result = $this->serializer->encode($data, $format, $context); @@ -116,7 +116,7 @@ public function encode(mixed $data, string $format, array $context = []): string public function decode(string $data, string $format, array $context = []): mixed { - $context[self::DEBUG_TRACE_ID] = $traceId = uniqid(); + $context[self::DEBUG_TRACE_ID] = $traceId = uniqid('', true); $startTime = microtime(true); $result = $this->serializer->decode($data, $format, $context); From f08629456bc65ceb7e25939f35b41433c6a24288 Mon Sep 17 00:00:00 2001 From: Christian Flothmann Date: Wed, 17 Jul 2024 10:14:50 +0200 Subject: [PATCH 247/297] do not use uniqid() for generating dev tool tokens --- Debug/TraceableSerializer.php | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/Debug/TraceableSerializer.php b/Debug/TraceableSerializer.php index 9cf7bad80..f04f3c911 100644 --- a/Debug/TraceableSerializer.php +++ b/Debug/TraceableSerializer.php @@ -37,7 +37,7 @@ public function __construct( public function serialize(mixed $data, string $format, array $context = []): string { - $context[self::DEBUG_TRACE_ID] = $traceId = uniqid('', true); + $context[self::DEBUG_TRACE_ID] = $traceId = bin2hex(random_bytes(4)); $startTime = microtime(true); $result = $this->serializer->serialize($data, $format, $context); @@ -52,7 +52,7 @@ public function serialize(mixed $data, string $format, array $context = []): str public function deserialize(mixed $data, string $type, string $format, array $context = []): mixed { - $context[self::DEBUG_TRACE_ID] = $traceId = uniqid('', true); + $context[self::DEBUG_TRACE_ID] = $traceId = bin2hex(random_bytes(4)); $startTime = microtime(true); $result = $this->serializer->deserialize($data, $type, $format, $context); @@ -67,7 +67,7 @@ public function deserialize(mixed $data, string $type, string $format, array $co public function normalize(mixed $object, ?string $format = null, array $context = []): array|string|int|float|bool|\ArrayObject|null { - $context[self::DEBUG_TRACE_ID] = $traceId = uniqid('', true); + $context[self::DEBUG_TRACE_ID] = $traceId = bin2hex(random_bytes(4)); $startTime = microtime(true); $result = $this->serializer->normalize($object, $format, $context); @@ -82,7 +82,7 @@ public function normalize(mixed $object, ?string $format = null, array $context public function denormalize(mixed $data, string $type, ?string $format = null, array $context = []): mixed { - $context[self::DEBUG_TRACE_ID] = $traceId = uniqid('', true); + $context[self::DEBUG_TRACE_ID] = $traceId = bin2hex(random_bytes(4)); $startTime = microtime(true); $result = $this->serializer->denormalize($data, $type, $format, $context); @@ -97,7 +97,7 @@ public function denormalize(mixed $data, string $type, ?string $format = null, a public function encode(mixed $data, string $format, array $context = []): string { - $context[self::DEBUG_TRACE_ID] = $traceId = uniqid('', true); + $context[self::DEBUG_TRACE_ID] = $traceId = bin2hex(random_bytes(4)); $startTime = microtime(true); $result = $this->serializer->encode($data, $format, $context); @@ -112,7 +112,7 @@ public function encode(mixed $data, string $format, array $context = []): string public function decode(string $data, string $format, array $context = []): mixed { - $context[self::DEBUG_TRACE_ID] = $traceId = uniqid('', true); + $context[self::DEBUG_TRACE_ID] = $traceId = bin2hex(random_bytes(4)); $startTime = microtime(true); $result = $this->serializer->decode($data, $format, $context); From 9a67fcf320561e96f94d62bbe0e169ac534a5718 Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Fri, 26 Jul 2024 15:11:24 +0200 Subject: [PATCH 248/297] Sync .github/expected-missing-return-types.diff --- Normalizer/AbstractNormalizer.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Normalizer/AbstractNormalizer.php b/Normalizer/AbstractNormalizer.php index ccada2ed6..aeae375fb 100644 --- a/Normalizer/AbstractNormalizer.php +++ b/Normalizer/AbstractNormalizer.php @@ -267,6 +267,8 @@ protected function getGroups(array $context): array /** * Is this attribute allowed? + * + * @return bool */ protected function isAllowedAttribute(object|string $classOrObject, string $attribute, ?string $format = null, array $context = []) { From 51611e545131dee861f3c570ce1dbec2cae3c96b Mon Sep 17 00:00:00 2001 From: Alexandre Daubois Date: Wed, 31 Jul 2024 16:13:26 +0200 Subject: [PATCH 249/297] Remove unused code and unnecessary `else` branches --- Encoder/XmlEncoder.php | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Encoder/XmlEncoder.php b/Encoder/XmlEncoder.php index a57030b10..e1a816380 100644 --- a/Encoder/XmlEncoder.php +++ b/Encoder/XmlEncoder.php @@ -152,9 +152,8 @@ public function decode(string $data, string $format, array $context = []): mixed } $data = array_merge($this->parseXmlAttributes($rootNode, $context), ['#' => $rootNode->nodeValue]); - $data = $this->addXmlNamespaces($data, $rootNode, $dom); - return $data; + return $this->addXmlNamespaces($data, $rootNode, $dom); } public function supportsEncoding(string $format): bool From 1e26038da323d5146a309f1d5d8c3c8dd770f448 Mon Sep 17 00:00:00 2001 From: Oskar Stark Date: Thu, 2 May 2024 14:41:09 +0200 Subject: [PATCH 250/297] [Serializer] Rework `XmlEncoderTest` --- Tests/Encoder/XmlEncoderTest.php | 399 +++++++++++++------------------ 1 file changed, 171 insertions(+), 228 deletions(-) diff --git a/Tests/Encoder/XmlEncoderTest.php b/Tests/Encoder/XmlEncoderTest.php index 5fc8fe983..ae96f2c14 100644 --- a/Tests/Encoder/XmlEncoderTest.php +++ b/Tests/Encoder/XmlEncoderTest.php @@ -39,46 +39,39 @@ protected function setUp(): void $this->encoder->setSerializer($serializer); } - public function testEncodeScalar() + /** + * @dataProvider validEncodeProvider + */ + public function testEncode(string $expected, mixed $data, array $context = []) { - $obj = new ScalarDummy(); - $obj->xmlFoo = 'foo'; - - $expected = ''."\n". - 'foo'."\n"; - - $this->assertEquals($expected, $this->encoder->encode($obj, 'xml')); + $this->assertSame($expected, $this->encoder->encode($data, 'xml', $context)); } - public function testEncodeArrayObject() - { - $obj = new \ArrayObject(['foo' => 'bar']); - - $expected = ''."\n". - 'bar'."\n"; - - $this->assertEquals($expected, $this->encoder->encode($obj, 'xml')); - } - - public function testEncodeEmptyArrayObject() + /** + * @return iterable + */ + public static function validEncodeProvider(): iterable { - $obj = new \ArrayObject(); + $obj = new ScalarDummy(); + $obj->xmlFoo = 'foo'; - $expected = ''."\n". - ''."\n"; + yield 'encode scalar' => [ + ''."\n" + .'foo'."\n", + $obj, + ]; - $this->assertEquals($expected, $this->encoder->encode($obj, 'xml')); - } + yield 'encode array object' => [ + ''."\n" + .'bar'."\n", + new \ArrayObject(['foo' => 'bar']), + ]; - public function testDocTypeIsNotAllowed() - { - $this->expectException(UnexpectedValueException::class); - $this->expectExceptionMessage('Document types are not allowed.'); - $this->encoder->decode('', 'foo'); - } + yield 'encode empty array object' => [ + ''."\n".''."\n", + new \ArrayObject(), + ]; - public function testAttributes() - { $obj = new ScalarDummy(); $obj->xmlFoo = [ 'foo-bar' => [ @@ -100,7 +93,9 @@ public function testAttributes() '@sring' => 'a', ], ]; - $expected = ''."\n". + + yield 'attributes' => [ + ''."\n". ''. ''. 'Test'. @@ -110,12 +105,10 @@ public function testAttributes() '3'. 'b'. ''. - ''."\n"; - $this->assertEquals($expected, $this->encoder->encode($obj, 'xml')); - } + ''."\n", + $obj, + ]; - public function testElementNameValid() - { $obj = new ScalarDummy(); $obj->xmlFoo = [ 'foo-bar' => 'a', @@ -123,70 +116,54 @@ public function testElementNameValid() 'föo_bär' => 'a', ]; - $expected = ''."\n". + yield 'element name valid' => [ + ''."\n". ''. 'a'. 'a'. 'a'. - ''."\n"; - - $this->assertEquals($expected, $this->encoder->encode($obj, 'xml')); - } + ''."\n", + $obj, + ]; - public function testEncodeSimpleXML() - { $xml = simplexml_load_string('Peter'); $array = ['person' => $xml]; - $expected = ''."\n". - 'Peter'."\n"; - - $this->assertEquals($expected, $this->encoder->encode($array, 'xml')); - } + yield 'encode SimpleXML' => [ + ''."\n". + 'Peter'."\n", + $array, + ]; - public function testEncodeXmlAttributes() - { $xml = simplexml_load_string('Peter'); $array = ['person' => $xml]; - $expected = ''."\n". - 'Peter'."\n"; - - $context = [ - 'xml_version' => '1.1', - 'xml_encoding' => 'utf-8', - 'xml_standalone' => true, + yield 'encode XML attributes' => [ + ''."\n". + 'Peter'."\n", + $array, + [ + 'xml_version' => '1.1', + 'xml_encoding' => 'utf-8', + 'xml_standalone' => true, + ], ]; - $this->assertSame($expected, $this->encoder->encode($array, 'xml', $context)); - } - - public function testEncodeRemovingEmptyTags() - { - $array = ['person' => ['firstname' => 'Peter', 'lastname' => null]]; - - $expected = ''."\n". - 'Peter'."\n"; - - $context = ['remove_empty_tags' => true]; - - $this->assertSame($expected, $this->encoder->encode($array, 'xml', $context)); - } - - public function testEncodeNotRemovingEmptyTags() - { - $array = ['person' => ['firstname' => 'Peter', 'lastname' => null]]; - - $expected = ''."\n". - 'Peter'."\n"; + yield 'encode remvoing empty tags' => [ + ''."\n". + 'Peter'."\n", + ['person' => ['firstname' => 'Peter', 'lastname' => null]], + ['remove_empty_tags' => true], + ]; - $this->assertSame($expected, $this->encoder->encode($array, 'xml')); - } + yield 'encode not removing empty tags' => [ + ''."\n". + 'Peter'."\n", + ['person' => ['firstname' => 'Peter', 'lastname' => null]], + ]; - public function testContext() - { - $array = ['person' => ['name' => 'George Abitbol', 'age' => null]]; - $expected = <<<'XML' + yield 'encode with context' => [ + <<<'XML' @@ -195,128 +172,139 @@ public function testContext() -XML; +XML, + ['person' => ['name' => 'George Abitbol', 'age' => null]], + [ + 'xml_format_output' => true, + 'save_options' => \LIBXML_NOEMPTYTAG, + ], + ]; - $context = [ - 'xml_format_output' => true, - 'save_options' => \LIBXML_NOEMPTYTAG, + yield 'encode scalar root attributes' => [ + ''."\n". + 'Paul'."\n", + [ + '#' => 'Paul', + '@eye-color' => 'brown', + ], ]; - $this->assertSame($expected, $this->encoder->encode($array, 'xml', $context)); - } + yield 'encode root attributes' => [ + ''."\n". + 'Paul'."\n", + [ + 'firstname' => 'Paul', + '@eye-color' => 'brown', + ], + ]; - public function testEncodeScalarRootAttributes() - { - $array = [ - '#' => 'Paul', - '@eye-color' => 'brown', + yield 'encode with CDATA wrapping with default pattern #1' => [ + ''."\n". + ']]>'."\n", + ['firstname' => 'Paul & Martha '], ]; - $expected = ''."\n". - 'Paul'."\n"; + yield 'encode with CDATA wrapping with default pattern #2' => [ + ''."\n". + 'O\'Donnel'."\n", + ['lastname' => 'O\'Donnel'], + ]; - $this->assertEquals($expected, $this->encoder->encode($array, 'xml')); - } + yield 'encode with CDATA wrapping with default pattern #3' => [ + ''."\n". + ''."\n", + ['firstname' => 'Paul & Martha'], + ]; - public function testEncodeRootAttributes() - { - $array = [ - 'firstname' => 'Paul', - '@eye-color' => 'brown', + yield 'encode with CDATA wrapping with custom pattern #1' => [ + ''."\n". + ']]>'."\n", + ['firstname' => 'Paul & Martha '], + ['cdata_wrapping_pattern' => '/[<>&"\']/'] ]; - $expected = ''."\n". - 'Paul'."\n"; + yield 'encode with CDATA wrapping with custom pattern #2' => [ + ''."\n". + ''."\n", + ['lastname' => 'O\'Donnel'], + ['cdata_wrapping_pattern' => '/[<>&"\']/'] + ]; - $this->assertEquals($expected, $this->encoder->encode($array, 'xml')); - } + yield 'encode with CDATA wrapping with custom pattern #3' => [ + ''."\n". + 'Paul and Martha'."\n", + ['firstname' => 'Paul and Martha'], + ['cdata_wrapping_pattern' => '/[<>&"\']/'] + ]; - /** - * @dataProvider encodeCdataWrappingWithDefaultPattern - */ - public function testEncodeCdataWrappingWithDefaultPattern($input, $expected) - { - $this->assertEquals($expected, $this->encoder->encode($input, 'xml')); - } + yield 'enable CDATA wrapping' => [ + ''."\n". + ']]>'."\n", + ['firstname' => 'Paul & Martha '], + ['cdata_wrapping' => true], + ]; - public static function encodeCdataWrappingWithDefaultPattern() - { - return [ - [ - ['firstname' => 'Paul and Martha'], - ''."\n".'Paul and Martha'."\n", - ], - [ - ['lastname' => 'O\'Donnel'], - ''."\n".'O\'Donnel'."\n", - ], - [ - ['firstname' => 'Paul & Martha '], - ''."\n".']]>'."\n", - ], + yield 'disable CDATA wrapping' => [ + ''."\n". + 'Paul & Martha <or Me>'."\n", + ['firstname' => 'Paul & Martha '], + ['cdata_wrapping' => false], ]; - } - /** - * @dataProvider encodeCdataWrappingWithCustomPattern - */ - public function testEncodeCdataWrappingWithCustomPattern($input, $expected) - { - $this->assertEquals($expected, $this->encoder->encode($input, 'xml', ['cdata_wrapping_pattern' => '/[<>&"\']/'])); - } + yield 'encode scalar with attribute' => [ + ''."\n". + 'Peter'."\n", + ['person' => ['@eye-color' => 'brown', '#' => 'Peter']], + ]; - public static function encodeCdataWrappingWithCustomPattern() - { - return [ - [ - ['firstname' => 'Paul and Martha'], - ''."\n".'Paul and Martha'."\n", - ], - [ - ['lastname' => 'O\'Donnel'], - ''."\n".''."\n", - ], - [ - ['firstname' => 'Paul & Martha '], - ''."\n".']]>'."\n", - ], + yield 'encode' => [ + self::getXmlSource(), + self::getObject(), + ]; + + yield 'encode with namespace' => [ + self::getNamespacedXmlSource(), + self::getNamespacedArray(), ]; } - public function testEnableCdataWrapping() + public function testEncodeSerializerXmlRootNodeNameOption() { + $options = ['xml_root_node_name' => 'test']; + $this->encoder = new XmlEncoder(); + $serializer = new Serializer([], ['xml' => new XmlEncoder()]); + $this->encoder->setSerializer($serializer); + $array = [ - 'firstname' => 'Paul & Martha ', + 'person' => ['@eye-color' => 'brown', '#' => 'Peter'], ]; $expected = ''."\n". - ']]>'."\n"; + 'Peter'."\n"; - $this->assertEquals($expected, $this->encoder->encode($array, 'xml', ['cdata_wrapping' => true])); + $this->assertSame($expected, $serializer->serialize($array, 'xml', $options)); } - public function testDisableCdataWrapping() + public function testEncodeTraversableWhenNormalizable() { - $array = [ - 'firstname' => 'Paul & Martha ', - ]; + $this->encoder = new XmlEncoder(); + $serializer = new Serializer([new CustomNormalizer()], ['xml' => new XmlEncoder()]); + $this->encoder->setSerializer($serializer); - $expected = ''."\n". - 'Paul & Martha <or Me>'."\n"; + $expected = <<<'XML' + +normalizedFoonormalizedBar - $this->assertEquals($expected, $this->encoder->encode($array, 'xml', ['cdata_wrapping' => false])); +XML; + + $this->assertSame($expected, $serializer->serialize(new NormalizableTraversableDummy(), 'xml')); } - public function testEncodeScalarWithAttribute() + public function testDocTypeIsNotAllowed() { - $array = [ - 'person' => ['@eye-color' => 'brown', '#' => 'Peter'], - ]; - - $expected = ''."\n". - 'Peter'."\n"; - - $this->assertEquals($expected, $this->encoder->encode($array, 'xml')); + $this->expectException(UnexpectedValueException::class); + $this->expectExceptionMessage('Document types are not allowed.'); + $this->encoder->decode('', 'foo'); } public function testDecodeScalar() @@ -324,7 +312,7 @@ public function testDecodeScalar() $source = ''."\n". 'foo'."\n"; - $this->assertEquals('foo', $this->encoder->decode($source, 'xml')); + $this->assertSame('foo', $this->encoder->decode($source, 'xml')); } public function testDecodeBigDigitAttributes() @@ -425,54 +413,6 @@ public function testDoesNotTypeCastStringsStartingWith0() $this->assertSame('018', $data['@a']); } - public function testEncode() - { - $source = $this->getXmlSource(); - $obj = $this->getObject(); - - $this->assertEquals($source, $this->encoder->encode($obj, 'xml')); - } - - public function testEncodeWithNamespace() - { - $source = $this->getNamespacedXmlSource(); - $array = $this->getNamespacedArray(); - - $this->assertEquals($source, $this->encoder->encode($array, 'xml')); - } - - public function testEncodeSerializerXmlRootNodeNameOption() - { - $options = ['xml_root_node_name' => 'test']; - $this->encoder = new XmlEncoder(); - $serializer = new Serializer([], ['xml' => new XmlEncoder()]); - $this->encoder->setSerializer($serializer); - - $array = [ - 'person' => ['@eye-color' => 'brown', '#' => 'Peter'], - ]; - - $expected = ''."\n". - 'Peter'."\n"; - - $this->assertEquals($expected, $serializer->serialize($array, 'xml', $options)); - } - - public function testEncodeTraversableWhenNormalizable() - { - $this->encoder = new XmlEncoder(); - $serializer = new Serializer([new CustomNormalizer()], ['xml' => new XmlEncoder()]); - $this->encoder->setSerializer($serializer); - - $expected = <<<'XML' - -normalizedFoonormalizedBar - -XML; - - $this->assertEquals($expected, $serializer->serialize(new NormalizableTraversableDummy(), 'xml')); - } - public function testEncodeException() { $this->expectException(NotEncodableValueException::class); @@ -816,7 +756,7 @@ public function testDecodeEmptyXml() $this->encoder->decode(' ', 'xml'); } - protected function getXmlSource() + protected static function getXmlSource(): string { return ''."\n". ''. @@ -829,7 +769,7 @@ protected function getXmlSource() ''."\n"; } - protected function getNamespacedXmlSource() + protected static function getNamespacedXmlSource(): string { return ''."\n". ''. @@ -842,7 +782,7 @@ protected function getNamespacedXmlSource() ''."\n"; } - protected function getNamespacedArray() + protected static function getNamespacedArray(): array { return [ '@xmlns' => 'http://www.w3.org/2005/Atom', @@ -876,7 +816,10 @@ protected function getNamespacedArray() ]; } - protected function getObject() + /** + * @return Dummy + */ + protected static function getObject(): object { $obj = new Dummy(); $obj->foo = 'foo'; From a6a73cb7ca3c2160bb97494ca881f4278652caf7 Mon Sep 17 00:00:00 2001 From: Alexandre Daubois Date: Mon, 5 Aug 2024 09:12:25 +0200 Subject: [PATCH 251/297] Fix multiple CS errors --- Tests/Encoder/XmlEncoderTest.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Tests/Encoder/XmlEncoderTest.php b/Tests/Encoder/XmlEncoderTest.php index ae96f2c14..31d2ddfc6 100644 --- a/Tests/Encoder/XmlEncoderTest.php +++ b/Tests/Encoder/XmlEncoderTest.php @@ -220,21 +220,21 @@ public static function validEncodeProvider(): iterable ''."\n". ']]>'."\n", ['firstname' => 'Paul & Martha '], - ['cdata_wrapping_pattern' => '/[<>&"\']/'] + ['cdata_wrapping_pattern' => '/[<>&"\']/'], ]; yield 'encode with CDATA wrapping with custom pattern #2' => [ ''."\n". ''."\n", ['lastname' => 'O\'Donnel'], - ['cdata_wrapping_pattern' => '/[<>&"\']/'] + ['cdata_wrapping_pattern' => '/[<>&"\']/'], ]; yield 'encode with CDATA wrapping with custom pattern #3' => [ ''."\n". 'Paul and Martha'."\n", ['firstname' => 'Paul and Martha'], - ['cdata_wrapping_pattern' => '/[<>&"\']/'] + ['cdata_wrapping_pattern' => '/[<>&"\']/'], ]; yield 'enable CDATA wrapping' => [ From a03a64fd42072e9b798ec442d8e4cf4a3c3255a4 Mon Sep 17 00:00:00 2001 From: Ryan Hendrickson <8751750+rynhndrcksn@users.noreply.github.com> Date: Mon, 5 Aug 2024 20:24:13 -0700 Subject: [PATCH 252/297] fix denormalizing mixed collection values --- Normalizer/AbstractObjectNormalizer.php | 6 ++++ .../AbstractObjectNormalizerTest.php | 29 +++++++++++++++++++ 2 files changed, 35 insertions(+) diff --git a/Normalizer/AbstractObjectNormalizer.php b/Normalizer/AbstractObjectNormalizer.php index 3275d976a..63068420b 100644 --- a/Normalizer/AbstractObjectNormalizer.php +++ b/Normalizer/AbstractObjectNormalizer.php @@ -767,6 +767,12 @@ private function validateAndDenormalize(Type $type, string $currentClass, string $class = null; } } + } elseif ($t instanceof ObjectType) { + $typeIdentifier = TypeIdentifier::OBJECT; + $class = $t->getClassName(); + } else { + $typeIdentifier = $t->getTypeIdentifier(); + $class = null; } } else { if ($t instanceof ObjectType) { diff --git a/Tests/Normalizer/AbstractObjectNormalizerTest.php b/Tests/Normalizer/AbstractObjectNormalizerTest.php index e1b1031da..a666185dd 100644 --- a/Tests/Normalizer/AbstractObjectNormalizerTest.php +++ b/Tests/Normalizer/AbstractObjectNormalizerTest.php @@ -1224,6 +1224,29 @@ public function provideDenormalizeWithFilterBoolData(): array [['foo' => 'something'], null], ]; } + + public function testDenormalizeArrayObject() + { + $normalizer = new class() extends AbstractObjectNormalizerDummy { + public function __construct() + { + parent::__construct(null, null, new PhpDocExtractor()); + } + + protected function isAllowedAttribute($classOrObject, string $attribute, ?string $format = null, array $context = []): bool + { + return true; + } + }; + $serializer = new Serializer([$normalizer]); + $normalizer->setSerializer($serializer); + + $actual = $normalizer->denormalize(['foo' => ['array' => ['key' => 'value']]], DummyWithArrayObject::class); + + $this->assertInstanceOf(DummyWithArrayObject::class, $actual); + $this->assertInstanceOf(\ArrayObject::class, $actual->foo); + $this->assertSame(1, $actual->foo->count()); + } } class AbstractObjectNormalizerDummy extends AbstractObjectNormalizer @@ -1515,6 +1538,12 @@ class BoolPropertyDummy public $foo; } +class DummyWithArrayObject +{ + /** @var \ArrayObject */ + public $foo; +} + class SerializerCollectionDummy implements SerializerInterface, DenormalizerInterface { private array $normalizers; From c2b207d4ad885a52e7f9c845d27e363d2826f0a6 Mon Sep 17 00:00:00 2001 From: Roy de Vos Burchart Date: Thu, 1 Aug 2024 17:21:17 +0200 Subject: [PATCH 253/297] Code style change in `@PER-CS2.0` affecting `@Symfony` (parentheses for anonymous classes) --- Tests/Context/ContextBuilderTraitTest.php | 4 ++-- .../AbstractNormalizerContextBuilderTest.php | 2 +- ...ractObjectNormalizerContextBuilderTest.php | 2 +- .../Factory/CacheMetadataFactoryTest.php | 2 +- Tests/Normalizer/AbstractNormalizerTest.php | 2 +- .../AbstractObjectNormalizerTest.php | 20 +++++++++---------- .../Features/CallbacksTestTrait.php | 4 ++-- Tests/Normalizer/MapDenormalizationTest.php | 2 +- Tests/Normalizer/ObjectNormalizerTest.php | 4 ++-- Tests/SerializerTest.php | 6 +++--- 10 files changed, 24 insertions(+), 24 deletions(-) diff --git a/Tests/Context/ContextBuilderTraitTest.php b/Tests/Context/ContextBuilderTraitTest.php index 17ad231a0..edd3937e2 100644 --- a/Tests/Context/ContextBuilderTraitTest.php +++ b/Tests/Context/ContextBuilderTraitTest.php @@ -22,7 +22,7 @@ class ContextBuilderTraitTest extends TestCase { public function testWithContext() { - $contextBuilder = new class() implements ContextBuilderInterface { + $contextBuilder = new class implements ContextBuilderInterface { use ContextBuilderTrait; }; @@ -37,7 +37,7 @@ public function testWithContext() public function testWith() { - $contextBuilder = new class() { + $contextBuilder = new class { use ContextBuilderTrait; public function withFoo(string $value): static diff --git a/Tests/Context/Normalizer/AbstractNormalizerContextBuilderTest.php b/Tests/Context/Normalizer/AbstractNormalizerContextBuilderTest.php index 4b8f0cc3f..4e92c54d8 100644 --- a/Tests/Context/Normalizer/AbstractNormalizerContextBuilderTest.php +++ b/Tests/Context/Normalizer/AbstractNormalizerContextBuilderTest.php @@ -25,7 +25,7 @@ class AbstractNormalizerContextBuilderTest extends TestCase protected function setUp(): void { - $this->contextBuilder = new class() extends AbstractNormalizerContextBuilder {}; + $this->contextBuilder = new class extends AbstractNormalizerContextBuilder {}; } /** diff --git a/Tests/Context/Normalizer/AbstractObjectNormalizerContextBuilderTest.php b/Tests/Context/Normalizer/AbstractObjectNormalizerContextBuilderTest.php index 410f2972b..c13760118 100644 --- a/Tests/Context/Normalizer/AbstractObjectNormalizerContextBuilderTest.php +++ b/Tests/Context/Normalizer/AbstractObjectNormalizerContextBuilderTest.php @@ -25,7 +25,7 @@ class AbstractObjectNormalizerContextBuilderTest extends TestCase protected function setUp(): void { - $this->contextBuilder = new class() extends AbstractObjectNormalizerContextBuilder {}; + $this->contextBuilder = new class extends AbstractObjectNormalizerContextBuilder {}; } /** diff --git a/Tests/Mapping/Factory/CacheMetadataFactoryTest.php b/Tests/Mapping/Factory/CacheMetadataFactoryTest.php index 6db0b95ae..e18dd707f 100644 --- a/Tests/Mapping/Factory/CacheMetadataFactoryTest.php +++ b/Tests/Mapping/Factory/CacheMetadataFactoryTest.php @@ -68,7 +68,7 @@ public function testInvalidClassThrowsException() public function testAnonymousClass() { - $anonymousObject = new class() { + $anonymousObject = new class { }; $metadata = new ClassMetadata($anonymousObject::class); diff --git a/Tests/Normalizer/AbstractNormalizerTest.php b/Tests/Normalizer/AbstractNormalizerTest.php index 3108fe3c6..74ca3d6d6 100644 --- a/Tests/Normalizer/AbstractNormalizerTest.php +++ b/Tests/Normalizer/AbstractNormalizerTest.php @@ -271,7 +271,7 @@ public function testVariadicConstructorDenormalization() public static function getNormalizerWithCustomNameConverter() { $extractor = new PhpDocExtractor(); - $nameConverter = new class() implements NameConverterInterface { + $nameConverter = new class implements NameConverterInterface { public function normalize(string $propertyName, ?string $class = null, ?string $format = null, array $context = []): string { return ucfirst($propertyName); diff --git a/Tests/Normalizer/AbstractObjectNormalizerTest.php b/Tests/Normalizer/AbstractObjectNormalizerTest.php index e1b1031da..685641ff1 100644 --- a/Tests/Normalizer/AbstractObjectNormalizerTest.php +++ b/Tests/Normalizer/AbstractObjectNormalizerTest.php @@ -526,7 +526,7 @@ public function testDenormalizeWithDiscriminatorMapUsesCorrectClassname() { $factory = new ClassMetadataFactory(new AttributeLoader()); - $loaderMock = new class() implements ClassMetadataFactoryInterface { + $loaderMock = new class implements ClassMetadataFactoryInterface { public function getMetadataFor($value): ClassMetadataInterface { if (AbstractDummy::class === $value) { @@ -561,7 +561,7 @@ public function testDenormalizeWithDiscriminatorMapAndObjectToPopulateUsesCorrec { $factory = new ClassMetadataFactory(new AttributeLoader()); - $loaderMock = new class() implements ClassMetadataFactoryInterface { + $loaderMock = new class implements ClassMetadataFactoryInterface { public function getMetadataFor($value): ClassMetadataInterface { if (AbstractDummy::class === $value) { @@ -614,7 +614,7 @@ public function hasMetadataFor($value): bool public function testDenormalizeWithNestedDiscriminatorMap() { - $classDiscriminatorResolver = new class() implements ClassDiscriminatorResolverInterface { + $classDiscriminatorResolver = new class implements ClassDiscriminatorResolverInterface { public function getMappingForClass(string $class): ?ClassDiscriminatorMapping { return match ($class) { @@ -867,7 +867,7 @@ public function testDenormalizeWithNumberAsSerializedNameAndNoArrayReindex() '99' => 'baz', ]; - $obj = new class() { + $obj = new class { #[SerializedName('1')] public $foo; @@ -891,7 +891,7 @@ public function testDenormalizeWithCorrectOrderOfAttributeAndProperty() ], ]; - $obj = new class() { + $obj = new class { #[SerializedPath('[data][id]')] public $id; }; @@ -902,7 +902,7 @@ public function testDenormalizeWithCorrectOrderOfAttributeAndProperty() public function testNormalizeBasedOnAllowedAttributes() { - $normalizer = new class() extends AbstractObjectNormalizer { + $normalizer = new class extends AbstractObjectNormalizer { public function getSupportedTypes(?string $format): array { return ['*' => false]; @@ -982,7 +982,7 @@ public function testProvidingContextCacheKeyGeneratesSameChildContextCacheKey() $foobar->bar = 'bar'; $foobar->baz = 'baz'; - $normalizer = new class() extends AbstractObjectNormalizerDummy { + $normalizer = new class extends AbstractObjectNormalizerDummy { public $childContextCacheKey; protected function extractAttributes(object $object, ?string $format = null, array $context = []): array @@ -1022,7 +1022,7 @@ public function testChildContextKeepsOriginalContextCacheKey() $foobar->bar = 'bar'; $foobar->baz = 'baz'; - $normalizer = new class() extends AbstractObjectNormalizerDummy { + $normalizer = new class extends AbstractObjectNormalizerDummy { public $childContextCacheKey; protected function extractAttributes(object $object, ?string $format = null, array $context = []): array @@ -1057,7 +1057,7 @@ public function testChildContextCacheKeyStaysFalseWhenOriginalCacheKeyIsFalse() $foobar->bar = 'bar'; $foobar->baz = 'baz'; - $normalizer = new class() extends AbstractObjectNormalizerDummy { + $normalizer = new class extends AbstractObjectNormalizerDummy { public $childContextCacheKey; protected function extractAttributes(object $object, ?string $format = null, array $context = []): array @@ -1087,7 +1087,7 @@ protected function createChildContext(array $parentContext, string $attribute, ? public function testDenormalizeXmlScalar() { - $normalizer = new class() extends AbstractObjectNormalizer { + $normalizer = new class extends AbstractObjectNormalizer { public function __construct() { parent::__construct(null, new MetadataAwareNameConverter(new ClassMetadataFactory(new AttributeLoader()))); diff --git a/Tests/Normalizer/Features/CallbacksTestTrait.php b/Tests/Normalizer/Features/CallbacksTestTrait.php index 09e2f8867..3a9191ae8 100644 --- a/Tests/Normalizer/Features/CallbacksTestTrait.php +++ b/Tests/Normalizer/Features/CallbacksTestTrait.php @@ -59,7 +59,7 @@ public function testNormalizeCallbacksWithNoConstructorArgument($callbacks, $val { $normalizer = $this->getNormalizerForCallbacksWithPropertyTypeExtractor(); - $obj = new class() extends CallbacksObject { + $obj = new class extends CallbacksObject { public function __construct() { } @@ -101,7 +101,7 @@ public function testDenormalizeCallbacksWithNoConstructorArgument($callbacks, $v { $normalizer = $this->getNormalizerForCallbacksWithPropertyTypeExtractor(); - $objWithNoConstructorArgument = new class() extends CallbacksObject { + $objWithNoConstructorArgument = new class extends CallbacksObject { public function __construct() { } diff --git a/Tests/Normalizer/MapDenormalizationTest.php b/Tests/Normalizer/MapDenormalizationTest.php index ea4515955..75ecbccb9 100644 --- a/Tests/Normalizer/MapDenormalizationTest.php +++ b/Tests/Normalizer/MapDenormalizationTest.php @@ -187,7 +187,7 @@ public function testNullableAbstractObject() private function getSerializer() { - $loaderMock = new class() implements ClassMetadataFactoryInterface { + $loaderMock = new class implements ClassMetadataFactoryInterface { public function getMetadataFor($value): ClassMetadataInterface { if (AbstractDummyValue::class === $value) { diff --git a/Tests/Normalizer/ObjectNormalizerTest.php b/Tests/Normalizer/ObjectNormalizerTest.php index 8a979e23f..323eba000 100644 --- a/Tests/Normalizer/ObjectNormalizerTest.php +++ b/Tests/Normalizer/ObjectNormalizerTest.php @@ -749,7 +749,7 @@ public function testDoesntHaveIssuesWithUnionConstTypes() $normalizer = new ObjectNormalizer(null, null, null, $extractor); $serializer = new Serializer([new ArrayDenormalizer(), new DateTimeNormalizer(), $normalizer]); - $this->assertSame('bar', $serializer->denormalize(['foo' => 'bar'], (new class() { + $this->assertSame('bar', $serializer->denormalize(['foo' => 'bar'], (new class { /** @var self::*|null */ public $foo; })::class)->foo); @@ -784,7 +784,7 @@ public function testDenormalizeFalsePseudoType() public function testAdvancedNameConverter() { - $nameConverter = new class() implements AdvancedNameConverterInterface { + $nameConverter = new class implements AdvancedNameConverterInterface { public function normalize(string $propertyName, ?string $class = null, ?string $format = null, array $context = []): string { return \sprintf('%s-%s-%s-%s', $propertyName, $class, $format, $context['foo']); diff --git a/Tests/SerializerTest.php b/Tests/SerializerTest.php index 7d4cbc30d..a600f0724 100644 --- a/Tests/SerializerTest.php +++ b/Tests/SerializerTest.php @@ -426,7 +426,7 @@ public function testDeserializeAndSerializeAbstractObjectsWithTheClassMetadataDi $example = new AbstractDummyFirstChild('foo-value', 'bar-value'); $example->setQuux(new DummyFirstChildQuux('quux')); - $loaderMock = new class() implements ClassMetadataFactoryInterface { + $loaderMock = new class implements ClassMetadataFactoryInterface { public function getMetadataFor($value): ClassMetadataInterface { if (AbstractDummy::class === $value) { @@ -607,10 +607,10 @@ public static function provideObjectOrCollectionTests() $data['c2'] = new \ArrayObject(['nested' => new \ArrayObject(['k' => 'v'])]); $data['d1'] = new \ArrayObject(['nested' => []]); $data['d2'] = new \ArrayObject(['nested' => ['k' => 'v']]); - $data['e1'] = new class() { + $data['e1'] = new class { public $map = []; }; - $data['e2'] = new class() { + $data['e2'] = new class { public $map = ['k' => 'v']; }; $data['f1'] = new class(new \ArrayObject()) { From bbd69a44cc5de4265b707f81116aadbcc580ccb4 Mon Sep 17 00:00:00 2001 From: Alexandre Daubois Date: Mon, 12 Aug 2024 11:46:28 +0200 Subject: [PATCH 254/297] [Serializer] Remove useless calls to `func_get_arg()` --- NameConverter/MetadataAwareNameConverter.php | 8 -------- 1 file changed, 8 deletions(-) diff --git a/NameConverter/MetadataAwareNameConverter.php b/NameConverter/MetadataAwareNameConverter.php index bc693bd90..eec3b42ce 100644 --- a/NameConverter/MetadataAwareNameConverter.php +++ b/NameConverter/MetadataAwareNameConverter.php @@ -43,10 +43,6 @@ public function __construct( public function normalize(string $propertyName, ?string $class = null, ?string $format = null, array $context = []): string { - $class = 1 < \func_num_args() ? func_get_arg(1) : null; - $format = 2 < \func_num_args() ? func_get_arg(2) : null; - $context = 3 < \func_num_args() ? func_get_arg(3) : []; - if (null === $class) { return $this->normalizeFallback($propertyName, $class, $format, $context); } @@ -60,10 +56,6 @@ public function normalize(string $propertyName, ?string $class = null, ?string $ public function denormalize(string $propertyName, ?string $class = null, ?string $format = null, array $context = []): string { - $class = 1 < \func_num_args() ? func_get_arg(1) : null; - $format = 2 < \func_num_args() ? func_get_arg(2) : null; - $context = 3 < \func_num_args() ? func_get_arg(3) : []; - if (null === $class) { return $this->denormalizeFallback($propertyName, $class, $format, $context); } From 5f9d7e2e164b02c400cfd7d83bb0fc11f33c218b Mon Sep 17 00:00:00 2001 From: HypeMC Date: Wed, 14 Aug 2024 23:21:30 +0200 Subject: [PATCH 255/297] [Serializer] Remove redundant @internal tags from traceable classes --- DataCollector/SerializerDataCollector.php | 2 +- Debug/TraceableEncoder.php | 2 +- Debug/TraceableNormalizer.php | 2 +- Debug/TraceableSerializer.php | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/DataCollector/SerializerDataCollector.php b/DataCollector/SerializerDataCollector.php index 671239d28..2880dea37 100644 --- a/DataCollector/SerializerDataCollector.php +++ b/DataCollector/SerializerDataCollector.php @@ -21,7 +21,7 @@ /** * @author Mathias Arlaud * - * @internal + * @final */ class SerializerDataCollector extends DataCollector implements LateDataCollectorInterface { diff --git a/Debug/TraceableEncoder.php b/Debug/TraceableEncoder.php index 0795d14ca..afefee0ee 100644 --- a/Debug/TraceableEncoder.php +++ b/Debug/TraceableEncoder.php @@ -23,7 +23,7 @@ * * @author Mathias Arlaud * - * @internal + * @final */ class TraceableEncoder implements EncoderInterface, DecoderInterface, SerializerAwareInterface { diff --git a/Debug/TraceableNormalizer.php b/Debug/TraceableNormalizer.php index fc4db40ad..88ab4863d 100644 --- a/Debug/TraceableNormalizer.php +++ b/Debug/TraceableNormalizer.php @@ -25,7 +25,7 @@ * * @author Mathias Arlaud * - * @internal + * @final */ class TraceableNormalizer implements NormalizerInterface, DenormalizerInterface, SerializerAwareInterface, NormalizerAwareInterface, DenormalizerAwareInterface, CacheableSupportsMethodInterface { diff --git a/Debug/TraceableSerializer.php b/Debug/TraceableSerializer.php index 789ae65ca..dd22e8678 100644 --- a/Debug/TraceableSerializer.php +++ b/Debug/TraceableSerializer.php @@ -24,7 +24,7 @@ * * @author Mathias Arlaud * - * @internal + * @final */ class TraceableSerializer implements SerializerInterface, NormalizerInterface, DenormalizerInterface, EncoderInterface, DecoderInterface { From a75d03d7720417f8a654e73e8f02acdea8779cd0 Mon Sep 17 00:00:00 2001 From: Christian Flothmann Date: Sat, 17 Aug 2024 09:51:47 +0200 Subject: [PATCH 256/297] clean up PHP version checks --- Tests/Encoder/CsvEncoderTest.php | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/Tests/Encoder/CsvEncoderTest.php b/Tests/Encoder/CsvEncoderTest.php index f487356cd..c0be73a8b 100644 --- a/Tests/Encoder/CsvEncoderTest.php +++ b/Tests/Encoder/CsvEncoderTest.php @@ -149,7 +149,7 @@ public function testEncodeCustomSettings() $this->encoder = new CsvEncoder([ CsvEncoder::DELIMITER_KEY => ';', CsvEncoder::ENCLOSURE_KEY => "'", - CsvEncoder::ESCAPE_CHAR_KEY => \PHP_VERSION_ID < 70400 ? '|' : '', + CsvEncoder::ESCAPE_CHAR_KEY => '', CsvEncoder::KEY_SEPARATOR_KEY => '-', ]); @@ -175,7 +175,7 @@ public function testEncodeCustomSettingsPassedInContext() , $this->encoder->encode($value, 'csv', [ CsvEncoder::DELIMITER_KEY => ';', CsvEncoder::ENCLOSURE_KEY => "'", - CsvEncoder::ESCAPE_CHAR_KEY => \PHP_VERSION_ID < 70400 ? '|' : '', + CsvEncoder::ESCAPE_CHAR_KEY => '', CsvEncoder::KEY_SEPARATOR_KEY => '-', ])); } @@ -185,7 +185,7 @@ public function testEncodeCustomSettingsPassedInConstructor() $encoder = new CsvEncoder([ CsvEncoder::DELIMITER_KEY => ';', CsvEncoder::ENCLOSURE_KEY => "'", - CsvEncoder::ESCAPE_CHAR_KEY => \PHP_VERSION_ID < 70400 ? '|' : '', + CsvEncoder::ESCAPE_CHAR_KEY => '', CsvEncoder::KEY_SEPARATOR_KEY => '-', ]); $value = ['a' => 'he\'llo', 'c' => ['d' => 'foo']]; @@ -574,7 +574,7 @@ public function testDecodeCustomSettings() $this->encoder = new CsvEncoder([ CsvEncoder::DELIMITER_KEY => ';', CsvEncoder::ENCLOSURE_KEY => "'", - CsvEncoder::ESCAPE_CHAR_KEY => \PHP_VERSION_ID < 70400 ? '|' : '', + CsvEncoder::ESCAPE_CHAR_KEY => '', CsvEncoder::KEY_SEPARATOR_KEY => '-', ]); @@ -596,7 +596,7 @@ public function testDecodeCustomSettingsPassedInContext() , 'csv', [ CsvEncoder::DELIMITER_KEY => ';', CsvEncoder::ENCLOSURE_KEY => "'", - CsvEncoder::ESCAPE_CHAR_KEY => \PHP_VERSION_ID < 70400 ? '|' : '', + CsvEncoder::ESCAPE_CHAR_KEY => '', CsvEncoder::KEY_SEPARATOR_KEY => '-', ])); } @@ -606,7 +606,7 @@ public function testDecodeCustomSettingsPassedInConstructor() $encoder = new CsvEncoder([ CsvEncoder::DELIMITER_KEY => ';', CsvEncoder::ENCLOSURE_KEY => "'", - CsvEncoder::ESCAPE_CHAR_KEY => \PHP_VERSION_ID < 70400 ? '|' : '', + CsvEncoder::ESCAPE_CHAR_KEY => '', CsvEncoder::KEY_SEPARATOR_KEY => '-', CsvEncoder::AS_COLLECTION_KEY => true, // Can be removed in 5.0 ]); From a06245d66340b8ba54c836787e19a6b2f1d681b2 Mon Sep 17 00:00:00 2001 From: Alexandre Daubois Date: Thu, 25 Jul 2024 11:31:45 +0200 Subject: [PATCH 257/297] [Serializer][Translation] Deprecate passing a non-empty CSV escape char --- CHANGELOG.md | 6 ++++ Context/Encoder/CsvEncoderContextBuilder.php | 4 +++ Encoder/CsvEncoder.php | 7 +++++ .../Encoder/CsvEncoderContextBuilderTest.php | 24 +++++++++++--- Tests/Encoder/CsvEncoderTest.php | 31 +++++++++++++++---- 5 files changed, 61 insertions(+), 11 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3118834d8..8473d9e9b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,12 @@ CHANGELOG ========= +7.2 +--- + + * Deprecate the `csv_escape_char` context option of `CsvEncoder` and the `CsvEncoder::ESCAPE_CHAR_KEY` constant + * Deprecate `CsvEncoderContextBuilder::withEscapeChar()` method + 7.1 --- diff --git a/Context/Encoder/CsvEncoderContextBuilder.php b/Context/Encoder/CsvEncoderContextBuilder.php index f449bc314..9f0d6da6f 100644 --- a/Context/Encoder/CsvEncoderContextBuilder.php +++ b/Context/Encoder/CsvEncoderContextBuilder.php @@ -62,10 +62,14 @@ public function withEnclosure(?string $enclosure): static * * Must be empty or a single character. * + * @deprecated since Symfony 7.2, to be removed in 8.0 + * * @throws InvalidArgumentException */ public function withEscapeChar(?string $escapeChar): static { + trigger_deprecation('symfony/serializer', '7.2', 'The "%s" method is deprecated. It will be removed in 8.0.', __METHOD__); + if (null !== $escapeChar && \strlen($escapeChar) > 1) { throw new InvalidArgumentException(\sprintf('The "%s" escape character must be empty or a single character.', $escapeChar)); } diff --git a/Encoder/CsvEncoder.php b/Encoder/CsvEncoder.php index 462bd663b..3902b5613 100644 --- a/Encoder/CsvEncoder.php +++ b/Encoder/CsvEncoder.php @@ -25,6 +25,9 @@ class CsvEncoder implements EncoderInterface, DecoderInterface public const FORMAT = 'csv'; public const DELIMITER_KEY = 'csv_delimiter'; public const ENCLOSURE_KEY = 'csv_enclosure'; + /** + * @deprecated since Symfony 7.2, to be removed in 8.0 + */ public const ESCAPE_CHAR_KEY = 'csv_escape_char'; public const KEY_SEPARATOR_KEY = 'csv_key_separator'; public const HEADERS_KEY = 'csv_headers'; @@ -53,6 +56,10 @@ class CsvEncoder implements EncoderInterface, DecoderInterface public function __construct(array $defaultContext = []) { + if (\array_key_exists(self::ESCAPE_CHAR_KEY, $defaultContext)) { + trigger_deprecation('symfony/serializer', '7.2', 'Setting the "csv_escape_char" option is deprecated. The option will be removed in 8.0.'); + } + $this->defaultContext = array_merge($this->defaultContext, $defaultContext); } diff --git a/Tests/Context/Encoder/CsvEncoderContextBuilderTest.php b/Tests/Context/Encoder/CsvEncoderContextBuilderTest.php index c71d41b63..bcaaf2a88 100644 --- a/Tests/Context/Encoder/CsvEncoderContextBuilderTest.php +++ b/Tests/Context/Encoder/CsvEncoderContextBuilderTest.php @@ -12,6 +12,7 @@ namespace Symfony\Component\Serializer\Tests\Context\Encoder; use PHPUnit\Framework\TestCase; +use Symfony\Bridge\PhpUnit\ExpectDeprecationTrait; use Symfony\Component\Serializer\Context\Encoder\CsvEncoderContextBuilder; use Symfony\Component\Serializer\Encoder\CsvEncoder; use Symfony\Component\Serializer\Exception\InvalidArgumentException; @@ -21,6 +22,8 @@ */ class CsvEncoderContextBuilderTest extends TestCase { + use ExpectDeprecationTrait; + private CsvEncoderContextBuilder $contextBuilder; protected function setUp(): void @@ -38,7 +41,6 @@ public function testWithers(array $values) $context = $this->contextBuilder ->withDelimiter($values[CsvEncoder::DELIMITER_KEY]) ->withEnclosure($values[CsvEncoder::ENCLOSURE_KEY]) - ->withEscapeChar($values[CsvEncoder::ESCAPE_CHAR_KEY]) ->withKeySeparator($values[CsvEncoder::KEY_SEPARATOR_KEY]) ->withHeaders($values[CsvEncoder::HEADERS_KEY]) ->withEscapedFormulas($values[CsvEncoder::ESCAPE_FORMULAS_KEY]) @@ -59,7 +61,6 @@ public static function withersDataProvider(): iterable yield 'With values' => [[ CsvEncoder::DELIMITER_KEY => ';', CsvEncoder::ENCLOSURE_KEY => '"', - CsvEncoder::ESCAPE_CHAR_KEY => '\\', CsvEncoder::KEY_SEPARATOR_KEY => '_', CsvEncoder::HEADERS_KEY => ['h1', 'h2'], CsvEncoder::ESCAPE_FORMULAS_KEY => true, @@ -72,7 +73,6 @@ public static function withersDataProvider(): iterable yield 'With null values' => [[ CsvEncoder::DELIMITER_KEY => null, CsvEncoder::ENCLOSURE_KEY => null, - CsvEncoder::ESCAPE_CHAR_KEY => null, CsvEncoder::KEY_SEPARATOR_KEY => null, CsvEncoder::HEADERS_KEY => null, CsvEncoder::ESCAPE_FORMULAS_KEY => null, @@ -88,7 +88,6 @@ public function testWithersWithoutValue() $context = $this->contextBuilder ->withDelimiter(null) ->withEnclosure(null) - ->withEscapeChar(null) ->withKeySeparator(null) ->withHeaders(null) ->withEscapedFormulas(null) @@ -101,7 +100,6 @@ public function testWithersWithoutValue() $this->assertSame([ CsvEncoder::DELIMITER_KEY => null, CsvEncoder::ENCLOSURE_KEY => null, - CsvEncoder::ESCAPE_CHAR_KEY => null, CsvEncoder::KEY_SEPARATOR_KEY => null, CsvEncoder::HEADERS_KEY => null, CsvEncoder::ESCAPE_FORMULAS_KEY => null, @@ -124,9 +122,25 @@ public function testCannotSetMultipleBytesAsEnclosure() $this->contextBuilder->withEnclosure('ọ'); } + /** + * @group legacy + */ public function testCannotSetMultipleBytesAsEscapeChar() { + $this->expectDeprecation('Since symfony/serializer 7.2: The "Symfony\Component\Serializer\Context\Encoder\CsvEncoderContextBuilder::withEscapeChar" method is deprecated. It will be removed in 8.0.'); + $this->expectException(InvalidArgumentException::class); $this->contextBuilder->withEscapeChar('ọ'); } + + /** + * @group legacy + */ + public function testWithEscapeCharIsDeprecated() + { + $this->expectDeprecation('Since symfony/serializer 7.2: The "Symfony\Component\Serializer\Context\Encoder\CsvEncoderContextBuilder::withEscapeChar" method is deprecated. It will be removed in 8.0.'); + $context = $this->contextBuilder->withEscapeChar('\\'); + + $this->assertSame(['csv_escape_char' => '\\'], $context->toArray()); + } } diff --git a/Tests/Encoder/CsvEncoderTest.php b/Tests/Encoder/CsvEncoderTest.php index f487356cd..e250d1c61 100644 --- a/Tests/Encoder/CsvEncoderTest.php +++ b/Tests/Encoder/CsvEncoderTest.php @@ -12,6 +12,7 @@ namespace Symfony\Component\Serializer\Tests\Encoder; use PHPUnit\Framework\TestCase; +use Symfony\Bridge\PhpUnit\ExpectDeprecationTrait; use Symfony\Component\Serializer\Encoder\CsvEncoder; use Symfony\Component\Serializer\Exception\UnexpectedValueException; @@ -20,6 +21,8 @@ */ class CsvEncoderTest extends TestCase { + use ExpectDeprecationTrait; + private CsvEncoder $encoder; protected function setUp(): void @@ -149,7 +152,6 @@ public function testEncodeCustomSettings() $this->encoder = new CsvEncoder([ CsvEncoder::DELIMITER_KEY => ';', CsvEncoder::ENCLOSURE_KEY => "'", - CsvEncoder::ESCAPE_CHAR_KEY => \PHP_VERSION_ID < 70400 ? '|' : '', CsvEncoder::KEY_SEPARATOR_KEY => '-', ]); @@ -175,7 +177,6 @@ public function testEncodeCustomSettingsPassedInContext() , $this->encoder->encode($value, 'csv', [ CsvEncoder::DELIMITER_KEY => ';', CsvEncoder::ENCLOSURE_KEY => "'", - CsvEncoder::ESCAPE_CHAR_KEY => \PHP_VERSION_ID < 70400 ? '|' : '', CsvEncoder::KEY_SEPARATOR_KEY => '-', ])); } @@ -185,7 +186,6 @@ public function testEncodeCustomSettingsPassedInConstructor() $encoder = new CsvEncoder([ CsvEncoder::DELIMITER_KEY => ';', CsvEncoder::ENCLOSURE_KEY => "'", - CsvEncoder::ESCAPE_CHAR_KEY => \PHP_VERSION_ID < 70400 ? '|' : '', CsvEncoder::KEY_SEPARATOR_KEY => '-', ]); $value = ['a' => 'he\'llo', 'c' => ['d' => 'foo']]; @@ -574,7 +574,6 @@ public function testDecodeCustomSettings() $this->encoder = new CsvEncoder([ CsvEncoder::DELIMITER_KEY => ';', CsvEncoder::ENCLOSURE_KEY => "'", - CsvEncoder::ESCAPE_CHAR_KEY => \PHP_VERSION_ID < 70400 ? '|' : '', CsvEncoder::KEY_SEPARATOR_KEY => '-', ]); @@ -596,7 +595,6 @@ public function testDecodeCustomSettingsPassedInContext() , 'csv', [ CsvEncoder::DELIMITER_KEY => ';', CsvEncoder::ENCLOSURE_KEY => "'", - CsvEncoder::ESCAPE_CHAR_KEY => \PHP_VERSION_ID < 70400 ? '|' : '', CsvEncoder::KEY_SEPARATOR_KEY => '-', ])); } @@ -606,7 +604,6 @@ public function testDecodeCustomSettingsPassedInConstructor() $encoder = new CsvEncoder([ CsvEncoder::DELIMITER_KEY => ';', CsvEncoder::ENCLOSURE_KEY => "'", - CsvEncoder::ESCAPE_CHAR_KEY => \PHP_VERSION_ID < 70400 ? '|' : '', CsvEncoder::KEY_SEPARATOR_KEY => '-', CsvEncoder::AS_COLLECTION_KEY => true, // Can be removed in 5.0 ]); @@ -710,4 +707,26 @@ public function testEndOfLinePassedInConstructor() $encoder = new CsvEncoder([CsvEncoder::END_OF_LINE => "\r\n"]); $this->assertSame("foo,bar\r\nhello,test\r\n", $encoder->encode($value, 'csv')); } + + /** + * @group legacy + */ + public function testPassingNonEmptyEscapeCharIsDeprecated() + { + $this->expectDeprecation('Since symfony/serializer 7.2: Setting the "csv_escape_char" option is deprecated. The option will be removed in 8.0.'); + $encoder = new CsvEncoder(['csv_escape_char' => '@']); + + $this->assertSame( + [[ + 'A, B@"' => 'D', + 'C' => 'E', + ]], + $encoder->decode(<<<'CSV' + "A, B@"", "C" + "D", "E" + CSV, + 'csv' + ) + ); + } } From d0c1cd1603f2509a138a541c9b0005bb722466a0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A9vin=20Dunglas?= Date: Thu, 22 Aug 2024 10:19:16 +0200 Subject: [PATCH 258/297] [Serializer] more precise type for CamelCaseToSnakeCaseNameConverter::$attributes --- NameConverter/CamelCaseToSnakeCaseNameConverter.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/NameConverter/CamelCaseToSnakeCaseNameConverter.php b/NameConverter/CamelCaseToSnakeCaseNameConverter.php index 47d69d3ab..033ec94b7 100644 --- a/NameConverter/CamelCaseToSnakeCaseNameConverter.php +++ b/NameConverter/CamelCaseToSnakeCaseNameConverter.php @@ -27,8 +27,8 @@ class CamelCaseToSnakeCaseNameConverter implements NameConverterInterface public const REQUIRE_SNAKE_CASE_PROPERTIES = 'require_snake_case_properties'; /** - * @param array|null $attributes The list of attributes to rename or null for all attributes - * @param bool $lowerCamelCase Use lowerCamelCase style + * @param string[]|null $attributes The list of attributes to rename or null for all attributes + * @param bool $lowerCamelCase Use lowerCamelCase style */ public function __construct( private ?array $attributes = null, From 0158b0e91b7cf7e744a6fb9acaeb613d1ca40dbb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A9vin=20Dunglas?= Date: Thu, 22 Aug 2024 11:39:57 +0200 Subject: [PATCH 259/297] [Serializer] Fix CamelCaseToSnakeCaseNameConverterTest::testDenormalizeWithContext --- Tests/NameConverter/CamelCaseToSnakeCaseNameConverterTest.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Tests/NameConverter/CamelCaseToSnakeCaseNameConverterTest.php b/Tests/NameConverter/CamelCaseToSnakeCaseNameConverterTest.php index fc9967f0c..d1edc2325 100644 --- a/Tests/NameConverter/CamelCaseToSnakeCaseNameConverterTest.php +++ b/Tests/NameConverter/CamelCaseToSnakeCaseNameConverterTest.php @@ -61,9 +61,9 @@ public static function attributeProvider() public function testDenormalizeWithContext() { $nameConverter = new CamelCaseToSnakeCaseNameConverter(null, true); - $denormalizedValue = $nameConverter->denormalize('last_name', null, null, [CamelCaseToSnakeCaseNameConverter::REQUIRE_SNAKE_CASE_PROPERTIES]); + $denormalizedValue = $nameConverter->denormalize('last_name', null, null, [CamelCaseToSnakeCaseNameConverter::REQUIRE_SNAKE_CASE_PROPERTIES => true]); - $this->assertSame($denormalizedValue, 'lastName'); + $this->assertSame('lastName', $denormalizedValue); } public function testErrorDenormalizeWithContext() From e31367ee8f489d0180023525624883ed87e30d00 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A9vin=20Dunglas?= Date: Thu, 22 Aug 2024 11:18:18 +0200 Subject: [PATCH 260/297] [Serializer] Add SnakeCaseToCamelCaseNameConverter --- CHANGELOG.md | 1 + .../SnakeCaseToCamelCaseNameConverter.php | 78 +++++++++++++++++++ .../SnakeCaseToCamelCaseNameConverterTest.php | 64 +++++++++++++++ 3 files changed, 143 insertions(+) create mode 100644 NameConverter/SnakeCaseToCamelCaseNameConverter.php create mode 100644 Tests/NameConverter/SnakeCaseToCamelCaseNameConverterTest.php diff --git a/CHANGELOG.md b/CHANGELOG.md index 8473d9e9b..bb31b565a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,7 @@ CHANGELOG * Deprecate the `csv_escape_char` context option of `CsvEncoder` and the `CsvEncoder::ESCAPE_CHAR_KEY` constant * Deprecate `CsvEncoderContextBuilder::withEscapeChar()` method + * Add `SnakeCaseToCamelCaseNameConverter` 7.1 --- diff --git a/NameConverter/SnakeCaseToCamelCaseNameConverter.php b/NameConverter/SnakeCaseToCamelCaseNameConverter.php new file mode 100644 index 000000000..cb93d3e98 --- /dev/null +++ b/NameConverter/SnakeCaseToCamelCaseNameConverter.php @@ -0,0 +1,78 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Serializer\NameConverter; + +use Symfony\Component\Serializer\Exception\UnexpectedPropertyException; + +/** + * Underscore to camelCase name converter. + * + * @author Kévin Dunglas + */ +final readonly class SnakeCaseToCamelCaseNameConverter implements NameConverterInterface +{ + /** + * Require all properties to be written in camelCase. + */ + public const REQUIRE_CAMEL_CASE_PROPERTIES = 'require_camel_case_properties'; + + /** + * @param string[]|null $attributes The list of attributes to rename or null for all attributes + * @param bool $lowerCamelCase Use lowerCamelCase style + */ + public function __construct( + private ?array $attributes = null, + private bool $lowerCamelCase = true, + ) { + } + + /** + * @param class-string|null $class + * @param array $context + */ + public function normalize(string $propertyName, ?string $class = null, ?string $format = null, array $context = []): string + { + if (null !== $this->attributes && !\in_array($propertyName, $this->attributes, true)) { + return $propertyName; + } + + $camelCasedName = preg_replace_callback( + '/(^|_|\.)+(.)/', + fn ($match) => ('.' === $match[1] ? '_' : '').strtoupper($match[2]), + $propertyName + ); + + if ($this->lowerCamelCase) { + $camelCasedName = lcfirst($camelCasedName); + } + + return $camelCasedName; + } + + /** + * @param class-string|null $class + * @param array $context + */ + public function denormalize(string $propertyName, ?string $class = null, ?string $format = null, array $context = []): string + { + if (($context[self::REQUIRE_CAMEL_CASE_PROPERTIES] ?? false) && $propertyName !== $this->normalize($propertyName, $class, $format, $context)) { + throw new UnexpectedPropertyException($propertyName); + } + + $snakeCased = strtolower(preg_replace('/[A-Z]/', '_\\0', lcfirst($propertyName))); + if (null === $this->attributes || \in_array($snakeCased, $this->attributes, true)) { + return $snakeCased; + } + + return $propertyName; + } +} diff --git a/Tests/NameConverter/SnakeCaseToCamelCaseNameConverterTest.php b/Tests/NameConverter/SnakeCaseToCamelCaseNameConverterTest.php new file mode 100644 index 000000000..2d2799e2c --- /dev/null +++ b/Tests/NameConverter/SnakeCaseToCamelCaseNameConverterTest.php @@ -0,0 +1,64 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Serializer\Tests\NameConverter; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\Serializer\Exception\UnexpectedPropertyException; +use Symfony\Component\Serializer\NameConverter\NameConverterInterface; +use Symfony\Component\Serializer\NameConverter\SnakeCaseToCamelCaseNameConverter; + +/** + * @author Kévin Dunglas + * @author Aurélien Pillevesse + */ +class SnakeCaseToCamelCaseNameConverterTest extends TestCase +{ + public function testInterface() + { + $attributeMetadata = new SnakeCaseToCamelCaseNameConverter(); + $this->assertInstanceOf(NameConverterInterface::class, $attributeMetadata); + } + + /** + * @dataProvider Symfony\Component\Serializer\Tests\NameConverter\CamelCaseToSnakeCaseNameConverterTest::attributeProvider + */ + public function testNormalize($underscored, $camelCased, $useLowerCamelCase) + { + $nameConverter = new SnakeCaseToCamelCaseNameConverter(null, $useLowerCamelCase); + $this->assertEquals($camelCased, $nameConverter->normalize($underscored)); + } + + /** + * @dataProvider Symfony\Component\Serializer\Tests\NameConverter\CamelCaseToSnakeCaseNameConverterTest::attributeProvider + */ + public function testDenormalize($underscored, $camelCased, $useLowerCamelCase) + { + $nameConverter = new SnakeCaseToCamelCaseNameConverter(null, $useLowerCamelCase); + $this->assertEquals($underscored, $nameConverter->denormalize($camelCased)); + } + + public function testDenormalizeWithContext() + { + $nameConverter = new SnakeCaseToCamelCaseNameConverter(null, true); + $denormalizedValue = $nameConverter->denormalize('lastName', null, null, [SnakeCaseToCamelCaseNameConverter::REQUIRE_CAMEL_CASE_PROPERTIES => true]); + + $this->assertSame('last_name', $denormalizedValue); + } + + public function testErrorDenormalizeWithContext() + { + $nameConverter = new SnakeCaseToCamelCaseNameConverter(null, true); + + $this->expectException(UnexpectedPropertyException::class); + $nameConverter->denormalize('last_name', null, null, [SnakeCaseToCamelCaseNameConverter::REQUIRE_CAMEL_CASE_PROPERTIES => true]); + } +} From bce9daacdce95cf5e774b17b10f9d8153c6e947d Mon Sep 17 00:00:00 2001 From: Attila Szeremi Date: Mon, 22 Jul 2024 10:09:14 +0200 Subject: [PATCH 261/297] [Serializer] Support subclasses of `DateTime` and `DateTimeImmutable` --- CHANGELOG.md | 1 + Normalizer/DateTimeNormalizer.php | 2 +- Tests/Normalizer/DateTimeNormalizerTest.php | 13 +++++++++++++ 3 files changed, 15 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3118834d8..232c8eda7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,7 @@ CHANGELOG * Add `CamelCaseToSnakeCaseNameConverter::REQUIRE_SNAKE_CASE_PROPERTIES` context option * Deprecate `AbstractNormalizerContextBuilder::withDefaultContructorArguments(?array $defaultContructorArguments)`, use `withDefaultConstructorArguments(?array $defaultConstructorArguments)` instead (note the missing `s` character in Contructor word in deprecated method) * Add `XmlEncoder::CDATA_WRAPPING_PATTERN` context option + * Support subclasses of `\DateTime` and `\DateTimeImmutable` for denormalization 7.0 --- diff --git a/Normalizer/DateTimeNormalizer.php b/Normalizer/DateTimeNormalizer.php index 55b2e130e..dfc498c19 100644 --- a/Normalizer/DateTimeNormalizer.php +++ b/Normalizer/DateTimeNormalizer.php @@ -138,7 +138,7 @@ public function denormalize(mixed $data, string $type, ?string $format = null, a public function supportsDenormalization(mixed $data, string $type, ?string $format = null, array $context = []): bool { - return isset(self::SUPPORTED_TYPES[$type]); + return is_a($type, \DateTimeInterface::class, true); } /** diff --git a/Tests/Normalizer/DateTimeNormalizerTest.php b/Tests/Normalizer/DateTimeNormalizerTest.php index 5dbf36fbe..a744325d4 100644 --- a/Tests/Normalizer/DateTimeNormalizerTest.php +++ b/Tests/Normalizer/DateTimeNormalizerTest.php @@ -232,6 +232,8 @@ public function testSupportsDenormalization() $this->assertTrue($this->normalizer->supportsDenormalization('2016-01-01T00:00:00+00:00', \DateTimeInterface::class)); $this->assertTrue($this->normalizer->supportsDenormalization('2016-01-01T00:00:00+00:00', \DateTime::class)); $this->assertTrue($this->normalizer->supportsDenormalization('2016-01-01T00:00:00+00:00', \DateTimeImmutable::class)); + $this->assertTrue($this->normalizer->supportsDenormalization('2016-01-01T00:00:00+00:00', DateTimeImmutableChild::class)); + $this->assertTrue($this->normalizer->supportsDenormalization('2016-01-01T00:00:00+00:00', DateTimeChild::class)); $this->assertFalse($this->normalizer->supportsDenormalization('foo', 'Bar')); } @@ -241,6 +243,10 @@ public function testDenormalize() $this->assertEquals(new \DateTimeImmutable('2016/01/01', new \DateTimeZone('UTC')), $this->normalizer->denormalize('2016-01-01T00:00:00+00:00', \DateTimeImmutable::class)); $this->assertEquals(new \DateTime('2016/01/01', new \DateTimeZone('UTC')), $this->normalizer->denormalize('2016-01-01T00:00:00+00:00', \DateTime::class)); $this->assertEquals(new \DateTime('2016/01/01', new \DateTimeZone('UTC')), $this->normalizer->denormalize(' 2016-01-01T00:00:00+00:00 ', \DateTime::class)); + $this->assertEquals(new DateTimeImmutableChild('2016/01/01', new \DateTimeZone('UTC')), $this->normalizer->denormalize('2016-01-01T00:00:00+00:00', DateTimeImmutableChild::class)); + $this->assertEquals(new DateTimeImmutableChild('2016/01/01', new \DateTimeZone('UTC')), $this->normalizer->denormalize('2016-01-01T00:00:00+00:00', DateTimeImmutableChild::class)); + $this->assertEquals(new DateTimeChild('2016/01/01', new \DateTimeZone('UTC')), $this->normalizer->denormalize('2016-01-01T00:00:00+00:00', DateTimeChild::class)); + $this->assertEquals(new DateTimeChild('2016/01/01', new \DateTimeZone('UTC')), $this->normalizer->denormalize(' 2016-01-01T00:00:00+00:00 ', DateTimeChild::class)); $this->assertEquals(new \DateTimeImmutable('2023-05-06T17:35:34.000000+0000', new \DateTimeZone('UTC')), $this->normalizer->denormalize(1683394534, \DateTimeImmutable::class, null, [DateTimeNormalizer::FORMAT_KEY => 'U'])); $this->assertEquals(new \DateTimeImmutable('2023-05-06T17:35:34.123400+0000', new \DateTimeZone('UTC')), $this->normalizer->denormalize(1683394534.1234, \DateTimeImmutable::class, null, [DateTimeNormalizer::FORMAT_KEY => 'U.u'])); } @@ -387,3 +393,10 @@ public function testDenormalizeFormatMismatchThrowsException() $this->normalizer->denormalize('2016-01-01T00:00:00+00:00', \DateTimeInterface::class, null, [DateTimeNormalizer::FORMAT_KEY => 'Y-m-d|']); } } + +class DateTimeChild extends \DateTime +{ +} +class DateTimeImmutableChild extends \DateTimeImmutable +{ +} From a79b424babd6f328453610866bf12ffc353e26dc Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Fri, 30 Aug 2024 17:56:36 +0200 Subject: [PATCH 262/297] CS fixes --- CHANGELOG.md | 2 +- Tests/Normalizer/DateTimeNormalizerTest.php | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 40d486a25..cb0466ada 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,7 @@ CHANGELOG * Deprecate the `csv_escape_char` context option of `CsvEncoder` and the `CsvEncoder::ESCAPE_CHAR_KEY` constant * Deprecate `CsvEncoderContextBuilder::withEscapeChar()` method * Add `SnakeCaseToCamelCaseNameConverter` + * Support subclasses of `\DateTime` and `\DateTimeImmutable` for denormalization 7.1 --- @@ -18,7 +19,6 @@ CHANGELOG * Add `CamelCaseToSnakeCaseNameConverter::REQUIRE_SNAKE_CASE_PROPERTIES` context option * Deprecate `AbstractNormalizerContextBuilder::withDefaultContructorArguments(?array $defaultContructorArguments)`, use `withDefaultConstructorArguments(?array $defaultConstructorArguments)` instead (note the missing `s` character in Contructor word in deprecated method) * Add `XmlEncoder::CDATA_WRAPPING_PATTERN` context option - * Support subclasses of `\DateTime` and `\DateTimeImmutable` for denormalization 7.0 --- diff --git a/Tests/Normalizer/DateTimeNormalizerTest.php b/Tests/Normalizer/DateTimeNormalizerTest.php index a744325d4..81219652b 100644 --- a/Tests/Normalizer/DateTimeNormalizerTest.php +++ b/Tests/Normalizer/DateTimeNormalizerTest.php @@ -397,6 +397,7 @@ public function testDenormalizeFormatMismatchThrowsException() class DateTimeChild extends \DateTime { } + class DateTimeImmutableChild extends \DateTimeImmutable { } From cb6cff4433f9d2a675eee3e15448c5dd2cacce79 Mon Sep 17 00:00:00 2001 From: Alexandre Daubois Date: Thu, 12 Sep 2024 20:19:16 +0200 Subject: [PATCH 263/297] [Serializer][Uid] Add the `Uuid::FORMAT_RFC_9562` and `UidNormalizer::NORMALIZATION_FORMAT_RFC9562` constants --- CHANGELOG.md | 1 + Normalizer/UidNormalizer.php | 1 + 2 files changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index cb0466ada..b62f84ba2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,7 @@ CHANGELOG * Deprecate `CsvEncoderContextBuilder::withEscapeChar()` method * Add `SnakeCaseToCamelCaseNameConverter` * Support subclasses of `\DateTime` and `\DateTimeImmutable` for denormalization + * Add the `UidNormalizer::NORMALIZATION_FORMAT_RFC9562` constant 7.1 --- diff --git a/Normalizer/UidNormalizer.php b/Normalizer/UidNormalizer.php index b107c9d36..2e370bdd7 100644 --- a/Normalizer/UidNormalizer.php +++ b/Normalizer/UidNormalizer.php @@ -23,6 +23,7 @@ final class UidNormalizer implements NormalizerInterface, DenormalizerInterface public const NORMALIZATION_FORMAT_BASE58 = 'base58'; public const NORMALIZATION_FORMAT_BASE32 = 'base32'; public const NORMALIZATION_FORMAT_RFC4122 = 'rfc4122'; + public const NORMALIZATION_FORMAT_RFC9562 = self::NORMALIZATION_FORMAT_RFC4122; public const NORMALIZATION_FORMATS = [ self::NORMALIZATION_FORMAT_CANONICAL, self::NORMALIZATION_FORMAT_BASE58, From 5613373d6742561a0349ceee29bde4e8e245e83a Mon Sep 17 00:00:00 2001 From: Mihai Stancu Date: Tue, 10 Sep 2024 20:07:29 +0300 Subject: [PATCH 264/297] [Serializer] Fix for method named `get()` --- Normalizer/ObjectNormalizer.php | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/Normalizer/ObjectNormalizer.php b/Normalizer/ObjectNormalizer.php index a663083db..a8c887e50 100644 --- a/Normalizer/ObjectNormalizer.php +++ b/Normalizer/ObjectNormalizer.php @@ -100,14 +100,19 @@ protected function extractAttributes(object $object, ?string $format = null, arr $name = $reflMethod->name; $attributeName = null; - if (str_starts_with($name, 'get') || str_starts_with($name, 'has') || str_starts_with($name, 'can')) { + if (3 < \strlen($name) && match ($name[0]) { + 'g' => str_starts_with($name, 'get'), + 'h' => str_starts_with($name, 'has'), + 'c' => str_starts_with($name, 'can'), + default => false, + }) { // getters, hassers and canners $attributeName = substr($name, 3); if (!$reflClass->hasProperty($attributeName)) { $attributeName = lcfirst($attributeName); } - } elseif (str_starts_with($name, 'is')) { + } elseif ('is' !== $name && str_starts_with($name, 'is')) { // issers $attributeName = substr($name, 2); From a2e5a855105a0335fff5966684fc613c312f68cc Mon Sep 17 00:00:00 2001 From: valtzu Date: Fri, 13 Sep 2024 19:09:13 +0300 Subject: [PATCH 265/297] Fix `TemplateType` handling in `AbstractObjectNormalizer` --- .../AbstractObjectNormalizerTest.php | 76 +++++++++++++++++++ 1 file changed, 76 insertions(+) diff --git a/Tests/Normalizer/AbstractObjectNormalizerTest.php b/Tests/Normalizer/AbstractObjectNormalizerTest.php index a666185dd..26f9be4ad 100644 --- a/Tests/Normalizer/AbstractObjectNormalizerTest.php +++ b/Tests/Normalizer/AbstractObjectNormalizerTest.php @@ -13,6 +13,7 @@ use PHPUnit\Framework\TestCase; use Symfony\Component\PropertyInfo\Extractor\PhpDocExtractor; +use Symfony\Component\PropertyInfo\Extractor\PhpStanExtractor; use Symfony\Component\PropertyInfo\Extractor\ReflectionExtractor; use Symfony\Component\PropertyInfo\PropertyInfoExtractor; use Symfony\Component\PropertyInfo\Type as LegacyType; @@ -37,6 +38,7 @@ use Symfony\Component\Serializer\NameConverter\MetadataAwareNameConverter; use Symfony\Component\Serializer\Normalizer\AbstractNormalizer; use Symfony\Component\Serializer\Normalizer\AbstractObjectNormalizer; +use Symfony\Component\Serializer\Normalizer\ArrayDenormalizer; use Symfony\Component\Serializer\Normalizer\BackedEnumNormalizer; use Symfony\Component\Serializer\Normalizer\CustomNormalizer; use Symfony\Component\Serializer\Normalizer\DateTimeNormalizer; @@ -1247,6 +1249,52 @@ protected function isAllowedAttribute($classOrObject, string $attribute, ?string $this->assertInstanceOf(\ArrayObject::class, $actual->foo); $this->assertSame(1, $actual->foo->count()); } + + public function testTemplateTypeWhenAnObjectIsPassedToDenormalize() + { + $normalizer = new class ( + classMetadataFactory: new ClassMetadataFactory(new AttributeLoader()), + propertyTypeExtractor: new PropertyInfoExtractor(typeExtractors: [new PhpStanExtractor(), new ReflectionExtractor()]) + ) extends AbstractObjectNormalizerDummy { + protected function isAllowedAttribute($classOrObject, string $attribute, ?string $format = null, array $context = []): bool + { + return true; + } + }; + $serializer = new Serializer([$normalizer]); + $normalizer->setSerializer($serializer); + + $denormalizedData = $normalizer->denormalize(['value' => new DummyGenericsValue()], DummyGenericsValueWrapper::class); + + $this->assertInstanceOf(DummyGenericsValueWrapper::class, $denormalizedData); + $this->assertInstanceOf(DummyGenericsValue::class, $denormalizedData->value); + + $this->assertSame('dummy', $denormalizedData->value->type); + } + + public function testDenormalizeTemplateType() + { + $normalizer = new class ( + classMetadataFactory: new ClassMetadataFactory(new AttributeLoader()), + propertyTypeExtractor: new PropertyInfoExtractor(typeExtractors: [new PhpStanExtractor(), new ReflectionExtractor()]) + ) extends AbstractObjectNormalizerDummy { + protected function isAllowedAttribute($classOrObject, string $attribute, ?string $format = null, array $context = []): bool + { + return true; + } + }; + $serializer = new Serializer([new ArrayDenormalizer(), $normalizer]); + $normalizer->setSerializer($serializer); + + $denormalizedData = $normalizer->denormalize(['value' => ['type' => 'dummy'], 'values' => [['type' => 'dummy']]], DummyGenericsValueWrapper::class); + + $this->assertInstanceOf(DummyGenericsValueWrapper::class, $denormalizedData); + $this->assertInstanceOf(DummyGenericsValue::class, $denormalizedData->value); + $this->assertContainsOnlyInstancesOf(DummyGenericsValue::class, $denormalizedData->values); + $this->assertCount(1, $denormalizedData->values); + $this->assertSame('dummy', $denormalizedData->value->type); + $this->assertSame('dummy', $denormalizedData->values[0]->type); + } } class AbstractObjectNormalizerDummy extends AbstractObjectNormalizer @@ -1753,3 +1801,31 @@ public function getSupportedTypes(?string $format): array ]; } } + +#[DiscriminatorMap('type', ['dummy' => DummyGenericsValue::class])] +abstract class AbstractDummyGenericsValue +{ + public function __construct( + public string $type, + ) { + } +} + +class DummyGenericsValue extends AbstractDummyGenericsValue +{ + public function __construct() + { + parent::__construct('dummy'); + } +} + +/** + * @template T of AbstractDummyGenericsValue + */ +class DummyGenericsValueWrapper +{ + /** @var T */ + public mixed $value; + /** @var T[] */ + public array $values; +} From 60335737a9fd2c972126ce43344437d637085e77 Mon Sep 17 00:00:00 2001 From: Mathias Arlaud Date: Wed, 18 Sep 2024 17:33:32 +0200 Subject: [PATCH 266/297] [Serializer] Catch `NotNormalizableValueException` for variadic parameters --- Normalizer/AbstractNormalizer.php | 11 ++++++++- Tests/Fixtures/DummyWithVariadicParameter.php | 24 +++++++++++++++++++ Tests/SerializerTest.php | 15 ++++++++++++ 3 files changed, 49 insertions(+), 1 deletion(-) create mode 100644 Tests/Fixtures/DummyWithVariadicParameter.php diff --git a/Normalizer/AbstractNormalizer.php b/Normalizer/AbstractNormalizer.php index aeae375fb..c28a1f6cd 100644 --- a/Normalizer/AbstractNormalizer.php +++ b/Normalizer/AbstractNormalizer.php @@ -358,7 +358,16 @@ protected function instantiateObject(array &$data, string $class, array &$contex $variadicParameters = []; foreach ($data[$key] as $parameterKey => $parameterData) { - $variadicParameters[$parameterKey] = $this->denormalizeParameter($reflectionClass, $constructorParameter, $paramName, $parameterData, $attributeContext, $format); + try { + $variadicParameters[$parameterKey] = $this->denormalizeParameter($reflectionClass, $constructorParameter, $paramName, $parameterData, $attributeContext, $format); + } catch (NotNormalizableValueException $exception) { + if (!isset($context['not_normalizable_value_exceptions'])) { + throw $exception; + } + + $context['not_normalizable_value_exceptions'][] = $exception; + $params[$paramName] = $parameterData; + } } $params = array_merge(array_values($params), $variadicParameters); diff --git a/Tests/Fixtures/DummyWithVariadicParameter.php b/Tests/Fixtures/DummyWithVariadicParameter.php new file mode 100644 index 000000000..827111921 --- /dev/null +++ b/Tests/Fixtures/DummyWithVariadicParameter.php @@ -0,0 +1,24 @@ + + * + * 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\Uid\Uuid; + +class DummyWithVariadicParameter +{ + public array $variadic; + + public function __construct(Uuid ...$variadic) + { + $this->variadic = $variadic; + } +} diff --git a/Tests/SerializerTest.php b/Tests/SerializerTest.php index f7d364b7d..8f60ae1d4 100644 --- a/Tests/SerializerTest.php +++ b/Tests/SerializerTest.php @@ -61,6 +61,8 @@ use Symfony\Component\Serializer\Tests\Fixtures\DummyObjectWithEnumConstructor; use Symfony\Component\Serializer\Tests\Fixtures\DummyObjectWithEnumProperty; use Symfony\Component\Serializer\Tests\Fixtures\DummyWithObjectOrNull; +use Symfony\Component\Serializer\Tests\Fixtures\DummyWithVariadicParameter; +use Symfony\Component\Serializer\Tests\Fixtures\DummyWithVariadicProperty; use Symfony\Component\Serializer\Tests\Fixtures\FalseBuiltInDummy; use Symfony\Component\Serializer\Tests\Fixtures\FooImplementationDummy; use Symfony\Component\Serializer\Tests\Fixtures\FooInterfaceDummyDenormalizer; @@ -1637,6 +1639,19 @@ public function testPartialDenormalizationWithMissingConstructorTypes() $this->assertSame($expected, $exceptionsAsArray); } + + public function testPartialDenormalizationWithInvalidVariadicParameter() + { + $json = '{"variadic": ["a random string"]}'; + + $serializer = new Serializer([new UidNormalizer(), new ObjectNormalizer()], ['json' => new JsonEncoder()]); + + $this->expectException(PartialDenormalizationException::class); + + $serializer->deserialize($json, DummyWithVariadicParameter::class, 'json', [ + DenormalizerInterface::COLLECT_DENORMALIZATION_ERRORS => true, + ]); + } } class Model From 498bda022c46e0382e0a7d2aa0dbf9f250d07c11 Mon Sep 17 00:00:00 2001 From: Alexandre Daubois Date: Wed, 18 Sep 2024 13:33:46 +0200 Subject: [PATCH 267/297] Miscellaneous tests improvements --- Tests/Normalizer/PropertyNormalizerTest.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Tests/Normalizer/PropertyNormalizerTest.php b/Tests/Normalizer/PropertyNormalizerTest.php index 9773e65b7..e0e2f9dae 100644 --- a/Tests/Normalizer/PropertyNormalizerTest.php +++ b/Tests/Normalizer/PropertyNormalizerTest.php @@ -478,7 +478,7 @@ public function testMultiDimensionObject() RootDummy::class, 'any' ); - $this->assertEquals($root::class, RootDummy::class); + $this->assertSame(RootDummy::class, $root::class); // children (two dimension array) $this->assertCount(1, $root->children); From d93eac1ffd8e3b85070971cd2d95f2e9f7794575 Mon Sep 17 00:00:00 2001 From: HypeMC Date: Mon, 20 May 2024 06:59:56 +0200 Subject: [PATCH 268/297] [Serializer] Introduce named serializers --- CHANGELOG.md | 3 + DataCollector/SerializerDataCollector.php | 90 +-- Debug/TraceableEncoder.php | 5 +- Debug/TraceableNormalizer.php | 5 +- Debug/TraceableSerializer.php | 13 +- DependencyInjection/SerializerPass.php | 152 ++++- .../SerializerDataCollectorTest.php | 174 ++++-- Tests/Debug/TraceableEncoderTest.php | 26 +- Tests/Debug/TraceableNormalizerTest.php | 26 +- Tests/Debug/TraceableSerializerTest.php | 20 +- .../SerializerPassTest.php | 520 +++++++++++++++++- composer.json | 2 +- 12 files changed, 905 insertions(+), 131 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b62f84ba2..79a14b50f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,9 @@ CHANGELOG * Add `SnakeCaseToCamelCaseNameConverter` * Support subclasses of `\DateTime` and `\DateTimeImmutable` for denormalization * Add the `UidNormalizer::NORMALIZATION_FORMAT_RFC9562` constant + * Add support for configuring multiple serializer instances with different + default contexts, name converters, sets of normalizers and encoders + * Add support for collection profiles of multiple serializer instances 7.1 --- diff --git a/DataCollector/SerializerDataCollector.php b/DataCollector/SerializerDataCollector.php index 2880dea37..e87c51ca1 100644 --- a/DataCollector/SerializerDataCollector.php +++ b/DataCollector/SerializerDataCollector.php @@ -25,11 +25,22 @@ */ class SerializerDataCollector extends DataCollector implements LateDataCollectorInterface { + private const DATA_TEMPLATE = [ + 'serialize' => [], + 'deserialize' => [], + 'normalize' => [], + 'denormalize' => [], + 'encode' => [], + 'decode' => [], + ]; + + private array $dataGroupedByName; private array $collected = []; public function reset(): void { $this->data = []; + unset($this->dataGroupedByName); $this->collected = []; } @@ -43,14 +54,14 @@ public function getName(): string return 'serializer'; } - public function getData(): Data|array + public function getData(?string $name = null): Data|array { - return $this->data; + return null === $name ? $this->data : $this->getDataGroupedByName()[$name]; } - public function getHandledCount(): int + public function getHandledCount(?string $name = null): int { - return array_sum(array_map('count', $this->data)); + return array_sum(array_map('count', $this->getData($name))); } public function getTotalTime(): float @@ -64,110 +75,108 @@ public function getTotalTime(): float return $totalTime; } - public function collectSerialize(string $traceId, mixed $data, string $format, array $context, float $time, array $caller): void + public function getSerializerNames(): array + { + return array_keys($this->getDataGroupedByName()); + } + + public function collectSerialize(string $traceId, mixed $data, string $format, array $context, float $time, array $caller, string $name): void { unset($context[TraceableSerializer::DEBUG_TRACE_ID]); $this->collected[$traceId] = array_merge( $this->collected[$traceId] ?? [], - compact('data', 'format', 'context', 'time', 'caller'), + compact('data', 'format', 'context', 'time', 'caller', 'name'), ['method' => 'serialize'], ); } - public function collectDeserialize(string $traceId, mixed $data, string $type, string $format, array $context, float $time, array $caller): void + public function collectDeserialize(string $traceId, mixed $data, string $type, string $format, array $context, float $time, array $caller, string $name): void { unset($context[TraceableSerializer::DEBUG_TRACE_ID]); $this->collected[$traceId] = array_merge( $this->collected[$traceId] ?? [], - compact('data', 'format', 'type', 'context', 'time', 'caller'), + compact('data', 'format', 'type', 'context', 'time', 'caller', 'name'), ['method' => 'deserialize'], ); } - public function collectNormalize(string $traceId, mixed $data, ?string $format, array $context, float $time, array $caller): void + public function collectNormalize(string $traceId, mixed $data, ?string $format, array $context, float $time, array $caller, string $name): void { unset($context[TraceableSerializer::DEBUG_TRACE_ID]); $this->collected[$traceId] = array_merge( $this->collected[$traceId] ?? [], - compact('data', 'format', 'context', 'time', 'caller'), + compact('data', 'format', 'context', 'time', 'caller', 'name'), ['method' => 'normalize'], ); } - public function collectDenormalize(string $traceId, mixed $data, string $type, ?string $format, array $context, float $time, array $caller): void + public function collectDenormalize(string $traceId, mixed $data, string $type, ?string $format, array $context, float $time, array $caller, string $name): void { unset($context[TraceableSerializer::DEBUG_TRACE_ID]); $this->collected[$traceId] = array_merge( $this->collected[$traceId] ?? [], - compact('data', 'format', 'type', 'context', 'time', 'caller'), + compact('data', 'format', 'type', 'context', 'time', 'caller', 'name'), ['method' => 'denormalize'], ); } - public function collectEncode(string $traceId, mixed $data, ?string $format, array $context, float $time, array $caller): void + public function collectEncode(string $traceId, mixed $data, ?string $format, array $context, float $time, array $caller, string $name): void { unset($context[TraceableSerializer::DEBUG_TRACE_ID]); $this->collected[$traceId] = array_merge( $this->collected[$traceId] ?? [], - compact('data', 'format', 'context', 'time', 'caller'), + compact('data', 'format', 'context', 'time', 'caller', 'name'), ['method' => 'encode'], ); } - public function collectDecode(string $traceId, mixed $data, ?string $format, array $context, float $time, array $caller): void + public function collectDecode(string $traceId, mixed $data, ?string $format, array $context, float $time, array $caller, string $name): void { unset($context[TraceableSerializer::DEBUG_TRACE_ID]); $this->collected[$traceId] = array_merge( $this->collected[$traceId] ?? [], - compact('data', 'format', 'context', 'time', 'caller'), + compact('data', 'format', 'context', 'time', 'caller', 'name'), ['method' => 'decode'], ); } - public function collectNormalization(string $traceId, string $normalizer, float $time): void + public function collectNormalization(string $traceId, string $normalizer, float $time, string $name): void { $method = 'normalize'; - $this->collected[$traceId]['normalization'][] = compact('normalizer', 'method', 'time'); + $this->collected[$traceId]['normalization'][] = compact('normalizer', 'method', 'time', 'name'); } - public function collectDenormalization(string $traceId, string $normalizer, float $time): void + public function collectDenormalization(string $traceId, string $normalizer, float $time, string $name): void { $method = 'denormalize'; - $this->collected[$traceId]['normalization'][] = compact('normalizer', 'method', 'time'); + $this->collected[$traceId]['normalization'][] = compact('normalizer', 'method', 'time', 'name'); } - public function collectEncoding(string $traceId, string $encoder, float $time): void + public function collectEncoding(string $traceId, string $encoder, float $time, string $name): void { $method = 'encode'; - $this->collected[$traceId]['encoding'][] = compact('encoder', 'method', 'time'); + $this->collected[$traceId]['encoding'][] = compact('encoder', 'method', 'time', 'name'); } - public function collectDecoding(string $traceId, string $encoder, float $time): void + public function collectDecoding(string $traceId, string $encoder, float $time, string $name): void { $method = 'decode'; - $this->collected[$traceId]['encoding'][] = compact('encoder', 'method', 'time'); + $this->collected[$traceId]['encoding'][] = compact('encoder', 'method', 'time', 'name'); } public function lateCollect(): void { - $this->data = [ - 'serialize' => [], - 'deserialize' => [], - 'normalize' => [], - 'denormalize' => [], - 'encode' => [], - 'decode' => [], - ]; + $this->data = self::DATA_TEMPLATE; foreach ($this->collected as $collected) { if (!isset($collected['data'])) { @@ -184,6 +193,7 @@ public function lateCollect(): void 'normalization' => [], 'encoding' => [], 'caller' => $collected['caller'] ?? null, + 'name' => $collected['name'], ]; if (isset($collected['normalization'])) { @@ -220,6 +230,22 @@ public function lateCollect(): void } } + private function getDataGroupedByName(): array + { + if (!isset($this->dataGroupedByName)) { + $this->dataGroupedByName = []; + + foreach ($this->data as $method => $items) { + foreach ($items as $item) { + $this->dataGroupedByName[$item['name']] ??= self::DATA_TEMPLATE; + $this->dataGroupedByName[$item['name']][$method][] = $item; + } + } + } + + return $this->dataGroupedByName; + } + private function getMethodLocation(string $class, string $method): array { $reflection = new \ReflectionClass($class); diff --git a/Debug/TraceableEncoder.php b/Debug/TraceableEncoder.php index 42bf4868a..39e75e34f 100644 --- a/Debug/TraceableEncoder.php +++ b/Debug/TraceableEncoder.php @@ -30,6 +30,7 @@ class TraceableEncoder implements EncoderInterface, DecoderInterface, Serializer public function __construct( private EncoderInterface|DecoderInterface $encoder, private SerializerDataCollector $dataCollector, + private readonly string $serializerName = 'default', ) { } @@ -44,7 +45,7 @@ public function encode(mixed $data, string $format, array $context = []): string $time = microtime(true) - $startTime; if ($traceId = ($context[TraceableSerializer::DEBUG_TRACE_ID] ?? null)) { - $this->dataCollector->collectEncoding($traceId, $this->encoder::class, $time); + $this->dataCollector->collectEncoding($traceId, $this->encoder::class, $time, $this->serializerName); } return $encoded; @@ -70,7 +71,7 @@ public function decode(string $data, string $format, array $context = []): mixed $time = microtime(true) - $startTime; if ($traceId = ($context[TraceableSerializer::DEBUG_TRACE_ID] ?? null)) { - $this->dataCollector->collectDecoding($traceId, $this->encoder::class, $time); + $this->dataCollector->collectDecoding($traceId, $this->encoder::class, $time, $this->serializerName); } return $encoded; diff --git a/Debug/TraceableNormalizer.php b/Debug/TraceableNormalizer.php index c80636752..1b143e295 100644 --- a/Debug/TraceableNormalizer.php +++ b/Debug/TraceableNormalizer.php @@ -31,6 +31,7 @@ class TraceableNormalizer implements NormalizerInterface, DenormalizerInterface, public function __construct( private NormalizerInterface|DenormalizerInterface $normalizer, private SerializerDataCollector $dataCollector, + private readonly string $serializerName = 'default', ) { } @@ -50,7 +51,7 @@ public function normalize(mixed $object, ?string $format = null, array $context $time = microtime(true) - $startTime; if ($traceId = ($context[TraceableSerializer::DEBUG_TRACE_ID] ?? null)) { - $this->dataCollector->collectNormalization($traceId, $this->normalizer::class, $time); + $this->dataCollector->collectNormalization($traceId, $this->normalizer::class, $time, $this->serializerName); } return $normalized; @@ -76,7 +77,7 @@ public function denormalize(mixed $data, string $type, ?string $format = null, a $time = microtime(true) - $startTime; if ($traceId = ($context[TraceableSerializer::DEBUG_TRACE_ID] ?? null)) { - $this->dataCollector->collectDenormalization($traceId, $this->normalizer::class, $time); + $this->dataCollector->collectDenormalization($traceId, $this->normalizer::class, $time, $this->serializerName); } return $denormalized; diff --git a/Debug/TraceableSerializer.php b/Debug/TraceableSerializer.php index ab766bf43..a05bf4bf8 100644 --- a/Debug/TraceableSerializer.php +++ b/Debug/TraceableSerializer.php @@ -32,6 +32,7 @@ class TraceableSerializer implements SerializerInterface, NormalizerInterface, D public function __construct( private SerializerInterface&NormalizerInterface&DenormalizerInterface&EncoderInterface&DecoderInterface $serializer, private SerializerDataCollector $dataCollector, + private readonly string $serializerName = 'default', ) { } @@ -45,7 +46,7 @@ public function serialize(mixed $data, string $format, array $context = []): str $caller = $this->getCaller(__FUNCTION__, SerializerInterface::class); - $this->dataCollector->collectSerialize($traceId, $data, $format, $context, $time, $caller); + $this->dataCollector->collectSerialize($traceId, $data, $format, $context, $time, $caller, $this->serializerName); return $result; } @@ -60,7 +61,7 @@ public function deserialize(mixed $data, string $type, string $format, array $co $caller = $this->getCaller(__FUNCTION__, SerializerInterface::class); - $this->dataCollector->collectDeserialize($traceId, $data, $type, $format, $context, $time, $caller); + $this->dataCollector->collectDeserialize($traceId, $data, $type, $format, $context, $time, $caller, $this->serializerName); return $result; } @@ -75,7 +76,7 @@ public function normalize(mixed $object, ?string $format = null, array $context $caller = $this->getCaller(__FUNCTION__, NormalizerInterface::class); - $this->dataCollector->collectNormalize($traceId, $object, $format, $context, $time, $caller); + $this->dataCollector->collectNormalize($traceId, $object, $format, $context, $time, $caller, $this->serializerName); return $result; } @@ -90,7 +91,7 @@ public function denormalize(mixed $data, string $type, ?string $format = null, a $caller = $this->getCaller(__FUNCTION__, DenormalizerInterface::class); - $this->dataCollector->collectDenormalize($traceId, $data, $type, $format, $context, $time, $caller); + $this->dataCollector->collectDenormalize($traceId, $data, $type, $format, $context, $time, $caller, $this->serializerName); return $result; } @@ -105,7 +106,7 @@ public function encode(mixed $data, string $format, array $context = []): string $caller = $this->getCaller(__FUNCTION__, EncoderInterface::class); - $this->dataCollector->collectEncode($traceId, $data, $format, $context, $time, $caller); + $this->dataCollector->collectEncode($traceId, $data, $format, $context, $time, $caller, $this->serializerName); return $result; } @@ -120,7 +121,7 @@ public function decode(string $data, string $format, array $context = []): mixed $caller = $this->getCaller(__FUNCTION__, DecoderInterface::class); - $this->dataCollector->collectDecode($traceId, $data, $format, $context, $time, $caller); + $this->dataCollector->collectDecode($traceId, $data, $format, $context, $time, $caller, $this->serializerName); return $result; } diff --git a/DependencyInjection/SerializerPass.php b/DependencyInjection/SerializerPass.php index 2a429054b..bc1c6e10e 100644 --- a/DependencyInjection/SerializerPass.php +++ b/DependencyInjection/SerializerPass.php @@ -19,6 +19,7 @@ use Symfony\Component\DependencyInjection\Reference; use Symfony\Component\Serializer\Debug\TraceableEncoder; use Symfony\Component\Serializer\Debug\TraceableNormalizer; +use Symfony\Component\Serializer\SerializerInterface; /** * Adds all services with the tags "serializer.encoder" and "serializer.normalizer" as @@ -31,44 +32,177 @@ class SerializerPass implements CompilerPassInterface { use PriorityTaggedServiceTrait; + private const NAME_CONVERTER_METADATA_AWARE_ID = 'serializer.name_converter.metadata_aware'; + public function process(ContainerBuilder $container): void { if (!$container->hasDefinition('serializer')) { return; } - if (!$normalizers = $this->findAndSortTaggedServices('serializer.normalizer', $container)) { + $namedSerializers = $container->hasParameter('.serializer.named_serializers') + ? $container->getParameter('.serializer.named_serializers') : []; + + $this->createNamedSerializerTags($container, 'serializer.normalizer', 'include_built_in_normalizers', $namedSerializers); + $this->createNamedSerializerTags($container, 'serializer.encoder', 'include_built_in_encoders', $namedSerializers); + + if (!$normalizers = $this->findAndSortTaggedServices('serializer.normalizer.default', $container)) { throw new RuntimeException('You must tag at least one service as "serializer.normalizer" to use the "serializer" service.'); } - if (!$encoders = $this->findAndSortTaggedServices('serializer.encoder', $container)) { + if (!$encoders = $this->findAndSortTaggedServices('serializer.encoder.default', $container)) { throw new RuntimeException('You must tag at least one service as "serializer.encoder" to use the "serializer" service.'); } if ($container->hasParameter('serializer.default_context')) { $defaultContext = $container->getParameter('serializer.default_context'); - foreach (array_merge($normalizers, $encoders) as $service) { - $definition = $container->getDefinition($service); - $definition->setBindings(['array $defaultContext' => new BoundArgument($defaultContext, false)] + $definition->getBindings()); + $this->bindDefaultContext($container, array_merge($normalizers, $encoders), $defaultContext); + $container->getParameterBag()->remove('serializer.default_context'); + } + + $this->configureSerializer($container, 'serializer', $normalizers, $encoders, 'default'); + + if ($namedSerializers) { + $this->configureNamedSerializers($container); + } + } + + private function createNamedSerializerTags(ContainerBuilder $container, string $tagName, string $configName, array $namedSerializers): void + { + $serializerNames = array_keys($namedSerializers); + $withBuiltIn = array_filter($serializerNames, fn (string $name) => $namedSerializers[$name][$configName] ?? false); + + foreach ($container->findTaggedServiceIds($tagName) as $serviceId => $tags) { + $definition = $container->getDefinition($serviceId); + + foreach ($tags as $tag) { + $names = (array) ($tag['serializer'] ?? []); + + if (!$names) { + $names = ['default']; + } elseif (\in_array('*', $names, true)) { + $names = array_unique(['default', ...$serializerNames]); + } + + if ($tag['built_in'] ?? false) { + $names = array_unique(['default', ...$names, ...$withBuiltIn]); + } + + unset($tag['serializer'], $tag['built_in']); + + foreach ($names as $name) { + $definition->addTag($tagName.'.'.$name, $tag); + } } + } + } - $container->getParameterBag()->remove('serializer.default_context'); + private function bindDefaultContext(ContainerBuilder $container, array $services, array $defaultContext): void + { + foreach ($services as $id) { + $definition = $container->getDefinition((string) $id); + $definition->setBindings(['array $defaultContext' => new BoundArgument($defaultContext, false)] + $definition->getBindings()); } + } + private function configureSerializer(ContainerBuilder $container, string $id, array $normalizers, array $encoders, string $serializerName): void + { if ($container->getParameter('kernel.debug') && $container->hasDefinition('serializer.data_collector')) { foreach ($normalizers as $i => $normalizer) { $normalizers[$i] = $container->register('.debug.serializer.normalizer.'.$normalizer, TraceableNormalizer::class) - ->setArguments([$normalizer, new Reference('serializer.data_collector')]); + ->setArguments([$normalizer, new Reference('serializer.data_collector'), $serializerName]); } foreach ($encoders as $i => $encoder) { $encoders[$i] = $container->register('.debug.serializer.encoder.'.$encoder, TraceableEncoder::class) - ->setArguments([$encoder, new Reference('serializer.data_collector')]); + ->setArguments([$encoder, new Reference('serializer.data_collector'), $serializerName]); } } - $serializerDefinition = $container->getDefinition('serializer'); + $serializerDefinition = $container->getDefinition($id); $serializerDefinition->replaceArgument(0, $normalizers); $serializerDefinition->replaceArgument(1, $encoders); } + + private function configureNamedSerializers(ContainerBuilder $container): void + { + $defaultSerializerNameConverter = $container->hasParameter('.serializer.name_converter') + ? $container->getParameter('.serializer.name_converter') : null; + + foreach ($container->getParameter('.serializer.named_serializers') as $serializerName => $config) { + $config += ['default_context' => [], 'name_converter' => null]; + $serializerId = 'serializer.'.$serializerName; + + if (!$normalizers = $this->findAndSortTaggedServices('serializer.normalizer.'.$serializerName, $container)) { + throw new RuntimeException(\sprintf('The named serializer "%1$s" requires at least one registered normalizer. Tag the normalizers as "serializer.normalizer" with the "serializer" attribute set to "%1$s".', $serializerName)); + } + + if (!$encoders = $this->findAndSortTaggedServices('serializer.encoder.'.$serializerName, $container)) { + throw new RuntimeException(\sprintf('The named serializer "%1$s" requires at least one registered encoder. Tag the encoders as "serializer.encoder" with the "serializer" attribute set to "%1$s".', $serializerName)); + } + + $config['name_converter'] = $defaultSerializerNameConverter !== $config['name_converter'] + ? $this->buildChildNameConverterDefinition($container, $config['name_converter']) + : self::NAME_CONVERTER_METADATA_AWARE_ID; + + $normalizers = $this->buildChildDefinitions($container, $serializerName, $normalizers, $config); + $encoders = $this->buildChildDefinitions($container, $serializerName, $encoders, $config); + + $this->bindDefaultContext($container, array_merge($normalizers, $encoders), $config['default_context']); + + $container->registerChild($serializerId, 'serializer'); + $container->registerAliasForArgument($serializerId, SerializerInterface::class, $serializerName.'.serializer'); + + $this->configureSerializer($container, $serializerId, $normalizers, $encoders, $serializerName); + + if ($container->getParameter('kernel.debug') && $container->hasDefinition('debug.serializer')) { + $container->registerChild($debugId = 'debug.'.$serializerId, 'debug.serializer') + ->setDecoratedService($serializerId) + ->replaceArgument(0, new Reference($debugId.'.inner')) + ->replaceArgument(2, $serializerName); + } + } + } + + private function buildChildNameConverterDefinition(ContainerBuilder $container, ?string $nameConverter): ?string + { + $childId = self::NAME_CONVERTER_METADATA_AWARE_ID.'.'.ContainerBuilder::hash($nameConverter); + + if (!$container->hasDefinition($childId)) { + $childDefinition = $container->registerChild($childId, self::NAME_CONVERTER_METADATA_AWARE_ID.'.abstract'); + if (null !== $nameConverter) { + $childDefinition->addArgument(new Reference($nameConverter)); + } + } + + return $childId; + } + + private function buildChildDefinitions(ContainerBuilder $container, string $serializerName, array $services, array $config): array + { + foreach ($services as &$id) { + $childId = $id.'.'.$serializerName; + + $definition = $container->registerChild($childId, (string) $id); + + if (null !== $nameConverterIndex = $this->findNameConverterIndex($container, (string) $id)) { + $definition->replaceArgument($nameConverterIndex, new Reference($config['name_converter'])); + } + + $id = new Reference($childId); + } + + return $services; + } + + private function findNameConverterIndex(ContainerBuilder $container, string $id): int|string|null + { + foreach ($container->getDefinition($id)->getArguments() as $index => $argument) { + if ($argument instanceof Reference && self::NAME_CONVERTER_METADATA_AWARE_ID === (string) $argument) { + return $index; + } + } + + return null; + } } diff --git a/Tests/DataCollector/SerializerDataCollectorTest.php b/Tests/DataCollector/SerializerDataCollectorTest.php index aebde8efa..6a26565a8 100644 --- a/Tests/DataCollector/SerializerDataCollectorTest.php +++ b/Tests/DataCollector/SerializerDataCollectorTest.php @@ -25,8 +25,8 @@ public function testCollectSerialize() $dataCollector = new SerializerDataCollector(); $caller = ['name' => 'Foo.php', 'file' => 'src/Foo.php', 'line' => 123]; - $dataCollector->collectSerialize('traceIdOne', 'data', 'format', ['foo' => 'bar'], 1.0, $caller); - $dataCollector->collectDeserialize('traceIdTwo', 'data', 'type', 'format', ['foo' => 'bar'], 1.0, $caller); + $dataCollector->collectSerialize('traceIdOne', 'data', 'format', ['foo' => 'bar'], 1.0, $caller, 'default'); + $dataCollector->collectDeserialize('traceIdTwo', 'data', 'type', 'format', ['foo' => 'bar'], 1.0, $caller, 'default'); $dataCollector->lateCollect(); $collectedData = $this->castCollectedData($dataCollector->getData()); @@ -41,6 +41,7 @@ public function testCollectSerialize() 'normalization' => [], 'encoding' => [], 'caller' => $caller, + 'name' => 'default', ]], $collectedData['serialize']); $this->assertSame([[ @@ -53,6 +54,7 @@ public function testCollectSerialize() 'normalization' => [], 'encoding' => [], 'caller' => $caller, + 'name' => 'default', ]], $collectedData['deserialize']); } @@ -61,8 +63,8 @@ public function testCollectNormalize() $dataCollector = new SerializerDataCollector(); $caller = ['name' => 'Foo.php', 'file' => 'src/Foo.php', 'line' => 123]; - $dataCollector->collectNormalize('traceIdOne', 'data', 'format', ['foo' => 'bar'], 1.0, $caller); - $dataCollector->collectDenormalize('traceIdTwo', 'data', 'type', 'format', ['foo' => 'bar'], 1.0, $caller); + $dataCollector->collectNormalize('traceIdOne', 'data', 'format', ['foo' => 'bar'], 1.0, $caller, 'default'); + $dataCollector->collectDenormalize('traceIdTwo', 'data', 'type', 'format', ['foo' => 'bar'], 1.0, $caller, 'default'); $dataCollector->lateCollect(); $collectedData = $this->castCollectedData($dataCollector->getData()); @@ -77,6 +79,7 @@ public function testCollectNormalize() 'normalization' => [], 'encoding' => [], 'caller' => $caller, + 'name' => 'default', ]], $collectedData['normalize']); $this->assertSame([[ @@ -89,6 +92,7 @@ public function testCollectNormalize() 'normalization' => [], 'encoding' => [], 'caller' => $caller, + 'name' => 'default', ]], $collectedData['denormalize']); } @@ -97,8 +101,8 @@ public function testCollectEncode() $dataCollector = new SerializerDataCollector(); $caller = ['name' => 'Foo.php', 'file' => 'src/Foo.php', 'line' => 123]; - $dataCollector->collectEncode('traceIdOne', 'data', 'format', ['foo' => 'bar'], 1.0, $caller); - $dataCollector->collectDecode('traceIdTwo', 'data', 'format', ['foo' => 'bar'], 1.0, $caller); + $dataCollector->collectEncode('traceIdOne', 'data', 'format', ['foo' => 'bar'], 1.0, $caller, 'default'); + $dataCollector->collectDecode('traceIdTwo', 'data', 'format', ['foo' => 'bar'], 1.0, $caller, 'default'); $dataCollector->lateCollect(); $collectedData = $this->castCollectedData($dataCollector->getData()); @@ -113,6 +117,7 @@ public function testCollectEncode() 'normalization' => [], 'encoding' => [], 'caller' => $caller, + 'name' => 'default', ]], $collectedData['encode']); $this->assertSame([[ @@ -125,6 +130,7 @@ public function testCollectEncode() 'normalization' => [], 'encoding' => [], 'caller' => $caller, + 'name' => 'default', ]], $collectedData['decode']); } @@ -133,18 +139,18 @@ public function testCollectNormalization() $dataCollector = new SerializerDataCollector(); $caller = ['name' => 'Foo.php', 'file' => 'src/Foo.php', 'line' => 123]; - $dataCollector->collectNormalize('traceIdOne', 'data', 'format', ['foo' => 'bar'], 20.0, $caller); - $dataCollector->collectDenormalize('traceIdTwo', 'data', 'type', 'format', ['foo' => 'bar'], 20.0, $caller); + $dataCollector->collectNormalize('traceIdOne', 'data', 'format', ['foo' => 'bar'], 20.0, $caller, 'default'); + $dataCollector->collectDenormalize('traceIdTwo', 'data', 'type', 'format', ['foo' => 'bar'], 20.0, $caller, 'default'); - $dataCollector->collectNormalization('traceIdOne', DateTimeNormalizer::class, 1.0); - $dataCollector->collectNormalization('traceIdOne', DateTimeNormalizer::class, 2.0); - $dataCollector->collectNormalization('traceIdOne', ObjectNormalizer::class, 5.0); - $dataCollector->collectNormalization('traceIdOne', ObjectNormalizer::class, 10.0); + $dataCollector->collectNormalization('traceIdOne', DateTimeNormalizer::class, 1.0, 'default'); + $dataCollector->collectNormalization('traceIdOne', DateTimeNormalizer::class, 2.0, 'default'); + $dataCollector->collectNormalization('traceIdOne', ObjectNormalizer::class, 5.0, 'default'); + $dataCollector->collectNormalization('traceIdOne', ObjectNormalizer::class, 10.0, 'default'); - $dataCollector->collectNormalization('traceIdTwo', DateTimeNormalizer::class, 1.0); - $dataCollector->collectNormalization('traceIdTwo', DateTimeNormalizer::class, 2.0); - $dataCollector->collectNormalization('traceIdTwo', ObjectNormalizer::class, 5.0); - $dataCollector->collectNormalization('traceIdTwo', ObjectNormalizer::class, 10.0); + $dataCollector->collectNormalization('traceIdTwo', DateTimeNormalizer::class, 1.0, 'default'); + $dataCollector->collectNormalization('traceIdTwo', DateTimeNormalizer::class, 2.0, 'default'); + $dataCollector->collectNormalization('traceIdTwo', ObjectNormalizer::class, 5.0, 'default'); + $dataCollector->collectNormalization('traceIdTwo', ObjectNormalizer::class, 10.0, 'default'); $dataCollector->lateCollect(); $collectedData = $dataCollector->getData(); @@ -189,18 +195,18 @@ public function testCollectEncoding() $dataCollector = new SerializerDataCollector(); $caller = ['name' => 'Foo.php', 'file' => 'src/Foo.php', 'line' => 123]; - $dataCollector->collectEncode('traceIdOne', 'data', 'format', ['foo' => 'bar'], 20.0, $caller); - $dataCollector->collectDecode('traceIdTwo', 'data', 'format', ['foo' => 'bar'], 20.0, $caller); + $dataCollector->collectEncode('traceIdOne', 'data', 'format', ['foo' => 'bar'], 20.0, $caller, 'default'); + $dataCollector->collectDecode('traceIdTwo', 'data', 'format', ['foo' => 'bar'], 20.0, $caller, 'default'); - $dataCollector->collectEncoding('traceIdOne', JsonEncoder::class, 1.0); - $dataCollector->collectEncoding('traceIdOne', JsonEncoder::class, 2.0); - $dataCollector->collectEncoding('traceIdOne', CsvEncoder::class, 5.0); - $dataCollector->collectEncoding('traceIdOne', CsvEncoder::class, 10.0); + $dataCollector->collectEncoding('traceIdOne', JsonEncoder::class, 1.0, 'default'); + $dataCollector->collectEncoding('traceIdOne', JsonEncoder::class, 2.0, 'default'); + $dataCollector->collectEncoding('traceIdOne', CsvEncoder::class, 5.0, 'default'); + $dataCollector->collectEncoding('traceIdOne', CsvEncoder::class, 10.0, 'default'); - $dataCollector->collectDecoding('traceIdTwo', JsonEncoder::class, 1.0); - $dataCollector->collectDecoding('traceIdTwo', JsonEncoder::class, 2.0); - $dataCollector->collectDecoding('traceIdTwo', CsvEncoder::class, 5.0); - $dataCollector->collectDecoding('traceIdTwo', CsvEncoder::class, 10.0); + $dataCollector->collectDecoding('traceIdTwo', JsonEncoder::class, 1.0, 'default'); + $dataCollector->collectDecoding('traceIdTwo', JsonEncoder::class, 2.0, 'default'); + $dataCollector->collectDecoding('traceIdTwo', CsvEncoder::class, 5.0, 'default'); + $dataCollector->collectDecoding('traceIdTwo', CsvEncoder::class, 10.0, 'default'); $dataCollector->lateCollect(); $collectedData = $dataCollector->getData(); @@ -245,13 +251,13 @@ public function testCountHandled() $dataCollector = new SerializerDataCollector(); $caller = ['name' => 'Foo.php', 'file' => 'src/Foo.php', 'line' => 123]; - $dataCollector->collectSerialize('traceIdOne', 'data', 'format', ['foo' => 'bar'], 1.0, $caller); - $dataCollector->collectDeserialize('traceIdTwo', 'data', 'type', 'format', ['foo' => 'bar'], 1.0, $caller); - $dataCollector->collectNormalize('traceIdThree', 'data', 'format', ['foo' => 'bar'], 20.0, $caller); - $dataCollector->collectDenormalize('traceIdFour', 'data', 'type', 'format', ['foo' => 'bar'], 20.0, $caller); - $dataCollector->collectEncode('traceIdFive', 'data', 'format', ['foo' => 'bar'], 20.0, $caller); - $dataCollector->collectDecode('traceIdSix', 'data', 'format', ['foo' => 'bar'], 20.0, $caller); - $dataCollector->collectSerialize('traceIdSeven', 'data', 'format', ['foo' => 'bar'], 1.0, $caller); + $dataCollector->collectSerialize('traceIdOne', 'data', 'format', ['foo' => 'bar'], 1.0, $caller, 'default'); + $dataCollector->collectDeserialize('traceIdTwo', 'data', 'type', 'format', ['foo' => 'bar'], 1.0, $caller, 'default'); + $dataCollector->collectNormalize('traceIdThree', 'data', 'format', ['foo' => 'bar'], 20.0, $caller, 'default'); + $dataCollector->collectDenormalize('traceIdFour', 'data', 'type', 'format', ['foo' => 'bar'], 20.0, $caller, 'default'); + $dataCollector->collectEncode('traceIdFive', 'data', 'format', ['foo' => 'bar'], 20.0, $caller, 'default'); + $dataCollector->collectDecode('traceIdSix', 'data', 'format', ['foo' => 'bar'], 20.0, $caller, 'default'); + $dataCollector->collectSerialize('traceIdSeven', 'data', 'format', ['foo' => 'bar'], 1.0, $caller, 'default'); $dataCollector->lateCollect(); @@ -264,13 +270,13 @@ public function testGetTotalTime() $caller = ['name' => 'Foo.php', 'file' => 'src/Foo.php', 'line' => 123]; - $dataCollector->collectSerialize('traceIdOne', 'data', 'format', ['foo' => 'bar'], 1.0, $caller); - $dataCollector->collectDeserialize('traceIdTwo', 'data', 'type', 'format', ['foo' => 'bar'], 2.0, $caller); - $dataCollector->collectNormalize('traceIdThree', 'data', 'format', ['foo' => 'bar'], 3.0, $caller); - $dataCollector->collectDenormalize('traceIdFour', 'data', 'type', 'format', ['foo' => 'bar'], 4.0, $caller); - $dataCollector->collectEncode('traceIdFive', 'data', 'format', ['foo' => 'bar'], 5.0, $caller); - $dataCollector->collectDecode('traceIdSix', 'data', 'format', ['foo' => 'bar'], 6.0, $caller); - $dataCollector->collectSerialize('traceIdSeven', 'data', 'format', ['foo' => 'bar'], 7.0, $caller); + $dataCollector->collectSerialize('traceIdOne', 'data', 'format', ['foo' => 'bar'], 1.0, $caller, 'default'); + $dataCollector->collectDeserialize('traceIdTwo', 'data', 'type', 'format', ['foo' => 'bar'], 2.0, $caller, 'default'); + $dataCollector->collectNormalize('traceIdThree', 'data', 'format', ['foo' => 'bar'], 3.0, $caller, 'default'); + $dataCollector->collectDenormalize('traceIdFour', 'data', 'type', 'format', ['foo' => 'bar'], 4.0, $caller, 'default'); + $dataCollector->collectEncode('traceIdFive', 'data', 'format', ['foo' => 'bar'], 5.0, $caller, 'default'); + $dataCollector->collectDecode('traceIdSix', 'data', 'format', ['foo' => 'bar'], 6.0, $caller, 'default'); + $dataCollector->collectSerialize('traceIdSeven', 'data', 'format', ['foo' => 'bar'], 7.0, $caller, 'default'); $dataCollector->lateCollect(); @@ -282,7 +288,7 @@ public function testReset() $dataCollector = new SerializerDataCollector(); $caller = ['name' => 'Foo.php', 'file' => 'src/Foo.php', 'line' => 123]; - $dataCollector->collectSerialize('traceIdOne', 'data', 'format', ['foo' => 'bar'], 1.0, $caller); + $dataCollector->collectSerialize('traceIdOne', 'data', 'format', ['foo' => 'bar'], 1.0, $caller, 'default'); $dataCollector->lateCollect(); $this->assertNotSame([], $dataCollector->getData()); @@ -295,10 +301,10 @@ public function testDoNotCollectPartialTraces() { $dataCollector = new SerializerDataCollector(); - $dataCollector->collectNormalization('traceIdOne', DateTimeNormalizer::class, 1.0); - $dataCollector->collectDenormalization('traceIdTwo', DateTimeNormalizer::class, 1.0); - $dataCollector->collectEncoding('traceIdThree', CsvEncoder::class, 10.0); - $dataCollector->collectDecoding('traceIdFour', JsonEncoder::class, 1.0); + $dataCollector->collectNormalization('traceIdOne', DateTimeNormalizer::class, 1.0, 'default'); + $dataCollector->collectDenormalization('traceIdTwo', DateTimeNormalizer::class, 1.0, 'default'); + $dataCollector->collectEncoding('traceIdThree', CsvEncoder::class, 10.0, 'default'); + $dataCollector->collectDecoding('traceIdFour', JsonEncoder::class, 1.0, 'default'); $dataCollector->lateCollect(); @@ -312,6 +318,84 @@ public function testDoNotCollectPartialTraces() $this->assertSame([], $data['decode']); } + public function testNamedSerializers() + { + $dataCollector = new SerializerDataCollector(); + + $caller = ['name' => 'Foo.php', 'file' => 'src/Foo.php', 'line' => 123]; + $dataCollector->collectNormalization('traceIdOne', DateTimeNormalizer::class, 3.0, 'default'); + $dataCollector->collectEncoding('traceIdOne', CsvEncoder::class, 4.0, 'default'); + $dataCollector->collectSerialize('traceIdOne', 'data', 'format', ['foo' => 'bar'], 7.0, $caller, 'default'); + $dataCollector->collectNormalization('traceIdTwo', ObjectNormalizer::class, 3.0, 'default'); + $dataCollector->collectNormalize('traceIdTwo', 'data', 'format', ['foo' => 'bar'], 5.0, $caller, 'default'); + + $dataCollector->collectEncoding('traceIdThree', JsonEncoder::class, 4.0, 'api'); + $dataCollector->collectEncode('traceIdThree', 'data', 'format', ['foo' => 'bar'], 5.0, $caller, 'api'); + $dataCollector->collectDenormalization('traceIdFour', DateTimeNormalizer::class, 3.0, 'api'); + $dataCollector->collectDecoding('traceIdFour', CsvEncoder::class, 4.0, 'api'); + $dataCollector->collectDeserialize('traceIdFour', 'data', 'type', 'format', ['foo' => 'bar'], 7.0, $caller, 'api'); + $dataCollector->collectDenormalization('traceIdFive', ObjectNormalizer::class, 3.0, 'api'); + $dataCollector->collectDenormalize('traceIdFive', 'data', 'type', 'format', ['foo' => 'bar'], 5.0, $caller, 'api'); + $dataCollector->collectDecoding('traceIdSix', JsonEncoder::class, 4.0, 'api'); + $dataCollector->collectDecode('traceIdSix', 'data', 'format', ['foo' => 'bar'], 5.0, $caller, 'api'); + + $dataCollector->lateCollect(); + + $this->assertSame(6, $dataCollector->getHandledCount()); + + $collectedData = $dataCollector->getData(); + + $this->assertSame('default', $collectedData['serialize'][0]['name']); + $this->assertSame('DateTimeNormalizer', $collectedData['serialize'][0]['normalizer']['class']); + $this->assertSame('CsvEncoder', $collectedData['serialize'][0]['encoder']['class']); + $this->assertSame('default', $collectedData['normalize'][0]['name']); + $this->assertSame('ObjectNormalizer', $collectedData['normalize'][0]['normalizer']['class']); + + $this->assertSame('api', $collectedData['encode'][0]['name']); + $this->assertSame('JsonEncoder', $collectedData['encode'][0]['encoder']['class']); + $this->assertSame('api', $collectedData['deserialize'][0]['name']); + $this->assertSame('DateTimeNormalizer', $collectedData['deserialize'][0]['normalizer']['class']); + $this->assertSame('CsvEncoder', $collectedData['deserialize'][0]['encoder']['class']); + $this->assertSame('api', $collectedData['denormalize'][0]['name']); + $this->assertSame('ObjectNormalizer', $collectedData['denormalize'][0]['normalizer']['class']); + $this->assertSame('api', $collectedData['decode'][0]['name']); + $this->assertSame('JsonEncoder', $collectedData['decode'][0]['encoder']['class']); + + $this->assertSame(['default', 'api'], $dataCollector->getSerializerNames()); + + $this->assertSame(2, $dataCollector->getHandledCount('default')); + + $collectedData = $dataCollector->getData('default'); + + $this->assertSame('default', $collectedData['serialize'][0]['name']); + $this->assertSame('DateTimeNormalizer', $collectedData['serialize'][0]['normalizer']['class']); + $this->assertSame('CsvEncoder', $collectedData['serialize'][0]['encoder']['class']); + $this->assertSame('default', $collectedData['normalize'][0]['name']); + $this->assertSame('ObjectNormalizer', $collectedData['normalize'][0]['normalizer']['class']); + + $this->assertEmpty($collectedData['encode']); + $this->assertEmpty($collectedData['deserialize']); + $this->assertEmpty($collectedData['denormalize']); + $this->assertEmpty($collectedData['decode']); + + $this->assertSame(4, $dataCollector->getHandledCount('api')); + + $collectedData = $dataCollector->getData('api'); + + $this->assertEmpty($collectedData['serialize']); + $this->assertEmpty($collectedData['normalize']); + + $this->assertSame('api', $collectedData['encode'][0]['name']); + $this->assertSame('JsonEncoder', $collectedData['encode'][0]['encoder']['class']); + $this->assertSame('api', $collectedData['deserialize'][0]['name']); + $this->assertSame('DateTimeNormalizer', $collectedData['deserialize'][0]['normalizer']['class']); + $this->assertSame('CsvEncoder', $collectedData['deserialize'][0]['encoder']['class']); + $this->assertSame('api', $collectedData['denormalize'][0]['name']); + $this->assertSame('ObjectNormalizer', $collectedData['denormalize'][0]['normalizer']['class']); + $this->assertSame('api', $collectedData['decode'][0]['name']); + $this->assertSame('JsonEncoder', $collectedData['decode'][0]['encoder']['class']); + } + /** * Cast cloned vars to be able to test nested values. */ diff --git a/Tests/Debug/TraceableEncoderTest.php b/Tests/Debug/TraceableEncoderTest.php index ec38c0ef5..2ac0b8f1f 100644 --- a/Tests/Debug/TraceableEncoderTest.php +++ b/Tests/Debug/TraceableEncoderTest.php @@ -36,12 +36,14 @@ public function testForwardsToEncoder() ->with('data', 'format', $this->isType('array')) ->willReturn('decoded'); - $this->assertSame('encoded', (new TraceableEncoder($encoder, new SerializerDataCollector()))->encode('data', 'format')); - $this->assertSame('decoded', (new TraceableEncoder($decoder, new SerializerDataCollector()))->decode('data', 'format')); + $this->assertSame('encoded', (new TraceableEncoder($encoder, new SerializerDataCollector(), 'default'))->encode('data', 'format')); + $this->assertSame('decoded', (new TraceableEncoder($decoder, new SerializerDataCollector(), 'default'))->decode('data', 'format')); } public function testCollectEncodingData() { + $serializerName = uniqid('name', true); + $encoder = $this->createMock(EncoderInterface::class); $decoder = $this->createMock(DecoderInterface::class); @@ -49,14 +51,14 @@ public function testCollectEncodingData() $dataCollector ->expects($this->once()) ->method('collectEncoding') - ->with($this->isType('string'), $encoder::class, $this->isType('float')); + ->with($this->isType('string'), $encoder::class, $this->isType('float'), $serializerName); $dataCollector ->expects($this->once()) ->method('collectDecoding') - ->with($this->isType('string'), $decoder::class, $this->isType('float')); + ->with($this->isType('string'), $decoder::class, $this->isType('float'), $serializerName); - (new TraceableEncoder($encoder, $dataCollector))->encode('data', 'format', [TraceableSerializer::DEBUG_TRACE_ID => 'debug']); - (new TraceableEncoder($decoder, $dataCollector))->decode('data', 'format', [TraceableSerializer::DEBUG_TRACE_ID => 'debug']); + (new TraceableEncoder($encoder, $dataCollector, $serializerName))->encode('data', 'format', [TraceableSerializer::DEBUG_TRACE_ID => 'debug']); + (new TraceableEncoder($decoder, $dataCollector, $serializerName))->decode('data', 'format', [TraceableSerializer::DEBUG_TRACE_ID => 'debug']); } public function testNotCollectEncodingDataIfNoDebugTraceId() @@ -68,22 +70,22 @@ public function testNotCollectEncodingDataIfNoDebugTraceId() $dataCollector->expects($this->never())->method('collectEncoding'); $dataCollector->expects($this->never())->method('collectDecoding'); - (new TraceableEncoder($encoder, $dataCollector))->encode('data', 'format'); - (new TraceableEncoder($decoder, $dataCollector))->decode('data', 'format'); + (new TraceableEncoder($encoder, $dataCollector, 'default'))->encode('data', 'format'); + (new TraceableEncoder($decoder, $dataCollector, 'default'))->decode('data', 'format'); } public function testCannotEncodeIfNotEncoder() { $this->expectException(\BadMethodCallException::class); - (new TraceableEncoder($this->createMock(DecoderInterface::class), new SerializerDataCollector()))->encode('data', 'format'); + (new TraceableEncoder($this->createMock(DecoderInterface::class), new SerializerDataCollector(), 'default'))->encode('data', 'format'); } public function testCannotDecodeIfNotDecoder() { $this->expectException(\BadMethodCallException::class); - (new TraceableEncoder($this->createMock(EncoderInterface::class), new SerializerDataCollector()))->decode('data', 'format'); + (new TraceableEncoder($this->createMock(EncoderInterface::class), new SerializerDataCollector(), 'default'))->decode('data', 'format'); } public function testSupports() @@ -94,8 +96,8 @@ public function testSupports() $decoder = $this->createMock(DecoderInterface::class); $decoder->method('supportsDecoding')->willReturn(true); - $traceableEncoder = new TraceableEncoder($encoder, new SerializerDataCollector()); - $traceableDecoder = new TraceableEncoder($decoder, new SerializerDataCollector()); + $traceableEncoder = new TraceableEncoder($encoder, new SerializerDataCollector(), 'default'); + $traceableDecoder = new TraceableEncoder($decoder, new SerializerDataCollector(), 'default'); $this->assertTrue($traceableEncoder->supportsEncoding('data')); $this->assertTrue($traceableDecoder->supportsDecoding('data')); diff --git a/Tests/Debug/TraceableNormalizerTest.php b/Tests/Debug/TraceableNormalizerTest.php index 307bc7b6f..56c161392 100644 --- a/Tests/Debug/TraceableNormalizerTest.php +++ b/Tests/Debug/TraceableNormalizerTest.php @@ -38,12 +38,14 @@ public function testForwardsToNormalizer() ->with('data', 'type', 'format', $this->isType('array')) ->willReturn('denormalized'); - $this->assertSame('normalized', (new TraceableNormalizer($normalizer, new SerializerDataCollector()))->normalize('data', 'format')); - $this->assertSame('denormalized', (new TraceableNormalizer($denormalizer, new SerializerDataCollector()))->denormalize('data', 'type', 'format')); + $this->assertSame('normalized', (new TraceableNormalizer($normalizer, new SerializerDataCollector(), 'default'))->normalize('data', 'format')); + $this->assertSame('denormalized', (new TraceableNormalizer($denormalizer, new SerializerDataCollector(), 'default'))->denormalize('data', 'type', 'format')); } public function testCollectNormalizationData() { + $serializerName = uniqid('name', true); + $normalizer = $this->createMock(NormalizerInterface::class); $normalizer->method('getSupportedTypes')->willReturn(['*' => false]); $denormalizer = $this->createMock(DenormalizerInterface::class); @@ -53,14 +55,14 @@ public function testCollectNormalizationData() $dataCollector ->expects($this->once()) ->method('collectNormalization') - ->with($this->isType('string'), $normalizer::class, $this->isType('float')); + ->with($this->isType('string'), $normalizer::class, $this->isType('float'), $serializerName); $dataCollector ->expects($this->once()) ->method('collectDenormalization') - ->with($this->isType('string'), $denormalizer::class, $this->isType('float')); + ->with($this->isType('string'), $denormalizer::class, $this->isType('float'), $serializerName); - (new TraceableNormalizer($normalizer, $dataCollector))->normalize('data', 'format', [TraceableSerializer::DEBUG_TRACE_ID => 'debug']); - (new TraceableNormalizer($denormalizer, $dataCollector))->denormalize('data', 'type', 'format', [TraceableSerializer::DEBUG_TRACE_ID => 'debug']); + (new TraceableNormalizer($normalizer, $dataCollector, $serializerName))->normalize('data', 'format', [TraceableSerializer::DEBUG_TRACE_ID => 'debug']); + (new TraceableNormalizer($denormalizer, $dataCollector, $serializerName))->denormalize('data', 'type', 'format', [TraceableSerializer::DEBUG_TRACE_ID => 'debug']); } public function testNotCollectNormalizationDataIfNoDebugTraceId() @@ -74,22 +76,22 @@ public function testNotCollectNormalizationDataIfNoDebugTraceId() $dataCollector->expects($this->never())->method('collectNormalization'); $dataCollector->expects($this->never())->method('collectDenormalization'); - (new TraceableNormalizer($normalizer, $dataCollector))->normalize('data', 'format'); - (new TraceableNormalizer($denormalizer, $dataCollector))->denormalize('data', 'type', 'format'); + (new TraceableNormalizer($normalizer, $dataCollector, 'default'))->normalize('data', 'format'); + (new TraceableNormalizer($denormalizer, $dataCollector, 'default'))->denormalize('data', 'type', 'format'); } public function testCannotNormalizeIfNotNormalizer() { $this->expectException(\BadMethodCallException::class); - (new TraceableNormalizer($this->createMock(DenormalizerInterface::class), new SerializerDataCollector()))->normalize('data'); + (new TraceableNormalizer($this->createMock(DenormalizerInterface::class), new SerializerDataCollector(), 'default'))->normalize('data'); } public function testCannotDenormalizeIfNotDenormalizer() { $this->expectException(\BadMethodCallException::class); - (new TraceableNormalizer($this->createMock(NormalizerInterface::class), new SerializerDataCollector()))->denormalize('data', 'type'); + (new TraceableNormalizer($this->createMock(NormalizerInterface::class), new SerializerDataCollector(), 'default'))->denormalize('data', 'type'); } public function testSupports() @@ -102,8 +104,8 @@ public function testSupports() $denormalizer->method('getSupportedTypes')->willReturn(['*' => false]); $denormalizer->method('supportsDenormalization')->willReturn(true); - $traceableNormalizer = new TraceableNormalizer($normalizer, new SerializerDataCollector()); - $traceableDenormalizer = new TraceableNormalizer($denormalizer, new SerializerDataCollector()); + $traceableNormalizer = new TraceableNormalizer($normalizer, new SerializerDataCollector(), 'default'); + $traceableDenormalizer = new TraceableNormalizer($denormalizer, new SerializerDataCollector(), 'default'); $this->assertTrue($traceableNormalizer->supportsNormalization('data')); $this->assertTrue($traceableDenormalizer->supportsDenormalization('data', 'type')); diff --git a/Tests/Debug/TraceableSerializerTest.php b/Tests/Debug/TraceableSerializerTest.php index ea3c851c6..d697b270f 100644 --- a/Tests/Debug/TraceableSerializerTest.php +++ b/Tests/Debug/TraceableSerializerTest.php @@ -56,7 +56,7 @@ public function testForwardsToSerializer() ->with('data', 'format', $this->isType('array')) ->willReturn('decoded'); - $traceableSerializer = new TraceableSerializer($serializer, new SerializerDataCollector()); + $traceableSerializer = new TraceableSerializer($serializer, new SerializerDataCollector(), 'default'); $this->assertSame('serialized', $traceableSerializer->serialize('data', 'format')); $this->assertSame('deserialized', $traceableSerializer->deserialize('data', 'type', 'format')); @@ -68,33 +68,35 @@ public function testForwardsToSerializer() public function testCollectData() { + $serializerName = uniqid('name', true); + $dataCollector = $this->createMock(SerializerDataCollector::class); $dataCollector ->expects($this->once()) ->method('collectSerialize') - ->with($this->isType('string'), 'data', 'format', $this->isType('array'), $this->isType('float')); + ->with($this->isType('string'), 'data', 'format', $this->isType('array'), $this->isType('float'), $this->isType('array'), $serializerName); $dataCollector ->expects($this->once()) ->method('collectDeserialize') - ->with($this->isType('string'), 'data', 'type', 'format', $this->isType('array'), $this->isType('float')); + ->with($this->isType('string'), 'data', 'type', 'format', $this->isType('array'), $this->isType('float'), $this->isType('array'), $serializerName); $dataCollector ->expects($this->once()) ->method('collectNormalize') - ->with($this->isType('string'), 'data', 'format', $this->isType('array'), $this->isType('float')); + ->with($this->isType('string'), 'data', 'format', $this->isType('array'), $this->isType('float'), $this->isType('array'), $serializerName); $dataCollector ->expects($this->once()) ->method('collectDenormalize') - ->with($this->isType('string'), 'data', 'type', 'format', $this->isType('array'), $this->isType('float')); + ->with($this->isType('string'), 'data', 'type', 'format', $this->isType('array'), $this->isType('float'), $this->isType('array'), $serializerName); $dataCollector ->expects($this->once()) ->method('collectEncode') - ->with($this->isType('string'), 'data', 'format', $this->isType('array'), $this->isType('float')); + ->with($this->isType('string'), 'data', 'format', $this->isType('array'), $this->isType('float'), $this->isType('array'), $serializerName); $dataCollector ->expects($this->once()) ->method('collectDecode') - ->with($this->isType('string'), 'data', 'format', $this->isType('array'), $this->isType('float')); + ->with($this->isType('string'), 'data', 'format', $this->isType('array'), $this->isType('float'), $this->isType('array'), $serializerName); - $traceableSerializer = new TraceableSerializer(new Serializer(), $dataCollector); + $traceableSerializer = new TraceableSerializer(new Serializer(), $dataCollector, $serializerName); $traceableSerializer->serialize('data', 'format'); $traceableSerializer->deserialize('data', 'type', 'format'); @@ -117,7 +119,7 @@ public function testAddDebugTraceIdInContext() }); } - $traceableSerializer = new TraceableSerializer($serializer, new SerializerDataCollector()); + $traceableSerializer = new TraceableSerializer($serializer, new SerializerDataCollector(), 'default'); $traceableSerializer->serialize('data', 'format'); $traceableSerializer->deserialize('data', 'format', 'type'); diff --git a/Tests/DependencyInjection/SerializerPassTest.php b/Tests/DependencyInjection/SerializerPassTest.php index 037eafdb6..b721b1ba4 100644 --- a/Tests/DependencyInjection/SerializerPassTest.php +++ b/Tests/DependencyInjection/SerializerPassTest.php @@ -17,7 +17,9 @@ use Symfony\Component\DependencyInjection\Reference; use Symfony\Component\Serializer\Debug\TraceableEncoder; use Symfony\Component\Serializer\Debug\TraceableNormalizer; +use Symfony\Component\Serializer\Debug\TraceableSerializer; use Symfony\Component\Serializer\DependencyInjection\SerializerPass; +use Symfony\Component\Serializer\SerializerInterface; /** * Tests for the SerializerPass class. @@ -94,7 +96,7 @@ public function testBindSerializerDefaultContext() $this->assertEquals($bindings['array $defaultContext'], new BoundArgument(['enable_max_depth' => true], false)); } - public function testNormalizersAndEncodersAreDecoredAndOrderedWhenCollectingData() + public function testNormalizersAndEncodersAreDecoratedAndOrderedWhenCollectingData() { $container = new ContainerBuilder(); @@ -114,9 +116,525 @@ public function testNormalizersAndEncodersAreDecoredAndOrderedWhenCollectingData $this->assertEquals(TraceableNormalizer::class, $traceableNormalizerDefinition->getClass()); $this->assertEquals(new Reference('n'), $traceableNormalizerDefinition->getArgument(0)); $this->assertEquals(new Reference('serializer.data_collector'), $traceableNormalizerDefinition->getArgument(1)); + $this->assertSame('default', $traceableNormalizerDefinition->getArgument(2)); $this->assertEquals(TraceableEncoder::class, $traceableEncoderDefinition->getClass()); $this->assertEquals(new Reference('e'), $traceableEncoderDefinition->getArgument(0)); $this->assertEquals(new Reference('serializer.data_collector'), $traceableEncoderDefinition->getArgument(1)); + $this->assertSame('default', $traceableEncoderDefinition->getArgument(2)); + } + + /** + * @dataProvider provideDefaultSerializerTagsData + */ + public function testDefaultSerializerTagsAreResolvedCorrectly( + array $normalizerTagAttributes, + array $encoderTagAttributes, + array $expectedNormalizerTags, + array $expectedEncoderTags, + ) { + $container = new ContainerBuilder(); + + $container->setParameter('kernel.debug', false); + $container->setParameter('.serializer.named_serializers', []); + + $container->register('serializer')->setArguments([null, null]); + $container->register('n0')->addTag('serializer.normalizer', ['serializer' => 'default']); + $container->register('e0')->addTag('serializer.encoder', ['serializer' => 'default']); + + $normalizerDefinition = $container->register('n1')->addTag('serializer.normalizer', $normalizerTagAttributes); + $encoderDefinition = $container->register('e1')->addTag('serializer.encoder', $encoderTagAttributes); + + $serializerPass = new SerializerPass(); + $serializerPass->process($container); + + $this->assertSame($expectedNormalizerTags, $normalizerDefinition->getTag('serializer.normalizer.default')); + $this->assertSame($expectedEncoderTags, $encoderDefinition->getTag('serializer.encoder.default')); + } + + public static function provideDefaultSerializerTagsData(): iterable + { + yield 'include no name' => [ + [], + [], + [[]], + [[]], + ]; + + yield 'include name' => [ + ['serializer' => 'default'], + ['serializer' => 'default'], + [[]], + [[]], + ]; + + yield 'include built-in with different name' => [ + ['built_in' => true, 'serializer' => 'api'], + ['built_in' => true, 'serializer' => 'api'], + [[]], + [[]], + ]; + + yield 'include no name with priority' => [ + ['priority' => 200], + ['priority' => 100], + [['priority' => 200]], + [['priority' => 100]], + ]; + + yield 'include name with priority' => [ + ['serializer' => 'default', 'priority' => 200], + ['serializer' => 'default', 'priority' => 100], + [['priority' => 200]], + [['priority' => 100]], + ]; + + yield 'include wildcard' => [ + ['serializer' => '*'], + ['serializer' => '*'], + [[]], + [[]], + ]; + + yield 'is unique when built-in with name' => [ + ['built_in' => true, 'serializer' => 'default'], + ['built_in' => true, 'serializer' => 'default'], + [[]], + [[]], + ]; + + yield 'do not include different name' => [ + ['serializer' => 'api'], + ['serializer' => 'api'], + [], + [], + ]; + } + + /** + * @dataProvider provideNamedSerializerTagsData + */ + public function testNamedSerializerTagsAreResolvedCorrectly( + array $config, + array $normalizerTagAttributes, + array $encoderTagAttributes, + array $expectedNormalizerTags, + array $expectedEncoderTags, + ) { + $container = new ContainerBuilder(); + + $container->setParameter('kernel.debug', false); + $container->setParameter('.serializer.named_serializers', ['api' => $config]); + + $container->register('serializer')->setArguments([null, null]); + $container->register('n0')->addTag('serializer.normalizer', ['serializer' => ['default', 'api']]); + $container->register('e0')->addTag('serializer.encoder', ['serializer' => ['default', 'api']]); + + $normalizerDefinition = $container->register('n1')->addTag('serializer.normalizer', $normalizerTagAttributes); + $encoderDefinition = $container->register('e1')->addTag('serializer.encoder', $encoderTagAttributes); + + $serializerPass = new SerializerPass(); + $serializerPass->process($container); + + $this->assertSame($expectedNormalizerTags, $normalizerDefinition->getTag('serializer.normalizer.api')); + $this->assertSame($expectedEncoderTags, $encoderDefinition->getTag('serializer.encoder.api')); + } + + public static function provideNamedSerializerTagsData(): iterable + { + yield 'include built-in' => [ + ['include_built_in_normalizers' => true, 'include_built_in_encoders' => true], + ['built_in' => true], + ['built_in' => true], + [[]], + [[]], + ]; + + yield 'include built-in normalizers only' => [ + ['include_built_in_normalizers' => true, 'include_built_in_encoders' => false], + ['built_in' => true], + ['built_in' => true], + [[]], + [], + ]; + + yield 'include built-in encoders only' => [ + ['include_built_in_normalizers' => false, 'include_built_in_encoders' => true], + ['built_in' => true], + ['built_in' => true], + [], + [[]], + ]; + + yield 'include name' => [ + ['include_built_in_normalizers' => false, 'include_built_in_encoders' => false], + ['serializer' => 'api'], + ['serializer' => 'api'], + [[]], + [[]], + ]; + + yield 'include name with priority' => [ + ['include_built_in_normalizers' => false, 'include_built_in_encoders' => false], + ['serializer' => 'api', 'priority' => 200], + ['serializer' => 'api', 'priority' => 100], + [['priority' => 200]], + [['priority' => 100]], + ]; + + yield 'include wildcard' => [ + ['include_built_in_normalizers' => false, 'include_built_in_encoders' => false], + ['serializer' => '*'], + ['serializer' => '*'], + [[]], + [[]], + ]; + + yield 'do not include when include built-in not set' => [ + [], + ['built_in' => true], + ['built_in' => true], + [], + [], + ]; + + yield 'do not include not built-in and no name' => [ + ['include_built_in_normalizers' => false, 'include_built_in_encoders' => false], + [], + [], + [], + [], + ]; + + yield 'do not include different name' => [ + ['include_built_in_normalizers' => false, 'include_built_in_encoders' => false], + ['serializer' => 'api2'], + ['serializer' => 'api2'], + [], + [], + ]; + } + + public function testMultipleNamedSerializerTagsAreResolvedCorrectly() + { + $container = new ContainerBuilder(); + + $container->setParameter('kernel.debug', false); + $container->setParameter('.serializer.named_serializers', [ + 'api' => [], + 'api2' => [], + ]); + + $container->register('serializer')->setArguments([null, null]); + $container->register('n0')->addTag('serializer.normalizer', ['serializer' => 'default']); + $container->register('e0')->addTag('serializer.encoder', ['serializer' => 'default']); + + $normalizerDefinition = $container->register('n1')->addTag('serializer.normalizer', ['serializer' => ['api', 'api2']]); + $encoderDefinition = $container->register('e1') + ->addTag('serializer.encoder', ['serializer' => ['api', 'api2']]) + ->addTag('serializer.encoder', ['serializer' => ['api', 'api2'], 'priority' => 100]) + ; + + $serializerPass = new SerializerPass(); + $serializerPass->process($container); + + $this->assertTrue($normalizerDefinition->hasTag('serializer.normalizer.api')); + $this->assertCount(1, $normalizerDefinition->getTag('serializer.normalizer.api')); + $this->assertTrue($normalizerDefinition->hasTag('serializer.normalizer.api2')); + $this->assertCount(1, $normalizerDefinition->getTag('serializer.normalizer.api2')); + + $this->assertTrue($encoderDefinition->hasTag('serializer.encoder.api')); + $this->assertCount(2, $encoderDefinition->getTag('serializer.encoder.api')); + $this->assertTrue($encoderDefinition->hasTag('serializer.encoder.api2')); + $this->assertCount(2, $encoderDefinition->getTag('serializer.encoder.api2')); + } + + public function testThrowExceptionWhenNoNormalizersForNamedSerializers() + { + $container = new ContainerBuilder(); + $container->setParameter('kernel.debug', false); + $container->setParameter('.serializer.named_serializers', [ + 'api' => [], + ]); + + $container->register('serializer')->setArguments([null, null]); + $container->register('n0')->addTag('serializer.normalizer'); + $container->register('e0')->addTag('serializer.encoder', ['serializer' => '*']); + + $serializerPass = new SerializerPass(); + + $this->expectException(\RuntimeException::class); + $this->expectExceptionMessage('The named serializer "api" requires at least one registered normalizer. Tag the normalizers as "serializer.normalizer" with the "serializer" attribute set to "api".'); + + $serializerPass->process($container); + } + + public function testThrowExceptionWhenNoEncodersForNamedSerializers() + { + $container = new ContainerBuilder(); + $container->setParameter('kernel.debug', false); + $container->setParameter('.serializer.named_serializers', [ + 'api' => [], + ]); + + $container->register('serializer')->setArguments([null, null]); + $container->register('n0')->addTag('serializer.normalizer', ['serializer' => '*']); + $container->register('e0')->addTag('serializer.encoder'); + + $serializerPass = new SerializerPass(); + + $this->expectException(\RuntimeException::class); + $this->expectExceptionMessage('The named serializer "api" requires at least one registered encoder. Tag the encoders as "serializer.encoder" with the "serializer" attribute set to "api".'); + + $serializerPass->process($container); + } + + /** + * @testWith [null] + * ["some.converter"] + */ + public function testChildNameConverterIsNotBuiltWhenExpected(?string $nameConverter) + { + $container = new ContainerBuilder(); + $container->setParameter('kernel.debug', false); + $container->setParameter('.serializer.name_converter', $nameConverter); + $container->setParameter('.serializer.named_serializers', [ + 'api' => ['name_converter' => $nameConverter], + ]); + + $container->register('serializer')->setArguments([null, null]); + $container->register('n')->addTag('serializer.normalizer', ['serializer' => '*']); + $container->register('e')->addTag('serializer.encoder', ['serializer' => '*']); + + $serializerPass = new SerializerPass(); + $serializerPass->process($container); + + $this->assertFalse($container->hasDefinition('serializer.name_converter.metadata_aware.'.ContainerBuilder::hash($nameConverter))); + } + + /** + * @dataProvider provideChildNameConverterCases + */ + public function testChildNameConverterIsBuiltWhenExpected( + ?string $defaultSerializerNameConverter, + ?string $namedSerializerNameConverter, + string $nameConverterIdExists, + string $nameConverterIdDoesNotExist, + array $nameConverterArguments, + ) { + $container = new ContainerBuilder(); + $container->setParameter('kernel.debug', false); + $container->setParameter('.serializer.name_converter', $defaultSerializerNameConverter); + $container->setParameter('.serializer.named_serializers', [ + 'api' => ['name_converter' => $namedSerializerNameConverter], + ]); + + $container->register('serializer')->setArguments([null, null]); + $container->register('n')->addTag('serializer.normalizer', ['serializer' => '*']); + $container->register('e')->addTag('serializer.encoder', ['serializer' => '*']); + + $serializerPass = new SerializerPass(); + $serializerPass->process($container); + + $this->assertFalse($container->hasDefinition($nameConverterIdExists)); + $this->assertTrue($container->hasDefinition($nameConverterIdDoesNotExist)); + $this->assertEquals($nameConverterArguments, $container->getDefinition($nameConverterIdDoesNotExist)->getArguments()); + } + + public static function provideChildNameConverterCases(): iterable + { + $withNull = 'serializer.name_converter.metadata_aware.'.ContainerBuilder::hash(null); + $withConverter = 'serializer.name_converter.metadata_aware.'.ContainerBuilder::hash('some.converter'); + + yield [null, 'some.converter', $withNull, $withConverter, [new Reference('some.converter')]]; + yield ['some.converter', null, $withConverter, $withNull, []]; + } + + /** + * @dataProvider provideDifferentNamedSerializerConfigsCases + */ + public function testNamedSerializersCreateNewServices( + array $defaultSerializerDefaultContext, + ?string $defaultSerializerNameConverter, + array $namedSerializerConfig, + string $nameConverterId, + ) { + $container = new ContainerBuilder(); + $container->setParameter('kernel.debug', false); + $container->setParameter('serializer.default_context', $defaultSerializerDefaultContext); + $container->setParameter('.serializer.name_converter', $defaultSerializerNameConverter); + $container->setParameter('.serializer.named_serializers', [ + 'api' => $namedSerializerConfig, + ]); + + $container->register('serializer')->setArguments([null, null]); + $container->register('n') + ->addArgument(new Reference('serializer.name_converter.metadata_aware')) + ->addTag('serializer.normalizer', ['serializer' => '*']) + ; + $container->register('e') + ->addArgument(new Reference('serializer.name_converter.metadata_aware')) + ->addTag('serializer.encoder', ['serializer' => '*']) + ; + + $serializerPass = new SerializerPass(); + $serializerPass->process($container); + + $this->assertEquals([new Reference('n.api')], $container->getDefinition('serializer.api')->getArgument(0)); + $this->assertEquals(new Reference($nameConverterId), $container->getDefinition('n.api')->getArgument(0)); + $this->assertEquals([new Reference('e.api')], $container->getDefinition('serializer.api')->getArgument(1)); + $this->assertEquals(new Reference($nameConverterId), $container->getDefinition('e.api')->getArgument(0)); + } + + public static function provideDifferentNamedSerializerConfigsCases(): iterable + { + yield [ + ['a' => true, 'b' => 3], + null, + ['default_context' => ['c' => 3, 'a' => true]], + 'serializer.name_converter.metadata_aware', + ]; + yield [ + [], + 'some.converter', + ['name_converter' => null], + 'serializer.name_converter.metadata_aware.'.ContainerBuilder::hash(null), + ]; + yield [ + ['a' => true, 'b' => 3], + null, + ['default_context' => ['c' => 3, 'a' => true], 'name_converter' => 'some.converter'], + 'serializer.name_converter.metadata_aware.'.ContainerBuilder::hash('some.converter'), + ]; + } + + public function testServicesAreOrderedAccordingToPriorityForNamedSerializers() + { + $container = new ContainerBuilder(); + $container->setParameter('kernel.debug', false); + $container->setParameter('.serializer.named_serializers', [ + 'api' => [], + ]); + + $container->register('serializer')->setArguments([null, null]); + $container->register('n2') + ->addTag('serializer.normalizer', ['serializer' => '*', 'priority' => 100]) + ->addTag('serializer.encoder', ['serializer' => '*', 'priority' => 100]) + ; + $container->register('n1') + ->addTag('serializer.normalizer', ['serializer' => 'api', 'priority' => 200]) + ->addTag('serializer.encoder', ['serializer' => 'api', 'priority' => 200]) + ; + $container->register('n3') + ->addTag('serializer.normalizer', ['serializer' => 'api']) + ->addTag('serializer.encoder', ['serializer' => 'api']) + ; + + $serializerPass = new SerializerPass(); + $serializerPass->process($container); + + $this->assertTrue($container->hasDefinition('serializer.api')); + $definition = $container->getDefinition('serializer.api'); + + $expected = [ + new Reference('n1.api'), + new Reference('n2.api'), + new Reference('n3.api'), + ]; + $this->assertEquals($expected, $definition->getArgument(0)); + $this->assertEquals($expected, $definition->getArgument(1)); + } + + public function testBindSerializerDefaultContextToNamedSerializers() + { + $container = new ContainerBuilder(); + $container->setParameter('kernel.debug', false); + $container->setParameter('.serializer.named_serializers', [ + 'api' => ['default_context' => $defaultContext = ['enable_max_depth' => true]], + ]); + + $container->register('serializer')->setArguments([null, null]); + $definition = $container->register('n1') + ->addTag('serializer.normalizer', ['serializer' => '*']) + ->addTag('serializer.encoder', ['serializer' => '*']) + ; + + $serializerPass = new SerializerPass(); + $serializerPass->process($container); + + $this->assertEmpty($definition->getBindings()); + + $bindings = $container->getDefinition('n1.api')->getBindings(); + $this->assertArrayHasKey('array $defaultContext', $bindings); + $this->assertEquals($bindings['array $defaultContext'], new BoundArgument($defaultContext, false)); + } + + public function testNamedSerializersAreRegistered() + { + $container = new ContainerBuilder(); + $container->setParameter('kernel.debug', false); + $container->setParameter('.serializer.named_serializers', [ + 'api' => [], + 'api2' => [], + ]); + + $container->register('serializer')->setArguments([null, null]); + $container->register('n')->addTag('serializer.normalizer', ['serializer' => '*']); + $container->register('e')->addTag('serializer.encoder', ['serializer' => '*']); + + $serializerPass = new SerializerPass(); + $serializerPass->process($container); + + $this->assertFalse($container->hasAlias(\sprintf('%s $defaultSerializer', SerializerInterface::class))); + + $this->assertTrue($container->hasDefinition('serializer.api')); + $this->assertTrue($container->hasAlias(\sprintf('%s $apiSerializer', SerializerInterface::class))); + $this->assertTrue($container->hasDefinition('serializer.api2')); + $this->assertTrue($container->hasAlias(\sprintf('%s $api2Serializer', SerializerInterface::class))); + } + + public function testNormalizersAndEncodersAreDecoratedAndOrderedWhenCollectingDataForNamedSerializers() + { + $container = new ContainerBuilder(); + + $container->setParameter('kernel.debug', true); + $container->setParameter('.serializer.named_serializers', [ + 'api' => ['default_context' => ['enable_max_depth' => true]], + ]); + $container->register('serializer.data_collector'); + + $container->register('serializer')->setArguments([null, null]); + $container->register('n')->addTag('serializer.normalizer', ['serializer' => '*']); + $container->register('e')->addTag('serializer.encoder', ['serializer' => '*']); + + $container->register('debug.serializer', TraceableSerializer::class) + ->setDecoratedService('serializer') + ->setArguments([ + new Reference('debug.serializer.inner'), + new Reference('serializer.data_collector'), + 'default', + ]) + ; + + $serializerPass = new SerializerPass(); + $serializerPass->process($container); + + $traceableNormalizerDefinition = $container->getDefinition('.debug.serializer.normalizer.n.api'); + $traceableEncoderDefinition = $container->getDefinition('.debug.serializer.encoder.e.api'); + + $traceableSerializerDefinition = $container->getDefinition('debug.serializer.api'); + $this->assertSame('serializer.api', $traceableSerializerDefinition->getDecoratedService()[0]); + $this->assertEquals(new Reference('debug.serializer.api.inner'), $traceableSerializerDefinition->getArgument(0)); + $this->assertSame('api', $traceableSerializerDefinition->getArgument(2)); + + $this->assertEquals(TraceableNormalizer::class, $traceableNormalizerDefinition->getClass()); + $this->assertEquals(new Reference('n.api'), $traceableNormalizerDefinition->getArgument(0)); + $this->assertEquals(new Reference('serializer.data_collector'), $traceableNormalizerDefinition->getArgument(1)); + $this->assertSame('api', $traceableNormalizerDefinition->getArgument(2)); + + $this->assertEquals(TraceableEncoder::class, $traceableEncoderDefinition->getClass()); + $this->assertEquals(new Reference('e.api'), $traceableEncoderDefinition->getArgument(0)); + $this->assertEquals(new Reference('serializer.data_collector'), $traceableEncoderDefinition->getArgument(1)); + $this->assertSame('api', $traceableEncoderDefinition->getArgument(2)); } } diff --git a/composer.json b/composer.json index 0092a9643..4d3af9358 100644 --- a/composer.json +++ b/composer.json @@ -26,7 +26,7 @@ "symfony/cache": "^6.4|^7.0", "symfony/config": "^6.4|^7.0", "symfony/console": "^6.4|^7.0", - "symfony/dependency-injection": "^6.4|^7.0", + "symfony/dependency-injection": "^7.2", "symfony/error-handler": "^6.4|^7.0", "symfony/filesystem": "^6.4|^7.0", "symfony/form": "^6.4|^7.0", From 267cddf01bc284d6079818e71294b7452e2e4dab Mon Sep 17 00:00:00 2001 From: "Alexander M. Turek" Date: Thu, 19 Sep 2024 09:56:35 +0200 Subject: [PATCH 269/297] Switch to ExpectUserDeprecationMessageTrait --- Tests/Context/Encoder/CsvEncoderContextBuilderTest.php | 8 ++++---- Tests/Encoder/CsvEncoderTest.php | 6 +++--- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/Tests/Context/Encoder/CsvEncoderContextBuilderTest.php b/Tests/Context/Encoder/CsvEncoderContextBuilderTest.php index bcaaf2a88..fe39feb81 100644 --- a/Tests/Context/Encoder/CsvEncoderContextBuilderTest.php +++ b/Tests/Context/Encoder/CsvEncoderContextBuilderTest.php @@ -12,7 +12,7 @@ namespace Symfony\Component\Serializer\Tests\Context\Encoder; use PHPUnit\Framework\TestCase; -use Symfony\Bridge\PhpUnit\ExpectDeprecationTrait; +use Symfony\Bridge\PhpUnit\ExpectUserDeprecationMessageTrait; use Symfony\Component\Serializer\Context\Encoder\CsvEncoderContextBuilder; use Symfony\Component\Serializer\Encoder\CsvEncoder; use Symfony\Component\Serializer\Exception\InvalidArgumentException; @@ -22,7 +22,7 @@ */ class CsvEncoderContextBuilderTest extends TestCase { - use ExpectDeprecationTrait; + use ExpectUserDeprecationMessageTrait; private CsvEncoderContextBuilder $contextBuilder; @@ -127,7 +127,7 @@ public function testCannotSetMultipleBytesAsEnclosure() */ public function testCannotSetMultipleBytesAsEscapeChar() { - $this->expectDeprecation('Since symfony/serializer 7.2: The "Symfony\Component\Serializer\Context\Encoder\CsvEncoderContextBuilder::withEscapeChar" method is deprecated. It will be removed in 8.0.'); + $this->expectUserDeprecationMessage('Since symfony/serializer 7.2: The "Symfony\Component\Serializer\Context\Encoder\CsvEncoderContextBuilder::withEscapeChar" method is deprecated. It will be removed in 8.0.'); $this->expectException(InvalidArgumentException::class); $this->contextBuilder->withEscapeChar('ọ'); @@ -138,7 +138,7 @@ public function testCannotSetMultipleBytesAsEscapeChar() */ public function testWithEscapeCharIsDeprecated() { - $this->expectDeprecation('Since symfony/serializer 7.2: The "Symfony\Component\Serializer\Context\Encoder\CsvEncoderContextBuilder::withEscapeChar" method is deprecated. It will be removed in 8.0.'); + $this->expectUserDeprecationMessage('Since symfony/serializer 7.2: The "Symfony\Component\Serializer\Context\Encoder\CsvEncoderContextBuilder::withEscapeChar" method is deprecated. It will be removed in 8.0.'); $context = $this->contextBuilder->withEscapeChar('\\'); $this->assertSame(['csv_escape_char' => '\\'], $context->toArray()); diff --git a/Tests/Encoder/CsvEncoderTest.php b/Tests/Encoder/CsvEncoderTest.php index e250d1c61..048d790b0 100644 --- a/Tests/Encoder/CsvEncoderTest.php +++ b/Tests/Encoder/CsvEncoderTest.php @@ -12,7 +12,7 @@ namespace Symfony\Component\Serializer\Tests\Encoder; use PHPUnit\Framework\TestCase; -use Symfony\Bridge\PhpUnit\ExpectDeprecationTrait; +use Symfony\Bridge\PhpUnit\ExpectUserDeprecationMessageTrait; use Symfony\Component\Serializer\Encoder\CsvEncoder; use Symfony\Component\Serializer\Exception\UnexpectedValueException; @@ -21,7 +21,7 @@ */ class CsvEncoderTest extends TestCase { - use ExpectDeprecationTrait; + use ExpectUserDeprecationMessageTrait; private CsvEncoder $encoder; @@ -713,7 +713,7 @@ public function testEndOfLinePassedInConstructor() */ public function testPassingNonEmptyEscapeCharIsDeprecated() { - $this->expectDeprecation('Since symfony/serializer 7.2: Setting the "csv_escape_char" option is deprecated. The option will be removed in 8.0.'); + $this->expectUserDeprecationMessage('Since symfony/serializer 7.2: Setting the "csv_escape_char" option is deprecated. The option will be removed in 8.0.'); $encoder = new CsvEncoder(['csv_escape_char' => '@']); $this->assertSame( From df514ef72c3540217c9819e4479aa55acc431d27 Mon Sep 17 00:00:00 2001 From: "Alexander M. Turek" Date: Thu, 19 Sep 2024 23:14:15 +0200 Subject: [PATCH 270/297] Make more data providers static --- Tests/Normalizer/AbstractObjectNormalizerTest.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Tests/Normalizer/AbstractObjectNormalizerTest.php b/Tests/Normalizer/AbstractObjectNormalizerTest.php index 26f9be4ad..8ec4ac1d3 100644 --- a/Tests/Normalizer/AbstractObjectNormalizerTest.php +++ b/Tests/Normalizer/AbstractObjectNormalizerTest.php @@ -1211,7 +1211,7 @@ public function testDenormalizeBooleanTypeWithFilterBool(array $data, ?bool $exp $this->assertSame($expectedFoo, $dummy->foo); } - public function provideDenormalizeWithFilterBoolData(): array + public static function provideDenormalizeWithFilterBoolData(): array { return [ [['foo' => 'true'], true], From 71d6e1f70f00752d1469d0f5e83b0a51716f288b Mon Sep 17 00:00:00 2001 From: Christian Flothmann Date: Fri, 20 Sep 2024 13:43:16 +0200 Subject: [PATCH 271/297] fix tests --- Tests/Normalizer/AbstractObjectNormalizerTest.php | 5 +++++ composer.json | 4 +++- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/Tests/Normalizer/AbstractObjectNormalizerTest.php b/Tests/Normalizer/AbstractObjectNormalizerTest.php index d7e0f7d25..b4f5c103c 100644 --- a/Tests/Normalizer/AbstractObjectNormalizerTest.php +++ b/Tests/Normalizer/AbstractObjectNormalizerTest.php @@ -15,6 +15,7 @@ use Symfony\Component\PropertyInfo\Extractor\PhpDocExtractor; use Symfony\Component\PropertyInfo\Extractor\PhpStanExtractor; use Symfony\Component\PropertyInfo\Extractor\ReflectionExtractor; +use Symfony\Component\PropertyInfo\PropertyDocBlockExtractorInterface; use Symfony\Component\PropertyInfo\PropertyInfoExtractor; use Symfony\Component\PropertyInfo\Type as LegacyType; use Symfony\Component\Serializer\Attribute\Context; @@ -1274,6 +1275,10 @@ protected function isAllowedAttribute($classOrObject, string $attribute, ?string public function testDenormalizeTemplateType() { + if (!interface_exists(PropertyDocBlockExtractorInterface::class)) { + $this->markTestSkipped('The PropertyInfo component before Symfony 7.1 does not support template types.'); + } + $normalizer = new class ( classMetadataFactory: new ClassMetadataFactory(new AttributeLoader()), propertyTypeExtractor: new PropertyInfoExtractor(typeExtractors: [new PhpStanExtractor(), new ReflectionExtractor()]) diff --git a/composer.json b/composer.json index 0092a9643..948fa36fa 100644 --- a/composer.json +++ b/composer.json @@ -22,6 +22,7 @@ }, "require-dev": { "phpdocumentor/reflection-docblock": "^3.2|^4.0|^5.0", + "phpstan/phpdoc-parser": "^1.0", "seld/jsonlint": "^1.10", "symfony/cache": "^6.4|^7.0", "symfony/config": "^6.4|^7.0", @@ -37,7 +38,7 @@ "symfony/property-access": "^6.4|^7.0", "symfony/property-info": "^6.4|^7.0", "symfony/translation-contracts": "^2.5|^3", - "symfony/type-info": "^7.1", + "symfony/type-info": "^7.1.5", "symfony/uid": "^6.4|^7.0", "symfony/validator": "^6.4|^7.0", "symfony/var-dumper": "^6.4|^7.0", @@ -50,6 +51,7 @@ "symfony/dependency-injection": "<6.4", "symfony/property-access": "<6.4", "symfony/property-info": "<6.4", + "symfony/type-info": "<7.1.5", "symfony/uid": "<6.4", "symfony/validator": "<6.4", "symfony/yaml": "<6.4" From c86076815cd39e140b30ef68f02c21539ed0e4da Mon Sep 17 00:00:00 2001 From: Christian Flothmann Date: Mon, 23 Sep 2024 11:30:23 +0200 Subject: [PATCH 272/297] make data provider static --- Tests/Normalizer/Features/ContextMetadataTestTrait.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Tests/Normalizer/Features/ContextMetadataTestTrait.php b/Tests/Normalizer/Features/ContextMetadataTestTrait.php index 10f5a0030..787157628 100644 --- a/Tests/Normalizer/Features/ContextMetadataTestTrait.php +++ b/Tests/Normalizer/Features/ContextMetadataTestTrait.php @@ -77,7 +77,7 @@ public function testContextMetadataContextDenormalize(string $contextMetadataDum self::assertEquals('2011-07-28', $dummy->date->format('Y-m-d'), 'a specific denormalization context is used for this group'); } - public function contextMetadataDummyProvider(): array + public static function contextMetadataDummyProvider(): array { return [ [ContextMetadataDummy::class], From 2946530731d67a6a45aef8379a0879807616059e Mon Sep 17 00:00:00 2001 From: Aurimas Date: Tue, 24 Sep 2024 09:20:27 +0300 Subject: [PATCH 273/297] [Serializer] Readd AdvancedNameConverterInterface to MetadataAwareNameConverter. --- NameConverter/MetadataAwareNameConverter.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/NameConverter/MetadataAwareNameConverter.php b/NameConverter/MetadataAwareNameConverter.php index eec3b42ce..327d92dc1 100644 --- a/NameConverter/MetadataAwareNameConverter.php +++ b/NameConverter/MetadataAwareNameConverter.php @@ -18,7 +18,7 @@ /** * @author Fabien Bourigault */ -final class MetadataAwareNameConverter implements NameConverterInterface +final class MetadataAwareNameConverter implements AdvancedNameConverterInterface { /** * @var array> From 8f02a9837542c71b91f40a6cf7f38fa67b2b5f29 Mon Sep 17 00:00:00 2001 From: Mathias Arlaud Date: Wed, 18 Sep 2024 19:28:51 +0200 Subject: [PATCH 274/297] [Serializer] Deprecate `AdvancedNameConverterInterface` --- CHANGELOG.md | 1 + NameConverter/AdvancedNameConverterInterface.php | 2 ++ 2 files changed, 3 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 79a14b50f..a04c323d6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,7 @@ CHANGELOG * Add support for configuring multiple serializer instances with different default contexts, name converters, sets of normalizers and encoders * Add support for collection profiles of multiple serializer instances + * Deprecate `AdvancedNameConverterInterface`, use `NameConverterInterface` instead 7.1 --- diff --git a/NameConverter/AdvancedNameConverterInterface.php b/NameConverter/AdvancedNameConverterInterface.php index 1e74f4d20..975d28fd3 100644 --- a/NameConverter/AdvancedNameConverterInterface.php +++ b/NameConverter/AdvancedNameConverterInterface.php @@ -15,6 +15,8 @@ * Gives access to the class, the format and the context in the property name converters. * * @author Kévin Dunglas + * + * @deprecated since Symfony 7.2, use NameConverterInterface instead */ interface AdvancedNameConverterInterface extends NameConverterInterface { From e4c7264a86225ccf7e71cda8070667f30e968d06 Mon Sep 17 00:00:00 2001 From: Alexandre Daubois Date: Thu, 26 Sep 2024 10:09:09 +0200 Subject: [PATCH 275/297] Remove unused imports --- Tests/Fixtures/DummyObjectWithEnumProperty.php | 2 -- 1 file changed, 2 deletions(-) diff --git a/Tests/Fixtures/DummyObjectWithEnumProperty.php b/Tests/Fixtures/DummyObjectWithEnumProperty.php index f2677195f..70c6cff7b 100644 --- a/Tests/Fixtures/DummyObjectWithEnumProperty.php +++ b/Tests/Fixtures/DummyObjectWithEnumProperty.php @@ -2,8 +2,6 @@ namespace Symfony\Component\Serializer\Tests\Fixtures; -use Symfony\Component\Serializer\Tests\Fixtures\StringBackedEnumDummy; - class DummyObjectWithEnumProperty { public StringBackedEnumDummy $get; From 8be421505938b11a0ca4f656e4322232236386f0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andre=CC=81=20Laugks?= Date: Fri, 13 Sep 2024 13:10:52 +0200 Subject: [PATCH 276/297] [Serializer] Fix `ObjectNormalizer` gives warnings on normalizing with public static property --- Normalizer/ObjectNormalizer.php | 2 +- Tests/Normalizer/ObjectNormalizerTest.php | 10 ++++++++++ 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/Normalizer/ObjectNormalizer.php b/Normalizer/ObjectNormalizer.php index a8c887e50..c8473a62c 100644 --- a/Normalizer/ObjectNormalizer.php +++ b/Normalizer/ObjectNormalizer.php @@ -197,7 +197,7 @@ protected function isAllowedAttribute($classOrObject, string $attribute, ?string if ($context['_read_attributes'] ?? true) { if (!isset(self::$isReadableCache[$class.$attribute])) { - self::$isReadableCache[$class.$attribute] = (\is_object($classOrObject) && $this->propertyAccessor->isReadable($classOrObject, $attribute)) || $this->propertyInfoExtractor->isReadable($class, $attribute) || $this->hasAttributeAccessorMethod($class, $attribute); + self::$isReadableCache[$class.$attribute] = $this->propertyInfoExtractor->isReadable($class, $attribute) || $this->hasAttributeAccessorMethod($class, $attribute) || (\is_object($classOrObject) && $this->propertyAccessor->isReadable($classOrObject, $attribute)); } return self::$isReadableCache[$class.$attribute]; diff --git a/Tests/Normalizer/ObjectNormalizerTest.php b/Tests/Normalizer/ObjectNormalizerTest.php index e314ac74f..5b028e8c0 100644 --- a/Tests/Normalizer/ObjectNormalizerTest.php +++ b/Tests/Normalizer/ObjectNormalizerTest.php @@ -927,6 +927,16 @@ public function testDenormalizeWithPropertyPath() $this->assertEquals($expected, $obj); } + + public function testObjectNormalizerWithAttributeLoaderAndObjectHasStaticProperty() + { + $class = new class { + public static string $foo; + }; + + $normalizer = new ObjectNormalizer(new ClassMetadataFactory(new AttributeLoader())); + $this->assertSame([], $normalizer->normalize($class)); + } } class ProxyObjectDummy extends ObjectDummy From e71f70329e19a18607ea0a53391a4837e10218c8 Mon Sep 17 00:00:00 2001 From: Alexandre Daubois Date: Thu, 3 Oct 2024 14:15:19 +0200 Subject: [PATCH 277/297] Various CS fix for consistency --- Tests/Normalizer/AbstractObjectNormalizerTest.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Tests/Normalizer/AbstractObjectNormalizerTest.php b/Tests/Normalizer/AbstractObjectNormalizerTest.php index 28324c48b..499fa8bff 100644 --- a/Tests/Normalizer/AbstractObjectNormalizerTest.php +++ b/Tests/Normalizer/AbstractObjectNormalizerTest.php @@ -1230,7 +1230,7 @@ public static function provideDenormalizeWithFilterBoolData(): array public function testDenormalizeArrayObject() { - $normalizer = new class() extends AbstractObjectNormalizerDummy { + $normalizer = new class extends AbstractObjectNormalizerDummy { public function __construct() { parent::__construct(null, null, new PhpDocExtractor()); From 7c0c971e7cd929e6b34bf991cc16f245979885e6 Mon Sep 17 00:00:00 2001 From: Alexandre Daubois Date: Mon, 14 Oct 2024 20:03:05 +0200 Subject: [PATCH 278/297] Reduce common control flows --- Command/DebugCommand.php | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/Command/DebugCommand.php b/Command/DebugCommand.php index 3e70c93a5..7df4d6bc8 100644 --- a/Command/DebugCommand.php +++ b/Command/DebugCommand.php @@ -75,15 +75,14 @@ private function dumpSerializerDataForClass(InputInterface $input, OutputInterfa ]; } + $io->section($title); + if (!$rows) { - $io->section($title); $io->text('No Serializer data were found for this class.'); return; } - $io->section($title); - $table = new Table($output); $table->setHeaders(['Property', 'Options']); $table->setRows($rows); From b8f4851e37b94914bf269f32a720a203e7501a52 Mon Sep 17 00:00:00 2001 From: Mathias Arlaud Date: Wed, 14 Aug 2024 15:51:51 +0200 Subject: [PATCH 279/297] [TypeInfo] Redesign Type methods and nullability --- Normalizer/AbstractObjectNormalizer.php | 41 +++++++++++++++---------- Normalizer/ArrayDenormalizer.php | 7 ++++- composer.json | 4 +-- 3 files changed, 33 insertions(+), 19 deletions(-) diff --git a/Normalizer/AbstractObjectNormalizer.php b/Normalizer/AbstractObjectNormalizer.php index 9db241298..82aaa290d 100644 --- a/Normalizer/AbstractObjectNormalizer.php +++ b/Normalizer/AbstractObjectNormalizer.php @@ -32,12 +32,14 @@ use Symfony\Component\Serializer\Mapping\ClassMetadataInterface; use Symfony\Component\Serializer\Mapping\Factory\ClassMetadataFactoryInterface; use Symfony\Component\Serializer\NameConverter\NameConverterInterface; -use Symfony\Component\TypeInfo\Exception\LogicException as TypeInfoLogicException; use Symfony\Component\TypeInfo\Type; +use Symfony\Component\TypeInfo\Type\BuiltinType; use Symfony\Component\TypeInfo\Type\CollectionType; use Symfony\Component\TypeInfo\Type\IntersectionType; +use Symfony\Component\TypeInfo\Type\NullableType; use Symfony\Component\TypeInfo\Type\ObjectType; use Symfony\Component\TypeInfo\Type\UnionType; +use Symfony\Component\TypeInfo\Type\WrappingTypeInterface; use Symfony\Component\TypeInfo\TypeIdentifier; /** @@ -644,11 +646,9 @@ private function validateAndDenormalizeLegacy(array $types, string $currentClass private function validateAndDenormalize(Type $type, string $currentClass, string $attribute, mixed $data, ?string $format, array $context): mixed { $expectedTypes = []; - $isUnionType = $type->asNonNullable() instanceof UnionType; $e = null; $extraAttributesException = null; $missingConstructorArgumentsException = null; - $isNullable = false; $types = match (true) { $type instanceof IntersectionType => throw new LogicException('Unable to handle intersection type.'), @@ -667,11 +667,13 @@ private function validateAndDenormalize(Type $type, string $currentClass, string $collectionValueType = $t->getCollectionValueType(); } - $t = $t->getBaseType(); + while ($t instanceof WrappingTypeInterface) { + $t = $t->getWrappedType(); + } // Fix a collection that contains the only one element // This is special to xml format only - if ('xml' === $format && $collectionValueType && !$collectionValueType->isA(TypeIdentifier::MIXED) && (!\is_array($data) || !\is_int(key($data)))) { + if ('xml' === $format && $collectionValueType && !$collectionValueType->isIdentifiedBy(TypeIdentifier::MIXED) && (!\is_array($data) || !\is_int(key($data)))) { $data = [$data]; } @@ -694,8 +696,6 @@ private function validateAndDenormalize(Type $type, string $currentClass, string if (TypeIdentifier::STRING === $typeIdentifier) { return ''; } - - $isNullable = $isNullable ?: $type->isNullable(); } switch ($typeIdentifier) { @@ -731,10 +731,9 @@ private function validateAndDenormalize(Type $type, string $currentClass, string } if ($collectionValueType) { - try { - $collectionValueBaseType = $collectionValueType->getBaseType(); - } catch (TypeInfoLogicException) { - $collectionValueBaseType = Type::mixed(); + $collectionValueBaseType = $collectionValueType; + while ($collectionValueBaseType instanceof WrappingTypeInterface) { + $collectionValueBaseType = $collectionValueBaseType->getWrappedType(); } if ($collectionValueBaseType instanceof ObjectType) { @@ -742,15 +741,25 @@ private function validateAndDenormalize(Type $type, string $currentClass, string $class = $collectionValueBaseType->getClassName().'[]'; $context['key_type'] = $collectionKeyType; $context['value_type'] = $collectionValueType; - } elseif (TypeIdentifier::ARRAY === $collectionValueBaseType->getTypeIdentifier()) { + } elseif ($collectionValueBaseType instanceof BuiltinType && TypeIdentifier::ARRAY === $collectionValueBaseType->getTypeIdentifier()) { // get inner type for any nested array $innerType = $collectionValueType; + if ($innerType instanceof NullableType) { + $innerType = $innerType->getWrappedType(); + } // note that it will break for any other builtinType $dimensions = '[]'; while ($innerType instanceof CollectionType) { $dimensions .= '[]'; $innerType = $innerType->getCollectionValueType(); + if ($innerType instanceof NullableType) { + $innerType = $innerType->getWrappedType(); + } + } + + while ($innerType instanceof WrappingTypeInterface) { + $innerType = $innerType->getWrappedType(); } if ($innerType instanceof ObjectType) { @@ -832,17 +841,17 @@ private function validateAndDenormalize(Type $type, string $currentClass, string return $data; } } catch (NotNormalizableValueException|InvalidArgumentException $e) { - if (!$isUnionType && !$isNullable) { + if (!$type instanceof UnionType) { throw $e; } } catch (ExtraAttributesException $e) { - if (!$isUnionType && !$isNullable) { + if (!$type instanceof UnionType) { throw $e; } $extraAttributesException ??= $e; } catch (MissingConstructorArgumentsException $e) { - if (!$isUnionType && !$isNullable) { + if (!$type instanceof UnionType) { throw $e; } @@ -862,7 +871,7 @@ private function validateAndDenormalize(Type $type, string $currentClass, string throw $missingConstructorArgumentsException; } - if (!$isUnionType && $e) { + if ($e && !($type instanceof UnionType && !$type instanceof NullableType)) { throw $e; } diff --git a/Normalizer/ArrayDenormalizer.php b/Normalizer/ArrayDenormalizer.php index 94de2de34..08fae04df 100644 --- a/Normalizer/ArrayDenormalizer.php +++ b/Normalizer/ArrayDenormalizer.php @@ -16,7 +16,9 @@ use Symfony\Component\Serializer\Exception\InvalidArgumentException; use Symfony\Component\Serializer\Exception\NotNormalizableValueException; use Symfony\Component\TypeInfo\Type; +use Symfony\Component\TypeInfo\Type\BuiltinType; use Symfony\Component\TypeInfo\Type\UnionType; +use Symfony\Component\TypeInfo\TypeIdentifier; /** * Denormalizes arrays of objects. @@ -54,7 +56,10 @@ public function denormalize(mixed $data, string $type, ?string $format = null, a $typeIdentifiers = []; if (null !== $keyType = ($context['key_type'] ?? null)) { if ($keyType instanceof Type) { - $typeIdentifiers = array_map(fn (Type $t): string => $t->getBaseType()->getTypeIdentifier()->value, $keyType instanceof UnionType ? $keyType->getTypes() : [$keyType]); + /** @var list|BuiltinType> */ + $keyTypes = $keyType instanceof UnionType ? $keyType->getTypes() : [$keyType]; + + $typeIdentifiers = array_map(fn (BuiltinType $t): string => $t->getTypeIdentifier()->value, $keyTypes); } else { $typeIdentifiers = array_map(fn (LegacyType $t): string => $t->getBuiltinType(), \is_array($keyType) ? $keyType : [$keyType]); } diff --git a/composer.json b/composer.json index 8691e2240..bb325dfef 100644 --- a/composer.json +++ b/composer.json @@ -38,7 +38,7 @@ "symfony/property-access": "^6.4|^7.0", "symfony/property-info": "^6.4|^7.0", "symfony/translation-contracts": "^2.5|^3", - "symfony/type-info": "^7.1.5", + "symfony/type-info": "^7.2", "symfony/uid": "^6.4|^7.0", "symfony/validator": "^6.4|^7.0", "symfony/var-dumper": "^6.4|^7.0", @@ -51,7 +51,7 @@ "symfony/dependency-injection": "<6.4", "symfony/property-access": "<6.4", "symfony/property-info": "<6.4", - "symfony/type-info": "<7.1.5", + "symfony/type-info": "<7.2", "symfony/uid": "<6.4", "symfony/validator": "<6.4", "symfony/yaml": "<6.4" From 56bcd41f8e8792b15ca363b730e26c7c536a7873 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A9vin=20Dunglas?= Date: Thu, 10 Oct 2024 12:40:40 +0200 Subject: [PATCH 280/297] [Serializer] Improve `AttributeLoader` --- Mapping/Loader/AttributeLoader.php | 69 +++++++++++++----------------- 1 file changed, 30 insertions(+), 39 deletions(-) diff --git a/Mapping/Loader/AttributeLoader.php b/Mapping/Loader/AttributeLoader.php index 272e236b6..6bcbed36c 100644 --- a/Mapping/Loader/AttributeLoader.php +++ b/Mapping/Loader/AttributeLoader.php @@ -58,23 +58,12 @@ public function loadClassMetadata(ClassMetadataInterface $classMetadata): bool $attributesMetadata = $classMetadata->getAttributesMetadata(); foreach ($this->loadAttributes($reflectionClass) as $annotation) { - if ($annotation instanceof DiscriminatorMap) { - $classMetadata->setClassDiscriminatorMapping(new ClassDiscriminatorMapping( - $annotation->getTypeProperty(), - $annotation->getMapping() - )); - continue; - } - - if ($annotation instanceof Groups) { - $classGroups = $annotation->getGroups(); - - continue; - } - - if ($annotation instanceof Context) { - $classContextAnnotation = $annotation; - } + match (true) { + $annotation instanceof DiscriminatorMap => $classMetadata->setClassDiscriminatorMapping(new ClassDiscriminatorMapping($annotation->getTypeProperty(), $annotation->getMapping())), + $annotation instanceof Groups => $classGroups = $annotation->getGroups(), + $annotation instanceof Context => $classContextAnnotation = $annotation, + default => null, + }; } foreach ($reflectionClass->getProperties() as $property) { @@ -83,33 +72,35 @@ public function loadClassMetadata(ClassMetadataInterface $classMetadata): bool $classMetadata->addAttributeMetadata($attributesMetadata[$property->name]); } + $attributeMetadata = $attributesMetadata[$property->name]; if ($property->getDeclaringClass()->name === $className) { if ($classContextAnnotation) { - $this->setAttributeContextsForGroups($classContextAnnotation, $attributesMetadata[$property->name]); + $this->setAttributeContextsForGroups($classContextAnnotation, $attributeMetadata); } foreach ($classGroups as $group) { - $attributesMetadata[$property->name]->addGroup($group); + $attributeMetadata->addGroup($group); } foreach ($this->loadAttributes($property) as $annotation) { + $loaded = true; + if ($annotation instanceof Groups) { foreach ($annotation->getGroups() as $group) { - $attributesMetadata[$property->name]->addGroup($group); + $attributeMetadata->addGroup($group); } - } elseif ($annotation instanceof MaxDepth) { - $attributesMetadata[$property->name]->setMaxDepth($annotation->getMaxDepth()); - } elseif ($annotation instanceof SerializedName) { - $attributesMetadata[$property->name]->setSerializedName($annotation->getSerializedName()); - } elseif ($annotation instanceof SerializedPath) { - $attributesMetadata[$property->name]->setSerializedPath($annotation->getSerializedPath()); - } elseif ($annotation instanceof Ignore) { - $attributesMetadata[$property->name]->setIgnore(true); - } elseif ($annotation instanceof Context) { - $this->setAttributeContextsForGroups($annotation, $attributesMetadata[$property->name]); + + continue; } - $loaded = true; + match (true) { + $annotation instanceof MaxDepth => $attributeMetadata->setMaxDepth($annotation->getMaxDepth()), + $annotation instanceof SerializedName => $attributeMetadata->setSerializedName($annotation->getSerializedName()), + $annotation instanceof SerializedPath => $attributeMetadata->setSerializedPath($annotation->getSerializedPath()), + $annotation instanceof Ignore => $attributeMetadata->setIgnore(true), + $annotation instanceof Context => $this->setAttributeContextsForGroups($annotation, $attributeMetadata), + default => null, + }; } } } @@ -206,17 +197,17 @@ private function loadAttributes(\ReflectionMethod|\ReflectionClass|\ReflectionPr private function setAttributeContextsForGroups(Context $annotation, AttributeMetadataInterface $attributeMetadata): void { - if ($annotation->getContext()) { - $attributeMetadata->setNormalizationContextForGroups($annotation->getContext(), $annotation->getGroups()); - $attributeMetadata->setDenormalizationContextForGroups($annotation->getContext(), $annotation->getGroups()); - } + $context = $annotation->getContext(); + $groups = $annotation->getGroups(); + $normalizationContext = $annotation->getNormalizationContext(); + $denormalizationContext = $annotation->getDenormalizationContext(); - if ($annotation->getNormalizationContext()) { - $attributeMetadata->setNormalizationContextForGroups($annotation->getNormalizationContext(), $annotation->getGroups()); + if ($normalizationContext || $context) { + $attributeMetadata->setNormalizationContextForGroups($normalizationContext ?: $context, $groups); } - if ($annotation->getDenormalizationContext()) { - $attributeMetadata->setDenormalizationContextForGroups($annotation->getDenormalizationContext(), $annotation->getGroups()); + if ($denormalizationContext || $context) { + $attributeMetadata->setDenormalizationContextForGroups($denormalizationContext ?: $context, $groups); } } From 9d862d66198f3c2e30404228629ef4c18d5d608e Mon Sep 17 00:00:00 2001 From: eRIZ Date: Tue, 21 May 2024 20:55:10 +0200 Subject: [PATCH 281/297] [Serializer] fixed object normalizer for a class with `cancel` method --- Mapping/Loader/AttributeLoader.php | 2 +- Normalizer/GetSetMethodNormalizer.php | 8 +- Normalizer/ObjectNormalizer.php | 5 +- .../Attributes/AccessorishGetters.php | 39 ++++++++++ .../Loader/AttributeLoaderTestCase.php | 17 ++++ .../Normalizer/GetSetMethodNormalizerTest.php | 51 ++++++++++++ Tests/Normalizer/ObjectNormalizerTest.php | 78 +++++++++++++++++++ 7 files changed, 194 insertions(+), 6 deletions(-) create mode 100644 Tests/Fixtures/Attributes/AccessorishGetters.php diff --git a/Mapping/Loader/AttributeLoader.php b/Mapping/Loader/AttributeLoader.php index 8acd5a8c5..6bd967b5f 100644 --- a/Mapping/Loader/AttributeLoader.php +++ b/Mapping/Loader/AttributeLoader.php @@ -129,7 +129,7 @@ public function loadClassMetadata(ClassMetadataInterface $classMetadata): bool } $accessorOrMutator = preg_match('/^(get|is|has|set)(.+)$/i', $method->name, $matches); - if ($accessorOrMutator) { + if ($accessorOrMutator && !ctype_lower($matches[2][0])) { $attributeName = lcfirst($matches[2]); if (isset($attributesMetadata[$attributeName])) { diff --git a/Normalizer/GetSetMethodNormalizer.php b/Normalizer/GetSetMethodNormalizer.php index 50dd48628..951005545 100644 --- a/Normalizer/GetSetMethodNormalizer.php +++ b/Normalizer/GetSetMethodNormalizer.php @@ -105,8 +105,8 @@ private function isGetMethod(\ReflectionMethod $method): bool return !$method->isStatic() && !($method->getAttributes(Ignore::class) || $method->getAttributes(LegacyIgnore::class)) && !$method->getNumberOfRequiredParameters() - && ((2 < ($methodLength = \strlen($method->name)) && str_starts_with($method->name, 'is')) - || (3 < $methodLength && (str_starts_with($method->name, 'has') || str_starts_with($method->name, 'get'))) + && ((2 < ($methodLength = \strlen($method->name)) && str_starts_with($method->name, 'is') && !ctype_lower($method->name[2])) + || (3 < $methodLength && (str_starts_with($method->name, 'has') || str_starts_with($method->name, 'get')) && !ctype_lower($method->name[3])) ); } @@ -118,7 +118,9 @@ private function isSetMethod(\ReflectionMethod $method): bool return !$method->isStatic() && !$method->getAttributes(Ignore::class) && 0 < $method->getNumberOfParameters() - && str_starts_with($method->name, 'set'); + && str_starts_with($method->name, 'set') + && !ctype_lower($method->name[3]) + ; } protected function extractAttributes(object $object, ?string $format = null, array $context = []): array diff --git a/Normalizer/ObjectNormalizer.php b/Normalizer/ObjectNormalizer.php index c8473a62c..e93d7b4cc 100644 --- a/Normalizer/ObjectNormalizer.php +++ b/Normalizer/ObjectNormalizer.php @@ -100,7 +100,8 @@ protected function extractAttributes(object $object, ?string $format = null, arr $name = $reflMethod->name; $attributeName = null; - if (3 < \strlen($name) && match ($name[0]) { + // ctype_lower check to find out if method looks like accessor but actually is not, e.g. hash, cancel + if (3 < \strlen($name) && !ctype_lower($name[3]) && match ($name[0]) { 'g' => str_starts_with($name, 'get'), 'h' => str_starts_with($name, 'has'), 'c' => str_starts_with($name, 'can'), @@ -112,7 +113,7 @@ protected function extractAttributes(object $object, ?string $format = null, arr if (!$reflClass->hasProperty($attributeName)) { $attributeName = lcfirst($attributeName); } - } elseif ('is' !== $name && str_starts_with($name, 'is')) { + } elseif ('is' !== $name && str_starts_with($name, 'is') && !ctype_lower($name[2])) { // issers $attributeName = substr($name, 2); diff --git a/Tests/Fixtures/Attributes/AccessorishGetters.php b/Tests/Fixtures/Attributes/AccessorishGetters.php new file mode 100644 index 000000000..f434e84f3 --- /dev/null +++ b/Tests/Fixtures/Attributes/AccessorishGetters.php @@ -0,0 +1,39 @@ + + * + * 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\Attributes; + +class AccessorishGetters +{ + public function hash(): void + { + } + + public function cancel() + { + } + + public function getField1() + { + } + + public function isField2() + { + } + + public function hasField3() + { + } + + public function setField4() + { + } +} diff --git a/Tests/Mapping/Loader/AttributeLoaderTestCase.php b/Tests/Mapping/Loader/AttributeLoaderTestCase.php index 99615d382..73cb674c2 100644 --- a/Tests/Mapping/Loader/AttributeLoaderTestCase.php +++ b/Tests/Mapping/Loader/AttributeLoaderTestCase.php @@ -21,6 +21,7 @@ use Symfony\Component\Serializer\Mapping\Loader\AnnotationLoader; use Symfony\Component\Serializer\Mapping\Loader\AttributeLoader; use Symfony\Component\Serializer\Mapping\Loader\LoaderInterface; +use Symfony\Component\Serializer\Tests\Fixtures\Attributes\AccessorishGetters; use Symfony\Component\Serializer\Tests\Mapping\Loader\Features\ContextMappingTestTrait; use Symfony\Component\Serializer\Tests\Mapping\TestClassMetadataFactory; @@ -212,6 +213,22 @@ public function testLoadGroupsOnClass() self::assertSame(['a'], $attributesMetadata['baz']->getGroups()); } + public function testIgnoresAccessorishGetters() + { + $classMetadata = new ClassMetadata(AccessorishGetters::class); + $this->loader->loadClassMetadata($classMetadata); + + $attributesMetadata = $classMetadata->getAttributesMetadata(); + + self::assertCount(4, $classMetadata->getAttributesMetadata()); + + self::assertArrayHasKey('field1', $attributesMetadata); + self::assertArrayHasKey('field2', $attributesMetadata); + self::assertArrayHasKey('field3', $attributesMetadata); + self::assertArrayHasKey('field4', $attributesMetadata); + self::assertArrayNotHasKey('h', $attributesMetadata); + } + /** * @group legacy */ diff --git a/Tests/Normalizer/GetSetMethodNormalizerTest.php b/Tests/Normalizer/GetSetMethodNormalizerTest.php index ca5d25910..4398fbdab 100644 --- a/Tests/Normalizer/GetSetMethodNormalizerTest.php +++ b/Tests/Normalizer/GetSetMethodNormalizerTest.php @@ -515,6 +515,14 @@ public function testNormalizeWithDiscriminator() $this->assertSame(['type' => 'one', 'url' => 'URL_ONE'], $normalizer->normalize(new GetSetMethodDiscriminatedDummyOne())); } + public function testNormalizeWithMethodNamesSimilarToAccessors() + { + $classMetadataFactory = new ClassMetadataFactory(new AttributeLoader()); + $normalizer = new GetSetMethodNormalizer($classMetadataFactory); + + $this->assertSame(['class' => 'class', 123 => 123], $normalizer->normalize(new GetSetWithAccessorishMethod())); + } + public function testDenormalizeWithDiscriminator() { $classMetadataFactory = new ClassMetadataFactory(new AttributeLoader()); @@ -902,3 +910,46 @@ public function setBar($bar = null, $other = true) $this->bar = $bar; } } + +class GetSetWithAccessorishMethod +{ + public function cancel() + { + return 'cancel'; + } + + public function hash() + { + return 'hash'; + } + + public function getClass() + { + return 'class'; + } + + public function setClass() + { + } + + public function get123() + { + return 123; + } + + public function set123() + { + } + + public function gettings() + { + } + + public function settings() + { + } + + public function isolate() + { + } +} diff --git a/Tests/Normalizer/ObjectNormalizerTest.php b/Tests/Normalizer/ObjectNormalizerTest.php index 5b028e8c0..822c0016e 100644 --- a/Tests/Normalizer/ObjectNormalizerTest.php +++ b/Tests/Normalizer/ObjectNormalizerTest.php @@ -937,6 +937,24 @@ public function testObjectNormalizerWithAttributeLoaderAndObjectHasStaticPropert $normalizer = new ObjectNormalizer(new ClassMetadataFactory(new AttributeLoader())); $this->assertSame([], $normalizer->normalize($class)); } + + public function testNormalizeWithMethodNamesSimilarToAccessors() + { + $classMetadataFactory = new ClassMetadataFactory(new AttributeLoader()); + $normalizer = new ObjectNormalizer($classMetadataFactory); + + $object = new ObjectWithAccessorishMethods(); + $normalized = $normalizer->normalize($object); + + $this->assertFalse($object->isAccessorishCalled()); + $this->assertSame([ + 'accessorishCalled' => false, + 'tell' => true, + 'class' => true, + 'responsibility' => true, + 123 => 321 + ], $normalized); + } } class ProxyObjectDummy extends ObjectDummy @@ -1219,3 +1237,63 @@ class ObjectDummyWithIgnoreAttributeAndPrivateProperty private $private = 'private'; } + +class ObjectWithAccessorishMethods +{ + private $accessorishCalled = false; + + public function isAccessorishCalled() + { + return $this->accessorishCalled; + } + + public function cancel() + { + $this->accessorishCalled = true; + } + + public function hash() + { + $this->accessorishCalled = true; + } + + public function canTell() + { + return true; + } + + public function getClass() + { + return true; + } + + public function hasResponsibility() + { + return true; + } + + public function get_foo() + { + return 'bar'; + } + + public function get123() + { + return 321; + } + + public function gettings() + { + $this->accessorishCalled = true; + } + + public function settings() + { + $this->accessorishCalled = true; + } + + public function isolate() + { + $this->accessorishCalled = true; + } +} From 96421e664c2c3077a1e0858a02e62a7a4d66ce79 Mon Sep 17 00:00:00 2001 From: Antoine Lamirault Date: Wed, 16 Oct 2024 22:04:16 +0200 Subject: [PATCH 282/297] [Serializer] Rename annotations to attributes in AttributeLoader --- Mapping/Loader/AttributeLoader.php | 64 +++++++++---------- .../{Annotation => Attribute}/ContextTest.php | 2 +- .../DiscriminatorMapTest.php | 8 +-- .../{Annotation => Attribute}/GroupsTest.php | 2 +- .../MaxDepthTest.php | 2 +- .../SerializedNameTest.php | 2 +- .../SerializedPathTest.php | 2 +- Tests/Fixtures/Attributes/GroupDummy.php | 4 +- ...ditionalGetterWithoutIgnoreAttributes.php} | 2 +- ...my.php => ChildOfGroupsAttributeDummy.php} | 2 +- Tests/Mapping/Loader/AttributeLoaderTest.php | 8 +-- 11 files changed, 49 insertions(+), 49 deletions(-) rename Tests/{Annotation => Attribute}/ContextTest.php (98%) rename Tests/{Annotation => Attribute}/DiscriminatorMapTest.php (82%) rename Tests/{Annotation => Attribute}/GroupsTest.php (95%) rename Tests/{Annotation => Attribute}/MaxDepthTest.php (94%) rename Tests/{Annotation => Attribute}/SerializedNameTest.php (94%) rename Tests/{Annotation => Attribute}/SerializedPathTest.php (95%) rename Tests/Fixtures/Attributes/{IgnoreDummyAdditionalGetterWithoutIgnoreAnnotations.php => IgnoreDummyAdditionalGetterWithoutIgnoreAttributes.php} (85%) rename Tests/Fixtures/{ChildOfGroupsAnnotationDummy.php => ChildOfGroupsAttributeDummy.php} (82%) diff --git a/Mapping/Loader/AttributeLoader.php b/Mapping/Loader/AttributeLoader.php index 6bcbed36c..ca92aa88b 100644 --- a/Mapping/Loader/AttributeLoader.php +++ b/Mapping/Loader/AttributeLoader.php @@ -53,15 +53,15 @@ public function loadClassMetadata(ClassMetadataInterface $classMetadata): bool $className = $reflectionClass->name; $loaded = false; $classGroups = []; - $classContextAnnotation = null; + $classContextAttribute = null; $attributesMetadata = $classMetadata->getAttributesMetadata(); - foreach ($this->loadAttributes($reflectionClass) as $annotation) { + foreach ($this->loadAttributes($reflectionClass) as $attribute) { match (true) { - $annotation instanceof DiscriminatorMap => $classMetadata->setClassDiscriminatorMapping(new ClassDiscriminatorMapping($annotation->getTypeProperty(), $annotation->getMapping())), - $annotation instanceof Groups => $classGroups = $annotation->getGroups(), - $annotation instanceof Context => $classContextAnnotation = $annotation, + $attribute instanceof DiscriminatorMap => $classMetadata->setClassDiscriminatorMapping(new ClassDiscriminatorMapping($attribute->getTypeProperty(), $attribute->getMapping())), + $attribute instanceof Groups => $classGroups = $attribute->getGroups(), + $attribute instanceof Context => $classContextAttribute = $attribute, default => null, }; } @@ -74,19 +74,19 @@ public function loadClassMetadata(ClassMetadataInterface $classMetadata): bool $attributeMetadata = $attributesMetadata[$property->name]; if ($property->getDeclaringClass()->name === $className) { - if ($classContextAnnotation) { - $this->setAttributeContextsForGroups($classContextAnnotation, $attributeMetadata); + if ($classContextAttribute) { + $this->setAttributeContextsForGroups($classContextAttribute, $attributeMetadata); } foreach ($classGroups as $group) { $attributeMetadata->addGroup($group); } - foreach ($this->loadAttributes($property) as $annotation) { + foreach ($this->loadAttributes($property) as $attribute) { $loaded = true; - if ($annotation instanceof Groups) { - foreach ($annotation->getGroups() as $group) { + if ($attribute instanceof Groups) { + foreach ($attribute->getGroups() as $group) { $attributeMetadata->addGroup($group); } @@ -94,11 +94,11 @@ public function loadClassMetadata(ClassMetadataInterface $classMetadata): bool } match (true) { - $annotation instanceof MaxDepth => $attributeMetadata->setMaxDepth($annotation->getMaxDepth()), - $annotation instanceof SerializedName => $attributeMetadata->setSerializedName($annotation->getSerializedName()), - $annotation instanceof SerializedPath => $attributeMetadata->setSerializedPath($annotation->getSerializedPath()), - $annotation instanceof Ignore => $attributeMetadata->setIgnore(true), - $annotation instanceof Context => $this->setAttributeContextsForGroups($annotation, $attributeMetadata), + $attribute instanceof MaxDepth => $attributeMetadata->setMaxDepth($attribute->getMaxDepth()), + $attribute instanceof SerializedName => $attributeMetadata->setSerializedName($attribute->getSerializedName()), + $attribute instanceof SerializedPath => $attributeMetadata->setSerializedPath($attribute->getSerializedPath()), + $attribute instanceof Ignore => $attributeMetadata->setIgnore(true), + $attribute instanceof Context => $this->setAttributeContextsForGroups($attribute, $attributeMetadata), default => null, }; } @@ -126,43 +126,43 @@ public function loadClassMetadata(ClassMetadataInterface $classMetadata): bool } } - foreach ($this->loadAttributes($method) as $annotation) { - if ($annotation instanceof Groups) { + foreach ($this->loadAttributes($method) as $attribute) { + if ($attribute instanceof Groups) { if (!$accessorOrMutator) { throw new MappingException(\sprintf('Groups on "%s::%s()" cannot be added. Groups can only be added on methods beginning with "get", "is", "has" or "set".', $className, $method->name)); } - foreach ($annotation->getGroups() as $group) { + foreach ($attribute->getGroups() as $group) { $attributeMetadata->addGroup($group); } - } elseif ($annotation instanceof MaxDepth) { + } elseif ($attribute instanceof MaxDepth) { if (!$accessorOrMutator) { throw new MappingException(\sprintf('MaxDepth on "%s::%s()" cannot be added. MaxDepth can only be added on methods beginning with "get", "is", "has" or "set".', $className, $method->name)); } - $attributeMetadata->setMaxDepth($annotation->getMaxDepth()); - } elseif ($annotation instanceof SerializedName) { + $attributeMetadata->setMaxDepth($attribute->getMaxDepth()); + } elseif ($attribute instanceof SerializedName) { if (!$accessorOrMutator) { throw new MappingException(\sprintf('SerializedName on "%s::%s()" cannot be added. SerializedName can only be added on methods beginning with "get", "is", "has" or "set".', $className, $method->name)); } - $attributeMetadata->setSerializedName($annotation->getSerializedName()); - } elseif ($annotation instanceof SerializedPath) { + $attributeMetadata->setSerializedName($attribute->getSerializedName()); + } elseif ($attribute instanceof SerializedPath) { if (!$accessorOrMutator) { throw new MappingException(\sprintf('SerializedPath on "%s::%s()" cannot be added. SerializedPath can only be added on methods beginning with "get", "is", "has" or "set".', $className, $method->name)); } - $attributeMetadata->setSerializedPath($annotation->getSerializedPath()); - } elseif ($annotation instanceof Ignore) { + $attributeMetadata->setSerializedPath($attribute->getSerializedPath()); + } elseif ($attribute instanceof Ignore) { if ($accessorOrMutator) { $attributeMetadata->setIgnore(true); } - } elseif ($annotation instanceof Context) { + } elseif ($attribute instanceof Context) { if (!$accessorOrMutator) { throw new MappingException(\sprintf('Context on "%s::%s()" cannot be added. Context can only be added on methods beginning with "get", "is", "has" or "set".', $className, $method->name)); } - $this->setAttributeContextsForGroups($annotation, $attributeMetadata); + $this->setAttributeContextsForGroups($attribute, $attributeMetadata); } $loaded = true; @@ -195,12 +195,12 @@ private function loadAttributes(\ReflectionMethod|\ReflectionClass|\ReflectionPr } } - private function setAttributeContextsForGroups(Context $annotation, AttributeMetadataInterface $attributeMetadata): void + private function setAttributeContextsForGroups(Context $attribute, AttributeMetadataInterface $attributeMetadata): void { - $context = $annotation->getContext(); - $groups = $annotation->getGroups(); - $normalizationContext = $annotation->getNormalizationContext(); - $denormalizationContext = $annotation->getDenormalizationContext(); + $context = $attribute->getContext(); + $groups = $attribute->getGroups(); + $normalizationContext = $attribute->getNormalizationContext(); + $denormalizationContext = $attribute->getDenormalizationContext(); if ($normalizationContext || $context) { $attributeMetadata->setNormalizationContextForGroups($normalizationContext ?: $context, $groups); diff --git a/Tests/Annotation/ContextTest.php b/Tests/Attribute/ContextTest.php similarity index 98% rename from Tests/Annotation/ContextTest.php rename to Tests/Attribute/ContextTest.php index 84ff41b8a..cfe175050 100644 --- a/Tests/Annotation/ContextTest.php +++ b/Tests/Attribute/ContextTest.php @@ -9,7 +9,7 @@ * file that was distributed with this source code. */ -namespace Symfony\Component\Serializer\Tests\Annotation; +namespace Symfony\Component\Serializer\Tests\Attribute; use PHPUnit\Framework\TestCase; use Symfony\Component\Serializer\Attribute\Context; diff --git a/Tests/Annotation/DiscriminatorMapTest.php b/Tests/Attribute/DiscriminatorMapTest.php similarity index 82% rename from Tests/Annotation/DiscriminatorMapTest.php rename to Tests/Attribute/DiscriminatorMapTest.php index bbd112e26..497bc6201 100644 --- a/Tests/Annotation/DiscriminatorMapTest.php +++ b/Tests/Attribute/DiscriminatorMapTest.php @@ -9,7 +9,7 @@ * file that was distributed with this source code. */ -namespace Symfony\Component\Serializer\Tests\Annotation; +namespace Symfony\Component\Serializer\Tests\Attribute; use PHPUnit\Framework\TestCase; use Symfony\Component\Serializer\Attribute\DiscriminatorMap; @@ -22,16 +22,16 @@ class DiscriminatorMapTest extends TestCase { public function testGetTypePropertyAndMapping() { - $annotation = new DiscriminatorMap(typeProperty: 'type', mapping: [ + $attribute = new DiscriminatorMap(typeProperty: 'type', mapping: [ 'foo' => 'FooClass', 'bar' => 'BarClass', ]); - $this->assertEquals('type', $annotation->getTypeProperty()); + $this->assertEquals('type', $attribute->getTypeProperty()); $this->assertEquals([ 'foo' => 'FooClass', 'bar' => 'BarClass', - ], $annotation->getMapping()); + ], $attribute->getMapping()); } public function testExceptionWithEmptyTypeProperty() diff --git a/Tests/Annotation/GroupsTest.php b/Tests/Attribute/GroupsTest.php similarity index 95% rename from Tests/Annotation/GroupsTest.php rename to Tests/Attribute/GroupsTest.php index 38ec518bd..266cbc4f4 100644 --- a/Tests/Annotation/GroupsTest.php +++ b/Tests/Attribute/GroupsTest.php @@ -9,7 +9,7 @@ * file that was distributed with this source code. */ -namespace Symfony\Component\Serializer\Tests\Annotation; +namespace Symfony\Component\Serializer\Tests\Attribute; use PHPUnit\Framework\TestCase; use Symfony\Component\Serializer\Attribute\Groups; diff --git a/Tests/Annotation/MaxDepthTest.php b/Tests/Attribute/MaxDepthTest.php similarity index 94% rename from Tests/Annotation/MaxDepthTest.php rename to Tests/Attribute/MaxDepthTest.php index f2ff35c87..e611bfbc4 100644 --- a/Tests/Annotation/MaxDepthTest.php +++ b/Tests/Attribute/MaxDepthTest.php @@ -9,7 +9,7 @@ * file that was distributed with this source code. */ -namespace Symfony\Component\Serializer\Tests\Annotation; +namespace Symfony\Component\Serializer\Tests\Attribute; use PHPUnit\Framework\TestCase; use Symfony\Component\Serializer\Attribute\MaxDepth; diff --git a/Tests/Annotation/SerializedNameTest.php b/Tests/Attribute/SerializedNameTest.php similarity index 94% rename from Tests/Annotation/SerializedNameTest.php rename to Tests/Attribute/SerializedNameTest.php index 3a829aecf..c645e7e85 100644 --- a/Tests/Annotation/SerializedNameTest.php +++ b/Tests/Attribute/SerializedNameTest.php @@ -9,7 +9,7 @@ * file that was distributed with this source code. */ -namespace Symfony\Component\Serializer\Tests\Annotation; +namespace Symfony\Component\Serializer\Tests\Attribute; use PHPUnit\Framework\TestCase; use Symfony\Component\Serializer\Attribute\SerializedName; diff --git a/Tests/Annotation/SerializedPathTest.php b/Tests/Attribute/SerializedPathTest.php similarity index 95% rename from Tests/Annotation/SerializedPathTest.php rename to Tests/Attribute/SerializedPathTest.php index f5bbfa62b..7ba31fc22 100644 --- a/Tests/Annotation/SerializedPathTest.php +++ b/Tests/Attribute/SerializedPathTest.php @@ -9,7 +9,7 @@ * file that was distributed with this source code. */ -namespace Symfony\Component\Serializer\Tests\Annotation; +namespace Symfony\Component\Serializer\Tests\Attribute; use PHPUnit\Framework\TestCase; use Symfony\Component\PropertyAccess\PropertyPath; diff --git a/Tests/Fixtures/Attributes/GroupDummy.php b/Tests/Fixtures/Attributes/GroupDummy.php index 5c34c95a4..dd88507be 100644 --- a/Tests/Fixtures/Attributes/GroupDummy.php +++ b/Tests/Fixtures/Attributes/GroupDummy.php @@ -12,7 +12,7 @@ namespace Symfony\Component\Serializer\Tests\Fixtures\Attributes; use Symfony\Component\Serializer\Attribute\Groups; -use Symfony\Component\Serializer\Tests\Fixtures\ChildOfGroupsAnnotationDummy; +use Symfony\Component\Serializer\Tests\Fixtures\ChildOfGroupsAttributeDummy; /** * @author Kévin Dunglas @@ -23,7 +23,7 @@ class GroupDummy extends GroupDummyParent implements GroupDummyInterface private $foo; #[Groups(['b', 'c', 'name_converter'])] protected $bar; - #[ChildOfGroupsAnnotationDummy] + #[ChildOfGroupsAttributeDummy] protected $quux; private $fooBar; private $symfony; diff --git a/Tests/Fixtures/Attributes/IgnoreDummyAdditionalGetterWithoutIgnoreAnnotations.php b/Tests/Fixtures/Attributes/IgnoreDummyAdditionalGetterWithoutIgnoreAttributes.php similarity index 85% rename from Tests/Fixtures/Attributes/IgnoreDummyAdditionalGetterWithoutIgnoreAnnotations.php rename to Tests/Fixtures/Attributes/IgnoreDummyAdditionalGetterWithoutIgnoreAttributes.php index 21abb870b..5d3e8f47f 100644 --- a/Tests/Fixtures/Attributes/IgnoreDummyAdditionalGetterWithoutIgnoreAnnotations.php +++ b/Tests/Fixtures/Attributes/IgnoreDummyAdditionalGetterWithoutIgnoreAttributes.php @@ -2,7 +2,7 @@ namespace Symfony\Component\Serializer\Tests\Fixtures\Attributes; -class IgnoreDummyAdditionalGetterWithoutIgnoreAnnotations +class IgnoreDummyAdditionalGetterWithoutIgnoreAttributes { private $myValue; diff --git a/Tests/Fixtures/ChildOfGroupsAnnotationDummy.php b/Tests/Fixtures/ChildOfGroupsAttributeDummy.php similarity index 82% rename from Tests/Fixtures/ChildOfGroupsAnnotationDummy.php rename to Tests/Fixtures/ChildOfGroupsAttributeDummy.php index 9a163012f..4fc81fa9d 100644 --- a/Tests/Fixtures/ChildOfGroupsAnnotationDummy.php +++ b/Tests/Fixtures/ChildOfGroupsAttributeDummy.php @@ -5,7 +5,7 @@ use Symfony\Component\Serializer\Attribute\Groups; #[\Attribute(\Attribute::TARGET_METHOD | \Attribute::TARGET_PROPERTY)] -final class ChildOfGroupsAnnotationDummy extends Groups +final class ChildOfGroupsAttributeDummy extends Groups { public function __construct() { diff --git a/Tests/Mapping/Loader/AttributeLoaderTest.php b/Tests/Mapping/Loader/AttributeLoaderTest.php index 5b6ef3de7..fd7f2b729 100644 --- a/Tests/Mapping/Loader/AttributeLoaderTest.php +++ b/Tests/Mapping/Loader/AttributeLoaderTest.php @@ -33,7 +33,7 @@ use Symfony\Component\Serializer\Tests\Fixtures\Attributes\GroupDummyParent; use Symfony\Component\Serializer\Tests\Fixtures\Attributes\IgnoreDummy; use Symfony\Component\Serializer\Tests\Fixtures\Attributes\IgnoreDummyAdditionalGetter; -use Symfony\Component\Serializer\Tests\Fixtures\Attributes\IgnoreDummyAdditionalGetterWithoutIgnoreAnnotations; +use Symfony\Component\Serializer\Tests\Fixtures\Attributes\IgnoreDummyAdditionalGetterWithoutIgnoreAttributes; use Symfony\Component\Serializer\Tests\Fixtures\Attributes\MaxDepthDummy; use Symfony\Component\Serializer\Tests\Fixtures\Attributes\SerializedNameDummy; use Symfony\Component\Serializer\Tests\Fixtures\Attributes\SerializedPathDummy; @@ -181,7 +181,7 @@ public function testCanHandleUnrelatedIgnoredMethods() $this->assertSame(['id'], array_keys($metadata->getAttributesMetadata())); } - public function testIgnoreGetterWithRequiredParameterIfIgnoreAnnotationIsUsed() + public function testIgnoreGetterWithRequiredParameterIfIgnoreAttributeIsUsed() { $classMetadata = new ClassMetadata(IgnoreDummyAdditionalGetter::class); $this->getLoaderForContextMapping()->loadClassMetadata($classMetadata); @@ -191,9 +191,9 @@ public function testIgnoreGetterWithRequiredParameterIfIgnoreAnnotationIsUsed() self::assertArrayHasKey('extraValue2', $attributes); } - public function testIgnoreGetterWithRequiredParameterIfIgnoreAnnotationIsNotUsed() + public function testIgnoreGetterWithRequiredParameterIfIgnoreAttributeIsNotUsed() { - $classMetadata = new ClassMetadata(IgnoreDummyAdditionalGetterWithoutIgnoreAnnotations::class); + $classMetadata = new ClassMetadata(IgnoreDummyAdditionalGetterWithoutIgnoreAttributes::class); $this->getLoaderForContextMapping()->loadClassMetadata($classMetadata); $attributes = $classMetadata->getAttributesMetadata(); From 6e7d9f9378bee4352de15bc7883a28294f4b82d6 Mon Sep 17 00:00:00 2001 From: Mathias Arlaud Date: Tue, 29 Oct 2024 10:06:08 +0100 Subject: [PATCH 283/297] [Serializer] Revert default groups --- CHANGELOG.md | 1 - NameConverter/MetadataAwareNameConverter.php | 7 +-- Normalizer/AbstractNormalizer.php | 11 +--- Tests/Fixtures/Attributes/GroupDummy.php | 24 -------- Tests/Mapping/TestClassMetadataFactory.php | 8 --- Tests/Normalizer/Features/GroupsTestTrait.php | 56 +------------------ .../Normalizer/GetSetMethodNormalizerTest.php | 2 - Tests/Normalizer/ObjectNormalizerTest.php | 2 - Tests/Normalizer/PropertyNormalizerTest.php | 4 -- 9 files changed, 8 insertions(+), 107 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3118834d8..463572046 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,7 +6,6 @@ CHANGELOG * Add arguments `$class`, `$format` and `$context` to `NameConverterInterface::normalize()` and `NameConverterInterface::denormalize()` * Add `DateTimeNormalizer::CAST_KEY` context option - * Add `Default` and "class name" default groups * Add `AbstractNormalizer::FILTER_BOOL` context option * Add `CamelCaseToSnakeCaseNameConverter::REQUIRE_SNAKE_CASE_PROPERTIES` context option * Deprecate `AbstractNormalizerContextBuilder::withDefaultContructorArguments(?array $defaultContructorArguments)`, use `withDefaultConstructorArguments(?array $defaultConstructorArguments)` instead (note the missing `s` character in Contructor word in deprecated method) diff --git a/NameConverter/MetadataAwareNameConverter.php b/NameConverter/MetadataAwareNameConverter.php index 327d92dc1..445ad7422 100644 --- a/NameConverter/MetadataAwareNameConverter.php +++ b/NameConverter/MetadataAwareNameConverter.php @@ -128,16 +128,13 @@ private function getCacheValueForAttributesMetadata(string $class, array $contex } $metadataGroups = $metadata->getGroups(); - $contextGroups = (array) ($context[AbstractNormalizer::GROUPS] ?? []); - $contextGroupsHasBeenDefined = [] !== $contextGroups; - $contextGroups = array_merge($contextGroups, ['Default', (false !== $nsSep = strrpos($class, '\\')) ? substr($class, $nsSep + 1) : $class]); - if ($contextGroupsHasBeenDefined && !$metadataGroups) { + if ($contextGroups && !$metadataGroups) { continue; } - if ($metadataGroups && !array_intersect(array_merge($metadataGroups, ['*']), $contextGroups)) { + if ($metadataGroups && !array_intersect($metadataGroups, $contextGroups) && !\in_array('*', $contextGroups, true)) { continue; } diff --git a/Normalizer/AbstractNormalizer.php b/Normalizer/AbstractNormalizer.php index 6f065984c..d8f796b4c 100644 --- a/Normalizer/AbstractNormalizer.php +++ b/Normalizer/AbstractNormalizer.php @@ -223,17 +223,12 @@ protected function getAllowedAttributes(string|object $classOrObject, array $con return false; } - $classMetadata = $this->classMetadataFactory->getMetadataFor($classOrObject); - $class = $classMetadata->getName(); - $groups = $this->getGroups($context); - $groupsHasBeenDefined = [] !== $groups; - $groups = array_merge($groups, ['Default', (false !== $nsSep = strrpos($class, '\\')) ? substr($class, $nsSep + 1) : $class]); $allowedAttributes = []; $ignoreUsed = false; - foreach ($classMetadata->getAttributesMetadata() as $attributeMetadata) { + foreach ($this->classMetadataFactory->getMetadataFor($classOrObject)->getAttributesMetadata() as $attributeMetadata) { if ($ignore = $attributeMetadata->isIgnored()) { $ignoreUsed = true; } @@ -241,14 +236,14 @@ protected function getAllowedAttributes(string|object $classOrObject, array $con // If you update this check, update accordingly the one in Symfony\Component\PropertyInfo\Extractor\SerializerExtractor::getProperties() if ( !$ignore - && (!$groupsHasBeenDefined || array_intersect(array_merge($attributeMetadata->getGroups(), ['*']), $groups)) + && ([] === $groups || \in_array('*', $groups, true) || array_intersect($attributeMetadata->getGroups(), $groups)) && $this->isAllowedAttribute($classOrObject, $name = $attributeMetadata->getName(), null, $context) ) { $allowedAttributes[] = $attributesAsString ? $name : $attributeMetadata; } } - if (!$ignoreUsed && !$groupsHasBeenDefined && $allowExtraAttributes) { + if (!$ignoreUsed && [] === $groups && $allowExtraAttributes) { // Backward Compatibility with the code using this method written before the introduction of @Ignore return false; } diff --git a/Tests/Fixtures/Attributes/GroupDummy.php b/Tests/Fixtures/Attributes/GroupDummy.php index 5c34c95a4..749e841a5 100644 --- a/Tests/Fixtures/Attributes/GroupDummy.php +++ b/Tests/Fixtures/Attributes/GroupDummy.php @@ -27,10 +27,6 @@ class GroupDummy extends GroupDummyParent implements GroupDummyInterface protected $quux; private $fooBar; private $symfony; - #[Groups(['Default'])] - private $default; - #[Groups(['GroupDummy'])] - private $className; #[Groups(['b'])] public function setBar($bar) @@ -84,24 +80,4 @@ public function setQuux($quux): void { $this->quux = $quux; } - - public function setDefault($default) - { - $this->default = $default; - } - - public function getDefault() - { - return $this->default; - } - - public function setClassName($className) - { - $this->className = $className; - } - - public function getClassName() - { - return $this->className; - } } diff --git a/Tests/Mapping/TestClassMetadataFactory.php b/Tests/Mapping/TestClassMetadataFactory.php index d617ffaeb..61147316a 100644 --- a/Tests/Mapping/TestClassMetadataFactory.php +++ b/Tests/Mapping/TestClassMetadataFactory.php @@ -63,14 +63,6 @@ public static function createClassMetadata(string $namespace, bool $withParent = $symfony->addGroup('name_converter'); } - $default = new AttributeMetadata('default'); - $default->addGroup('Default'); - $expected->addAttributeMetadata($default); - - $className = new AttributeMetadata('className'); - $className->addGroup('GroupDummy'); - $expected->addAttributeMetadata($className); - // load reflection class so that the comparison passes $expected->getReflectionClass(); diff --git a/Tests/Normalizer/Features/GroupsTestTrait.php b/Tests/Normalizer/Features/GroupsTestTrait.php index ba4d76323..621ceec41 100644 --- a/Tests/Normalizer/Features/GroupsTestTrait.php +++ b/Tests/Normalizer/Features/GroupsTestTrait.php @@ -36,13 +36,9 @@ public function testGroupsNormalize() $obj->setSymfony('symfony'); $obj->setKevin('kevin'); $obj->setCoopTilleuls('coopTilleuls'); - $obj->setDefault('default'); - $obj->setClassName('className'); $this->assertEquals([ 'bar' => 'bar', - 'default' => 'default', - 'className' => 'className', ], $normalizer->normalize($obj, null, ['groups' => ['c']])); $this->assertEquals([ @@ -52,26 +48,7 @@ public function testGroupsNormalize() 'bar' => 'bar', 'kevin' => 'kevin', 'coopTilleuls' => 'coopTilleuls', - 'default' => 'default', - 'className' => 'className', ], $normalizer->normalize($obj, null, ['groups' => ['a', 'c']])); - - $this->assertEquals([ - 'default' => 'default', - 'className' => 'className', - ], $normalizer->normalize($obj, null, ['groups' => ['unknown']])); - - $this->assertEquals([ - 'quux' => 'quux', - 'symfony' => 'symfony', - 'foo' => 'foo', - 'fooBar' => 'fooBar', - 'bar' => 'bar', - 'kevin' => 'kevin', - 'coopTilleuls' => 'coopTilleuls', - 'default' => 'default', - 'className' => 'className', - ], $normalizer->normalize($obj)); } public function testGroupsDenormalize() @@ -79,27 +56,10 @@ public function testGroupsDenormalize() $normalizer = $this->getDenormalizerForGroups(); $obj = new GroupDummy(); - $obj->setDefault('default'); - $obj->setClassName('className'); - - $data = [ - 'foo' => 'foo', - 'bar' => 'bar', - 'quux' => 'quux', - 'default' => 'default', - 'className' => 'className', - ]; - - $denormalized = $normalizer->denormalize( - $data, - GroupDummy::class, - null, - ['groups' => ['unknown']] - ); - $this->assertEquals($obj, $denormalized); - $obj->setFoo('foo'); + $data = ['foo' => 'foo', 'bar' => 'bar']; + $denormalized = $normalizer->denormalize( $data, GroupDummy::class, @@ -117,11 +77,6 @@ public function testGroupsDenormalize() ['groups' => ['a', 'b']] ); $this->assertEquals($obj, $denormalized); - - $obj->setQuux('quux'); - - $denormalized = $normalizer->denormalize($data, GroupDummy::class); - $this->assertEquals($obj, $denormalized); } public function testNormalizeNoPropertyInGroup() @@ -130,12 +85,7 @@ public function testNormalizeNoPropertyInGroup() $obj = new GroupDummy(); $obj->setFoo('foo'); - $obj->setDefault('default'); - $obj->setClassName('className'); - $this->assertEquals([ - 'default' => 'default', - 'className' => 'className', - ], $normalizer->normalize($obj, null, ['groups' => ['notExist']])); + $this->assertEquals([], $normalizer->normalize($obj, null, ['groups' => ['notExist']])); } } diff --git a/Tests/Normalizer/GetSetMethodNormalizerTest.php b/Tests/Normalizer/GetSetMethodNormalizerTest.php index f846a0d97..2c30a57bc 100644 --- a/Tests/Normalizer/GetSetMethodNormalizerTest.php +++ b/Tests/Normalizer/GetSetMethodNormalizerTest.php @@ -302,8 +302,6 @@ public function testGroupsNormalizeWithNameConverter() 'bar' => null, 'foo_bar' => '@dunglas', 'symfony' => '@coopTilleuls', - 'default' => null, - 'class_name' => null, ], $this->normalizer->normalize($obj, null, [GetSetMethodNormalizer::GROUPS => ['name_converter']]) ); diff --git a/Tests/Normalizer/ObjectNormalizerTest.php b/Tests/Normalizer/ObjectNormalizerTest.php index 50866a1b2..ed6a9d3be 100644 --- a/Tests/Normalizer/ObjectNormalizerTest.php +++ b/Tests/Normalizer/ObjectNormalizerTest.php @@ -507,8 +507,6 @@ public function testGroupsNormalizeWithNameConverter() 'bar' => null, 'foo_bar' => '@dunglas', 'symfony' => '@coopTilleuls', - 'default' => null, - 'class_name' => null, ], $this->normalizer->normalize($obj, null, [ObjectNormalizer::GROUPS => ['name_converter']]) ); diff --git a/Tests/Normalizer/PropertyNormalizerTest.php b/Tests/Normalizer/PropertyNormalizerTest.php index b93a7bb9f..9ac85920e 100644 --- a/Tests/Normalizer/PropertyNormalizerTest.php +++ b/Tests/Normalizer/PropertyNormalizerTest.php @@ -195,8 +195,6 @@ public function testNormalizeWithParentClass() 'fooBar' => null, 'symfony' => null, 'baz' => 'baz', - 'default' => null, - 'className' => null, ], $this->normalizer->normalize($group, 'any') ); @@ -321,8 +319,6 @@ public function testGroupsNormalizeWithNameConverter() 'bar' => null, 'foo_bar' => '@dunglas', 'symfony' => '@coopTilleuls', - 'default' => null, - 'class_name' => null, ], $this->normalizer->normalize($obj, null, [PropertyNormalizer::GROUPS => ['name_converter']]) ); From 2154d9a0520fa3d223ac36aad773376fb1d0a36a Mon Sep 17 00:00:00 2001 From: Christian Flothmann Date: Thu, 7 Nov 2024 16:24:12 +0100 Subject: [PATCH 284/297] fix support for phpstan/phpdoc-parser 2 --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index 948fa36fa..d7bef296b 100644 --- a/composer.json +++ b/composer.json @@ -22,7 +22,7 @@ }, "require-dev": { "phpdocumentor/reflection-docblock": "^3.2|^4.0|^5.0", - "phpstan/phpdoc-parser": "^1.0", + "phpstan/phpdoc-parser": "^1.0|^2.0", "seld/jsonlint": "^1.10", "symfony/cache": "^6.4|^7.0", "symfony/config": "^6.4|^7.0", From c210d2bf4d81e47e1444eaf246c5d0c60ad8672d Mon Sep 17 00:00:00 2001 From: Christian Flothmann Date: Thu, 14 Nov 2024 10:51:05 +0100 Subject: [PATCH 285/297] prevent failures around not existing TypeInfo classes Having a getType() method on an extractor is not enough. Such a method may exist to be forward-compatible with the TypeInfo component. We still must not call it if the TypeInfo component is not installed to prevent running into errors for not-defined classes when the TypeInfo component is not installed. --- Normalizer/AbstractObjectNormalizer.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Normalizer/AbstractObjectNormalizer.php b/Normalizer/AbstractObjectNormalizer.php index 63068420b..3e3ef426a 100644 --- a/Normalizer/AbstractObjectNormalizer.php +++ b/Normalizer/AbstractObjectNormalizer.php @@ -938,7 +938,7 @@ private function getType(string $currentClass, string $attribute): Type|array|nu */ private function getPropertyType(string $className, string $property): Type|array|null { - if (method_exists($this->propertyTypeExtractor, 'getType')) { + if (class_exists(Type::class) && method_exists($this->propertyTypeExtractor, 'getType')) { return $this->propertyTypeExtractor->getType($className, $property); } From 7afcfbb87f161666091f1c4d0382ffdd5a07ac26 Mon Sep 17 00:00:00 2001 From: Mathias Arlaud Date: Thu, 14 Nov 2024 13:01:20 +0100 Subject: [PATCH 286/297] [Serializer][PropertyInfo][Validator] TypeInfo 7.2 compatibility --- Normalizer/AbstractObjectNormalizer.php | 72 +++++++++++++++++++++---- Normalizer/ArrayDenormalizer.php | 12 ++++- 2 files changed, 74 insertions(+), 10 deletions(-) diff --git a/Normalizer/AbstractObjectNormalizer.php b/Normalizer/AbstractObjectNormalizer.php index 63068420b..f8a3a41b5 100644 --- a/Normalizer/AbstractObjectNormalizer.php +++ b/Normalizer/AbstractObjectNormalizer.php @@ -34,10 +34,13 @@ use Symfony\Component\Serializer\NameConverter\NameConverterInterface; use Symfony\Component\TypeInfo\Exception\LogicException as TypeInfoLogicException; use Symfony\Component\TypeInfo\Type; +use Symfony\Component\TypeInfo\Type\BuiltinType; use Symfony\Component\TypeInfo\Type\CollectionType; use Symfony\Component\TypeInfo\Type\IntersectionType; +use Symfony\Component\TypeInfo\Type\NullableType; use Symfony\Component\TypeInfo\Type\ObjectType; use Symfony\Component\TypeInfo\Type\UnionType; +use Symfony\Component\TypeInfo\Type\WrappingTypeInterface; use Symfony\Component\TypeInfo\TypeIdentifier; /** @@ -644,7 +647,14 @@ private function validateAndDenormalizeLegacy(array $types, string $currentClass private function validateAndDenormalize(Type $type, string $currentClass, string $attribute, mixed $data, ?string $format, array $context): mixed { $expectedTypes = []; - $isUnionType = $type->asNonNullable() instanceof UnionType; + + // BC layer for type-info < 7.2 + if (method_exists(Type::class, 'asNonNullable')) { + $isUnionType = $type->asNonNullable() instanceof UnionType; + } else { + $isUnionType = $type instanceof UnionType; + } + $e = null; $extraAttributesException = null; $missingConstructorArgumentsException = null; @@ -667,12 +677,23 @@ private function validateAndDenormalize(Type $type, string $currentClass, string $collectionValueType = $t->getCollectionValueType(); } - $t = $t->getBaseType(); + // BC layer for type-info < 7.2 + if (method_exists(Type::class, 'getBaseType')) { + $t = $t->getBaseType(); + } else { + while ($t instanceof WrappingTypeInterface) { + $t = $t->getWrappedType(); + } + } // Fix a collection that contains the only one element // This is special to xml format only - if ('xml' === $format && $collectionValueType && !$collectionValueType->isA(TypeIdentifier::MIXED) && (!\is_array($data) || !\is_int(key($data)))) { - $data = [$data]; + if ('xml' === $format && $collectionValueType && (!\is_array($data) || !\is_int(key($data)))) { + // BC layer for type-info < 7.2 + $isMixedType = method_exists(Type::class, 'isA') ? $collectionValueType->isA(TypeIdentifier::MIXED) : $collectionValueType->isIdentifiedBy(TypeIdentifier::MIXED); + if (!$isMixedType) { + $data = [$data]; + } } // This try-catch should cover all NotNormalizableValueException (and all return branches after the first @@ -695,7 +716,10 @@ private function validateAndDenormalize(Type $type, string $currentClass, string return ''; } - $isNullable = $isNullable ?: $type->isNullable(); + // BC layer for type-info < 7.2 + if (method_exists(Type::class, 'isNullable')) { + $isNullable = $isNullable ?: $type->isNullable(); + } } switch ($typeIdentifier) { @@ -732,7 +756,16 @@ private function validateAndDenormalize(Type $type, string $currentClass, string if ($collectionValueType) { try { - $collectionValueBaseType = $collectionValueType->getBaseType(); + $collectionValueBaseType = $collectionValueType; + + // BC layer for type-info < 7.2 + if (!interface_exists(WrappingTypeInterface::class)) { + $collectionValueBaseType = $collectionValueType->getBaseType(); + } else { + while ($collectionValueBaseType instanceof WrappingTypeInterface) { + $collectionValueBaseType = $collectionValueBaseType->getWrappedType(); + } + } } catch (TypeInfoLogicException) { $collectionValueBaseType = Type::mixed(); } @@ -742,15 +775,29 @@ private function validateAndDenormalize(Type $type, string $currentClass, string $class = $collectionValueBaseType->getClassName().'[]'; $context['key_type'] = $collectionKeyType; $context['value_type'] = $collectionValueType; - } elseif (TypeIdentifier::ARRAY === $collectionValueBaseType->getTypeIdentifier()) { + } elseif ( + // BC layer for type-info < 7.2 + !class_exists(NullableType::class) && TypeIdentifier::ARRAY === $collectionValueBaseType->getTypeIdentifier() + || $collectionValueBaseType instanceof BuiltinType && TypeIdentifier::ARRAY === $collectionValueBaseType->getTypeIdentifier() + ) { // get inner type for any nested array $innerType = $collectionValueType; + if ($innerType instanceof NullableType) { + $innerType = $innerType->getWrappedType(); + } // note that it will break for any other builtinType $dimensions = '[]'; while ($innerType instanceof CollectionType) { $dimensions .= '[]'; $innerType = $innerType->getCollectionValueType(); + if ($innerType instanceof NullableType) { + $innerType = $innerType->getWrappedType(); + } + } + + while ($innerType instanceof WrappingTypeInterface) { + $innerType = $innerType->getWrappedType(); } if ($innerType instanceof ObjectType) { @@ -862,8 +909,15 @@ private function validateAndDenormalize(Type $type, string $currentClass, string throw $missingConstructorArgumentsException; } - if (!$isUnionType && $e) { - throw $e; + // BC layer for type-info < 7.2 + if (!class_exists(NullableType::class)) { + if (!$isUnionType && $e) { + throw $e; + } + } else { + if ($e && !($type instanceof UnionType && !$type instanceof NullableType)) { + throw $e; + } } if ($context[self::DISABLE_TYPE_ENFORCEMENT] ?? $this->defaultContext[self::DISABLE_TYPE_ENFORCEMENT] ?? false) { diff --git a/Normalizer/ArrayDenormalizer.php b/Normalizer/ArrayDenormalizer.php index 347030c24..964d74b61 100644 --- a/Normalizer/ArrayDenormalizer.php +++ b/Normalizer/ArrayDenormalizer.php @@ -16,7 +16,9 @@ use Symfony\Component\Serializer\Exception\InvalidArgumentException; use Symfony\Component\Serializer\Exception\NotNormalizableValueException; use Symfony\Component\TypeInfo\Type; +use Symfony\Component\TypeInfo\Type\BuiltinType; use Symfony\Component\TypeInfo\Type\UnionType; +use Symfony\Component\TypeInfo\TypeIdentifier; /** * Denormalizes arrays of objects. @@ -59,7 +61,15 @@ public function denormalize(mixed $data, string $type, ?string $format = null, a $typeIdentifiers = []; if (null !== $keyType = ($context['key_type'] ?? null)) { if ($keyType instanceof Type) { - $typeIdentifiers = array_map(fn (Type $t): string => $t->getBaseType()->getTypeIdentifier()->value, $keyType instanceof UnionType ? $keyType->getTypes() : [$keyType]); + // BC layer for type-info < 7.2 + if (method_exists(Type::class, 'getBaseType')) { + $typeIdentifiers = array_map(fn (Type $t): string => $t->getBaseType()->getTypeIdentifier()->value, $keyType instanceof UnionType ? $keyType->getTypes() : [$keyType]); + } else { + /** @var list|BuiltinType> */ + $keyTypes = $keyType instanceof UnionType ? $keyType->getTypes() : [$keyType]; + + $typeIdentifiers = array_map(fn (BuiltinType $t): string => $t->getTypeIdentifier()->value, $keyTypes); + } } else { $typeIdentifiers = array_map(fn (LegacyType $t): string => $t->getBuiltinType(), \is_array($keyType) ? $keyType : [$keyType]); } From 5fff3abe545e26b2e024d18ad6e3f797e649f513 Mon Sep 17 00:00:00 2001 From: Mathias Arlaud Date: Thu, 14 Nov 2024 13:01:20 +0100 Subject: [PATCH 287/297] [TypeInfo][Serializer][PropertyInfo][Validator] TypeInfo 7.1 compatibility --- Normalizer/AbstractObjectNormalizer.php | 59 ++++++++++++++++++++----- Normalizer/ArrayDenormalizer.php | 13 ++++-- composer.json | 3 +- 3 files changed, 59 insertions(+), 16 deletions(-) diff --git a/Normalizer/AbstractObjectNormalizer.php b/Normalizer/AbstractObjectNormalizer.php index 3dbb750f1..fb45a924b 100644 --- a/Normalizer/AbstractObjectNormalizer.php +++ b/Normalizer/AbstractObjectNormalizer.php @@ -32,6 +32,7 @@ use Symfony\Component\Serializer\Mapping\ClassMetadataInterface; use Symfony\Component\Serializer\Mapping\Factory\ClassMetadataFactoryInterface; use Symfony\Component\Serializer\NameConverter\NameConverterInterface; +use Symfony\Component\TypeInfo\Exception\LogicException as TypeInfoLogicException; use Symfony\Component\TypeInfo\Type; use Symfony\Component\TypeInfo\Type\BuiltinType; use Symfony\Component\TypeInfo\Type\CollectionType; @@ -646,6 +647,14 @@ private function validateAndDenormalizeLegacy(array $types, string $currentClass private function validateAndDenormalize(Type $type, string $currentClass, string $attribute, mixed $data, ?string $format, array $context): mixed { $expectedTypes = []; + + // BC layer for type-info < 7.2 + if (method_exists(Type::class, 'asNonNullable')) { + $isUnionType = $type->asNonNullable() instanceof UnionType; + } else { + $isUnionType = $type instanceof UnionType; + } + $e = null; $extraAttributesException = null; $missingConstructorArgumentsException = null; @@ -667,14 +676,23 @@ private function validateAndDenormalize(Type $type, string $currentClass, string $collectionValueType = $t->getCollectionValueType(); } - while ($t instanceof WrappingTypeInterface) { - $t = $t->getWrappedType(); + // BC layer for type-info < 7.2 + if (method_exists(Type::class, 'getBaseType')) { + $t = $t->getBaseType(); + } else { + while ($t instanceof WrappingTypeInterface) { + $t = $t->getWrappedType(); + } } // Fix a collection that contains the only one element // This is special to xml format only - if ('xml' === $format && $collectionValueType && !$collectionValueType->isIdentifiedBy(TypeIdentifier::MIXED) && (!\is_array($data) || !\is_int(key($data)))) { - $data = [$data]; + if ('xml' === $format && $collectionValueType && (!\is_array($data) || !\is_int(key($data)))) { + // BC layer for type-info < 7.2 + $isMixedType = method_exists(Type::class, 'isA') ? $collectionValueType->isA(TypeIdentifier::MIXED) : $collectionValueType->isIdentifiedBy(TypeIdentifier::MIXED); + if (!$isMixedType) { + $data = [$data]; + } } // This try-catch should cover all NotNormalizableValueException (and all return branches after the first @@ -731,9 +749,19 @@ private function validateAndDenormalize(Type $type, string $currentClass, string } if ($collectionValueType) { - $collectionValueBaseType = $collectionValueType; - while ($collectionValueBaseType instanceof WrappingTypeInterface) { - $collectionValueBaseType = $collectionValueBaseType->getWrappedType(); + try { + $collectionValueBaseType = $collectionValueType; + + // BC layer for type-info < 7.2 + if (!interface_exists(WrappingTypeInterface::class)) { + $collectionValueBaseType = $collectionValueType->getBaseType(); + } else { + while ($collectionValueBaseType instanceof WrappingTypeInterface) { + $collectionValueBaseType = $collectionValueBaseType->getWrappedType(); + } + } + } catch (TypeInfoLogicException) { + $collectionValueBaseType = Type::mixed(); } if ($collectionValueBaseType instanceof ObjectType) { @@ -741,7 +769,11 @@ private function validateAndDenormalize(Type $type, string $currentClass, string $class = $collectionValueBaseType->getClassName().'[]'; $context['key_type'] = $collectionKeyType; $context['value_type'] = $collectionValueType; - } elseif ($collectionValueBaseType instanceof BuiltinType && TypeIdentifier::ARRAY === $collectionValueBaseType->getTypeIdentifier()) { + } elseif ( + // BC layer for type-info < 7.2 + !class_exists(NullableType::class) && TypeIdentifier::ARRAY === $collectionValueBaseType->getTypeIdentifier() + || $collectionValueBaseType instanceof BuiltinType && TypeIdentifier::ARRAY === $collectionValueBaseType->getTypeIdentifier() + ) { // get inner type for any nested array $innerType = $collectionValueType; if ($innerType instanceof NullableType) { @@ -871,8 +903,15 @@ private function validateAndDenormalize(Type $type, string $currentClass, string throw $missingConstructorArgumentsException; } - if ($e && !($type instanceof UnionType && !$type instanceof NullableType)) { - throw $e; + // BC layer for type-info < 7.2 + if (!class_exists(NullableType::class)) { + if (!$isUnionType && $e) { + throw $e; + } + } else { + if ($e && !($type instanceof UnionType && !$type instanceof NullableType)) { + throw $e; + } } if ($context[self::DISABLE_TYPE_ENFORCEMENT] ?? $this->defaultContext[self::DISABLE_TYPE_ENFORCEMENT] ?? false) { diff --git a/Normalizer/ArrayDenormalizer.php b/Normalizer/ArrayDenormalizer.php index 08fae04df..96c4d259c 100644 --- a/Normalizer/ArrayDenormalizer.php +++ b/Normalizer/ArrayDenormalizer.php @@ -56,10 +56,15 @@ public function denormalize(mixed $data, string $type, ?string $format = null, a $typeIdentifiers = []; if (null !== $keyType = ($context['key_type'] ?? null)) { if ($keyType instanceof Type) { - /** @var list|BuiltinType> */ - $keyTypes = $keyType instanceof UnionType ? $keyType->getTypes() : [$keyType]; - - $typeIdentifiers = array_map(fn (BuiltinType $t): string => $t->getTypeIdentifier()->value, $keyTypes); + // BC layer for type-info < 7.2 + if (method_exists(Type::class, 'getBaseType')) { + $typeIdentifiers = array_map(fn (Type $t): string => $t->getBaseType()->getTypeIdentifier()->value, $keyType instanceof UnionType ? $keyType->getTypes() : [$keyType]); + } else { + /** @var list|BuiltinType> */ + $keyTypes = $keyType instanceof UnionType ? $keyType->getTypes() : [$keyType]; + + $typeIdentifiers = array_map(fn (BuiltinType $t): string => $t->getTypeIdentifier()->value, $keyTypes); + } } else { $typeIdentifiers = array_map(fn (LegacyType $t): string => $t->getBuiltinType(), \is_array($keyType) ? $keyType : [$keyType]); } diff --git a/composer.json b/composer.json index 4e6865523..d8809fa07 100644 --- a/composer.json +++ b/composer.json @@ -38,7 +38,7 @@ "symfony/property-access": "^6.4|^7.0", "symfony/property-info": "^6.4|^7.0", "symfony/translation-contracts": "^2.5|^3", - "symfony/type-info": "^7.2", + "symfony/type-info": "^7.1", "symfony/uid": "^6.4|^7.0", "symfony/validator": "^6.4|^7.0", "symfony/var-dumper": "^6.4|^7.0", @@ -51,7 +51,6 @@ "symfony/dependency-injection": "<6.4", "symfony/property-access": "<6.4", "symfony/property-info": "<6.4", - "symfony/type-info": "<7.2", "symfony/uid": "<6.4", "symfony/validator": "<6.4", "symfony/yaml": "<6.4" From 81f032d2ee6a3cd8b75990941a1e3f87c8ad086e Mon Sep 17 00:00:00 2001 From: wanxiangchwng Date: Sat, 23 Nov 2024 10:47:03 +0800 Subject: [PATCH 288/297] chore: fix some typos Signed-off-by: wanxiangchwng --- Tests/Encoder/XmlEncoderTest.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Tests/Encoder/XmlEncoderTest.php b/Tests/Encoder/XmlEncoderTest.php index 31d2ddfc6..0eb332e80 100644 --- a/Tests/Encoder/XmlEncoderTest.php +++ b/Tests/Encoder/XmlEncoderTest.php @@ -149,7 +149,7 @@ public static function validEncodeProvider(): iterable ], ]; - yield 'encode remvoing empty tags' => [ + yield 'encode removing empty tags' => [ ''."\n". 'Peter'."\n", ['person' => ['firstname' => 'Peter', 'lastname' => null]], From c9a49af37c46114a884a9c0945f7f75a3e8b1ec0 Mon Sep 17 00:00:00 2001 From: Dariusz Ruminski Date: Mon, 25 Nov 2024 01:26:19 +0100 Subject: [PATCH 289/297] CS: re-apply trailing_comma_in_multiline --- Tests/Normalizer/ObjectNormalizerTest.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Tests/Normalizer/ObjectNormalizerTest.php b/Tests/Normalizer/ObjectNormalizerTest.php index 93ed5e468..d45586b44 100644 --- a/Tests/Normalizer/ObjectNormalizerTest.php +++ b/Tests/Normalizer/ObjectNormalizerTest.php @@ -976,7 +976,7 @@ public function testNormalizeWithMethodNamesSimilarToAccessors() 'tell' => true, 'class' => true, 'responsibility' => true, - 123 => 321 + 123 => 321, ], $normalized); } } From 3f5ed9f5e6c02e3853109190ba38408f5e1d2dd0 Mon Sep 17 00:00:00 2001 From: Wouter de Jong Date: Sat, 16 Nov 2024 15:49:06 +0100 Subject: [PATCH 290/297] Proofread UPGRADE guide --- CHANGELOG.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9b7a1fac3..4c36d5885 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,8 +4,8 @@ CHANGELOG 7.2 --- - * Deprecate the `csv_escape_char` context option of `CsvEncoder` and the `CsvEncoder::ESCAPE_CHAR_KEY` constant - * Deprecate `CsvEncoderContextBuilder::withEscapeChar()` method + * Deprecate the `csv_escape_char` context option of `CsvEncoder`, the `CsvEncoder::ESCAPE_CHAR_KEY` constant + and the `CsvEncoderContextBuilder::withEscapeChar()` method, following its deprecation in PHP 8.4 * Add `SnakeCaseToCamelCaseNameConverter` * Support subclasses of `\DateTime` and `\DateTimeImmutable` for denormalization * Add the `UidNormalizer::NORMALIZATION_FORMAT_RFC9562` constant From ab913397b9aeee6d89277dded14fa8259dc1e46d Mon Sep 17 00:00:00 2001 From: Vincent Langlet Date: Sun, 29 Dec 2024 22:22:56 +0100 Subject: [PATCH 291/297] Fix exception thrown by YamlEncoder --- Encoder/YamlEncoder.php | 8 +++++++- Tests/Encoder/YamlEncoderTest.php | 9 +++++++++ 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/Encoder/YamlEncoder.php b/Encoder/YamlEncoder.php index 223cd7933..1013129db 100644 --- a/Encoder/YamlEncoder.php +++ b/Encoder/YamlEncoder.php @@ -11,8 +11,10 @@ namespace Symfony\Component\Serializer\Encoder; +use Symfony\Component\Serializer\Exception\NotEncodableValueException; use Symfony\Component\Serializer\Exception\RuntimeException; use Symfony\Component\Yaml\Dumper; +use Symfony\Component\Yaml\Exception\ParseException; use Symfony\Component\Yaml\Parser; use Symfony\Component\Yaml\Yaml; @@ -85,7 +87,11 @@ public function decode(string $data, string $format, array $context = []): mixed { $context = array_merge($this->defaultContext, $context); - return $this->parser->parse($data, $context[self::YAML_FLAGS]); + try { + return $this->parser->parse($data, $context[self::YAML_FLAGS]); + } catch (ParseException $e) { + throw new NotEncodableValueException($e->getMessage(), $e->getCode(), $e); + } } public function supportsDecoding(string $format): bool diff --git a/Tests/Encoder/YamlEncoderTest.php b/Tests/Encoder/YamlEncoderTest.php index 33ee49f5d..f647fe423 100644 --- a/Tests/Encoder/YamlEncoderTest.php +++ b/Tests/Encoder/YamlEncoderTest.php @@ -13,6 +13,7 @@ use PHPUnit\Framework\TestCase; use Symfony\Component\Serializer\Encoder\YamlEncoder; +use Symfony\Component\Serializer\Exception\NotEncodableValueException; use Symfony\Component\Yaml\Yaml; /** @@ -81,4 +82,12 @@ public function testContext() $this->assertEquals(['foo' => $obj], $encoder->decode("foo: !php/object 'O:8:\"stdClass\":1:{s:3:\"bar\";i:2;}'", 'yaml')); $this->assertEquals(['foo' => null], $encoder->decode("foo: !php/object 'O:8:\"stdClass\":1:{s:3:\"bar\";i:2;}'", 'yaml', [YamlEncoder::YAML_FLAGS => 0])); } + + public function testInvalidYaml() + { + $encoder = new YamlEncoder(); + + $this->expectException(NotEncodableValueException::class); + $encoder->decode("\t", 'yaml'); + } } From ff34e9f1d46a8e918a8ddc2a4ff384eaca45ce7f Mon Sep 17 00:00:00 2001 From: djordy Date: Tue, 14 Jan 2025 14:22:11 +0100 Subject: [PATCH 292/297] [Serializer] [ObjectNormalizer] Filter int when using FILTER_BOOL --- Normalizer/AbstractObjectNormalizer.php | 4 +-- .../AbstractObjectNormalizerTest.php | 31 +++++++++++++------ 2 files changed, 24 insertions(+), 11 deletions(-) diff --git a/Normalizer/AbstractObjectNormalizer.php b/Normalizer/AbstractObjectNormalizer.php index aad68f7ba..1860425f9 100644 --- a/Normalizer/AbstractObjectNormalizer.php +++ b/Normalizer/AbstractObjectNormalizer.php @@ -569,7 +569,7 @@ private function validateAndDenormalizeLegacy(array $types, string $currentClass return (float) $data; } - if (LegacyType::BUILTIN_TYPE_BOOL === $builtinType && \is_string($data) && ($context[self::FILTER_BOOL] ?? false)) { + if (LegacyType::BUILTIN_TYPE_BOOL === $builtinType && (\is_string($data) || \is_int($data)) && ($context[self::FILTER_BOOL] ?? false)) { return filter_var($data, \FILTER_VALIDATE_BOOL, \FILTER_NULL_ON_FAILURE); } @@ -854,7 +854,7 @@ private function validateAndDenormalize(Type $type, string $currentClass, string return (float) $data; } - if (TypeIdentifier::BOOL === $typeIdentifier && \is_string($data) && ($context[self::FILTER_BOOL] ?? false)) { + if (TypeIdentifier::BOOL === $typeIdentifier && (\is_string($data) || \is_int($data)) && ($context[self::FILTER_BOOL] ?? false)) { return filter_var($data, \FILTER_VALIDATE_BOOL, \FILTER_NULL_ON_FAILURE); } diff --git a/Tests/Normalizer/AbstractObjectNormalizerTest.php b/Tests/Normalizer/AbstractObjectNormalizerTest.php index b4f5c103c..27f3c2084 100644 --- a/Tests/Normalizer/AbstractObjectNormalizerTest.php +++ b/Tests/Normalizer/AbstractObjectNormalizerTest.php @@ -1216,15 +1216,34 @@ public static function provideDenormalizeWithFilterBoolData(): array { return [ [['foo' => 'true'], true], + [['foo' => 'True'], true], + [['foo' => 'TRUE'], true], [['foo' => '1'], true], + [['foo' => 1], true], [['foo' => 'yes'], true], + [['foo' => 'Yes'], true], + [['foo' => 'YES'], true], + [['foo' => 'on'], true], + [['foo' => 'On'], true], + [['foo' => 'ON'], true], [['foo' => 'false'], false], + [['foo' => 'False'], false], + [['foo' => 'FALSE'], false], [['foo' => '0'], false], + [['foo' => 0], false], [['foo' => 'no'], false], + [['foo' => 'No'], false], + [['foo' => 'NO'], false], + [['foo' => 'off'], false], + [['foo' => 'Off'], false], + [['foo' => 'OFF'], false], [['foo' => ''], false], [['foo' => null], null], [['foo' => 'null'], null], [['foo' => 'something'], null], + [['foo' => 'foo'], null], + [['foo' => 1234567890], null], + [['foo' => -1234567890], null], ]; } @@ -1253,10 +1272,7 @@ protected function isAllowedAttribute($classOrObject, string $attribute, ?string public function testTemplateTypeWhenAnObjectIsPassedToDenormalize() { - $normalizer = new class ( - classMetadataFactory: new ClassMetadataFactory(new AttributeLoader()), - propertyTypeExtractor: new PropertyInfoExtractor(typeExtractors: [new PhpStanExtractor(), new ReflectionExtractor()]) - ) extends AbstractObjectNormalizerDummy { + $normalizer = new class(classMetadataFactory: new ClassMetadataFactory(new AttributeLoader()), propertyTypeExtractor: new PropertyInfoExtractor(typeExtractors: [new PhpStanExtractor(), new ReflectionExtractor()])) extends AbstractObjectNormalizerDummy { protected function isAllowedAttribute($classOrObject, string $attribute, ?string $format = null, array $context = []): bool { return true; @@ -1279,10 +1295,7 @@ public function testDenormalizeTemplateType() $this->markTestSkipped('The PropertyInfo component before Symfony 7.1 does not support template types.'); } - $normalizer = new class ( - classMetadataFactory: new ClassMetadataFactory(new AttributeLoader()), - propertyTypeExtractor: new PropertyInfoExtractor(typeExtractors: [new PhpStanExtractor(), new ReflectionExtractor()]) - ) extends AbstractObjectNormalizerDummy { + $normalizer = new class(classMetadataFactory: new ClassMetadataFactory(new AttributeLoader()), propertyTypeExtractor: new PropertyInfoExtractor(typeExtractors: [new PhpStanExtractor(), new ReflectionExtractor()])) extends AbstractObjectNormalizerDummy { protected function isAllowedAttribute($classOrObject, string $attribute, ?string $format = null, array $context = []): bool { return true; @@ -1587,7 +1600,7 @@ class TruePropertyDummy class BoolPropertyDummy { - /** @var null|bool */ + /** @var bool|null */ public $foo; } From 6ad986f62276da4c8c69754decfaa445a89cb6e3 Mon Sep 17 00:00:00 2001 From: Valmonzo Date: Fri, 15 Nov 2024 16:13:35 +0100 Subject: [PATCH 293/297] [Serializer] fix default context in Serializer --- DependencyInjection/SerializerPass.php | 1 + Serializer.php | 8 +++--- .../SerializerPassTest.php | 7 +++-- Tests/SerializerTest.php | 26 +++++++++++++++++++ 4 files changed, 37 insertions(+), 5 deletions(-) diff --git a/DependencyInjection/SerializerPass.php b/DependencyInjection/SerializerPass.php index d0b0deb48..c2959ecda 100644 --- a/DependencyInjection/SerializerPass.php +++ b/DependencyInjection/SerializerPass.php @@ -56,6 +56,7 @@ public function process(ContainerBuilder $container) } $container->getParameterBag()->remove('serializer.default_context'); + $container->getDefinition('serializer')->setArgument('$defaultContext', $defaultContext); } if ($container->getParameter('kernel.debug') && $container->hasDefinition('serializer.data_collector')) { diff --git a/Serializer.php b/Serializer.php index 7044c2f20..e17042097 100644 --- a/Serializer.php +++ b/Serializer.php @@ -84,10 +84,12 @@ class Serializer implements SerializerInterface, ContextAwareNormalizerInterface /** * @param array $normalizers * @param array $encoders + * @param array $defaultContext */ public function __construct( private array $normalizers = [], array $encoders = [], + private array $defaultContext = [], ) { foreach ($normalizers as $normalizer) { if ($normalizer instanceof SerializerAwareInterface) { @@ -163,12 +165,12 @@ public function normalize(mixed $data, ?string $format = null, array $context = return $data; } - if (\is_array($data) && !$data && ($context[self::EMPTY_ARRAY_AS_OBJECT] ?? false)) { + if (\is_array($data) && !$data && ($context[self::EMPTY_ARRAY_AS_OBJECT] ?? $this->defaultContext[self::EMPTY_ARRAY_AS_OBJECT] ?? false)) { return new \ArrayObject(); } if (is_iterable($data)) { - if ($data instanceof \Countable && ($context[AbstractObjectNormalizer::PRESERVE_EMPTY_OBJECTS] ?? false) && !\count($data)) { + if ($data instanceof \Countable && ($context[AbstractObjectNormalizer::PRESERVE_EMPTY_OBJECTS] ?? $this->defaultContext[AbstractObjectNormalizer::PRESERVE_EMPTY_OBJECTS] ?? false) && !\count($data)) { return new \ArrayObject(); } @@ -220,7 +222,7 @@ public function denormalize(mixed $data, string $type, ?string $format = null, a throw new NotNormalizableValueException(sprintf('Could not denormalize object of type "%s", no supporting normalizer found.', $type)); } - if (isset($context[DenormalizerInterface::COLLECT_DENORMALIZATION_ERRORS])) { + if (isset($context[DenormalizerInterface::COLLECT_DENORMALIZATION_ERRORS]) || isset($this->defaultContext[DenormalizerInterface::COLLECT_DENORMALIZATION_ERRORS])) { unset($context[DenormalizerInterface::COLLECT_DENORMALIZATION_ERRORS]); $context['not_normalizable_value_exceptions'] = []; $errors = &$context['not_normalizable_value_exceptions']; diff --git a/Tests/DependencyInjection/SerializerPassTest.php b/Tests/DependencyInjection/SerializerPassTest.php index eb77263f4..b2f4fa7ad 100644 --- a/Tests/DependencyInjection/SerializerPassTest.php +++ b/Tests/DependencyInjection/SerializerPassTest.php @@ -77,9 +77,11 @@ public function testServicesAreOrderedAccordingToPriority() public function testBindSerializerDefaultContext() { + $context = ['enable_max_depth' => true]; + $container = new ContainerBuilder(); $container->setParameter('kernel.debug', false); - $container->register('serializer')->setArguments([null, null]); + $container->register('serializer')->setArguments([null, null, []]); $container->setParameter('serializer.default_context', ['enable_max_depth' => true]); $definition = $container->register('n1')->addTag('serializer.normalizer')->addTag('serializer.encoder'); @@ -87,7 +89,8 @@ public function testBindSerializerDefaultContext() $serializerPass->process($container); $bindings = $definition->getBindings(); - $this->assertEquals($bindings['array $defaultContext'], new BoundArgument(['enable_max_depth' => true], false)); + $this->assertEquals($bindings['array $defaultContext'], new BoundArgument($context, false)); + $this->assertEquals($context, $container->getDefinition('serializer')->getArgument('$defaultContext')); } public function testNormalizersAndEncodersAreDecoredAndOrderedWhenCollectingData() diff --git a/Tests/SerializerTest.php b/Tests/SerializerTest.php index 8f60ae1d4..8a8a54e98 100644 --- a/Tests/SerializerTest.php +++ b/Tests/SerializerTest.php @@ -1652,6 +1652,32 @@ public function testPartialDenormalizationWithInvalidVariadicParameter() DenormalizerInterface::COLLECT_DENORMALIZATION_ERRORS => true, ]); } + + public function testEmptyArrayAsObjectDefaultContext() + { + $serializer = new Serializer( + defaultContext: [Serializer::EMPTY_ARRAY_AS_OBJECT => true], + ); + $this->assertEquals(new \ArrayObject(), $serializer->normalize([])); + } + + public function testPreserveEmptyObjectsAsDefaultContext() + { + $serializer = new Serializer( + defaultContext: [AbstractObjectNormalizer::PRESERVE_EMPTY_OBJECTS => true], + ); + $this->assertEquals(new \ArrayObject(), $serializer->normalize(new \ArrayIterator())); + } + + public function testCollectDenormalizationErrorsDefaultContext() + { + $data = ['variadic' => ['a random string']]; + $serializer = new Serializer([new UidNormalizer(), new ObjectNormalizer()], [], [DenormalizerInterface::COLLECT_DENORMALIZATION_ERRORS => true]); + + $this->expectException(PartialDenormalizationException::class); + + $serializer->denormalize($data, DummyWithVariadicParameter::class); + } } class Model From ed5e8b563e10496a4f62557fbcb52b4191f2157d Mon Sep 17 00:00:00 2001 From: HypeMC Date: Wed, 5 Feb 2025 08:18:49 +0100 Subject: [PATCH 294/297] [Serializer] Handle default context in named Serializer --- DependencyInjection/SerializerPass.php | 2 +- Tests/DependencyInjection/SerializerPassTest.php | 4 +++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/DependencyInjection/SerializerPass.php b/DependencyInjection/SerializerPass.php index bf1296e93..7b7f6f1c2 100644 --- a/DependencyInjection/SerializerPass.php +++ b/DependencyInjection/SerializerPass.php @@ -151,7 +151,7 @@ private function configureNamedSerializers(ContainerBuilder $container): void $this->bindDefaultContext($container, array_merge($normalizers, $encoders), $config['default_context']); - $container->registerChild($serializerId, 'serializer'); + $container->registerChild($serializerId, 'serializer')->setArgument('$defaultContext', $config['default_context']); $container->registerAliasForArgument($serializerId, SerializerInterface::class, $serializerName.'.serializer'); $this->configureSerializer($container, $serializerId, $normalizers, $encoders, $serializerName); diff --git a/Tests/DependencyInjection/SerializerPassTest.php b/Tests/DependencyInjection/SerializerPassTest.php index ca54460a7..769243be2 100644 --- a/Tests/DependencyInjection/SerializerPassTest.php +++ b/Tests/DependencyInjection/SerializerPassTest.php @@ -556,7 +556,7 @@ public function testBindSerializerDefaultContextToNamedSerializers() 'api' => ['default_context' => $defaultContext = ['enable_max_depth' => true]], ]); - $container->register('serializer')->setArguments([null, null]); + $container->register('serializer')->setArguments([null, null, []]); $definition = $container->register('n1') ->addTag('serializer.normalizer', ['serializer' => '*']) ->addTag('serializer.encoder', ['serializer' => '*']) @@ -570,6 +570,8 @@ public function testBindSerializerDefaultContextToNamedSerializers() $bindings = $container->getDefinition('n1.api')->getBindings(); $this->assertArrayHasKey('array $defaultContext', $bindings); $this->assertEquals($bindings['array $defaultContext'], new BoundArgument($defaultContext, false)); + $this->assertArrayNotHasKey('$defaultContext', $container->getDefinition('serializer')->getArguments()); + $this->assertEquals($defaultContext, $container->getDefinition('serializer.api')->getArgument('$defaultContext')); } public function testNamedSerializersAreRegistered() From a221b2f6066af304d760cff7a26f201b4fab4aef Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Josef=20Hlavat=C3=BD?= <107676055+Pepperoni1337@users.noreply.github.com> Date: Sun, 23 Feb 2025 11:21:59 +0100 Subject: [PATCH 295/297] Update GetSetMethodNormalizer.php Fix: Add length check for setter method detection --- Normalizer/GetSetMethodNormalizer.php | 1 + 1 file changed, 1 insertion(+) diff --git a/Normalizer/GetSetMethodNormalizer.php b/Normalizer/GetSetMethodNormalizer.php index 951005545..3cb9b992b 100644 --- a/Normalizer/GetSetMethodNormalizer.php +++ b/Normalizer/GetSetMethodNormalizer.php @@ -118,6 +118,7 @@ private function isSetMethod(\ReflectionMethod $method): bool return !$method->isStatic() && !$method->getAttributes(Ignore::class) && 0 < $method->getNumberOfParameters() + && 3 < \strlen($method->name) && str_starts_with($method->name, 'set') && !ctype_lower($method->name[3]) ; From d8b75b2c8144c29ac43b235738411f7cca6d584d Mon Sep 17 00:00:00 2001 From: HypeMC Date: Mon, 24 Mar 2025 05:35:59 +0100 Subject: [PATCH 296/297] [Serializer] Fix ObjectNormalizer default context with named serializers --- DependencyInjection/SerializerPass.php | 38 +++++++++--- .../SerializerPassTest.php | 62 ++++++++++++++++++- 2 files changed, 92 insertions(+), 8 deletions(-) diff --git a/DependencyInjection/SerializerPass.php b/DependencyInjection/SerializerPass.php index 7b7f6f1c2..179b7a3d9 100644 --- a/DependencyInjection/SerializerPass.php +++ b/DependencyInjection/SerializerPass.php @@ -19,6 +19,7 @@ use Symfony\Component\DependencyInjection\Reference; use Symfony\Component\Serializer\Debug\TraceableEncoder; use Symfony\Component\Serializer\Debug\TraceableNormalizer; +use Symfony\Component\Serializer\Normalizer\ObjectNormalizer; use Symfony\Component\Serializer\SerializerInterface; /** @@ -54,17 +55,27 @@ public function process(ContainerBuilder $container): void throw new RuntimeException('You must tag at least one service as "serializer.encoder" to use the "serializer" service.'); } + $defaultContext = []; if ($container->hasParameter('serializer.default_context')) { $defaultContext = $container->getParameter('serializer.default_context'); - $this->bindDefaultContext($container, array_merge($normalizers, $encoders), $defaultContext); $container->getParameterBag()->remove('serializer.default_context'); $container->getDefinition('serializer')->setArgument('$defaultContext', $defaultContext); } + /** @var ?string $circularReferenceHandler */ + $circularReferenceHandler = $container->hasParameter('.serializer.circular_reference_handler') + ? $container->getParameter('.serializer.circular_reference_handler') : null; + + /** @var ?string $maxDepthHandler */ + $maxDepthHandler = $container->hasParameter('.serializer.max_depth_handler') + ? $container->getParameter('.serializer.max_depth_handler') : null; + + $this->bindDefaultContext($container, array_merge($normalizers, $encoders), $defaultContext, $circularReferenceHandler, $maxDepthHandler); + $this->configureSerializer($container, 'serializer', $normalizers, $encoders, 'default'); if ($namedSerializers) { - $this->configureNamedSerializers($container); + $this->configureNamedSerializers($container, $circularReferenceHandler, $maxDepthHandler); } } @@ -98,11 +109,22 @@ private function createNamedSerializerTags(ContainerBuilder $container, string $ } } - private function bindDefaultContext(ContainerBuilder $container, array $services, array $defaultContext): void + private function bindDefaultContext(ContainerBuilder $container, array $services, array $defaultContext, ?string $circularReferenceHandler, ?string $maxDepthHandler): void { foreach ($services as $id) { $definition = $container->getDefinition((string) $id); - $definition->setBindings(['array $defaultContext' => new BoundArgument($defaultContext, false)] + $definition->getBindings()); + + $context = $defaultContext; + if (is_a($definition->getClass(), ObjectNormalizer::class, true)) { + if (null !== $circularReferenceHandler) { + $context += ['circular_reference_handler' => new Reference($circularReferenceHandler)]; + } + if (null !== $maxDepthHandler) { + $context += ['max_depth_handler' => new Reference($maxDepthHandler)]; + } + } + + $definition->setBindings(['array $defaultContext' => new BoundArgument($context, false)] + $definition->getBindings()); } } @@ -125,7 +147,7 @@ private function configureSerializer(ContainerBuilder $container, string $id, ar $serializerDefinition->replaceArgument(1, $encoders); } - private function configureNamedSerializers(ContainerBuilder $container): void + private function configureNamedSerializers(ContainerBuilder $container, ?string $circularReferenceHandler, ?string $maxDepthHandler): void { $defaultSerializerNameConverter = $container->hasParameter('.serializer.name_converter') ? $container->getParameter('.serializer.name_converter') : null; @@ -149,7 +171,7 @@ private function configureNamedSerializers(ContainerBuilder $container): void $normalizers = $this->buildChildDefinitions($container, $serializerName, $normalizers, $config); $encoders = $this->buildChildDefinitions($container, $serializerName, $encoders, $config); - $this->bindDefaultContext($container, array_merge($normalizers, $encoders), $config['default_context']); + $this->bindDefaultContext($container, array_merge($normalizers, $encoders), $config['default_context'], $circularReferenceHandler, $maxDepthHandler); $container->registerChild($serializerId, 'serializer')->setArgument('$defaultContext', $config['default_context']); $container->registerAliasForArgument($serializerId, SerializerInterface::class, $serializerName.'.serializer'); @@ -184,7 +206,9 @@ private function buildChildDefinitions(ContainerBuilder $container, string $seri foreach ($services as &$id) { $childId = $id.'.'.$serializerName; - $definition = $container->registerChild($childId, (string) $id); + $definition = $container->registerChild($childId, (string) $id) + ->setClass($container->getDefinition((string) $id)->getClass()) + ; if (null !== $nameConverterIndex = $this->findNameConverterIndex($container, (string) $id)) { $definition->replaceArgument($nameConverterIndex, new Reference($config['name_converter'])); diff --git a/Tests/DependencyInjection/SerializerPassTest.php b/Tests/DependencyInjection/SerializerPassTest.php index 769243be2..88ec02b87 100644 --- a/Tests/DependencyInjection/SerializerPassTest.php +++ b/Tests/DependencyInjection/SerializerPassTest.php @@ -19,6 +19,7 @@ use Symfony\Component\Serializer\Debug\TraceableNormalizer; use Symfony\Component\Serializer\Debug\TraceableSerializer; use Symfony\Component\Serializer\DependencyInjection\SerializerPass; +use Symfony\Component\Serializer\Normalizer\ObjectNormalizer; use Symfony\Component\Serializer\SerializerInterface; /** @@ -99,6 +100,32 @@ public function testBindSerializerDefaultContext() $this->assertEquals($context, $container->getDefinition('serializer')->getArgument('$defaultContext')); } + /** + * @testWith [{}, {}] + * [{"serializer.default_context": {"enable_max_depth": true}}, {"enable_max_depth": true}] + * [{".serializer.circular_reference_handler": "foo"}, {"circular_reference_handler": "foo"}] + * [{".serializer.max_depth_handler": "bar"}, {"max_depth_handler": "bar"}] + * [{"serializer.default_context": {"enable_max_depth": true}, ".serializer.circular_reference_handler": "foo", ".serializer.max_depth_handler": "bar"}, {"enable_max_depth": true, "circular_reference_handler": "foo", "max_depth_handler": "bar"}] + */ + public function testBindObjectNormalizerDefaultContext(array $parameters, array $context) + { + $container = new ContainerBuilder(); + $container->setParameter('kernel.debug', false); + $container->register('serializer')->setArguments([null, null, []]); + $container->getParameterBag()->add($parameters); + $definition = $container->register('serializer.normalizer.object') + ->setClass(ObjectNormalizer::class) + ->addTag('serializer.normalizer') + ->addTag('serializer.encoder') + ; + + $serializerPass = new SerializerPass(); + $serializerPass->process($container); + + $bindings = $definition->getBindings(); + $this->assertEquals($bindings['array $defaultContext'], new BoundArgument($context, false)); + } + public function testNormalizersAndEncodersAreDecoratedAndOrderedWhenCollectingData() { $container = new ContainerBuilder(); @@ -565,7 +592,9 @@ public function testBindSerializerDefaultContextToNamedSerializers() $serializerPass = new SerializerPass(); $serializerPass->process($container); - $this->assertEmpty($definition->getBindings()); + $bindings = $definition->getBindings(); + $this->assertArrayHasKey('array $defaultContext', $bindings); + $this->assertEquals($bindings['array $defaultContext'], new BoundArgument([], false)); $bindings = $container->getDefinition('n1.api')->getBindings(); $this->assertArrayHasKey('array $defaultContext', $bindings); @@ -574,6 +603,37 @@ public function testBindSerializerDefaultContextToNamedSerializers() $this->assertEquals($defaultContext, $container->getDefinition('serializer.api')->getArgument('$defaultContext')); } + /** + * @testWith [{}, {}, {}] + * [{"enable_max_depth": true}, {}, {"enable_max_depth": true}] + * [{}, {".serializer.circular_reference_handler": "foo"}, {"circular_reference_handler": "foo"}] + * [{}, {".serializer.max_depth_handler": "bar"}, {"max_depth_handler": "bar"}] + * [{"enable_max_depth": true}, {".serializer.circular_reference_handler": "foo", ".serializer.max_depth_handler": "bar"}, {"enable_max_depth": true, "circular_reference_handler": "foo", "max_depth_handler": "bar"}] + */ + public function testBindNamedSerializerObjectNormalizerDefaultContext(array $defaultContext, array $parameters, array $context) + { + $container = new ContainerBuilder(); + $container->setParameter('kernel.debug', false); + $container->setParameter('.serializer.named_serializers', [ + 'api' => ['default_context' => $defaultContext], + ]); + + $container->register('serializer')->setArguments([null, null, []]); + $container->getParameterBag()->add($parameters); + $container->register('serializer.normalizer.object') + ->setClass(ObjectNormalizer::class) + ->addTag('serializer.normalizer', ['serializer' => '*']) + ->addTag('serializer.encoder', ['serializer' => '*']) + ; + + $serializerPass = new SerializerPass(); + $serializerPass->process($container); + + $bindings = $container->getDefinition('serializer.normalizer.object.api')->getBindings(); + $this->assertArrayHasKey('array $defaultContext', $bindings); + $this->assertEquals($bindings['array $defaultContext'], new BoundArgument($context, false)); + } + public function testNamedSerializersAreRegistered() { $container = new ContainerBuilder(); From c45f8f7763afb11e85772c0c1debb8f272c17f51 Mon Sep 17 00:00:00 2001 From: Fabien Potencier Date: Sun, 27 Apr 2025 15:26:02 +0200 Subject: [PATCH 297/297] Remove unneeded use statements --- Tests/SerializerTest.php | 1 - 1 file changed, 1 deletion(-) diff --git a/Tests/SerializerTest.php b/Tests/SerializerTest.php index 8a8a54e98..da5ccc15e 100644 --- a/Tests/SerializerTest.php +++ b/Tests/SerializerTest.php @@ -62,7 +62,6 @@ use Symfony\Component\Serializer\Tests\Fixtures\DummyObjectWithEnumProperty; use Symfony\Component\Serializer\Tests\Fixtures\DummyWithObjectOrNull; use Symfony\Component\Serializer\Tests\Fixtures\DummyWithVariadicParameter; -use Symfony\Component\Serializer\Tests\Fixtures\DummyWithVariadicProperty; use Symfony\Component\Serializer\Tests\Fixtures\FalseBuiltInDummy; use Symfony\Component\Serializer\Tests\Fixtures\FooImplementationDummy; use Symfony\Component\Serializer\Tests\Fixtures\FooInterfaceDummyDenormalizer;