From 584eb93021d1b700411ef144c9e1472a4610a399 Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Tue, 23 May 2023 17:24:39 +0200 Subject: [PATCH 001/110] [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 ef4de68c078af366983e232de5dcac5936687fd3 Mon Sep 17 00:00:00 2001 From: Thomas Calvet Date: Mon, 5 Jun 2023 11:18:04 +0200 Subject: [PATCH 002/110] [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 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 003/110] 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 004/110] [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 005/110] 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 006/110] 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 007/110] [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 008/110] [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 2040f03ff5775e8634e89ae67aac25b1e0fd3d9e Mon Sep 17 00:00:00 2001 From: "Alexander M. Turek" Date: Mon, 24 Jul 2023 12:45:07 +0200 Subject: [PATCH 009/110] [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 c01af18f859414575398ad55b41b1141ddf8eacc Mon Sep 17 00:00:00 2001 From: "Alexander M. Turek" Date: Thu, 27 Jul 2023 17:31:50 +0200 Subject: [PATCH 010/110] [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 011/110] 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 0efbf491595f4ac4a964b8f308bdb06219cd6296 Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Wed, 2 Aug 2023 12:57:19 +0200 Subject: [PATCH 012/110] [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 3a5cc601e3c847ebfa40cdced74b7d0d26d1fb05 Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Thu, 17 Aug 2023 10:38:05 +0200 Subject: [PATCH 013/110] 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 3b1d742b9472f03dd3033257f7d20eb7b317f040 Mon Sep 17 00:00:00 2001 From: Alexandre Daubois Date: Tue, 22 Aug 2023 15:45:13 +0200 Subject: [PATCH 014/110] [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 4054a0f5a663107ee282fb24d3acdf41c9e98217 Mon Sep 17 00:00:00 2001 From: Alexandre Daubois Date: Tue, 17 Oct 2023 16:30:56 +0200 Subject: [PATCH 015/110] [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 0f236adcd3382c53c419ec264d788a62420e63dd Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Wed, 18 Oct 2023 14:57:55 +0200 Subject: [PATCH 016/110] [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 da47df901314499303079a1884109c2e061a9f0d Mon Sep 17 00:00:00 2001 From: Christian Flothmann Date: Tue, 24 Oct 2023 13:46:50 +0200 Subject: [PATCH 017/110] 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 018/110] [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 803d8614eda7dac54d44171125aa897bac9ff160 Mon Sep 17 00:00:00 2001 From: Christian Flothmann Date: Tue, 31 Oct 2023 19:13:03 +0100 Subject: [PATCH 019/110] 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 d823f5e9641ad3f54f66f38803221be9a726681a Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Wed, 15 Nov 2023 16:44:10 +0100 Subject: [PATCH 020/110] [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 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 021/110] [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 022/110] 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 39a8568daf6bb37be410fa0ebe772b8fd6cdcd35 Mon Sep 17 00:00:00 2001 From: Christian Flothmann Date: Wed, 29 Nov 2023 09:53:14 +0100 Subject: [PATCH 023/110] 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 467ed3ffdfc6c6fa6ad857a0bb61b705eccf1d96 Mon Sep 17 00:00:00 2001 From: Antoine Lamirault Date: Mon, 20 Nov 2023 19:32:45 +0100 Subject: [PATCH 024/110] [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 025/110] [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 026/110] 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 027/110] 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 2620a67e4ecc1a6c25009417af44ea970fcd1799 Mon Sep 17 00:00:00 2001 From: Christian Flothmann Date: Fri, 22 Dec 2023 10:13:01 +0100 Subject: [PATCH 028/110] 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 029/110] [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 030/110] [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 031/110] 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 032/110] 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 033/110] 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 034/110] 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 51359502748e605e7ab755c8eb800c08d4ab75cc Mon Sep 17 00:00:00 2001 From: Tomas Date: Wed, 13 Dec 2023 12:51:12 +0200 Subject: [PATCH 035/110] [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 036/110] [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 037/110] [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 038/110] 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 039/110] 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 040/110] 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 9e3063cd774a7518a5558f8bdab99a37369062b1 Mon Sep 17 00:00:00 2001 From: Dariusz Ruminski Date: Mon, 18 Mar 2024 20:27:13 +0100 Subject: [PATCH 041/110] 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 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 042/110] 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 043/110] [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 044/110] [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 36ec3968e08e189a588b6f43e0087f18fdcc340a Mon Sep 17 00:00:00 2001 From: Christian Flothmann Date: Fri, 5 Apr 2024 21:58:03 +0200 Subject: [PATCH 045/110] 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 046/110] 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 260e68552160e6dd283276144a93f51de107c5a6 Mon Sep 17 00:00:00 2001 From: Christian Flothmann Date: Tue, 9 Apr 2024 10:33:09 +0200 Subject: [PATCH 047/110] 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 a3ea83c0e08e86fc0150b1da897b267a6b806b42 Mon Sep 17 00:00:00 2001 From: Christian Flothmann Date: Tue, 16 Apr 2024 22:27:56 +0200 Subject: [PATCH 048/110] 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 049/110] 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 050/110] 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 051/110] 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 529d35861cb1b96a8b1bf68b07e37477260c85db Mon Sep 17 00:00:00 2001 From: alexpozzi Date: Thu, 18 Apr 2024 16:39:43 +0200 Subject: [PATCH 052/110] [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 053/110] 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 054/110] [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 055/110] 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 056/110] 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 d7e6907e0c9c7e606e0a057d03ec6a78b12bb32c Mon Sep 17 00:00:00 2001 From: HypeMC Date: Tue, 28 May 2024 01:30:34 +0200 Subject: [PATCH 057/110] [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 058/110] 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 059/110] 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 060/110] 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 061/110] 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 062/110] [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 f78926de1d52c57a27e72f4c3ebd1fe0ef7efc70 Mon Sep 17 00:00:00 2001 From: Dariusz Ruminski Date: Sun, 16 Jun 2024 17:17:26 +0200 Subject: [PATCH 063/110] 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 064/110] [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 065/110] [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 066/110] 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 067/110] [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 068/110] 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 069/110] [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 070/110] [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 071/110] 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 f08629456bc65ceb7e25939f35b41433c6a24288 Mon Sep 17 00:00:00 2001 From: Christian Flothmann Date: Wed, 17 Jul 2024 10:14:50 +0200 Subject: [PATCH 072/110] 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 51611e545131dee861f3c570ce1dbec2cae3c96b Mon Sep 17 00:00:00 2001 From: Alexandre Daubois Date: Wed, 31 Jul 2024 16:13:26 +0200 Subject: [PATCH 073/110] 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 074/110] [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 075/110] 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 076/110] 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 077/110] 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 078/110] [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 a06245d66340b8ba54c836787e19a6b2f1d681b2 Mon Sep 17 00:00:00 2001 From: Alexandre Daubois Date: Thu, 25 Jul 2024 11:31:45 +0200 Subject: [PATCH 079/110] [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 080/110] [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 081/110] [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 082/110] [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 083/110] [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 084/110] 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 085/110] [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 a2e5a855105a0335fff5966684fc613c312f68cc Mon Sep 17 00:00:00 2001 From: valtzu Date: Fri, 13 Sep 2024 19:09:13 +0300 Subject: [PATCH 086/110] 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 498bda022c46e0382e0a7d2aa0dbf9f250d07c11 Mon Sep 17 00:00:00 2001 From: Alexandre Daubois Date: Wed, 18 Sep 2024 13:33:46 +0200 Subject: [PATCH 087/110] 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 088/110] [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 089/110] 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 090/110] 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 091/110] 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 2946530731d67a6a45aef8379a0879807616059e Mon Sep 17 00:00:00 2001 From: Aurimas Date: Tue, 24 Sep 2024 09:20:27 +0300 Subject: [PATCH 092/110] [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 093/110] [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 094/110] 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 e71f70329e19a18607ea0a53391a4837e10218c8 Mon Sep 17 00:00:00 2001 From: Alexandre Daubois Date: Thu, 3 Oct 2024 14:15:19 +0200 Subject: [PATCH 095/110] 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 096/110] 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 097/110] [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 098/110] [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 96421e664c2c3077a1e0858a02e62a7a4d66ce79 Mon Sep 17 00:00:00 2001 From: Antoine Lamirault Date: Wed, 16 Oct 2024 22:04:16 +0200 Subject: [PATCH 099/110] [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 100/110] [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 101/110] 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 102/110] 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 103/110] [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 104/110] [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 105/110] 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 106/110] 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 107/110] 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 ff34e9f1d46a8e918a8ddc2a4ff384eaca45ce7f Mon Sep 17 00:00:00 2001 From: djordy Date: Tue, 14 Jan 2025 14:22:11 +0100 Subject: [PATCH 108/110] [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 ed5e8b563e10496a4f62557fbcb52b4191f2157d Mon Sep 17 00:00:00 2001 From: HypeMC Date: Wed, 5 Feb 2025 08:18:49 +0100 Subject: [PATCH 109/110] [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 d8b75b2c8144c29ac43b235738411f7cca6d584d Mon Sep 17 00:00:00 2001 From: HypeMC Date: Mon, 24 Mar 2025 05:35:59 +0100 Subject: [PATCH 110/110] [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();