From 88eeb782a3da661bf481ce6b57e99ef66aeedbdf Mon Sep 17 00:00:00 2001 From: Thomas Calvet Date: Wed, 11 Sep 2024 18:39:40 +0200 Subject: [PATCH 1/6] [Uid][Serializer][Validator] Mention RFC 9562 --- Normalizer/UidNormalizer.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Normalizer/UidNormalizer.php b/Normalizer/UidNormalizer.php index 70b5e158d5d..aa2a8b4fee8 100644 --- a/Normalizer/UidNormalizer.php +++ b/Normalizer/UidNormalizer.php @@ -24,7 +24,7 @@ final class UidNormalizer implements NormalizerInterface, DenormalizerInterface, public const NORMALIZATION_FORMAT_CANONICAL = 'canonical'; public const NORMALIZATION_FORMAT_BASE58 = 'base58'; public const NORMALIZATION_FORMAT_BASE32 = 'base32'; - public const NORMALIZATION_FORMAT_RFC4122 = 'rfc4122'; + public const NORMALIZATION_FORMAT_RFC4122 = 'rfc4122'; // RFC 9562 obsoleted RFC 4122 but the format is the same private $defaultContext = [ self::NORMALIZATION_FORMAT_KEY => self::NORMALIZATION_FORMAT_CANONICAL, From 5613373d6742561a0349ceee29bde4e8e245e83a Mon Sep 17 00:00:00 2001 From: Mihai Stancu Date: Tue, 10 Sep 2024 20:07:29 +0300 Subject: [PATCH 2/6] [Serializer] Fix for method named `get()` --- Normalizer/ObjectNormalizer.php | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/Normalizer/ObjectNormalizer.php b/Normalizer/ObjectNormalizer.php index a663083dbac..a8c887e50fa 100644 --- a/Normalizer/ObjectNormalizer.php +++ b/Normalizer/ObjectNormalizer.php @@ -100,14 +100,19 @@ protected function extractAttributes(object $object, ?string $format = null, arr $name = $reflMethod->name; $attributeName = null; - if (str_starts_with($name, 'get') || str_starts_with($name, 'has') || str_starts_with($name, 'can')) { + if (3 < \strlen($name) && match ($name[0]) { + 'g' => str_starts_with($name, 'get'), + 'h' => str_starts_with($name, 'has'), + 'c' => str_starts_with($name, 'can'), + default => false, + }) { // getters, hassers and canners $attributeName = substr($name, 3); if (!$reflClass->hasProperty($attributeName)) { $attributeName = lcfirst($attributeName); } - } elseif (str_starts_with($name, 'is')) { + } elseif ('is' !== $name && str_starts_with($name, 'is')) { // issers $attributeName = substr($name, 2); From a2e5a855105a0335fff5966684fc613c312f68cc Mon Sep 17 00:00:00 2001 From: valtzu Date: Fri, 13 Sep 2024 19:09:13 +0300 Subject: [PATCH 3/6] 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 a666185dd6a..26f9be4ad71 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 df514ef72c3540217c9819e4479aa55acc431d27 Mon Sep 17 00:00:00 2001 From: "Alexander M. Turek" Date: Thu, 19 Sep 2024 23:14:15 +0200 Subject: [PATCH 4/6] 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 26f9be4ad71..8ec4ac1d316 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 0f100dfa5b3bc8b052d15940d1168e5fa1e1a59a Mon Sep 17 00:00:00 2001 From: Alexandre Daubois Date: Fri, 20 Sep 2024 09:50:07 +0200 Subject: [PATCH 5/6] Mutate remaining data providers to static ones --- 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 b700f6ee713..e413be0c189 100644 --- a/Tests/Normalizer/AbstractObjectNormalizerTest.php +++ b/Tests/Normalizer/AbstractObjectNormalizerTest.php @@ -701,7 +701,7 @@ public function testDenormalizeBooleanTypesWithNotMatchingData(array $data, stri $normalizer->denormalize($data, $type); } - public function provideBooleanTypesData() + public static function provideBooleanTypesData() { return [ [['foo' => true], FalsePropertyDummy::class], From 71d6e1f70f00752d1469d0f5e83b0a51716f288b Mon Sep 17 00:00:00 2001 From: Christian Flothmann Date: Fri, 20 Sep 2024 13:43:16 +0200 Subject: [PATCH 6/6] 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 d7e0f7d25b9..b4f5c103ca7 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 0092a9643af..948fa36fa0d 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"