From 4e66fc197ec1c2dc94566230ce0fbd3d8e4081a6 Mon Sep 17 00:00:00 2001 From: Nyholm Date: Tue, 5 Dec 2023 16:29:21 +0100 Subject: [PATCH 01/13] [Serializer] Add support for auto generated custom normalizers --- .../DependencyInjection/Configuration.php | 34 ++ .../FrameworkExtension.php | 4 + .../FrameworkBundle/FrameworkBundle.php | 2 +- .../Resources/config/property_info.php | 8 + .../Resources/config/serializer.php | 26 ++ .../Component/PropertyInfo/CHANGELOG.md | 2 + ...structorArgumentTypeExtractorAggregate.php | 43 +++ ...structorArgumentTypeExtractorInterface.php | 2 - .../Component/Serializer/.editorconfig | 9 + src/Symfony/Component/Serializer/.gitignore | 1 + .../Serializer/Annotation/Serializable.php | 21 ++ .../Serializer/Attribute/Serializable.php | 27 ++ .../Serializer/Builder/BuildResult.php | 35 ++ .../Serializer/Builder/ClassDefinition.php | 111 ++++++ .../Builder/CodeGenerator/Attribute.php | 91 +++++ .../Builder/CodeGenerator/ClassGenerator.php | 138 +++++++ .../Builder/CodeGenerator/Method.php | 132 +++++++ .../Builder/CodeGenerator/Property.php | 105 ++++++ .../Builder/DefinitionExtractor.php | 148 ++++++++ .../Serializer/Builder/NormalizerBuilder.php | 352 ++++++++++++++++++ .../Serializer/Builder/PropertyDefinition.php | 176 +++++++++ src/Symfony/Component/Serializer/CHANGELOG.md | 5 + .../CustomNormalizerHelper.php | 82 ++++ .../DependencyInjection/SerializerPass.php | 25 +- .../DenormalizingUnionFailedException.php | 32 ++ .../Builder/CodeGenerator/AttributeTest.php | 77 ++++ .../CodeGenerator/ClassGeneratorTest.php | 135 +++++++ .../Builder/CodeGenerator/Fixtures/Cat.php | 29 ++ .../Builder/CodeGenerator/Fixtures/Full.php | 41 ++ .../Builder/CodeGenerator/Fixtures/Person.php | 34 ++ .../Builder/CodeGenerator/MethodTest.php | 101 +++++ .../Builder/CodeGenerator/PropertyTest.php | 92 +++++ .../Tests/Builder/FixtureHelper.php | 97 +++++ .../Builder/NormalizerBuilderFixtureTest.php | 51 +++ .../Tests/Builder/generateUpdatedFixtures.php | 48 +++ .../FullTypeHints/ComplexTypesConstructor.php | 82 ++++ .../ComplexTypesPublicProperties.php | 22 ++ .../FullTypeHints/ComplexTypesSetter.php | 79 ++++ .../FullTypeHints/ConstructorInjection.php | 77 ++++ .../ConstructorWithDefaultValue.php | 25 ++ .../FullTypeHints/DummyObject.php | 8 + .../ComplexTypesConstructor.php | 147 ++++++++ .../ComplexTypesPublicProperties.php | 154 ++++++++ .../ExpectedNormalizer/ComplexTypesSetter.php | 154 ++++++++ .../ConstructorInjection.php | 106 ++++++ .../ConstructorWithDefaultValue.php | 113 ++++++ .../ExpectedNormalizer/ExtraSetter.php | 50 +++ .../ExpectedNormalizer/InheritanceChild.php | 76 ++++ .../NonReadableProperty.php | 69 ++++ .../ExpectedNormalizer/PrivateConstructor.php | 47 +++ .../ExpectedNormalizer/PublicProperties.php | 120 ++++++ .../ExpectedNormalizer/SetterInjection.php | 121 ++++++ .../FullTypeHints/ExtraSetter.php | 35 ++ .../FullTypeHints/InheritanceChild.php | 52 +++ .../FullTypeHints/InheritanceParent.php | 42 +++ .../FullTypeHints/NonReadableProperty.php | 25 ++ .../FullTypeHints/PrivateConstructor.php | 20 + .../FullTypeHints/PublicProperties.php | 15 + .../FullTypeHints/SetterInjection.php | 103 +++++ .../FullTypeHints/SmartObject.php | 8 + .../ConstructorAndSetterInjection.php | 85 +++++ .../NoTypeHints/ConstructorInjection.php | 73 ++++ .../ConstructorAndSetterInjection.php | 89 +++++ .../ConstructorInjection.php | 83 +++++ .../ExpectedNormalizer/InheritanceChild.php | 98 +++++ .../ExpectedNormalizer/PublicProperties.php | 101 +++++ .../ExpectedNormalizer/SetterInjection.php | 98 +++++ .../NoTypeHints/InheritanceChild.php | 52 +++ .../NoTypeHints/InheritanceParent.php | 45 +++ .../NoTypeHints/PublicProperties.php | 17 + .../NoTypeHints/SetterInjection.php | 105 ++++++ 71 files changed, 4908 insertions(+), 4 deletions(-) create mode 100644 src/Symfony/Component/PropertyInfo/Extractor/ConstructorArgumentTypeExtractorAggregate.php create mode 100644 src/Symfony/Component/Serializer/.editorconfig create mode 100644 src/Symfony/Component/Serializer/Annotation/Serializable.php create mode 100644 src/Symfony/Component/Serializer/Attribute/Serializable.php create mode 100644 src/Symfony/Component/Serializer/Builder/BuildResult.php create mode 100644 src/Symfony/Component/Serializer/Builder/ClassDefinition.php create mode 100644 src/Symfony/Component/Serializer/Builder/CodeGenerator/Attribute.php create mode 100644 src/Symfony/Component/Serializer/Builder/CodeGenerator/ClassGenerator.php create mode 100644 src/Symfony/Component/Serializer/Builder/CodeGenerator/Method.php create mode 100644 src/Symfony/Component/Serializer/Builder/CodeGenerator/Property.php create mode 100644 src/Symfony/Component/Serializer/Builder/DefinitionExtractor.php create mode 100644 src/Symfony/Component/Serializer/Builder/NormalizerBuilder.php create mode 100644 src/Symfony/Component/Serializer/Builder/PropertyDefinition.php create mode 100644 src/Symfony/Component/Serializer/DependencyInjection/CustomNormalizerHelper.php create mode 100644 src/Symfony/Component/Serializer/Exception/DenormalizingUnionFailedException.php create mode 100644 src/Symfony/Component/Serializer/Tests/Builder/CodeGenerator/AttributeTest.php create mode 100644 src/Symfony/Component/Serializer/Tests/Builder/CodeGenerator/ClassGeneratorTest.php create mode 100644 src/Symfony/Component/Serializer/Tests/Builder/CodeGenerator/Fixtures/Cat.php create mode 100644 src/Symfony/Component/Serializer/Tests/Builder/CodeGenerator/Fixtures/Full.php create mode 100644 src/Symfony/Component/Serializer/Tests/Builder/CodeGenerator/Fixtures/Person.php create mode 100644 src/Symfony/Component/Serializer/Tests/Builder/CodeGenerator/MethodTest.php create mode 100644 src/Symfony/Component/Serializer/Tests/Builder/CodeGenerator/PropertyTest.php create mode 100644 src/Symfony/Component/Serializer/Tests/Builder/FixtureHelper.php create mode 100644 src/Symfony/Component/Serializer/Tests/Builder/NormalizerBuilderFixtureTest.php create mode 100644 src/Symfony/Component/Serializer/Tests/Builder/generateUpdatedFixtures.php create mode 100644 src/Symfony/Component/Serializer/Tests/Fixtures/CustomNormalizer/FullTypeHints/ComplexTypesConstructor.php create mode 100644 src/Symfony/Component/Serializer/Tests/Fixtures/CustomNormalizer/FullTypeHints/ComplexTypesPublicProperties.php create mode 100644 src/Symfony/Component/Serializer/Tests/Fixtures/CustomNormalizer/FullTypeHints/ComplexTypesSetter.php create mode 100644 src/Symfony/Component/Serializer/Tests/Fixtures/CustomNormalizer/FullTypeHints/ConstructorInjection.php create mode 100644 src/Symfony/Component/Serializer/Tests/Fixtures/CustomNormalizer/FullTypeHints/ConstructorWithDefaultValue.php create mode 100644 src/Symfony/Component/Serializer/Tests/Fixtures/CustomNormalizer/FullTypeHints/DummyObject.php create mode 100644 src/Symfony/Component/Serializer/Tests/Fixtures/CustomNormalizer/FullTypeHints/ExpectedNormalizer/ComplexTypesConstructor.php create mode 100644 src/Symfony/Component/Serializer/Tests/Fixtures/CustomNormalizer/FullTypeHints/ExpectedNormalizer/ComplexTypesPublicProperties.php create mode 100644 src/Symfony/Component/Serializer/Tests/Fixtures/CustomNormalizer/FullTypeHints/ExpectedNormalizer/ComplexTypesSetter.php create mode 100644 src/Symfony/Component/Serializer/Tests/Fixtures/CustomNormalizer/FullTypeHints/ExpectedNormalizer/ConstructorInjection.php create mode 100644 src/Symfony/Component/Serializer/Tests/Fixtures/CustomNormalizer/FullTypeHints/ExpectedNormalizer/ConstructorWithDefaultValue.php create mode 100644 src/Symfony/Component/Serializer/Tests/Fixtures/CustomNormalizer/FullTypeHints/ExpectedNormalizer/ExtraSetter.php create mode 100644 src/Symfony/Component/Serializer/Tests/Fixtures/CustomNormalizer/FullTypeHints/ExpectedNormalizer/InheritanceChild.php create mode 100644 src/Symfony/Component/Serializer/Tests/Fixtures/CustomNormalizer/FullTypeHints/ExpectedNormalizer/NonReadableProperty.php create mode 100644 src/Symfony/Component/Serializer/Tests/Fixtures/CustomNormalizer/FullTypeHints/ExpectedNormalizer/PrivateConstructor.php create mode 100644 src/Symfony/Component/Serializer/Tests/Fixtures/CustomNormalizer/FullTypeHints/ExpectedNormalizer/PublicProperties.php create mode 100644 src/Symfony/Component/Serializer/Tests/Fixtures/CustomNormalizer/FullTypeHints/ExpectedNormalizer/SetterInjection.php create mode 100644 src/Symfony/Component/Serializer/Tests/Fixtures/CustomNormalizer/FullTypeHints/ExtraSetter.php create mode 100644 src/Symfony/Component/Serializer/Tests/Fixtures/CustomNormalizer/FullTypeHints/InheritanceChild.php create mode 100644 src/Symfony/Component/Serializer/Tests/Fixtures/CustomNormalizer/FullTypeHints/InheritanceParent.php create mode 100644 src/Symfony/Component/Serializer/Tests/Fixtures/CustomNormalizer/FullTypeHints/NonReadableProperty.php create mode 100644 src/Symfony/Component/Serializer/Tests/Fixtures/CustomNormalizer/FullTypeHints/PrivateConstructor.php create mode 100644 src/Symfony/Component/Serializer/Tests/Fixtures/CustomNormalizer/FullTypeHints/PublicProperties.php create mode 100644 src/Symfony/Component/Serializer/Tests/Fixtures/CustomNormalizer/FullTypeHints/SetterInjection.php create mode 100644 src/Symfony/Component/Serializer/Tests/Fixtures/CustomNormalizer/FullTypeHints/SmartObject.php create mode 100644 src/Symfony/Component/Serializer/Tests/Fixtures/CustomNormalizer/NoTypeHints/ConstructorAndSetterInjection.php create mode 100644 src/Symfony/Component/Serializer/Tests/Fixtures/CustomNormalizer/NoTypeHints/ConstructorInjection.php create mode 100644 src/Symfony/Component/Serializer/Tests/Fixtures/CustomNormalizer/NoTypeHints/ExpectedNormalizer/ConstructorAndSetterInjection.php create mode 100644 src/Symfony/Component/Serializer/Tests/Fixtures/CustomNormalizer/NoTypeHints/ExpectedNormalizer/ConstructorInjection.php create mode 100644 src/Symfony/Component/Serializer/Tests/Fixtures/CustomNormalizer/NoTypeHints/ExpectedNormalizer/InheritanceChild.php create mode 100644 src/Symfony/Component/Serializer/Tests/Fixtures/CustomNormalizer/NoTypeHints/ExpectedNormalizer/PublicProperties.php create mode 100644 src/Symfony/Component/Serializer/Tests/Fixtures/CustomNormalizer/NoTypeHints/ExpectedNormalizer/SetterInjection.php create mode 100644 src/Symfony/Component/Serializer/Tests/Fixtures/CustomNormalizer/NoTypeHints/InheritanceChild.php create mode 100644 src/Symfony/Component/Serializer/Tests/Fixtures/CustomNormalizer/NoTypeHints/InheritanceParent.php create mode 100644 src/Symfony/Component/Serializer/Tests/Fixtures/CustomNormalizer/NoTypeHints/PublicProperties.php create mode 100644 src/Symfony/Component/Serializer/Tests/Fixtures/CustomNormalizer/NoTypeHints/SetterInjection.php diff --git a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Configuration.php b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Configuration.php index 702b4f6109077..5bac9212d57c6 100644 --- a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Configuration.php +++ b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Configuration.php @@ -1124,6 +1124,40 @@ private function addSerializerSection(ArrayNodeDefinition $rootNode, callable $e ->defaultValue([]) ->prototype('variable')->end() ->end() + ->arrayNode('auto_normalizer') + ->addDefaultsIfNotSet() + ->fixXmlConfig('path') + ->children() + ->arrayNode('paths') + ->validate() + ->ifTrue(function ($data): bool { + foreach ($data as $key => $value) { + if (!\is_string($key)) { + return true; + } + if (!\is_string($value)) { + return true; + } + } + + return false; + }) + ->thenInvalid('The value must be an array with keys and values. Keys should be the start of a namespace and the values should be a file path.') + ->end() + ->info('Paths where we store classes we want to automatically create normalizers for.') + ->normalizeKeys(false) + ->defaultValue([]) + ->example(['App\\Model' => 'src/Model', 'App\\Entity' => 'src/Entity']) + ->useAttributeAsKey('name') + ->variablePrototype() + ->validate() + ->ifTrue(fn ($value): bool => !\is_string($value)) + ->thenInvalid('The value must be a string representing a path relative to the project root.') + ->end() + ->end() + ->end() + ->end() + ->end() ->end() ->end() ->end() diff --git a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php index 9c9446fb03ce0..924d6893348e5 100644 --- a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php +++ b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php @@ -1861,6 +1861,8 @@ private function registerSerializerConfiguration(array $config, ContainerBuilder $container->removeDefinition('serializer.normalizer.translatable'); } + $container->getDefinition('serializer.custom_normalizer_helper')->replaceArgument(2, $config['auto_normalizer']['paths']); + $serializerLoaders = []; if (isset($config['enable_attributes']) && $config['enable_attributes']) { $attributeLoader = new Definition(AttributeLoader::class); @@ -1940,12 +1942,14 @@ private function registerPropertyInfoConfiguration(ContainerBuilder $container, ) { $definition = $container->register('property_info.phpstan_extractor', PhpStanExtractor::class); $definition->addTag('property_info.type_extractor', ['priority' => -1000]); + $definition->addTag('property_info.property_info.constructor_argument_type_extractor'); } if (ContainerBuilder::willBeAvailable('phpdocumentor/reflection-docblock', DocBlockFactoryInterface::class, ['symfony/framework-bundle', 'symfony/property-info'], true)) { $definition = $container->register('property_info.php_doc_extractor', PhpDocExtractor::class); $definition->addTag('property_info.description_extractor', ['priority' => -1000]); $definition->addTag('property_info.type_extractor', ['priority' => -1001]); + $definition->addTag('property_info.property_info.constructor_argument_type_extractor'); } if ($container->getParameter('kernel.debug')) { diff --git a/src/Symfony/Bundle/FrameworkBundle/FrameworkBundle.php b/src/Symfony/Bundle/FrameworkBundle/FrameworkBundle.php index 26784bec367d2..6c69612a6bae8 100644 --- a/src/Symfony/Bundle/FrameworkBundle/FrameworkBundle.php +++ b/src/Symfony/Bundle/FrameworkBundle/FrameworkBundle.php @@ -150,8 +150,8 @@ public function build(ContainerBuilder $container): void $this->addCompilerPassIfExists($container, TranslationExtractorPass::class); $this->addCompilerPassIfExists($container, TranslationDumperPass::class); $container->addCompilerPass(new FragmentRendererPass()); - $this->addCompilerPassIfExists($container, SerializerPass::class); $this->addCompilerPassIfExists($container, PropertyInfoPass::class); + $this->addCompilerPassIfExists($container, SerializerPass::class); $container->addCompilerPass(new ControllerArgumentValueResolverPass()); $container->addCompilerPass(new CachePoolPass(), PassConfig::TYPE_BEFORE_OPTIMIZATION, 32); $container->addCompilerPass(new CachePoolClearerPass(), PassConfig::TYPE_AFTER_REMOVING); diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/config/property_info.php b/src/Symfony/Bundle/FrameworkBundle/Resources/config/property_info.php index 90587839d54c4..cf472432df942 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Resources/config/property_info.php +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/config/property_info.php @@ -11,6 +11,8 @@ namespace Symfony\Component\DependencyInjection\Loader\Configurator; +use Symfony\Component\PropertyInfo\Extractor\ConstructorArgumentTypeExtractorAggregate; +use Symfony\Component\PropertyInfo\Extractor\ConstructorArgumentTypeExtractorInterface; use Symfony\Component\PropertyInfo\Extractor\ReflectionExtractor; use Symfony\Component\PropertyInfo\PropertyAccessExtractorInterface; use Symfony\Component\PropertyInfo\PropertyDescriptionExtractorInterface; @@ -45,8 +47,14 @@ ->tag('property_info.type_extractor', ['priority' => -1002]) ->tag('property_info.access_extractor', ['priority' => -1000]) ->tag('property_info.initializable_extractor', ['priority' => -1000]) + ->tag('property_info.property_info.constructor_argument_type_extractor') ->alias(PropertyReadInfoExtractorInterface::class, 'property_info.reflection_extractor') ->alias(PropertyWriteInfoExtractorInterface::class, 'property_info.reflection_extractor') + + ->set('property_info.constructor_argument_type_extractor_aggregate', ConstructorArgumentTypeExtractorAggregate::class) + ->args([tagged_iterator('property_info.constructor_argument_type_extractor')]) + ->alias(ConstructorArgumentTypeExtractorInterface::class, 'property_info.constructor_argument_type_extractor_aggregate') + ; }; diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/config/serializer.php b/src/Symfony/Bundle/FrameworkBundle/Resources/config/serializer.php index 799cfb2900f9d..dc455d49620ac 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Resources/config/serializer.php +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/config/serializer.php @@ -16,7 +16,14 @@ use Symfony\Component\Cache\Adapter\PhpArrayAdapter; use Symfony\Component\ErrorHandler\ErrorRenderer\HtmlErrorRenderer; use Symfony\Component\ErrorHandler\ErrorRenderer\SerializerErrorRenderer; +use Symfony\Component\PropertyInfo\Extractor\ConstructorArgumentTypeExtractorInterface; use Symfony\Component\PropertyInfo\Extractor\SerializerExtractor; +use Symfony\Component\PropertyInfo\PropertyInfoExtractorInterface; +use Symfony\Component\PropertyInfo\PropertyReadInfoExtractorInterface; +use Symfony\Component\PropertyInfo\PropertyWriteInfoExtractorInterface; +use Symfony\Component\Serializer\Builder\DefinitionExtractor; +use Symfony\Component\Serializer\Builder\NormalizerBuilder; +use Symfony\Component\Serializer\DependencyInjection\CustomNormalizerHelper; use Symfony\Component\Serializer\Encoder\CsvEncoder; use Symfony\Component\Serializer\Encoder\DecoderInterface; use Symfony\Component\Serializer\Encoder\EncoderInterface; @@ -169,6 +176,25 @@ service('serializer.mapping.cache.symfony'), ]) + // Auto Normalizer Builder + ->set('serializer.auto_normalizer.builder', NormalizerBuilder::class) + ->set('serializer.auto_normalizer.definition_extractor', DefinitionExtractor::class) + ->args([ + service(PropertyInfoExtractorInterface::class), + service(PropertyReadInfoExtractorInterface::class), + service(PropertyWriteInfoExtractorInterface::class), + service(ConstructorArgumentTypeExtractorInterface::class), + ]) + + ->set('serializer.custom_normalizer_helper', CustomNormalizerHelper::class) + ->args([ + service('serializer.auto_normalizer.builder'), + service('serializer.auto_normalizer.definition_extractor'), + [], + param('kernel.project_dir'), + service('logger')->nullOnInvalid(), + ]) + // Encoders ->set('serializer.encoder.xml', XmlEncoder::class) ->tag('serializer.encoder') diff --git a/src/Symfony/Component/PropertyInfo/CHANGELOG.md b/src/Symfony/Component/PropertyInfo/CHANGELOG.md index 6e0a2ff449dec..f234d4631e713 100644 --- a/src/Symfony/Component/PropertyInfo/CHANGELOG.md +++ b/src/Symfony/Component/PropertyInfo/CHANGELOG.md @@ -5,6 +5,8 @@ CHANGELOG --- * Introduce `PropertyDocBlockExtractorInterface` to extract a property's doc block + * Make ConstructorArgumentTypeExtractorInterface non-internal + * Add `ConstructorArgumentTypeExtractorAggregate` to aggregate multiple `ConstructorArgumentTypeExtractorInterface` implementations 6.4 --- diff --git a/src/Symfony/Component/PropertyInfo/Extractor/ConstructorArgumentTypeExtractorAggregate.php b/src/Symfony/Component/PropertyInfo/Extractor/ConstructorArgumentTypeExtractorAggregate.php new file mode 100644 index 0000000000000..408871f40edc5 --- /dev/null +++ b/src/Symfony/Component/PropertyInfo/Extractor/ConstructorArgumentTypeExtractorAggregate.php @@ -0,0 +1,43 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\PropertyInfo\Extractor; + +/** + * @author Tobias Nyholm + */ +class ConstructorArgumentTypeExtractorAggregate implements ConstructorArgumentTypeExtractorInterface +{ + /** + * @param iterable $extractors + */ + public function __construct( + private readonly iterable $extractors = [], + ) { + } + + public function getTypesFromConstructor(string $class, string $property): ?array + { + $output = []; + foreach ($this->extractors as $extractor) { + $value = $extractor->getTypesFromConstructor($class, $property); + if (null !== $value) { + $output[] = $value; + } + } + + if ([] === $output) { + return null; + } + + return array_merge([], ...$output); + } +} diff --git a/src/Symfony/Component/PropertyInfo/Extractor/ConstructorArgumentTypeExtractorInterface.php b/src/Symfony/Component/PropertyInfo/Extractor/ConstructorArgumentTypeExtractorInterface.php index cbde902e98015..666aaba7ffb5f 100644 --- a/src/Symfony/Component/PropertyInfo/Extractor/ConstructorArgumentTypeExtractorInterface.php +++ b/src/Symfony/Component/PropertyInfo/Extractor/ConstructorArgumentTypeExtractorInterface.php @@ -17,8 +17,6 @@ * Infers the constructor argument type. * * @author Dmitrii Poddubnyi - * - * @internal */ interface ConstructorArgumentTypeExtractorInterface { diff --git a/src/Symfony/Component/Serializer/.editorconfig b/src/Symfony/Component/Serializer/.editorconfig new file mode 100644 index 0000000000000..3ae2aa3ddb065 --- /dev/null +++ b/src/Symfony/Component/Serializer/.editorconfig @@ -0,0 +1,9 @@ +# Ignore paths +[Tests/CodeGenerator/Fixtures/**] +trim_trailing_whitespace = false + +[Tests/Fixtures/CustomNormalizer/**/ExpectedNormalizer/**] +trim_trailing_whitespace = false +insert_final_newline = false +indent_size = unset +indent_style = unset diff --git a/src/Symfony/Component/Serializer/.gitignore b/src/Symfony/Component/Serializer/.gitignore index c49a5d8df5c65..525face3fb3e3 100644 --- a/src/Symfony/Component/Serializer/.gitignore +++ b/src/Symfony/Component/Serializer/.gitignore @@ -1,3 +1,4 @@ vendor/ composer.lock phpunit.xml +Tests/_output diff --git a/src/Symfony/Component/Serializer/Annotation/Serializable.php b/src/Symfony/Component/Serializer/Annotation/Serializable.php new file mode 100644 index 0000000000000..5df0631b00a3b --- /dev/null +++ b/src/Symfony/Component/Serializer/Annotation/Serializable.php @@ -0,0 +1,21 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Serializer\Annotation; + +class_exists(\Symfony\Component\Serializer\Attribute\Serializable::class); + +if (false) { + #[\Attribute(\Attribute::TARGET_CLASS)] + class Serializable extends \Symfony\Component\Serializer\Attribute\Serializable + { + } +} diff --git a/src/Symfony/Component/Serializer/Attribute/Serializable.php b/src/Symfony/Component/Serializer/Attribute/Serializable.php new file mode 100644 index 0000000000000..cb8d95e746230 --- /dev/null +++ b/src/Symfony/Component/Serializer/Attribute/Serializable.php @@ -0,0 +1,27 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Serializer\Attribute; + +/** + * Classes with this attribute will get a custom normalizer to improve speed when + * serializing/deserializing. + * + * @author Tobias Nyholm + */ +#[\Attribute(\Attribute::TARGET_CLASS)] +class Serializable +{ +} + +if (!class_exists(\Symfony\Component\Serializer\Annotation\Serializable::class, false)) { + class_alias(Serializable::class, \Symfony\Component\Serializer\Annotation\Serializable::class); +} diff --git a/src/Symfony/Component/Serializer/Builder/BuildResult.php b/src/Symfony/Component/Serializer/Builder/BuildResult.php new file mode 100644 index 0000000000000..7080cd45362da --- /dev/null +++ b/src/Symfony/Component/Serializer/Builder/BuildResult.php @@ -0,0 +1,35 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Serializer\Builder; + +/** + * @author Tobias Nyholm + * + * @experimental in 7.1 + */ +class BuildResult +{ + public function __construct( + // The full file location where the class is stored + public readonly string $filePath, + // Just the class name + public readonly string $className, + // Class name with namespace + public readonly string $classNs, + ) { + } + + public function loadClass() + { + require_once $this->filePath; + } +} diff --git a/src/Symfony/Component/Serializer/Builder/ClassDefinition.php b/src/Symfony/Component/Serializer/Builder/ClassDefinition.php new file mode 100644 index 0000000000000..323de5ce4bc9f --- /dev/null +++ b/src/Symfony/Component/Serializer/Builder/ClassDefinition.php @@ -0,0 +1,111 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Serializer\Builder; + +/** + * This contains all necessary information about a class to create a custom Normalizer. + * It has an array of PropertyDefinitions. + * + * @author Tobias Nyholm + * + * @experimental in 7.1 + */ +class ClassDefinition +{ + public const CONSTRUCTOR_NONE = 'none'; + public const CONSTRUCTOR_NON_PUBLIC = 'non_public'; + public const CONSTRUCTOR_PUBLIC = 'public'; + + private string $sourceClassName; + private string $namespaceAndClass; + private string $newNamespace; + private string $newClassName; + private string $constructorType = self::CONSTRUCTOR_NONE; + + /** + * @var PropertyDefinition[] + */ + private array $definitions = []; + + public function __construct(string $namespaceAndClass, string $newClassName, string $newNamespace) + { + $this->namespaceAndClass = $namespaceAndClass; + $this->newNamespace = $newNamespace; + $this->newClassName = $newClassName; + $this->sourceClassName = substr($namespaceAndClass, strrpos($namespaceAndClass, '\\') + 1); + } + + public function getSourceClassName(): string + { + return $this->sourceClassName; + } + + public function getNamespaceAndClass(): string + { + return $this->namespaceAndClass; + } + + /** + * @return PropertyDefinition[] + */ + public function getDefinitions(): array + { + return $this->definitions; + } + + public function getDefinition(string $propertyName): ?PropertyDefinition + { + return $this->definitions[$propertyName] ?? null; + } + + public function addDefinition(PropertyDefinition $definition): void + { + $this->definitions[$definition->getPropertyName()] = $definition; + } + + public function getConstructorType(): string + { + return $this->constructorType; + } + + public function setConstructorType(string $constructorType): void + { + $this->constructorType = $constructorType; + } + + public function getNewNamespace(): string + { + return $this->newNamespace; + } + + public function getNewClassName(): string + { + return $this->newClassName; + } + + /** + * @return PropertyDefinition[] + */ + public function getConstructorArguments(): array + { + $arguments = []; + foreach ($this->definitions as $def) { + $order = $def->getConstructorArgumentOrder(); + if (null === $order) { + continue; + } + $arguments[$order] = $def; + } + + return $arguments; + } +} diff --git a/src/Symfony/Component/Serializer/Builder/CodeGenerator/Attribute.php b/src/Symfony/Component/Serializer/Builder/CodeGenerator/Attribute.php new file mode 100644 index 0000000000000..a0a82a4f48ef3 --- /dev/null +++ b/src/Symfony/Component/Serializer/Builder/CodeGenerator/Attribute.php @@ -0,0 +1,91 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Serializer\Builder\CodeGenerator; + +/** + * Represents a new PHP attribute. + * + * @internal + * + * @author Tobias Nyholm + */ +class Attribute +{ + private string $name; + /** @var array */ + private array $parameters = []; + + public static function create(string $name): self + { + $method = new self(); + $method->setName($name); + + return $method; + } + + public function setName(string $name): self + { + $this->name = $name; + + return $this; + } + + public function addParameter(?string $name, self|int|string|bool|float|null|array $value): self + { + if (null === $name) { + $this->parameters[] = $value; + } else { + $this->parameters[$name] = $value; + } + + return $this; + } + + public function toString(bool $nested = false): string + { + $parameters = []; + foreach ($this->parameters as $name => $value) { + $parameter = ''; + if (\is_string($name)) { + $parameter .= $name.': '; + } + $parameters[] = $parameter.$this->getValue($value); + } + + if ([] === $parameters && !$nested) { + $output = $this->name; + } else { + $output = sprintf('%s(%s)', $this->name, implode(', ', $parameters)); + } + + if ($nested) { + return 'new '.$output; + } + + return sprintf('#[%s]', $output); + } + + private function getValue($value): string + { + if (\is_array($value)) { + $value = sprintf('[%s]', implode(', ', array_map(fn ($value) => $this->getValue($value), $value))); + } elseif ($value instanceof self) { + $value = $value->toString(true); + } elseif (\is_string($value)) { + $value = '"'.$value.'"'; + } else { + $value = var_export($value, true); + } + + return $value; + } +} diff --git a/src/Symfony/Component/Serializer/Builder/CodeGenerator/ClassGenerator.php b/src/Symfony/Component/Serializer/Builder/CodeGenerator/ClassGenerator.php new file mode 100644 index 0000000000000..776191f2e244d --- /dev/null +++ b/src/Symfony/Component/Serializer/Builder/CodeGenerator/ClassGenerator.php @@ -0,0 +1,138 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Serializer\Builder\CodeGenerator; + +/** + * Generate a new PHP class. + * + * @internal + * + * @author Tobias Nyholm + */ +class ClassGenerator +{ + private ?string $name; + private ?string $namespace; + private ?string $extends = null; + private array $imports = []; + private array $implements = []; + /** @var Property[] */ + private array $properties = []; + /** @var Method[] */ + private array $methods = []; + /** @var Attribute[] */ + private array $attributes = []; + private ?string $fileComment = null; + private ?string $classComment = null; + + public function __construct(string $name = null, string $namespace = null) + { + $this->name = $name; + $this->namespace = $namespace; + } + + public function setExtends(?string $class) + { + $this->extends = $class; + } + + public function addImplements(string $class) + { + $this->implements[] = $class; + } + + public function addAttribute(Attribute $attribute): void + { + $this->attributes[] = $attribute; + } + + public function addMethod(Method $method): void + { + $this->methods[] = $method; + } + + public function addProperty(Property $property): void + { + $this->properties[] = $property; + } + + public function addImport(string $class): void + { + $this->imports[] = $class; + } + + public function setFileComment(?string $fileComment): void + { + $this->fileComment = $fileComment; + } + + public function setClassComment(?string $classComment): void + { + $this->classComment = $classComment; + } + + public function toString(string $indentation = ' '): string + { + $output = 'fileComment) { + $lines = explode(\PHP_EOL, $this->fileComment); + $output .= sprintf('/*'.\PHP_EOL.' * %s'.\PHP_EOL.' */'.\PHP_EOL.\PHP_EOL, implode(\PHP_EOL.' * ', $lines)); + } + + if ($this->namespace) { + $output .= 'namespace '.$this->namespace.';'.\PHP_EOL.\PHP_EOL; + } + + if ([] !== $this->imports) { + foreach ($this->imports as $import) { + $output .= 'use '.$import.';'.\PHP_EOL; + } + $output .= \PHP_EOL; + } + + if ($this->classComment) { + $lines = explode(\PHP_EOL, $this->classComment); + $output .= sprintf('/**'.\PHP_EOL.' * %s'.\PHP_EOL.' */'.\PHP_EOL, implode(\PHP_EOL.' * ', $lines)); + } + + foreach ($this->attributes as $attribute) { + $output .= $attribute->toString().\PHP_EOL; + } + + $output .= 'class '.$this->name; + if ($this->extends) { + $output .= ' extends '.$this->extends; + } + if ($this->implements) { + $output .= ' implements '.implode(', ', $this->implements); + } + $output .= \PHP_EOL.'{'.\PHP_EOL; + + if ([] !== $this->properties) { + foreach ($this->properties as $property) { + $lines = explode(\PHP_EOL, $property->toString()); + $output .= $indentation.implode(\PHP_EOL.$indentation, $lines).\PHP_EOL; + } + $output .= \PHP_EOL; + } + + foreach ($this->methods as $method) { + $lines = explode(\PHP_EOL, $method->toString($indentation)); + $output .= $indentation.implode(\PHP_EOL.$indentation, $lines).\PHP_EOL.\PHP_EOL; + } + + $output .= '}'.\PHP_EOL; + + return $output; + } +} diff --git a/src/Symfony/Component/Serializer/Builder/CodeGenerator/Method.php b/src/Symfony/Component/Serializer/Builder/CodeGenerator/Method.php new file mode 100644 index 0000000000000..63453fc7b3550 --- /dev/null +++ b/src/Symfony/Component/Serializer/Builder/CodeGenerator/Method.php @@ -0,0 +1,132 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Serializer\Builder\CodeGenerator; + +/** + * Represents a new PHP method. + * + * @internal + * + * @author Tobias Nyholm + */ +class Method +{ + private string $name; + private string $visibility = 'public'; + private ?string $returnType = null; + private array $arguments = []; + private ?string $body = ''; + private array $attributes = []; + private ?string $comment = null; + + public static function create(string $name): self + { + $method = new self(); + $method->setName($name); + + return $method; + } + + public function setName(string $name): self + { + $this->name = $name; + + return $this; + } + + public function setVisibility(string $visibility): self + { + $this->visibility = $visibility; + + return $this; + } + + public function setReturnType(?string $returnType): self + { + $this->returnType = $returnType; + + return $this; + } + + public function addArgument(string $name, string $type = null, $default = null): self + { + $this->arguments[$name] = [$type, $default, 3 === \func_num_args()]; + + return $this; + } + + public function setBody(?string $body): self + { + $this->body = $body; + + return $this; + } + + public function addAttribute(Attribute $attribute): self + { + $this->attributes[] = $attribute; + + return $this; + } + + public function setComment(?string $comment): self + { + $this->comment = $comment; + + return $this; + } + + public function toString(string $indentation = ''): string + { + $arguments = []; + foreach ($this->arguments as $name => [$type, $default, $hasDefault]) { + $argument = '$'.$name; + + if ($type) { + $argument = sprintf('%s %s', $type, $argument); + } + if ($hasDefault) { + $argument = sprintf('%s = %s', $argument, [] === $default ? '[]' : var_export($default, true)); + } + $arguments[] = $argument; + } + + $output = ''; + if ($this->comment) { + $lines = explode(\PHP_EOL, $this->comment); + $output .= sprintf('/**'.\PHP_EOL.' * %s'.\PHP_EOL.' */'.\PHP_EOL, implode(\PHP_EOL.' * ', $lines)); + } + + if ($this->attributes) { + foreach ($this->attributes as $attribute) { + $output .= $attribute->toString().\PHP_EOL; + } + } + + $output .= sprintf('%s function %s(%s)', $this->visibility, $this->name, implode(', ', $arguments)); + if ($this->returnType) { + $output = sprintf('%s: %s', $output, $this->returnType); + } + + if (null === $this->body) { + return sprintf('abstract %s;', $output); + } + + if ('' === $this->body) { + return sprintf('%s'.\PHP_EOL.'{'.\PHP_EOL.'}', $output); + } + + $lines = explode(\PHP_EOL, $this->body); + + return sprintf('%s'.\PHP_EOL.'{'.\PHP_EOL.'%s'.\PHP_EOL.'}', $output, $indentation.implode(\PHP_EOL.$indentation, $lines)); + } +} diff --git a/src/Symfony/Component/Serializer/Builder/CodeGenerator/Property.php b/src/Symfony/Component/Serializer/Builder/CodeGenerator/Property.php new file mode 100644 index 0000000000000..deb7101eaf242 --- /dev/null +++ b/src/Symfony/Component/Serializer/Builder/CodeGenerator/Property.php @@ -0,0 +1,105 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Serializer\Builder\CodeGenerator; + +/** + * Represents a new PHP property. + * + * @internal + * + * @author Tobias Nyholm + */ +class Property +{ + private string $name; + private string $visibility = 'public'; + private ?string $type = null; + private $defaultValue; + private bool $hasDefaultValue = false; + private array $attributes = []; + private ?string $comment = null; + + public static function create(string $name): self + { + $property = new self(); + $property->setName($name); + + return $property; + } + + public function setName(string $name): self + { + $this->name = $name; + + return $this; + } + + public function setVisibility(string $visibility): self + { + $this->visibility = $visibility; + + return $this; + } + + public function setType(?string $type): self + { + $this->type = $type; + + return $this; + } + + public function setDefaultValue($defaultValue): self + { + $this->defaultValue = $defaultValue; + $this->hasDefaultValue = true; + + return $this; + } + + public function addAttribute(Attribute $attribute): self + { + $this->attributes[] = $attribute; + + return $this; + } + + public function setComment(?string $comment): self + { + $this->comment = $comment; + + return $this; + } + + public function toString(): string + { + $output = ''; + if ($this->comment) { + $lines = explode(\PHP_EOL, $this->comment); + $output .= sprintf('/**'.\PHP_EOL.' * %s'.\PHP_EOL.' */'.\PHP_EOL, implode(\PHP_EOL.' * ', $lines)); + } + + if ($this->attributes) { + foreach ($this->attributes as $attribute) { + $output .= $attribute->toString().\PHP_EOL; + } + } + + $lastLine = sprintf('%s $%s', $this->type, $this->name); + $lastLine = sprintf('%s %s', $this->visibility, trim($lastLine)); + if ($this->hasDefaultValue) { + $lastLine = sprintf('%s = %s', $lastLine, [] === $this->defaultValue ? '[]' : var_export($this->defaultValue, true)); + } + $lastLine .= ';'; + + return $output.$lastLine; + } +} diff --git a/src/Symfony/Component/Serializer/Builder/DefinitionExtractor.php b/src/Symfony/Component/Serializer/Builder/DefinitionExtractor.php new file mode 100644 index 0000000000000..f657df2ea798c --- /dev/null +++ b/src/Symfony/Component/Serializer/Builder/DefinitionExtractor.php @@ -0,0 +1,148 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Serializer\Builder; + +use Symfony\Component\PropertyInfo\Extractor\ConstructorArgumentTypeExtractorInterface; +use Symfony\Component\PropertyInfo\PropertyInfoExtractorInterface; +use Symfony\Component\PropertyInfo\PropertyReadInfo; +use Symfony\Component\PropertyInfo\PropertyReadInfoExtractorInterface; +use Symfony\Component\PropertyInfo\PropertyWriteInfoExtractorInterface; +use Symfony\Component\PropertyInfo\Type; + +/** + * Take in a class and extract the definition. + * + * @author Tobias Nyholm + * + * @experimental in 7.1 + */ +class DefinitionExtractor +{ + public function __construct( + private PropertyInfoExtractorInterface $propertyInfo, + private PropertyReadInfoExtractorInterface $propertyReadInfoExtractor, + private PropertyWriteInfoExtractorInterface $propertyWriteInfoExtractor, + private ConstructorArgumentTypeExtractorInterface $constructorArgumentTypeExtractor, + ) { + } + + public function getDefinition(string $classNs): ClassDefinition + { + $className = str_replace('\\', '_', ltrim($classNs, '\\')); + $definition = new ClassDefinition($classNs, $className, 'Symfony\\Serializer\\Normalizer'); + $this->extractProperties($definition); + + return $definition; + } + + private function extractProperties(ClassDefinition $classDefinition): void + { + $classNs = $classDefinition->getNamespaceAndClass(); + + /* + * Extract constructor. + */ + $reflectionClass = new \ReflectionClass($classNs); + $constructor = $reflectionClass->getConstructor(); + if (null === $constructor) { + $classDefinition->setConstructorType(ClassDefinition::CONSTRUCTOR_NONE); + } elseif (!$constructor->isPublic()) { + $classDefinition->setConstructorType(ClassDefinition::CONSTRUCTOR_NON_PUBLIC); + } else { + $classDefinition->setConstructorType(ClassDefinition::CONSTRUCTOR_PUBLIC); + + foreach ($constructor->getParameters() as $i => $parameter) { + // We assume the constructor parameter name is the same as the property + $definition = $this->createOrGetDefinition($classDefinition, $parameter->getName()); + $definition->setConstructorArgumentOrder($i); + if ($parameter->isDefaultValueAvailable()) { + $definition->setConstructorDefaultValue($parameter->getDefaultValue()); + } + $types = $this->constructorArgumentTypeExtractor->getTypesFromConstructor($classNs, $parameter->getName()); + $this->parseTypes($definition, $types); + } + } + + /* + * Extract properties + */ + foreach ($this->propertyInfo->getProperties($classNs) ?? [] as $property) { + $definition = $this->createOrGetDefinition($classDefinition, $property); + $definition->setIsReadable($this->propertyInfo->isReadable($classNs, $property)); + $definition->setIsWriteable($this->propertyInfo->isWritable($classNs, $property)); + + $types = $this->propertyInfo->getTypes($classNs, $property); + $this->parseTypes($definition, $types); + + $info = $this->propertyReadInfoExtractor->getReadInfo($classNs, $property); + if (null !== $info && PropertyReadInfo::TYPE_METHOD === $info->getType()) { + $definition->setGetterName($info->getName()); + } + + $info = $this->propertyWriteInfoExtractor->getWriteInfo($classNs, $property); + if (null !== $info && PropertyReadInfo::TYPE_METHOD === $info->getType()) { + $definition->setSetterName($info->getName()); + } + } + } + + private function createOrGetDefinition(ClassDefinition $classDefinition, string $property): PropertyDefinition + { + $definition = $classDefinition->getDefinition($property); + if (null === $definition) { + $definition = new PropertyDefinition($property); + $classDefinition->addDefinition($definition); + } + + return $definition; + } + + /** + * @param Type[]|null $types + */ + private function parseTypes(PropertyDefinition $definition, ?array $types): void + { + $isCollection = false; + $targetClasses = []; + $builtInTypes = []; + + if (null !== $types) { + foreach ($types as $type) { + $this->parseType($type, $builtInTypes, $isCollection, $targetClasses); + } + } + + // Flip and remove empty values + $targetClasses = array_keys($targetClasses); + $targetClasses = array_filter($targetClasses); + $definition->setNonPrimitiveTypes($targetClasses); + $definition->setScalarTypes($builtInTypes); + $definition->setIsCollection($isCollection); + } + + private function parseType(Type $type, array &$builtInTypes, bool &$isCollection, array &$targetClasses): void + { + $builtinType = $type->getBuiltinType(); + + if (\in_array($builtinType, ['bool', 'int', 'float', 'string'])) { + $builtInTypes[] = $builtinType; + } + $isCollection = $isCollection || $type->isCollection(); + if (Type::BUILTIN_TYPE_OBJECT === $builtinType) { + $targetClasses[$type->getClassName()] = true; + } elseif ($type->isCollection()) { + foreach ($type->getCollectionValueTypes() as $collectionType) { + $this->parseType($collectionType, $builtInTypes, $isCollection, $targetClasses); + } + } + } +} diff --git a/src/Symfony/Component/Serializer/Builder/NormalizerBuilder.php b/src/Symfony/Component/Serializer/Builder/NormalizerBuilder.php new file mode 100644 index 0000000000000..473bd7dfb2a81 --- /dev/null +++ b/src/Symfony/Component/Serializer/Builder/NormalizerBuilder.php @@ -0,0 +1,352 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Serializer\Builder; + +use Symfony\Component\Serializer\Builder\CodeGenerator\ClassGenerator; +use Symfony\Component\Serializer\Builder\CodeGenerator\Method; +use Symfony\Component\Serializer\Builder\CodeGenerator\Property; +use Symfony\Component\Serializer\Exception\DenormalizingUnionFailedException; +use Symfony\Component\Serializer\Normalizer\DenormalizerAwareInterface; +use Symfony\Component\Serializer\Normalizer\DenormalizerInterface; +use Symfony\Component\Serializer\Normalizer\NormalizerAwareInterface; +use Symfony\Component\Serializer\Normalizer\NormalizerInterface; + +/** + * The main class to create a new Normalizer from a ClassDefinition. + * + * @author Tobias Nyholm + * + * @experimental in 7.1 + */ +class NormalizerBuilder +{ + public function build(ClassDefinition $definition, string $outputDir): BuildResult + { + @mkdir($outputDir, 0777, true); + $generator = new ClassGenerator($definition->getNewClassName(), $definition->getNewNamespace()); + + $generator->addImport($definition->getNamespaceAndClass()); + $this->addRequiredMethods($generator, $definition); + $this->addNormailizeMethod($generator, $definition); + $this->addDenormailizeMethod($generator, $definition); + + $outputFile = $outputDir.'/'.$definition->getNewClassName().'.php'; + file_put_contents($outputFile, $generator->toString()); + + return new BuildResult( + $outputFile, + $definition->getNewClassName(), + sprintf('%s\\%s', $definition->getNewNamespace(), $definition->getNewClassName()) + ); + } + + /** + * Generate a private helper class to normalize subtypes. + */ + private function generateNormalizeChildMethod(ClassGenerator $generator): void + { + $generator->addImport(NormalizerAwareInterface::class); + $generator->addImplements('NormalizerAwareInterface'); + + $generator->addProperty(Property::create('normalizer')->setType('null|NormalizerInterface')->setDefaultValue(null)->setVisibility('private')); + + // public function setNormalizer(NormalizerInterface $normalizer): void; + $generator->addMethod(Method::create('setNormalizer') + ->addArgument('normalizer', 'NormalizerInterface') + ->setReturnType('void') + ->setBody('$this->normalizer = $normalizer;') + ); + + $generator->addMethod(Method::create('normalizeChild') + ->setVisibility('private') + ->addArgument('object', 'mixed') + ->addArgument('format', '?string') + ->addArgument('context', 'array') + ->addArgument('canBeIterable', 'bool') + ->setReturnType('mixed') + ->setBody(<< \$this->normalizeChild(\$item, \$format, \$context, true), \$object); +} + +return \$this->normalizer->normalize(\$object, \$format, \$context); + +PHP + ) + ); + } + + /** + * Generate a private helper class to de-normalize subtypes. + */ + private function generateDenormalizeChildMethod(ClassGenerator $generator): void + { + $generator->addImport(DenormalizingUnionFailedException::class); + $generator->addImport(DenormalizerAwareInterface::class); + $generator->addImplements('DenormalizerAwareInterface'); + + $generator->addProperty(Property::create('denormalizer')->setType('null|DenormalizerInterface')->setDefaultValue(null)->setVisibility('private')); + + // public function setNormalizer(NormalizerInterface $normalizer): void; + $generator->addMethod(Method::create('setDenormalizer') + ->addArgument('denormalizer', 'DenormalizerInterface') + ->setReturnType('void') + ->setBody('$this->denormalizer = $denormalizer;') + ); + + $generator->addMethod(Method::create('denormalizeChild') + ->setVisibility('private') + ->addArgument('data', 'mixed') + ->addArgument('type', 'string') + ->addArgument('format', '?string') + ->addArgument('context', 'array') + ->addArgument('canBeIterable', 'bool') + ->setReturnType('mixed') + ->setBody(<< \$this->denormalizeChild(\$item, \$type, \$format, \$context, true), \$data); +} + +return \$this->denormalizer->denormalize(\$data, \$type, \$format, \$context); + +PHP + ) + ); + } + + /** + * Add methods required by NormalizerInterface and DenormalizerInterface. + */ + private function addRequiredMethods(ClassGenerator $generator, ClassDefinition $definition): void + { + $generator->addImport(NormalizerInterface::class); + $generator->addImport(DenormalizerInterface::class); + $generator->addImplements('NormalizerInterface'); + $generator->addImplements('DenormalizerInterface'); + + // public function getSupportedTypes(?string $format): array; + $generator->addMethod(Method::create('getSupportedTypes') + ->addArgument('format', '?string') + ->setReturnType('array') + ->setBody(sprintf('return [%s::class => true];', $definition->getSourceClassName())) + ); + + // public function supportsNormalization(mixed $data, string $format = null, array $context = []): bool; + $generator->addMethod(Method::create('supportsNormalization') + ->addArgument('data', 'mixed') + ->addArgument('format', '?string', null) + ->addArgument('context', 'array', []) + ->setReturnType('bool') + ->setBody(sprintf('return $data instanceof %s;', $definition->getSourceClassName())) + ); + + // public function supportsDenormalization(mixed $data, string $type, string $format = null, array $context = []): bool; + $generator->addMethod(Method::create('supportsDenormalization') + ->addArgument('data', 'mixed') + ->addArgument('type', 'string') + ->addArgument('format', '?string', null) + ->addArgument('context', 'array', []) + ->setReturnType('bool') + ->setBody(sprintf('return $type === %s::class;', $definition->getSourceClassName())) + ); + } + + private function addDenormailizeMethod(ClassGenerator $generator, ClassDefinition $definition): void + { + $needsChildDenormalizer = false; + $preCreateObject = ''; + + if (ClassDefinition::CONSTRUCTOR_NONE === $definition->getConstructorType()) { + $body = sprintf('$output = new %s();', $definition->getSourceClassName()).\PHP_EOL; + } elseif (ClassDefinition::CONSTRUCTOR_PUBLIC !== $definition->getConstructorType()) { + $body = sprintf('$output = (new \\ReflectionClass(%s::class))->newInstanceWithoutConstructor();', $definition->getSourceClassName()).\PHP_EOL; + } else { + $body = sprintf('$output = new %s(', $definition->getSourceClassName()).\PHP_EOL; + + foreach ($definition->getConstructorArguments() as $i => $propertyDefinition) { + $body .= ' '; + $variable = sprintf('$data[\'%s\']', $propertyDefinition->getNormalizedName()); + $targetClasses = $propertyDefinition->getNonPrimitiveTypes(); + $canBeIterable = $propertyDefinition->isCollection(); + + if ([] === $targetClasses && $propertyDefinition->hasConstructorDefaultValue()) { + $variable .= ' ?? '.var_export($propertyDefinition->getConstructorDefaultValue(), true); + } elseif ([] !== $targetClasses) { + $needsChildDenormalizer = true; + $printedCanBeIterable = $canBeIterable ? 'true' : 'false'; + $tempVariableName = '$argument'.$i; + + if (\count($targetClasses) > 1) { + $variableOutput = $this->generateCodeToDeserializeMultiplePossibleClasses($targetClasses, $printedCanBeIterable, $tempVariableName, $variable, $propertyDefinition->getNormalizedName(), $definition->getNamespaceAndClass()); + } else { + $variableOutput = <<denormalizeChild($variable, \\{$targetClasses[0]}::class, \$format, \$context, $printedCanBeIterable); + +PHP; + } + + if ($propertyDefinition->hasConstructorDefaultValue()) { + $export = var_export($propertyDefinition->getConstructorDefaultValue(), true); + $variableOutput = <<getNormalizedName()}', \$data)) { + {$tempVariableName} = {$export}; +} else { + {$variableOutput} +} +PHP; + } + + // Make sure we continue to reference the temp var + $variable = $tempVariableName; + $preCreateObject .= $variableOutput; + } + + $body .= $variable.','.\PHP_EOL; + } + + $body .= ');'.\PHP_EOL; + } + + $i = 0; + foreach ($definition->getDefinitions() as $propertyDefinition) { + if (!$propertyDefinition->isWriteable() || $propertyDefinition->isConstructorArgument()) { + continue; + } + + $variable = sprintf('$data[\'%s\']', $propertyDefinition->getNormalizedName()); + $accessor = ''; + $targetClasses = $propertyDefinition->getNonPrimitiveTypes(); + + if ([] !== $targetClasses) { + $needsChildDenormalizer = true; + $printedCanBeIterable = $propertyDefinition->isCollection() ? 'true' : 'false'; + $tempVariableName = '$setter'.$i++; + + if (\count($targetClasses) > 1) { + $accessor .= $this->generateCodeToDeserializeMultiplePossibleClasses($targetClasses, $printedCanBeIterable, $tempVariableName, $variable, $propertyDefinition->getNormalizedName(), $definition->getNamespaceAndClass()); + } else { + $accessor .= <<denormalizeChild($variable, \\{$targetClasses[0]}::class, \$format, \$context, $printedCanBeIterable); + +PHP; + } + $accessor .= ' '; + + // Make sure we continue to reference the temp var + $variable = $tempVariableName; + } + + if (null !== $method = $propertyDefinition->getSetterName()) { + $accessor .= sprintf('$output->%s(%s);', $method, $variable); + } else { + $accessor .= sprintf('$output->%s = %s;', $propertyDefinition->getPropertyName(), $variable); + } + + $body .= <<getNormalizedName()}', \$data)) { + $accessor +} + +PHP; + } + + $body .= \PHP_EOL.'return $output;'; + + $generator->addMethod(Method::create('denormalize') + ->addArgument('data', 'mixed') + ->addArgument('type', 'string') + ->addArgument('format', '?string', null) + ->addArgument('context', 'array', []) + ->setReturnType('mixed') + ->setBody($preCreateObject.\PHP_EOL.$body)); + + if ($needsChildDenormalizer) { + $this->generateDenormalizeChildMethod($generator); + } + } + + private function addNormailizeMethod(ClassGenerator $generator, ClassDefinition $definition): void + { + $body = ''; + $needsChildNormalizer = false; + foreach ($definition->getDefinitions() as $propertyDefinition) { + if (!$propertyDefinition->isReadable()) { + continue; + } + + if (null !== $method = $propertyDefinition->getGetterName()) { + $accessor = sprintf('$object->%s()', $method); + } else { + $accessor = sprintf('$object->%s', $propertyDefinition->getPropertyName()); + } + + if ($propertyDefinition->hasNoTypeDefinition() || [] !== $propertyDefinition->getNonPrimitiveTypes()) { + $needsChildNormalizer = true; + $accessor = sprintf('$this->normalizeChild(%s, $format, $context, %s)', $accessor, $propertyDefinition->isCollection() || $propertyDefinition->hasNoTypeDefinition() ? 'true' : 'false'); + } + + $body .= <<getNormalizedName()}' => $accessor, +PHP.\PHP_EOL; + } + + // public function normalize(mixed $object, string $format = null, array $context = []): array|string|int|float|bool|\ArrayObject|null; + $generator->addMethod(Method::create('normalize') + ->addArgument('object', 'mixed') + ->addArgument('format', '?string', null) + ->addArgument('context', 'array', []) + ->setReturnType('array|string|int|float|bool|\ArrayObject|null') + ->setComment(sprintf('@param %s $object', $definition->getSourceClassName())) + ->setBody('return ['.\PHP_EOL.$body.'];') + ); + + if ($needsChildNormalizer) { + $this->generateNormalizeChildMethod($generator); + } + } + + /** + * When the type-hint has many different classes, then we need to try to denormalize them + * one by one. We are happy when we dont get any exceptions thrown. + */ + private function generateCodeToDeserializeMultiplePossibleClasses(array $targetClasses, string $printedCanBeIterable, string $tempVariableName, string $variable, string $keyName, string $classNs): string + { + $printedArray = str_replace(\PHP_EOL, '', var_export($targetClasses, true)); + + return <<denormalizeChild($variable, \$class, \$format, \$context, $printedCanBeIterable); + {$tempVariableName}HasValue = true; + break; + } catch (\Throwable \$e) { + \$exceptions[] = \$e; + } +} +if (!{$tempVariableName}HasValue) { + throw new DenormalizingUnionFailedException('Failed to denormalize key "$keyName" of class "$classNs".', \$exceptions); +} + + +PHP; + } +} diff --git a/src/Symfony/Component/Serializer/Builder/PropertyDefinition.php b/src/Symfony/Component/Serializer/Builder/PropertyDefinition.php new file mode 100644 index 0000000000000..9258d8c92a316 --- /dev/null +++ b/src/Symfony/Component/Serializer/Builder/PropertyDefinition.php @@ -0,0 +1,176 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Serializer\Builder; + +/** + * All information about a specific property to be able to build a good Normalizer. + * + * @author Tobias Nyholm + * + * @internal + * + * @experimental in 7.1 + */ +class PropertyDefinition +{ + private string $propertyName; + private ?string $normalizedName = null; + private ?string $getterName = null; + private ?string $setterName = null; + private bool $isCollection = false; + private ?int $constructorArgument = null; + private mixed $constructorDefaultValue = null; + private bool $hasConstructorDefaultValue = false; + private bool $isReadable = false; + private bool $isWriteable = false; + + /** + * Ie, other classes. + * + * @var string[] + */ + private array $nonPrimitiveTypes = []; + + /** + * string, int, float, bool, null. + * + * @var string[] + */ + private array $scalarTypes = []; + + public function __construct(string $propertyName) + { + $this->propertyName = $propertyName; + } + + public function getPropertyName(): string + { + return $this->propertyName; + } + + public function isConstructorArgument(): bool + { + return null !== $this->constructorArgument; + } + + public function getConstructorArgumentOrder(): ?int + { + return $this->constructorArgument; + } + + /** + * First argument is 0, next argument is 1 etc.. + */ + public function setConstructorArgumentOrder(int $constructorArgument): void + { + $this->constructorArgument = $constructorArgument; + } + + public function setIsReadable(bool $isReadable): void + { + $this->isReadable = $isReadable; + } + + public function setIsWriteable(bool $isWriteable): void + { + $this->isWriteable = $isWriteable; + } + + public function setIsCollection(bool $isCollection): void + { + $this->isCollection = $isCollection; + } + + public function setNonPrimitiveTypes(array $nonPrimitiveTypes): void + { + $this->nonPrimitiveTypes = $nonPrimitiveTypes; + } + + public function setGetterName(?string $getterName): void + { + $this->getterName = $getterName; + } + + public function setSetterName(?string $setterName): void + { + $this->setterName = $setterName; + } + + public function isReadable(): bool + { + return $this->isReadable; + } + + public function isWriteable(): bool + { + return $this->isWriteable || $this->isConstructorArgument(); + } + + public function getGetterName(): ?string + { + return $this->getterName; + } + + public function getSetterName(): ?string + { + return $this->setterName; + } + + public function isCollection(): bool + { + return $this->isCollection; + } + + public function getNormalizedName(): string + { + return $this->normalizedName ?? $this->propertyName; + } + + /** + * @return string[] + */ + public function getNonPrimitiveTypes(): array + { + return $this->nonPrimitiveTypes; + } + + public function hasNoTypeDefinition(): bool + { + return [] === $this->nonPrimitiveTypes && [] === $this->scalarTypes; + } + + public function isOnlyScalarTypes(): ?bool + { + return [] === $this->nonPrimitiveTypes && [] !== $this->scalarTypes; + } + + public function setScalarTypes(array $scalarTypes): void + { + $this->scalarTypes = $scalarTypes; + } + + public function getConstructorDefaultValue(): mixed + { + return $this->constructorDefaultValue; + } + + public function setConstructorDefaultValue(mixed $constructorDefaultValue): void + { + $this->constructorDefaultValue = $constructorDefaultValue; + $this->hasConstructorDefaultValue = true; + } + + public function hasConstructorDefaultValue(): bool + { + return $this->hasConstructorDefaultValue; + } +} diff --git a/src/Symfony/Component/Serializer/CHANGELOG.md b/src/Symfony/Component/Serializer/CHANGELOG.md index b329cf1542334..a2b35fd36a02b 100644 --- a/src/Symfony/Component/Serializer/CHANGELOG.md +++ b/src/Symfony/Component/Serializer/CHANGELOG.md @@ -1,6 +1,11 @@ CHANGELOG ========= +7.1 +--- + + * Add attribute `Serializable` to automatically build a normalizer from a PHP class + 7.0 --- diff --git a/src/Symfony/Component/Serializer/DependencyInjection/CustomNormalizerHelper.php b/src/Symfony/Component/Serializer/DependencyInjection/CustomNormalizerHelper.php new file mode 100644 index 0000000000000..1b23803ecd719 --- /dev/null +++ b/src/Symfony/Component/Serializer/DependencyInjection/CustomNormalizerHelper.php @@ -0,0 +1,82 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Serializer\DependencyInjection; + +use Psr\Log\LoggerInterface; +use Symfony\Component\Finder\Finder; +use Symfony\Component\Finder\SplFileInfo; +use Symfony\Component\Serializer\Attribute\Serializable; +use Symfony\Component\Serializer\Builder\DefinitionExtractor; +use Symfony\Component\Serializer\Builder\NormalizerBuilder; + +/** + * Create custom normalizers and denormalizers. This class is used to glue things + * together with FrameworkBundle. + * + * @author Tobias Nyholm + * + * @internal + */ +class CustomNormalizerHelper +{ + public function __construct( + private NormalizerBuilder $builder, + private DefinitionExtractor $definitionExtractor, + private array $paths, + private string $projectDir, + private ?LoggerInterface $logger = null + ) { + } + + public function build(string $outputDir): iterable + { + foreach ($this->paths as $prefix => $inputPath) { + $path = $this->projectDir.\DIRECTORY_SEPARATOR.$inputPath; + if (!is_dir($path)) { + $this->logger?->error(sprintf('Path "%s" is not a directory', $path)); + continue; + } + + $finder = new Finder(); + $finder + ->files() + ->in($path) + ->name('/\.php$/'); + + foreach ($finder as $file) { + $classNs = $this->getClassName($prefix, $file); + if (!class_exists($classNs)) { + $this->logger?->warning(sprintf('Failed to guess class name for file "%s"', $file->getRealPath())); + continue; + } + + $reflectionClass = new \ReflectionClass($classNs); + if ([] === $reflectionClass->getAttributes(Serializable::class)) { + continue; + } + + $classDefinition = $this->definitionExtractor->getDefinition($classNs); + yield $this->builder->build($classDefinition, $outputDir); + } + } + } + + /** + * @return class-string + */ + private function getClassName(string $prefix, SplFileInfo $file): string + { + $namespace = rtrim(sprintf('%s\\%s', $prefix, $file->getRelativePath()), '\\'); + + return sprintf('%s\\%s', $namespace, $file->getFilenameWithoutExtension()); + } +} diff --git a/src/Symfony/Component/Serializer/DependencyInjection/SerializerPass.php b/src/Symfony/Component/Serializer/DependencyInjection/SerializerPass.php index 2a429054b0c7b..67d2505d82ea2 100644 --- a/src/Symfony/Component/Serializer/DependencyInjection/SerializerPass.php +++ b/src/Symfony/Component/Serializer/DependencyInjection/SerializerPass.php @@ -15,6 +15,7 @@ use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; use Symfony\Component\DependencyInjection\Compiler\PriorityTaggedServiceTrait; use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\DependencyInjection\Definition; use Symfony\Component\DependencyInjection\Exception\RuntimeException; use Symfony\Component\DependencyInjection\Reference; use Symfony\Component\Serializer\Debug\TraceableEncoder; @@ -24,8 +25,10 @@ * Adds all services with the tags "serializer.encoder" and "serializer.normalizer" as * encoders and normalizers to the "serializer" service. * - * @author Javier Lopez + * It also builds all custom Normalizer classes. + * * @author Robin Chalas + * @author Tobias Nyholm */ class SerializerPass implements CompilerPassInterface { @@ -37,6 +40,8 @@ public function process(ContainerBuilder $container): void return; } + $this->buildNormalizers($container); + if (!$normalizers = $this->findAndSortTaggedServices('serializer.normalizer', $container)) { throw new RuntimeException('You must tag at least one service as "serializer.normalizer" to use the "serializer" service.'); } @@ -71,4 +76,22 @@ public function process(ContainerBuilder $container): void $serializerDefinition->replaceArgument(0, $normalizers); $serializerDefinition->replaceArgument(1, $encoders); } + + public function buildNormalizers(ContainerBuilder $container): void + { + if (!$container->hasDefinition('serializer.custom_normalizer_helper')) { + return; + } + + $directory = $container->getParameter('kernel.build_dir').\DIRECTORY_SEPARATOR.'Symfony'.\DIRECTORY_SEPARATOR.'Serializer'.\DIRECTORY_SEPARATOR.'Normalizer'; + + /** @var CustomNormalizerHelper $builder */ + $builder = $container->get('serializer.custom_normalizer_helper'); + foreach ($builder->build($directory) as $result) { + $definition = new Definition($result->classNs); + $definition->setFile($result->filePath); + $definition->addTag('serializer.normalizer', ['priority' => 110]); + $container->setDefinition('serializer.normalizer.'.$result->className, $definition); + } + } } diff --git a/src/Symfony/Component/Serializer/Exception/DenormalizingUnionFailedException.php b/src/Symfony/Component/Serializer/Exception/DenormalizingUnionFailedException.php new file mode 100644 index 0000000000000..bd6b3de2d6bb4 --- /dev/null +++ b/src/Symfony/Component/Serializer/Exception/DenormalizingUnionFailedException.php @@ -0,0 +1,32 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Serializer\Exception; + +class DenormalizingUnionFailedException extends \RuntimeException +{ + private array $exceptions; + + public function __construct(string $message, array $exceptions) + { + $this->exceptions = $exceptions; + $first = $exceptions[array_key_first($exceptions)] ?? null; + parent::__construct($message, 0, $first); + } + + /** + * @return \Throwable[] + */ + public function getExceptions(): array + { + return $this->exceptions; + } +} diff --git a/src/Symfony/Component/Serializer/Tests/Builder/CodeGenerator/AttributeTest.php b/src/Symfony/Component/Serializer/Tests/Builder/CodeGenerator/AttributeTest.php new file mode 100644 index 0000000000000..878e9cb7231cf --- /dev/null +++ b/src/Symfony/Component/Serializer/Tests/Builder/CodeGenerator/AttributeTest.php @@ -0,0 +1,77 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Serializer\Tests\Builder\CodeGenerator; + +use App\CodeGenerator\_Attribute; +use PHPUnit\Framework\TestCase; + +class AttributeTest extends TestCase +{ + public function testNoParameters() + { + $output = _Attribute::create('Foobar')->toString(); + $this->assertEquals('#[Foobar]', $output); + + $output = _Attribute::create('Foo\\Bar')->toString(); + $this->assertEquals('#[Foo\\Bar]', $output); + } + + public function testParameters() + { + $output = _Attribute::create('Foobar') + ->addParameter(null, 7) + ->toString(); + $this->assertEquals('#[Foobar(7)]', $output); + + $output = _Attribute::create('Foobar') + ->addParameter(null, 7) + ->addParameter(null, true) + ->addParameter(null, false) + ->addParameter(null, null) + ->addParameter(null, 47.11) + ->addParameter(null, 'tobias') + ->addParameter(null, [47, 'test']) + ->toString(); + $this->assertEquals('#[Foobar(7, true, false, NULL, 47.11, "tobias", [47, "test"])]', $output); + } + + public function testNamedParameters() + { + $output = _Attribute::create('Foobar') + ->addParameter('seven', 7) + ->toString(); + $this->assertEquals('#[Foobar(seven: 7)]', $output); + + $output = _Attribute::create('Foobar') + ->addParameter('seven', 7) + ->addParameter('true', true) + ->addParameter('false', false) + ->addParameter('null', null) + ->addParameter('float', 47.11) + ->addParameter('string', 'tobias') + ->addParameter('array', [47, 'test']) + ->toString(); + $this->assertEquals('#[Foobar(seven: 7, true: true, false: false, null: NULL, float: 47.11, string: "tobias", array: [47, "test"])]', $output); + } + + public function testNested() + { + $nested = _Attribute::create('Baz') + ->addParameter('name', 'tobias'); + + $output = _Attribute::create('Foobar') + ->addParameter('seven', 7) + ->addParameter('nested', $nested) + ->toString(); + $this->assertEquals('#[Foobar(seven: 7, nested: new Baz(name: "tobias"))]', $output); + } +} diff --git a/src/Symfony/Component/Serializer/Tests/Builder/CodeGenerator/ClassGeneratorTest.php b/src/Symfony/Component/Serializer/Tests/Builder/CodeGenerator/ClassGeneratorTest.php new file mode 100644 index 0000000000000..8bedffb89ddd6 --- /dev/null +++ b/src/Symfony/Component/Serializer/Tests/Builder/CodeGenerator/ClassGeneratorTest.php @@ -0,0 +1,135 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Serializer\Tests\Builder\CodeGenerator; + +use App\CodeGenerator\_Attribute; +use App\CodeGenerator\_Method; +use App\CodeGenerator\_Property; +use App\CodeGenerator\ClassGenerator; +use PHPUnit\Framework\TestCase; + +class ClassGeneratorTest extends TestCase +{ + public function testPerson() + { + $generator = new ClassGenerator('Person', 'Test\\CodeGenerator\\Fixtures'); + + $generator->addProperty(_Property::create('name') + ->setVisibility('private') + ->setType('string') + ); + $generator->addProperty(_Property::create('age') + ->setVisibility('private') + ->setType('int') + ); + + $generator->addMethod(_Method::create('__construct') + ->addArgument('name', 'string') + ->addArgument('age', 'int') + ->setBody(<<name = \$name; +\$this->age = \$age; +PHP + )); + + $generator->addMethod(_Method::create('getName') + ->setReturnType('string') + ->setBody('return $this->name;') + ); + + $generator->addMethod(_Method::create('getAge') + ->setReturnType('int') + ->setBody('return $this->age;') + ); + + $output = $generator->toString(); + $this->assertEquals(file_get_contents(__DIR__.'/Fixtures/Person.php'), $output); + } + + /** + * Constructor argument promotion. + */ + public function testCat() + { + $generator = new ClassGenerator('Cat', 'Test\\CodeGenerator\\Fixtures'); + + $generator->addMethod(_Method::create('__construct') + ->addArgument('name', 'private string') + ->addArgument('age', 'private int') + ); + + $generator->addMethod(_Method::create('getName') + ->setReturnType('string') + ->setBody('return $this->name;') + ); + + $generator->addMethod(_Method::create('getAge') + ->setReturnType('int') + ->setBody('return $this->age;') + ); + + $output = $generator->toString(); + $this->assertEquals(file_get_contents(__DIR__.'/Fixtures/Cat.php'), $output); + } + + /** + * Try to flex all our features. + */ + public function testFull() + { + $generator = new ClassGenerator('Full', 'Test\\CodeGenerator\\Fixtures'); + $generator->addImport('Test\\CodeGenerator\\Fixtures\\Cat'); + $generator->addImport('Test\\CodeGenerator\\Fixtures\\Foo'); + $generator->addImport('Test\\CodeGenerator\\Fixtures\\Bar'); + $generator->addImport('Test\\CodeGenerator\\Fixtures\\MyAttribute'); + $generator->setExtends('Cat'); + $generator->addImplements('Foo'); + $generator->addImplements('Bar'); + + $generator->setFileComment('This is a fixture class. +We use it for verifying the code generation.'); + $generator->setClassComment(<<addProperty(_Property::create('name') + ->setVisibility('private') + ->setType('string') + ); + + $generator->addMethod(_Method::create('__construct') + ->addArgument('name', 'string', 'foobar') + ->setBody(<<name = \$name; +PHP + )); + + $generator->addMethod(_Method::create('getName') + ->setReturnType('string') + ->setBody('return $this->name;') + ->setComment(<<addAttribute(_Attribute::create('MyAttribute') + ->addParameter('name', 'test') + ); + + $output = $generator->toString(); + $this->assertEquals(file_get_contents(__DIR__.'/Fixtures/Full.php'), $output); + } +} diff --git a/src/Symfony/Component/Serializer/Tests/Builder/CodeGenerator/Fixtures/Cat.php b/src/Symfony/Component/Serializer/Tests/Builder/CodeGenerator/Fixtures/Cat.php new file mode 100644 index 0000000000000..2cfff588281d3 --- /dev/null +++ b/src/Symfony/Component/Serializer/Tests/Builder/CodeGenerator/Fixtures/Cat.php @@ -0,0 +1,29 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Serializer\Tests\Builder\CodeGenerator\Fixtures; + +class Cat +{ + public function __construct(private string $name, private int $age) + { + } + + public function getName(): string + { + return $this->name; + } + + public function getAge(): int + { + return $this->age; + } +} diff --git a/src/Symfony/Component/Serializer/Tests/Builder/CodeGenerator/Fixtures/Full.php b/src/Symfony/Component/Serializer/Tests/Builder/CodeGenerator/Fixtures/Full.php new file mode 100644 index 0000000000000..8c70e3367a60a --- /dev/null +++ b/src/Symfony/Component/Serializer/Tests/Builder/CodeGenerator/Fixtures/Full.php @@ -0,0 +1,41 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Serializer\Tests\Builder\CodeGenerator\Fixtures; + +use Test\CodeGenerator\Fixtures\Bar; +use Test\CodeGenerator\Fixtures\Cat; +use Test\CodeGenerator\Fixtures\Foo; +use Test\CodeGenerator\Fixtures\MyAttribute; + +/** + * Perfect class comment. + * + * It has some lines + */ +#[MyAttribute(name: 'test')] +class Full extends Cat implements Foo, Bar +{ + private string $name; + + public function __construct(string $name = 'foobar') + { + $this->name = $name; + } + + /** + * Returns the name of the cat. + */ + public function getName(): string + { + return $this->name; + } +} diff --git a/src/Symfony/Component/Serializer/Tests/Builder/CodeGenerator/Fixtures/Person.php b/src/Symfony/Component/Serializer/Tests/Builder/CodeGenerator/Fixtures/Person.php new file mode 100644 index 0000000000000..8118a2566f291 --- /dev/null +++ b/src/Symfony/Component/Serializer/Tests/Builder/CodeGenerator/Fixtures/Person.php @@ -0,0 +1,34 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Serializer\Tests\Builder\CodeGenerator\Fixtures; + +class Person +{ + private string $name; + private int $age; + + public function __construct(string $name, int $age) + { + $this->name = $name; + $this->age = $age; + } + + public function getName(): string + { + return $this->name; + } + + public function getAge(): int + { + return $this->age; + } +} diff --git a/src/Symfony/Component/Serializer/Tests/Builder/CodeGenerator/MethodTest.php b/src/Symfony/Component/Serializer/Tests/Builder/CodeGenerator/MethodTest.php new file mode 100644 index 0000000000000..eddf27e78cf56 --- /dev/null +++ b/src/Symfony/Component/Serializer/Tests/Builder/CodeGenerator/MethodTest.php @@ -0,0 +1,101 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Serializer\Tests\Builder\CodeGenerator; + +use App\CodeGenerator\_Method; +use PHPUnit\Framework\TestCase; + +class MethodTest extends TestCase +{ + public function testEmpty() + { + $output = _Method::create('foobar')->toString(); + $this->assertEquals('public function foobar() +{ +}', $output); + } + + public function testAbstractMethod() + { + $output = _Method::create('foobar')->setVisibility('protected')->setBody(null)->toString(); + $this->assertEquals('abstract protected function foobar();', $output); + } + + public function testVisibility() + { + $output = _Method::create('foobar')->setVisibility('private')->toString(); + $this->assertEquals('private function foobar() +{ +}', $output); + + $output = _Method::create('foobar')->setVisibility('protected')->toString(); + $this->assertEquals('protected function foobar() +{ +}', $output); + + // We dont care about logic + $output = _Method::create('foobar')->setVisibility('crazy')->toString(); + $this->assertEquals('crazy function foobar() +{ +}', $output); + } + + public function testReturnType() + { + $output = _Method::create('foobar')->setReturnType('int')->toString(); + $this->assertEquals('public function foobar(): int +{ +}', $output); + + $output = _Method::create('foobar')->setReturnType('mixed')->toString(); + $this->assertEquals('public function foobar(): mixed +{ +}', $output); + + $output = _Method::create('foobar')->setReturnType('?string')->toString(); + $this->assertEquals('public function foobar(): ?string +{ +}', $output); + } + + public function testArguments() + { + $output = _Method::create('foobar') + ->addArgument('foo') + ->addArgument('bar', null, 'test') + ->addArgument('baz', null, null) + ->toString(); + $this->assertEquals('public function foobar($foo, $bar = \'test\', $baz = NULL) +{ +}', $output); + + $output = _Method::create('foobar') + ->addArgument('foo', 'int') + ->addArgument('bar', 'string') + ->addArgument('baz', '?string', null) + ->toString(); + $this->assertEquals('public function foobar(int $foo, string $bar, ?string $baz = NULL) +{ +}', $output); + } + + public function testBody() + { + $output = _Method::create('foobar') + ->setBody('return 2;') + ->toString(' '); + $this->assertEquals('public function foobar() +{ + return 2; +}', $output); + } +} diff --git a/src/Symfony/Component/Serializer/Tests/Builder/CodeGenerator/PropertyTest.php b/src/Symfony/Component/Serializer/Tests/Builder/CodeGenerator/PropertyTest.php new file mode 100644 index 0000000000000..f98e796d04770 --- /dev/null +++ b/src/Symfony/Component/Serializer/Tests/Builder/CodeGenerator/PropertyTest.php @@ -0,0 +1,92 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Serializer\Tests\Builder\CodeGenerator; + +use App\CodeGenerator\_Property; +use PHPUnit\Framework\TestCase; + +class PropertyTest extends TestCase +{ + public function testNameOnly() + { + $output = _Property::create('foobar')->toString(); + $this->assertEquals('public $foobar;', $output); + } + + public function testVisibility() + { + $output = _Property::create('foobar')->setVisibility('private')->toString(); + $this->assertEquals('private $foobar;', $output); + + $output = _Property::create('foobar')->setVisibility('protected')->toString(); + $this->assertEquals('protected $foobar;', $output); + + // We dont care about logic + $output = _Property::create('foobar')->setVisibility('crazy')->toString(); + $this->assertEquals('crazy $foobar;', $output); + } + + public function testType() + { + $output = _Property::create('foobar')->setType('int')->toString(); + $this->assertEquals('public int $foobar;', $output); + + $output = _Property::create('foobar')->setType('mixed')->toString(); + $this->assertEquals('public mixed $foobar;', $output); + + $output = _Property::create('foobar')->setType('?string')->toString(); + $this->assertEquals('public ?string $foobar;', $output); + } + + public function testDefaultValue() + { + $output = _Property::create('foobar')->setDefaultValue('2')->toString(); + $this->assertEquals('public $foobar = \'2\';', $output); + + $output = _Property::create('foobar')->setDefaultValue(2)->toString(); + $this->assertEquals('public $foobar = 2;', $output); + + $output = _Property::create('foobar')->setDefaultValue(null)->toString(); + $this->assertEquals('public $foobar = NULL;', $output); + } + + public function testTypeAndDefaultValue() + { + $output = _Property::create('foobar')->setType('string')->setDefaultValue('2')->toString(); + $this->assertEquals('public string $foobar = \'2\';', $output); + + $output = _Property::create('foobar')->setType('int')->setDefaultValue(2)->toString(); + $this->assertEquals('public int $foobar = 2;', $output); + + $output = _Property::create('foobar')->setType('?int')->setDefaultValue(null)->toString(); + $this->assertEquals('public ?int $foobar = NULL;', $output); + + // We dont care about logic here. + $output = _Property::create('foobar')->setType('int')->setDefaultValue('test')->toString(); + $this->assertEquals('public int $foobar = \'test\';', $output); + } + + public function testComment() + { + $output = _Property::create('foobar') + ->setType('string') + ->setDefaultValue('2') + ->setComment('This is a comment') + ->toString(); + $this->assertEquals(<< + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Serializer\Tests\Builder; + +use Symfony\Component\PropertyInfo\Extractor\ConstructorArgumentTypeExtractorAggregate; +use Symfony\Component\PropertyInfo\Extractor\PhpDocExtractor; +use Symfony\Component\PropertyInfo\Extractor\ReflectionExtractor; +use Symfony\Component\PropertyInfo\PropertyInfoExtractor; +use Symfony\Component\Serializer\Builder\DefinitionExtractor; +use Symfony\Component\Serializer\Tests\Fixtures\CustomNormalizer\FullTypeHints; +use Symfony\Component\Serializer\Tests\Fixtures\CustomNormalizer\NoTypeHints; + +class FixtureHelper +{ + public static function getDefinitionExtractor(): DefinitionExtractor + { + $reflectionExtractor = new ReflectionExtractor(); + $constructorArgumentExtractor = new ConstructorArgumentTypeExtractorAggregate([ + $reflectionExtractor, + new PhpDocExtractor(), + ]); + + return new DefinitionExtractor( + propertyInfo: self::getPropertyInfoExtractor(), + propertyReadInfoExtractor: $reflectionExtractor, + propertyWriteInfoExtractor: $reflectionExtractor, + constructorArgumentTypeExtractor: $constructorArgumentExtractor, + ); + } + + public static function getFixturesAndResultFiles(): iterable + { + $rootDir = \dirname(__DIR__).'/Fixtures/CustomNormalizer'; + $data = [ + NoTypeHints\PublicProperties::class => $rootDir.'/NoTypeHints/ExpectedNormalizer/PublicProperties.php', + NoTypeHints\ConstructorInjection::class => $rootDir.'/NoTypeHints/ExpectedNormalizer/ConstructorInjection.php', + NoTypeHints\SetterInjection::class => $rootDir.'/NoTypeHints/ExpectedNormalizer/SetterInjection.php', + NoTypeHints\ConstructorAndSetterInjection::class => $rootDir.'/NoTypeHints/ExpectedNormalizer/ConstructorAndSetterInjection.php', + NoTypeHints\InheritanceChild::class => $rootDir.'/NoTypeHints/ExpectedNormalizer/InheritanceChild.php', + + FullTypeHints\PublicProperties::class => $rootDir.'/FullTypeHints/ExpectedNormalizer/PublicProperties.php', + FullTypeHints\ConstructorInjection::class => $rootDir.'/FullTypeHints/ExpectedNormalizer/ConstructorInjection.php', + FullTypeHints\SetterInjection::class => $rootDir.'/FullTypeHints/ExpectedNormalizer/SetterInjection.php', + FullTypeHints\InheritanceChild::class => $rootDir.'/FullTypeHints/ExpectedNormalizer/InheritanceChild.php', + FullTypeHints\PrivateConstructor::class => $rootDir.'/FullTypeHints/ExpectedNormalizer/PrivateConstructor.php', + FullTypeHints\ConstructorWithDefaultValue::class => $rootDir.'/FullTypeHints/ExpectedNormalizer/ConstructorWithDefaultValue.php', + FullTypeHints\ComplexTypesConstructor::class => $rootDir.'/FullTypeHints/ExpectedNormalizer/ComplexTypesConstructor.php', + FullTypeHints\ComplexTypesPublicProperties::class => $rootDir.'/FullTypeHints/ExpectedNormalizer/ComplexTypesPublicProperties.php', + FullTypeHints\ComplexTypesSetter::class => $rootDir.'/FullTypeHints/ExpectedNormalizer/ComplexTypesSetter.php', + FullTypeHints\ExtraSetter::class => $rootDir.'/FullTypeHints/ExpectedNormalizer/ExtraSetter.php', + FullTypeHints\NonReadableProperty::class => $rootDir.'/FullTypeHints/ExpectedNormalizer/NonReadableProperty.php', + ]; + + foreach ($data as $class => $normalizerFile) { + yield $class => [$class, $normalizerFile]; + } + } + + private static function getPropertyInfoExtractor(): PropertyInfoExtractor + { + // a full list of extractors is shown further below + $phpDocExtractor = new PhpDocExtractor(); + $reflectionExtractor = new ReflectionExtractor(); + + // list of PropertyListExtractorInterface (any iterable) + $listExtractors = [$reflectionExtractor]; + + // list of PropertyTypeExtractorInterface (any iterable) + $typeExtractors = [$phpDocExtractor, $reflectionExtractor]; + + // list of PropertyDescriptionExtractorInterface (any iterable) + $descriptionExtractors = [$phpDocExtractor]; + + // list of PropertyAccessExtractorInterface (any iterable) + $accessExtractors = [$reflectionExtractor]; + + // list of PropertyInitializableExtractorInterface (any iterable) + $propertyInitializableExtractors = [$reflectionExtractor]; + + return new PropertyInfoExtractor( + $listExtractors, + $typeExtractors, + $descriptionExtractors, + $accessExtractors, + $propertyInitializableExtractors + ); + } +} diff --git a/src/Symfony/Component/Serializer/Tests/Builder/NormalizerBuilderFixtureTest.php b/src/Symfony/Component/Serializer/Tests/Builder/NormalizerBuilderFixtureTest.php new file mode 100644 index 0000000000000..3154d9103b310 --- /dev/null +++ b/src/Symfony/Component/Serializer/Tests/Builder/NormalizerBuilderFixtureTest.php @@ -0,0 +1,51 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Serializer\Tests\Builder; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\Serializer\Builder\DefinitionExtractor; +use Symfony\Component\Serializer\Builder\NormalizerBuilder; + +class NormalizerBuilderFixtureTest extends TestCase +{ + private static NormalizerBuilder $builder; + private static DefinitionExtractor $definitionExtractor; + private static string $outputDir; + + public static function setUpBeforeClass(): void + { + self::$definitionExtractor = FixtureHelper::getDefinitionExtractor(); + self::$outputDir = \dirname(__DIR__).'/_output/SerializerBuilderFixtureTest'; + self::$builder = new NormalizerBuilder(); + + parent::setUpBeforeClass(); + } + + /** + * @dataProvider fixtureClassGenerator + */ + public function testBuildFixtures(string $inputClass, string $expectedOutputFile) + { + $def = self::$definitionExtractor->getDefinition($inputClass); + $result = self::$builder->build($def, self::$outputDir); + $result->loadClass(); + $this->assertTrue(class_exists($result->classNs)); + $this->assertFileEquals($expectedOutputFile, $result->filePath); + } + + public static function fixtureClassGenerator(): iterable + { + foreach (FixtureHelper::getFixturesAndResultFiles() as $class => $normalizerFile) { + yield $class => [$class, $normalizerFile]; + } + } +} diff --git a/src/Symfony/Component/Serializer/Tests/Builder/generateUpdatedFixtures.php b/src/Symfony/Component/Serializer/Tests/Builder/generateUpdatedFixtures.php new file mode 100644 index 0000000000000..dd4008c113a3a --- /dev/null +++ b/src/Symfony/Component/Serializer/Tests/Builder/generateUpdatedFixtures.php @@ -0,0 +1,48 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +function includeIfExists(string $file): bool +{ + return file_exists($file) && include $file; +} + +if ( + !includeIfExists(__DIR__.'/../../../../autoload.php') + && !includeIfExists(__DIR__.'/../../vendor/autoload.php') + && !includeIfExists(__DIR__.'/../../../../../../vendor/autoload.php') +) { + fwrite(\STDERR, 'Install dependencies using Composer.'.\PHP_EOL); + exit(1); +} + +use Symfony\Component\Serializer\Builder\NormalizerBuilder; +use Symfony\Component\Serializer\Tests\Builder\FixtureHelper; + +$outputDir = sys_get_temp_dir(); +$definitionExtractor = FixtureHelper::getDefinitionExtractor(); +$builder = new NormalizerBuilder(); + +echo \PHP_EOL; +$i = 0; +foreach (FixtureHelper::getFixturesAndResultFiles() as [$class, $outputFile]) { + $definition = $definitionExtractor->getDefinition($class); + $result = $builder->build($definition, $outputDir); + $result->loadClass(); + file_put_contents($outputFile, file_get_contents($result->filePath)); + echo '.'; + if (0 === ++$i % 20) { + echo \PHP_EOL; + } +} + +echo \PHP_EOL.\PHP_EOL; +echo 'Done generating fixtures.'; +echo \PHP_EOL; diff --git a/src/Symfony/Component/Serializer/Tests/Fixtures/CustomNormalizer/FullTypeHints/ComplexTypesConstructor.php b/src/Symfony/Component/Serializer/Tests/Fixtures/CustomNormalizer/FullTypeHints/ComplexTypesConstructor.php new file mode 100644 index 0000000000000..1d8152bc6b55c --- /dev/null +++ b/src/Symfony/Component/Serializer/Tests/Fixtures/CustomNormalizer/FullTypeHints/ComplexTypesConstructor.php @@ -0,0 +1,82 @@ +simple = $simple; + $this->array = $array; + $this->union = $union; + $this->nested = $nested; + $this->unionArray = $unionArray; + $this->simpleArray = $simpleArray; + } + + public function getSimple(): DummyObject + { + return $this->simple; + } + + + public function getArray(): array + { + return $this->array; + } + + + public function getUnion(): SmartObject|DummyObject + { + return $this->union; + } + + + public function getNested(): DummyObject&SmartObject + { + return $this->nested; + } + + public function getUnionArray(): array + { + return $this->unionArray; + } + + /** + * @return array + */ + public function getSimpleArray(): array + { + return $this->simpleArray; + } +} diff --git a/src/Symfony/Component/Serializer/Tests/Fixtures/CustomNormalizer/FullTypeHints/ComplexTypesPublicProperties.php b/src/Symfony/Component/Serializer/Tests/Fixtures/CustomNormalizer/FullTypeHints/ComplexTypesPublicProperties.php new file mode 100644 index 0000000000000..bdfa2ead4a93e --- /dev/null +++ b/src/Symfony/Component/Serializer/Tests/Fixtures/CustomNormalizer/FullTypeHints/ComplexTypesPublicProperties.php @@ -0,0 +1,22 @@ +simple; + } + + + public function getArray(): array + { + return $this->array; + } + + + public function getUnion(): SmartObject|DummyObject + { + return $this->union; + } + + + public function getNested(): DummyObject&SmartObject + { + return $this->nested; + } + + public function getUnionArray(): array + { + return $this->unionArray; + } + + public function setSimple(DummyObject $simple): void + { + $this->simple = $simple; + } + + public function setArray(array $array): void + { + $this->array = $array; + } + + public function setUnion(SmartObject|DummyObject $union): void + { + $this->union = $union; + } + + public function setNested(DummyObject&SmartObject $nested): void + { + $this->nested = $nested; + } + + public function setUnionArray(array $unionArray): void + { + $this->unionArray = $unionArray; + } +} diff --git a/src/Symfony/Component/Serializer/Tests/Fixtures/CustomNormalizer/FullTypeHints/ConstructorInjection.php b/src/Symfony/Component/Serializer/Tests/Fixtures/CustomNormalizer/FullTypeHints/ConstructorInjection.php new file mode 100644 index 0000000000000..dd4b3679cbdee --- /dev/null +++ b/src/Symfony/Component/Serializer/Tests/Fixtures/CustomNormalizer/FullTypeHints/ConstructorInjection.php @@ -0,0 +1,77 @@ +name = $name; + $this->age = $age; + $this->height = $height; + $this->handsome = $handsome; + $this->nameOfFriends = $nameOfFriends; + $this->picture = $picture; + $this->pet = $pet; + $this->relation = $relation; + } + + public function getName(): string + { + return $this->name; + } + + public function getAge(): int + { + return $this->age; + } + + public function getHeight(): float + { + return $this->height; + } + + public function isHandsome(): bool + { + return $this->handsome; + } + + public function getNameOfFriends(): array + { + return $this->nameOfFriends; + } + + public function getPicture() + { + return $this->picture; + } + + public function getPet(): ?string + { + return $this->pet; + } + + public function getRelation(): DummyObject + { + return $this->relation; + } + + public function getNotSet(): string + { + return $this->notSet; + } + + +} diff --git a/src/Symfony/Component/Serializer/Tests/Fixtures/CustomNormalizer/FullTypeHints/ConstructorWithDefaultValue.php b/src/Symfony/Component/Serializer/Tests/Fixtures/CustomNormalizer/FullTypeHints/ConstructorWithDefaultValue.php new file mode 100644 index 0000000000000..2ccc836e6c3ca --- /dev/null +++ b/src/Symfony/Component/Serializer/Tests/Fixtures/CustomNormalizer/FullTypeHints/ConstructorWithDefaultValue.php @@ -0,0 +1,25 @@ +foo = $foo; + $this->union = $union; + } + + public function getFoo(): int + { + return $this->foo; + } + + public function getUnion(): SmartObject|DummyObject|null + { + return $this->union; + } +} diff --git a/src/Symfony/Component/Serializer/Tests/Fixtures/CustomNormalizer/FullTypeHints/DummyObject.php b/src/Symfony/Component/Serializer/Tests/Fixtures/CustomNormalizer/FullTypeHints/DummyObject.php new file mode 100644 index 0000000000000..3a4d0a1d7c6d2 --- /dev/null +++ b/src/Symfony/Component/Serializer/Tests/Fixtures/CustomNormalizer/FullTypeHints/DummyObject.php @@ -0,0 +1,8 @@ + true]; + } + + public function supportsNormalization(mixed $data, ?string $format = NULL, array $context = []): bool + { + return $data instanceof ComplexTypesConstructor; + } + + public function supportsDenormalization(mixed $data, string $type, ?string $format = NULL, array $context = []): bool + { + return $type === ComplexTypesConstructor::class; + } + + /** + * @param ComplexTypesConstructor $object + */ + public function normalize(mixed $object, ?string $format = NULL, array $context = []): array|string|int|float|bool|\ArrayObject|null + { + return [ + 'simple' => $this->normalizeChild($object->getSimple(), $format, $context, false), + 'simpleArray' => $object->getSimpleArray(), + 'array' => $this->normalizeChild($object->getArray(), $format, $context, true), + 'union' => $this->normalizeChild($object->getUnion(), $format, $context, false), + 'nested' => $this->normalizeChild($object->getNested(), $format, $context, false), + 'unionArray' => $this->normalizeChild($object->getUnionArray(), $format, $context, true), + ]; + } + + public function setNormalizer(NormalizerInterface $normalizer): void + { + $this->normalizer = $normalizer; + } + + private function normalizeChild(mixed $object, ?string $format, array $context, bool $canBeIterable): mixed + { + if (is_scalar($object) || null === $object) { + return $object; + } + + if ($canBeIterable === true && is_iterable($object)) { + return array_map(fn($item) => $this->normalizeChild($item, $format, $context, true), $object); + } + + return $this->normalizer->normalize($object, $format, $context); + + } + + public function denormalize(mixed $data, string $type, ?string $format = NULL, array $context = []): mixed + { + $argument0 = $this->denormalizeChild($data['simple'], \Symfony\Component\Serializer\Tests\Fixtures\CustomNormalizer\FullTypeHints\DummyObject::class, $format, $context, false); + $argument2 = $this->denormalizeChild($data['array'], \Symfony\Component\Serializer\Tests\Fixtures\CustomNormalizer\FullTypeHints\DummyObject::class, $format, $context, true); + $exceptions = []; + $argument3HasValue = false; + foreach (array ( 0 => 'Symfony\\Component\\Serializer\\Tests\\Fixtures\\CustomNormalizer\\FullTypeHints\\SmartObject', 1 => 'Symfony\\Component\\Serializer\\Tests\\Fixtures\\CustomNormalizer\\FullTypeHints\\DummyObject',) as $class) { + try { + $argument3 = $this->denormalizeChild($data['union'], $class, $format, $context, false); + $argument3HasValue = true; + break; + } catch (\Throwable $e) { + $exceptions[] = $e; + } + } + if (!$argument3HasValue) { + throw new DenormalizingUnionFailedException('Failed to denormalize key "union" of class "Symfony\Component\Serializer\Tests\Fixtures\CustomNormalizer\FullTypeHints\ComplexTypesConstructor".', $exceptions); + } + + $exceptions = []; + $argument4HasValue = false; + foreach (array ( 0 => 'Symfony\\Component\\Serializer\\Tests\\Fixtures\\CustomNormalizer\\FullTypeHints\\DummyObject', 1 => 'Symfony\\Component\\Serializer\\Tests\\Fixtures\\CustomNormalizer\\FullTypeHints\\SmartObject',) as $class) { + try { + $argument4 = $this->denormalizeChild($data['nested'], $class, $format, $context, false); + $argument4HasValue = true; + break; + } catch (\Throwable $e) { + $exceptions[] = $e; + } + } + if (!$argument4HasValue) { + throw new DenormalizingUnionFailedException('Failed to denormalize key "nested" of class "Symfony\Component\Serializer\Tests\Fixtures\CustomNormalizer\FullTypeHints\ComplexTypesConstructor".', $exceptions); + } + + $exceptions = []; + $argument5HasValue = false; + foreach (array ( 0 => 'Symfony\\Component\\Serializer\\Tests\\Fixtures\\CustomNormalizer\\FullTypeHints\\DummyObject', 1 => 'Symfony\\Component\\Serializer\\Tests\\Fixtures\\CustomNormalizer\\FullTypeHints\\SmartObject',) as $class) { + try { + $argument5 = $this->denormalizeChild($data['unionArray'], $class, $format, $context, true); + $argument5HasValue = true; + break; + } catch (\Throwable $e) { + $exceptions[] = $e; + } + } + if (!$argument5HasValue) { + throw new DenormalizingUnionFailedException('Failed to denormalize key "unionArray" of class "Symfony\Component\Serializer\Tests\Fixtures\CustomNormalizer\FullTypeHints\ComplexTypesConstructor".', $exceptions); + } + + + $output = new ComplexTypesConstructor( + $argument0, + $data['simpleArray'], + $argument2, + $argument3, + $argument4, + $argument5, + ); + + return $output; + } + + public function setDenormalizer(DenormalizerInterface $denormalizer): void + { + $this->denormalizer = $denormalizer; + } + + private function denormalizeChild(mixed $data, string $type, ?string $format, array $context, bool $canBeIterable): mixed + { + if (is_scalar($data) || null === $data) { + return $data; + } + + if ($canBeIterable === true && is_iterable($data)) { + return array_map(fn($item) => $this->denormalizeChild($item, $type, $format, $context, true), $data); + } + + return $this->denormalizer->denormalize($data, $type, $format, $context); + + } + +} diff --git a/src/Symfony/Component/Serializer/Tests/Fixtures/CustomNormalizer/FullTypeHints/ExpectedNormalizer/ComplexTypesPublicProperties.php b/src/Symfony/Component/Serializer/Tests/Fixtures/CustomNormalizer/FullTypeHints/ExpectedNormalizer/ComplexTypesPublicProperties.php new file mode 100644 index 0000000000000..9ce85cc10fe93 --- /dev/null +++ b/src/Symfony/Component/Serializer/Tests/Fixtures/CustomNormalizer/FullTypeHints/ExpectedNormalizer/ComplexTypesPublicProperties.php @@ -0,0 +1,154 @@ + true]; + } + + public function supportsNormalization(mixed $data, ?string $format = NULL, array $context = []): bool + { + return $data instanceof ComplexTypesPublicProperties; + } + + public function supportsDenormalization(mixed $data, string $type, ?string $format = NULL, array $context = []): bool + { + return $type === ComplexTypesPublicProperties::class; + } + + /** + * @param ComplexTypesPublicProperties $object + */ + public function normalize(mixed $object, ?string $format = NULL, array $context = []): array|string|int|float|bool|\ArrayObject|null + { + return [ + 'simple' => $this->normalizeChild($object->simple, $format, $context, false), + 'array' => $this->normalizeChild($object->array, $format, $context, true), + 'union' => $this->normalizeChild($object->union, $format, $context, false), + 'nested' => $this->normalizeChild($object->nested, $format, $context, false), + 'unionArray' => $this->normalizeChild($object->unionArray, $format, $context, true), + ]; + } + + public function setNormalizer(NormalizerInterface $normalizer): void + { + $this->normalizer = $normalizer; + } + + private function normalizeChild(mixed $object, ?string $format, array $context, bool $canBeIterable): mixed + { + if (is_scalar($object) || null === $object) { + return $object; + } + + if ($canBeIterable === true && is_iterable($object)) { + return array_map(fn($item) => $this->normalizeChild($item, $format, $context, true), $object); + } + + return $this->normalizer->normalize($object, $format, $context); + + } + + public function denormalize(mixed $data, string $type, ?string $format = NULL, array $context = []): mixed + { + + $output = new ComplexTypesPublicProperties(); + if (array_key_exists('simple', $data)) { + $setter0 = $this->denormalizeChild($data['simple'], \Symfony\Component\Serializer\Tests\Fixtures\CustomNormalizer\FullTypeHints\DummyObject::class, $format, $context, false); + $output->simple = $setter0; + } + if (array_key_exists('array', $data)) { + $setter1 = $this->denormalizeChild($data['array'], \Symfony\Component\Serializer\Tests\Fixtures\CustomNormalizer\FullTypeHints\DummyObject::class, $format, $context, true); + $output->array = $setter1; + } + if (array_key_exists('union', $data)) { + $exceptions = []; + $setter2HasValue = false; + foreach (array ( 0 => 'Symfony\\Component\\Serializer\\Tests\\Fixtures\\CustomNormalizer\\FullTypeHints\\DummyObject', 1 => 'Symfony\\Component\\Serializer\\Tests\\Fixtures\\CustomNormalizer\\FullTypeHints\\SmartObject',) as $class) { + try { + $setter2 = $this->denormalizeChild($data['union'], $class, $format, $context, false); + $setter2HasValue = true; + break; + } catch (\Throwable $e) { + $exceptions[] = $e; + } + } + if (!$setter2HasValue) { + throw new DenormalizingUnionFailedException('Failed to denormalize key "union" of class "Symfony\Component\Serializer\Tests\Fixtures\CustomNormalizer\FullTypeHints\ComplexTypesPublicProperties".', $exceptions); + } + + $output->union = $setter2; + } + if (array_key_exists('nested', $data)) { + $exceptions = []; + $setter3HasValue = false; + foreach (array ( 0 => 'Symfony\\Component\\Serializer\\Tests\\Fixtures\\CustomNormalizer\\FullTypeHints\\DummyObject', 1 => 'Symfony\\Component\\Serializer\\Tests\\Fixtures\\CustomNormalizer\\FullTypeHints\\SmartObject',) as $class) { + try { + $setter3 = $this->denormalizeChild($data['nested'], $class, $format, $context, false); + $setter3HasValue = true; + break; + } catch (\Throwable $e) { + $exceptions[] = $e; + } + } + if (!$setter3HasValue) { + throw new DenormalizingUnionFailedException('Failed to denormalize key "nested" of class "Symfony\Component\Serializer\Tests\Fixtures\CustomNormalizer\FullTypeHints\ComplexTypesPublicProperties".', $exceptions); + } + + $output->nested = $setter3; + } + if (array_key_exists('unionArray', $data)) { + $exceptions = []; + $setter4HasValue = false; + foreach (array ( 0 => 'Symfony\\Component\\Serializer\\Tests\\Fixtures\\CustomNormalizer\\FullTypeHints\\DummyObject', 1 => 'Symfony\\Component\\Serializer\\Tests\\Fixtures\\CustomNormalizer\\FullTypeHints\\SmartObject',) as $class) { + try { + $setter4 = $this->denormalizeChild($data['unionArray'], $class, $format, $context, true); + $setter4HasValue = true; + break; + } catch (\Throwable $e) { + $exceptions[] = $e; + } + } + if (!$setter4HasValue) { + throw new DenormalizingUnionFailedException('Failed to denormalize key "unionArray" of class "Symfony\Component\Serializer\Tests\Fixtures\CustomNormalizer\FullTypeHints\ComplexTypesPublicProperties".', $exceptions); + } + + $output->unionArray = $setter4; + } + + return $output; + } + + public function setDenormalizer(DenormalizerInterface $denormalizer): void + { + $this->denormalizer = $denormalizer; + } + + private function denormalizeChild(mixed $data, string $type, ?string $format, array $context, bool $canBeIterable): mixed + { + if (is_scalar($data) || null === $data) { + return $data; + } + + if ($canBeIterable === true && is_iterable($data)) { + return array_map(fn($item) => $this->denormalizeChild($item, $type, $format, $context, true), $data); + } + + return $this->denormalizer->denormalize($data, $type, $format, $context); + + } + +} diff --git a/src/Symfony/Component/Serializer/Tests/Fixtures/CustomNormalizer/FullTypeHints/ExpectedNormalizer/ComplexTypesSetter.php b/src/Symfony/Component/Serializer/Tests/Fixtures/CustomNormalizer/FullTypeHints/ExpectedNormalizer/ComplexTypesSetter.php new file mode 100644 index 0000000000000..89e10a022b654 --- /dev/null +++ b/src/Symfony/Component/Serializer/Tests/Fixtures/CustomNormalizer/FullTypeHints/ExpectedNormalizer/ComplexTypesSetter.php @@ -0,0 +1,154 @@ + true]; + } + + public function supportsNormalization(mixed $data, ?string $format = NULL, array $context = []): bool + { + return $data instanceof ComplexTypesSetter; + } + + public function supportsDenormalization(mixed $data, string $type, ?string $format = NULL, array $context = []): bool + { + return $type === ComplexTypesSetter::class; + } + + /** + * @param ComplexTypesSetter $object + */ + public function normalize(mixed $object, ?string $format = NULL, array $context = []): array|string|int|float|bool|\ArrayObject|null + { + return [ + 'simple' => $this->normalizeChild($object->getSimple(), $format, $context, false), + 'array' => $this->normalizeChild($object->getArray(), $format, $context, true), + 'union' => $this->normalizeChild($object->getUnion(), $format, $context, false), + 'nested' => $this->normalizeChild($object->getNested(), $format, $context, false), + 'unionArray' => $this->normalizeChild($object->getUnionArray(), $format, $context, true), + ]; + } + + public function setNormalizer(NormalizerInterface $normalizer): void + { + $this->normalizer = $normalizer; + } + + private function normalizeChild(mixed $object, ?string $format, array $context, bool $canBeIterable): mixed + { + if (is_scalar($object) || null === $object) { + return $object; + } + + if ($canBeIterable === true && is_iterable($object)) { + return array_map(fn($item) => $this->normalizeChild($item, $format, $context, true), $object); + } + + return $this->normalizer->normalize($object, $format, $context); + + } + + public function denormalize(mixed $data, string $type, ?string $format = NULL, array $context = []): mixed + { + + $output = new ComplexTypesSetter(); + if (array_key_exists('simple', $data)) { + $setter0 = $this->denormalizeChild($data['simple'], \Symfony\Component\Serializer\Tests\Fixtures\CustomNormalizer\FullTypeHints\DummyObject::class, $format, $context, false); + $output->setSimple($setter0); + } + if (array_key_exists('array', $data)) { + $setter1 = $this->denormalizeChild($data['array'], \Symfony\Component\Serializer\Tests\Fixtures\CustomNormalizer\FullTypeHints\DummyObject::class, $format, $context, true); + $output->setArray($setter1); + } + if (array_key_exists('union', $data)) { + $exceptions = []; + $setter2HasValue = false; + foreach (array ( 0 => 'Symfony\\Component\\Serializer\\Tests\\Fixtures\\CustomNormalizer\\FullTypeHints\\SmartObject', 1 => 'Symfony\\Component\\Serializer\\Tests\\Fixtures\\CustomNormalizer\\FullTypeHints\\DummyObject',) as $class) { + try { + $setter2 = $this->denormalizeChild($data['union'], $class, $format, $context, false); + $setter2HasValue = true; + break; + } catch (\Throwable $e) { + $exceptions[] = $e; + } + } + if (!$setter2HasValue) { + throw new DenormalizingUnionFailedException('Failed to denormalize key "union" of class "Symfony\Component\Serializer\Tests\Fixtures\CustomNormalizer\FullTypeHints\ComplexTypesSetter".', $exceptions); + } + + $output->setUnion($setter2); + } + if (array_key_exists('nested', $data)) { + $exceptions = []; + $setter3HasValue = false; + foreach (array ( 0 => 'Symfony\\Component\\Serializer\\Tests\\Fixtures\\CustomNormalizer\\FullTypeHints\\DummyObject', 1 => 'Symfony\\Component\\Serializer\\Tests\\Fixtures\\CustomNormalizer\\FullTypeHints\\SmartObject',) as $class) { + try { + $setter3 = $this->denormalizeChild($data['nested'], $class, $format, $context, false); + $setter3HasValue = true; + break; + } catch (\Throwable $e) { + $exceptions[] = $e; + } + } + if (!$setter3HasValue) { + throw new DenormalizingUnionFailedException('Failed to denormalize key "nested" of class "Symfony\Component\Serializer\Tests\Fixtures\CustomNormalizer\FullTypeHints\ComplexTypesSetter".', $exceptions); + } + + $output->setNested($setter3); + } + if (array_key_exists('unionArray', $data)) { + $exceptions = []; + $setter4HasValue = false; + foreach (array ( 0 => 'Symfony\\Component\\Serializer\\Tests\\Fixtures\\CustomNormalizer\\FullTypeHints\\DummyObject', 1 => 'Symfony\\Component\\Serializer\\Tests\\Fixtures\\CustomNormalizer\\FullTypeHints\\SmartObject',) as $class) { + try { + $setter4 = $this->denormalizeChild($data['unionArray'], $class, $format, $context, true); + $setter4HasValue = true; + break; + } catch (\Throwable $e) { + $exceptions[] = $e; + } + } + if (!$setter4HasValue) { + throw new DenormalizingUnionFailedException('Failed to denormalize key "unionArray" of class "Symfony\Component\Serializer\Tests\Fixtures\CustomNormalizer\FullTypeHints\ComplexTypesSetter".', $exceptions); + } + + $output->setUnionArray($setter4); + } + + return $output; + } + + public function setDenormalizer(DenormalizerInterface $denormalizer): void + { + $this->denormalizer = $denormalizer; + } + + private function denormalizeChild(mixed $data, string $type, ?string $format, array $context, bool $canBeIterable): mixed + { + if (is_scalar($data) || null === $data) { + return $data; + } + + if ($canBeIterable === true && is_iterable($data)) { + return array_map(fn($item) => $this->denormalizeChild($item, $type, $format, $context, true), $data); + } + + return $this->denormalizer->denormalize($data, $type, $format, $context); + + } + +} diff --git a/src/Symfony/Component/Serializer/Tests/Fixtures/CustomNormalizer/FullTypeHints/ExpectedNormalizer/ConstructorInjection.php b/src/Symfony/Component/Serializer/Tests/Fixtures/CustomNormalizer/FullTypeHints/ExpectedNormalizer/ConstructorInjection.php new file mode 100644 index 0000000000000..0c007deb82d0f --- /dev/null +++ b/src/Symfony/Component/Serializer/Tests/Fixtures/CustomNormalizer/FullTypeHints/ExpectedNormalizer/ConstructorInjection.php @@ -0,0 +1,106 @@ + true]; + } + + public function supportsNormalization(mixed $data, ?string $format = NULL, array $context = []): bool + { + return $data instanceof ConstructorInjection; + } + + public function supportsDenormalization(mixed $data, string $type, ?string $format = NULL, array $context = []): bool + { + return $type === ConstructorInjection::class; + } + + /** + * @param ConstructorInjection $object + */ + public function normalize(mixed $object, ?string $format = NULL, array $context = []): array|string|int|float|bool|\ArrayObject|null + { + return [ + 'name' => $object->getName(), + 'age' => $object->getAge(), + 'height' => $object->getHeight(), + 'handsome' => $object->isHandsome(), + 'nameOfFriends' => $this->normalizeChild($object->getNameOfFriends(), $format, $context, true), + 'picture' => $this->normalizeChild($object->getPicture(), $format, $context, true), + 'pet' => $object->getPet(), + 'relation' => $this->normalizeChild($object->getRelation(), $format, $context, false), + 'notSet' => $object->getNotSet(), + ]; + } + + public function setNormalizer(NormalizerInterface $normalizer): void + { + $this->normalizer = $normalizer; + } + + private function normalizeChild(mixed $object, ?string $format, array $context, bool $canBeIterable): mixed + { + if (is_scalar($object) || null === $object) { + return $object; + } + + if ($canBeIterable === true && is_iterable($object)) { + return array_map(fn($item) => $this->normalizeChild($item, $format, $context, true), $object); + } + + return $this->normalizer->normalize($object, $format, $context); + + } + + public function denormalize(mixed $data, string $type, ?string $format = NULL, array $context = []): mixed + { + $argument7 = $this->denormalizeChild($data['relation'], \Symfony\Component\Serializer\Tests\Fixtures\CustomNormalizer\FullTypeHints\DummyObject::class, $format, $context, false); + + $output = new ConstructorInjection( + $data['name'], + $data['age'], + $data['height'], + $data['handsome'], + $data['nameOfFriends'], + $data['picture'], + $data['pet'], + $argument7, + ); + + return $output; + } + + public function setDenormalizer(DenormalizerInterface $denormalizer): void + { + $this->denormalizer = $denormalizer; + } + + private function denormalizeChild(mixed $data, string $type, ?string $format, array $context, bool $canBeIterable): mixed + { + if (is_scalar($data) || null === $data) { + return $data; + } + + if ($canBeIterable === true && is_iterable($data)) { + return array_map(fn($item) => $this->denormalizeChild($item, $type, $format, $context, true), $data); + } + + return $this->denormalizer->denormalize($data, $type, $format, $context); + + } + +} diff --git a/src/Symfony/Component/Serializer/Tests/Fixtures/CustomNormalizer/FullTypeHints/ExpectedNormalizer/ConstructorWithDefaultValue.php b/src/Symfony/Component/Serializer/Tests/Fixtures/CustomNormalizer/FullTypeHints/ExpectedNormalizer/ConstructorWithDefaultValue.php new file mode 100644 index 0000000000000..23e2e66792547 --- /dev/null +++ b/src/Symfony/Component/Serializer/Tests/Fixtures/CustomNormalizer/FullTypeHints/ExpectedNormalizer/ConstructorWithDefaultValue.php @@ -0,0 +1,113 @@ + true]; + } + + public function supportsNormalization(mixed $data, ?string $format = NULL, array $context = []): bool + { + return $data instanceof ConstructorWithDefaultValue; + } + + public function supportsDenormalization(mixed $data, string $type, ?string $format = NULL, array $context = []): bool + { + return $type === ConstructorWithDefaultValue::class; + } + + /** + * @param ConstructorWithDefaultValue $object + */ + public function normalize(mixed $object, ?string $format = NULL, array $context = []): array|string|int|float|bool|\ArrayObject|null + { + return [ + 'foo' => $object->getFoo(), + 'union' => $this->normalizeChild($object->getUnion(), $format, $context, false), + ]; + } + + public function setNormalizer(NormalizerInterface $normalizer): void + { + $this->normalizer = $normalizer; + } + + private function normalizeChild(mixed $object, ?string $format, array $context, bool $canBeIterable): mixed + { + if (is_scalar($object) || null === $object) { + return $object; + } + + if ($canBeIterable === true && is_iterable($object)) { + return array_map(fn($item) => $this->normalizeChild($item, $format, $context, true), $object); + } + + return $this->normalizer->normalize($object, $format, $context); + + } + + public function denormalize(mixed $data, string $type, ?string $format = NULL, array $context = []): mixed + { + if (!array_key_exists('union', $data)) { + $argument1 = NULL; + } else { + $exceptions = []; + $argument1HasValue = false; + foreach (array ( 0 => 'Symfony\\Component\\Serializer\\Tests\\Fixtures\\CustomNormalizer\\FullTypeHints\\SmartObject', 1 => 'Symfony\\Component\\Serializer\\Tests\\Fixtures\\CustomNormalizer\\FullTypeHints\\DummyObject',) as $class) { + try { + $argument1 = $this->denormalizeChild($data['union'], $class, $format, $context, false); + $argument1HasValue = true; + break; + } catch (\Throwable $e) { + $exceptions[] = $e; + } + } + if (!$argument1HasValue) { + throw new DenormalizingUnionFailedException('Failed to denormalize key "union" of class "Symfony\Component\Serializer\Tests\Fixtures\CustomNormalizer\FullTypeHints\ConstructorWithDefaultValue".', $exceptions); + } + + + } + $output = new ConstructorWithDefaultValue( + $data['foo'] ?? 4711, + $argument1, + $data['x'] ?? \Symfony\Component\Serializer\Tests\Fixtures\CustomNormalizer\FullTypeHints\SmartObject::__set_state(array( + )), + ); + + return $output; + } + + public function setDenormalizer(DenormalizerInterface $denormalizer): void + { + $this->denormalizer = $denormalizer; + } + + private function denormalizeChild(mixed $data, string $type, ?string $format, array $context, bool $canBeIterable): mixed + { + if (is_scalar($data) || null === $data) { + return $data; + } + + if ($canBeIterable === true && is_iterable($data)) { + return array_map(fn($item) => $this->denormalizeChild($item, $type, $format, $context, true), $data); + } + + return $this->denormalizer->denormalize($data, $type, $format, $context); + + } + +} diff --git a/src/Symfony/Component/Serializer/Tests/Fixtures/CustomNormalizer/FullTypeHints/ExpectedNormalizer/ExtraSetter.php b/src/Symfony/Component/Serializer/Tests/Fixtures/CustomNormalizer/FullTypeHints/ExpectedNormalizer/ExtraSetter.php new file mode 100644 index 0000000000000..e05db9eb5edb2 --- /dev/null +++ b/src/Symfony/Component/Serializer/Tests/Fixtures/CustomNormalizer/FullTypeHints/ExpectedNormalizer/ExtraSetter.php @@ -0,0 +1,50 @@ + true]; + } + + public function supportsNormalization(mixed $data, ?string $format = NULL, array $context = []): bool + { + return $data instanceof ExtraSetter; + } + + public function supportsDenormalization(mixed $data, string $type, ?string $format = NULL, array $context = []): bool + { + return $type === ExtraSetter::class; + } + + /** + * @param ExtraSetter $object + */ + public function normalize(mixed $object, ?string $format = NULL, array $context = []): array|string|int|float|bool|\ArrayObject|null + { + return [ + 'name' => $object->getName(), + 'age' => $object->getAge(), + ]; + } + + public function denormalize(mixed $data, string $type, ?string $format = NULL, array $context = []): mixed + { + + $output = new ExtraSetter( + $data['name'], + ); + if (array_key_exists('age', $data)) { + $output->setAge($data['age']); + } + + return $output; + } + +} diff --git a/src/Symfony/Component/Serializer/Tests/Fixtures/CustomNormalizer/FullTypeHints/ExpectedNormalizer/InheritanceChild.php b/src/Symfony/Component/Serializer/Tests/Fixtures/CustomNormalizer/FullTypeHints/ExpectedNormalizer/InheritanceChild.php new file mode 100644 index 0000000000000..fd943a09954cd --- /dev/null +++ b/src/Symfony/Component/Serializer/Tests/Fixtures/CustomNormalizer/FullTypeHints/ExpectedNormalizer/InheritanceChild.php @@ -0,0 +1,76 @@ + true]; + } + + public function supportsNormalization(mixed $data, ?string $format = NULL, array $context = []): bool + { + return $data instanceof InheritanceChild; + } + + public function supportsDenormalization(mixed $data, string $type, ?string $format = NULL, array $context = []): bool + { + return $type === InheritanceChild::class; + } + + /** + * @param InheritanceChild $object + */ + public function normalize(mixed $object, ?string $format = NULL, array $context = []): array|string|int|float|bool|\ArrayObject|null + { + return [ + 'childCute' => $object->getChildCute(), + 'cute' => $object->isCute(), + 'childName' => $object->childName, + 'name' => $object->name, + 'childAge' => $object->getChildAge(), + 'childHeight' => $object->getChildHeight(), + 'age' => $object->getAge(), + 'height' => $object->getHeight(), + 'handsome' => $object->isHandsome(), + ]; + } + + public function denormalize(mixed $data, string $type, ?string $format = NULL, array $context = []): mixed + { + + $output = new InheritanceChild( + $data['childCute'], + $data['cute'], + ); + if (array_key_exists('childName', $data)) { + $output->childName = $data['childName']; + } + if (array_key_exists('name', $data)) { + $output->name = $data['name']; + } + if (array_key_exists('childAge', $data)) { + $output->setChildAge($data['childAge']); + } + if (array_key_exists('childHeight', $data)) { + $output->setChildHeight($data['childHeight']); + } + if (array_key_exists('age', $data)) { + $output->setAge($data['age']); + } + if (array_key_exists('height', $data)) { + $output->setHeight($data['height']); + } + if (array_key_exists('handsome', $data)) { + $output->setHandsome($data['handsome']); + } + + return $output; + } + +} diff --git a/src/Symfony/Component/Serializer/Tests/Fixtures/CustomNormalizer/FullTypeHints/ExpectedNormalizer/NonReadableProperty.php b/src/Symfony/Component/Serializer/Tests/Fixtures/CustomNormalizer/FullTypeHints/ExpectedNormalizer/NonReadableProperty.php new file mode 100644 index 0000000000000..be6c9d421f5be --- /dev/null +++ b/src/Symfony/Component/Serializer/Tests/Fixtures/CustomNormalizer/FullTypeHints/ExpectedNormalizer/NonReadableProperty.php @@ -0,0 +1,69 @@ + true]; + } + + public function supportsNormalization(mixed $data, ?string $format = NULL, array $context = []): bool + { + return $data instanceof NonReadableProperty; + } + + public function supportsDenormalization(mixed $data, string $type, ?string $format = NULL, array $context = []): bool + { + return $type === NonReadableProperty::class; + } + + /** + * @param NonReadableProperty $object + */ + public function normalize(mixed $object, ?string $format = NULL, array $context = []): array|string|int|float|bool|\ArrayObject|null + { + return [ + 'name' => $object->getName(), + 'funnyName' => $this->normalizeChild($object->getFunnyName(), $format, $context, true), + ]; + } + + public function setNormalizer(NormalizerInterface $normalizer): void + { + $this->normalizer = $normalizer; + } + + private function normalizeChild(mixed $object, ?string $format, array $context, bool $canBeIterable): mixed + { + if (is_scalar($object) || null === $object) { + return $object; + } + + if ($canBeIterable === true && is_iterable($object)) { + return array_map(fn($item) => $this->normalizeChild($item, $format, $context, true), $object); + } + + return $this->normalizer->normalize($object, $format, $context); + + } + + public function denormalize(mixed $data, string $type, ?string $format = NULL, array $context = []): mixed + { + + $output = new NonReadableProperty( + $data['name'], + ); + + return $output; + } + +} diff --git a/src/Symfony/Component/Serializer/Tests/Fixtures/CustomNormalizer/FullTypeHints/ExpectedNormalizer/PrivateConstructor.php b/src/Symfony/Component/Serializer/Tests/Fixtures/CustomNormalizer/FullTypeHints/ExpectedNormalizer/PrivateConstructor.php new file mode 100644 index 0000000000000..cc17a9c8c4e96 --- /dev/null +++ b/src/Symfony/Component/Serializer/Tests/Fixtures/CustomNormalizer/FullTypeHints/ExpectedNormalizer/PrivateConstructor.php @@ -0,0 +1,47 @@ + true]; + } + + public function supportsNormalization(mixed $data, ?string $format = NULL, array $context = []): bool + { + return $data instanceof PrivateConstructor; + } + + public function supportsDenormalization(mixed $data, string $type, ?string $format = NULL, array $context = []): bool + { + return $type === PrivateConstructor::class; + } + + /** + * @param PrivateConstructor $object + */ + public function normalize(mixed $object, ?string $format = NULL, array $context = []): array|string|int|float|bool|\ArrayObject|null + { + return [ + 'foo' => $object->foo, + ]; + } + + public function denormalize(mixed $data, string $type, ?string $format = NULL, array $context = []): mixed + { + + $output = (new \ReflectionClass(PrivateConstructor::class))->newInstanceWithoutConstructor(); + if (array_key_exists('foo', $data)) { + $output->foo = $data['foo']; + } + + return $output; + } + +} diff --git a/src/Symfony/Component/Serializer/Tests/Fixtures/CustomNormalizer/FullTypeHints/ExpectedNormalizer/PublicProperties.php b/src/Symfony/Component/Serializer/Tests/Fixtures/CustomNormalizer/FullTypeHints/ExpectedNormalizer/PublicProperties.php new file mode 100644 index 0000000000000..3b53341a6d827 --- /dev/null +++ b/src/Symfony/Component/Serializer/Tests/Fixtures/CustomNormalizer/FullTypeHints/ExpectedNormalizer/PublicProperties.php @@ -0,0 +1,120 @@ + true]; + } + + public function supportsNormalization(mixed $data, ?string $format = NULL, array $context = []): bool + { + return $data instanceof PublicProperties; + } + + public function supportsDenormalization(mixed $data, string $type, ?string $format = NULL, array $context = []): bool + { + return $type === PublicProperties::class; + } + + /** + * @param PublicProperties $object + */ + public function normalize(mixed $object, ?string $format = NULL, array $context = []): array|string|int|float|bool|\ArrayObject|null + { + return [ + 'name' => $object->name, + 'age' => $object->age, + 'height' => $object->height, + 'handsome' => $object->handsome, + 'nameOfFriends' => $this->normalizeChild($object->nameOfFriends, $format, $context, true), + 'picture' => $this->normalizeChild($object->picture, $format, $context, true), + 'pet' => $object->pet, + 'relation' => $this->normalizeChild($object->relation, $format, $context, false), + ]; + } + + public function setNormalizer(NormalizerInterface $normalizer): void + { + $this->normalizer = $normalizer; + } + + private function normalizeChild(mixed $object, ?string $format, array $context, bool $canBeIterable): mixed + { + if (is_scalar($object) || null === $object) { + return $object; + } + + if ($canBeIterable === true && is_iterable($object)) { + return array_map(fn($item) => $this->normalizeChild($item, $format, $context, true), $object); + } + + return $this->normalizer->normalize($object, $format, $context); + + } + + public function denormalize(mixed $data, string $type, ?string $format = NULL, array $context = []): mixed + { + + $output = new PublicProperties(); + if (array_key_exists('name', $data)) { + $output->name = $data['name']; + } + if (array_key_exists('age', $data)) { + $output->age = $data['age']; + } + if (array_key_exists('height', $data)) { + $output->height = $data['height']; + } + if (array_key_exists('handsome', $data)) { + $output->handsome = $data['handsome']; + } + if (array_key_exists('nameOfFriends', $data)) { + $output->nameOfFriends = $data['nameOfFriends']; + } + if (array_key_exists('picture', $data)) { + $output->picture = $data['picture']; + } + if (array_key_exists('pet', $data)) { + $output->pet = $data['pet']; + } + if (array_key_exists('relation', $data)) { + $setter0 = $this->denormalizeChild($data['relation'], \Symfony\Component\Serializer\Tests\Fixtures\CustomNormalizer\FullTypeHints\DummyObject::class, $format, $context, false); + $output->relation = $setter0; + } + + return $output; + } + + public function setDenormalizer(DenormalizerInterface $denormalizer): void + { + $this->denormalizer = $denormalizer; + } + + private function denormalizeChild(mixed $data, string $type, ?string $format, array $context, bool $canBeIterable): mixed + { + if (is_scalar($data) || null === $data) { + return $data; + } + + if ($canBeIterable === true && is_iterable($data)) { + return array_map(fn($item) => $this->denormalizeChild($item, $type, $format, $context, true), $data); + } + + return $this->denormalizer->denormalize($data, $type, $format, $context); + + } + +} diff --git a/src/Symfony/Component/Serializer/Tests/Fixtures/CustomNormalizer/FullTypeHints/ExpectedNormalizer/SetterInjection.php b/src/Symfony/Component/Serializer/Tests/Fixtures/CustomNormalizer/FullTypeHints/ExpectedNormalizer/SetterInjection.php new file mode 100644 index 0000000000000..9315d0806420c --- /dev/null +++ b/src/Symfony/Component/Serializer/Tests/Fixtures/CustomNormalizer/FullTypeHints/ExpectedNormalizer/SetterInjection.php @@ -0,0 +1,121 @@ + true]; + } + + public function supportsNormalization(mixed $data, ?string $format = NULL, array $context = []): bool + { + return $data instanceof SetterInjection; + } + + public function supportsDenormalization(mixed $data, string $type, ?string $format = NULL, array $context = []): bool + { + return $type === SetterInjection::class; + } + + /** + * @param SetterInjection $object + */ + public function normalize(mixed $object, ?string $format = NULL, array $context = []): array|string|int|float|bool|\ArrayObject|null + { + return [ + 'name' => $object->getName(), + 'age' => $object->getAge(), + 'height' => $object->getHeight(), + 'handsome' => $object->isHandsome(), + 'nameOfFriends' => $this->normalizeChild($object->getNameOfFriends(), $format, $context, true), + 'picture' => $this->normalizeChild($object->getPicture(), $format, $context, true), + 'pet' => $object->getPet(), + 'relation' => $this->normalizeChild($object->getRelation(), $format, $context, false), + 'notSet' => $object->getNotSet(), + ]; + } + + public function setNormalizer(NormalizerInterface $normalizer): void + { + $this->normalizer = $normalizer; + } + + private function normalizeChild(mixed $object, ?string $format, array $context, bool $canBeIterable): mixed + { + if (is_scalar($object) || null === $object) { + return $object; + } + + if ($canBeIterable === true && is_iterable($object)) { + return array_map(fn($item) => $this->normalizeChild($item, $format, $context, true), $object); + } + + return $this->normalizer->normalize($object, $format, $context); + + } + + public function denormalize(mixed $data, string $type, ?string $format = NULL, array $context = []): mixed + { + + $output = new SetterInjection(); + if (array_key_exists('name', $data)) { + $output->setName($data['name']); + } + if (array_key_exists('age', $data)) { + $output->setAge($data['age']); + } + if (array_key_exists('height', $data)) { + $output->setHeight($data['height']); + } + if (array_key_exists('handsome', $data)) { + $output->setHandsome($data['handsome']); + } + if (array_key_exists('nameOfFriends', $data)) { + $output->setNameOfFriends($data['nameOfFriends']); + } + if (array_key_exists('picture', $data)) { + $output->setPicture($data['picture']); + } + if (array_key_exists('pet', $data)) { + $output->setPet($data['pet']); + } + if (array_key_exists('relation', $data)) { + $setter0 = $this->denormalizeChild($data['relation'], \Symfony\Component\Serializer\Tests\Fixtures\CustomNormalizer\FullTypeHints\DummyObject::class, $format, $context, false); + $output->setRelation($setter0); + } + + return $output; + } + + public function setDenormalizer(DenormalizerInterface $denormalizer): void + { + $this->denormalizer = $denormalizer; + } + + private function denormalizeChild(mixed $data, string $type, ?string $format, array $context, bool $canBeIterable): mixed + { + if (is_scalar($data) || null === $data) { + return $data; + } + + if ($canBeIterable === true && is_iterable($data)) { + return array_map(fn($item) => $this->denormalizeChild($item, $type, $format, $context, true), $data); + } + + return $this->denormalizer->denormalize($data, $type, $format, $context); + + } + +} diff --git a/src/Symfony/Component/Serializer/Tests/Fixtures/CustomNormalizer/FullTypeHints/ExtraSetter.php b/src/Symfony/Component/Serializer/Tests/Fixtures/CustomNormalizer/FullTypeHints/ExtraSetter.php new file mode 100644 index 0000000000000..fc6b1bfc2150c --- /dev/null +++ b/src/Symfony/Component/Serializer/Tests/Fixtures/CustomNormalizer/FullTypeHints/ExtraSetter.php @@ -0,0 +1,35 @@ +name = $name; + } + + public function getName(): string + { + return $this->name; + } + + public function setName(string $name): void + { + $this->name = $name; + } + + public function getAge(): int + { + return $this->age; + } + + public function setAge(int $age): void + { + $this->age = $age; + } + +} diff --git a/src/Symfony/Component/Serializer/Tests/Fixtures/CustomNormalizer/FullTypeHints/InheritanceChild.php b/src/Symfony/Component/Serializer/Tests/Fixtures/CustomNormalizer/FullTypeHints/InheritanceChild.php new file mode 100644 index 0000000000000..5e8777f8b92c1 --- /dev/null +++ b/src/Symfony/Component/Serializer/Tests/Fixtures/CustomNormalizer/FullTypeHints/InheritanceChild.php @@ -0,0 +1,52 @@ +childCute = $childCute; + parent::__construct($cute); + } + + public function getChildCute(): bool + { + return $this->childCute; + } + + public function getChildAge(): int + { + return $this->childAge; + } + + public function setChildAge(int $childAge): void + { + $this->childAge = $childAge; + } + + public function getChildHeight(): float + { + return $this->childHeight; + } + + public function setChildHeight(float $childHeight): void + { + $this->childHeight = $childHeight; + } + + public function getAge(): int + { + return $this->age; + } + + public function setAge(int $age): void + { + $this->age = $age; + } +} diff --git a/src/Symfony/Component/Serializer/Tests/Fixtures/CustomNormalizer/FullTypeHints/InheritanceParent.php b/src/Symfony/Component/Serializer/Tests/Fixtures/CustomNormalizer/FullTypeHints/InheritanceParent.php new file mode 100644 index 0000000000000..454083c69bda8 --- /dev/null +++ b/src/Symfony/Component/Serializer/Tests/Fixtures/CustomNormalizer/FullTypeHints/InheritanceParent.php @@ -0,0 +1,42 @@ +cute = $cute; + } + + public function isCute(): bool + { + return $this->cute; + } + + public function getHeight(): float + { + return $this->height; + } + + public function setHeight(float $height): void + { + $this->height = $height; + } + + public function isHandsome(): bool + { + return $this->handsome; + } + + public function setHandsome(bool $handsome): void + { + $this->handsome = $handsome; + } +} diff --git a/src/Symfony/Component/Serializer/Tests/Fixtures/CustomNormalizer/FullTypeHints/NonReadableProperty.php b/src/Symfony/Component/Serializer/Tests/Fixtures/CustomNormalizer/FullTypeHints/NonReadableProperty.php new file mode 100644 index 0000000000000..13e62cefa87df --- /dev/null +++ b/src/Symfony/Component/Serializer/Tests/Fixtures/CustomNormalizer/FullTypeHints/NonReadableProperty.php @@ -0,0 +1,25 @@ +name = $name; + $this->count = strlen($name); + } + + public function getName(): string + { + return $this->name; + } + + public function getFunnyName() + { + return $this->name.'_'.$this->count; + } +} diff --git a/src/Symfony/Component/Serializer/Tests/Fixtures/CustomNormalizer/FullTypeHints/PrivateConstructor.php b/src/Symfony/Component/Serializer/Tests/Fixtures/CustomNormalizer/FullTypeHints/PrivateConstructor.php new file mode 100644 index 0000000000000..1d51bf646b5d1 --- /dev/null +++ b/src/Symfony/Component/Serializer/Tests/Fixtures/CustomNormalizer/FullTypeHints/PrivateConstructor.php @@ -0,0 +1,20 @@ +foo = $foo; + return $model; + } + +} diff --git a/src/Symfony/Component/Serializer/Tests/Fixtures/CustomNormalizer/FullTypeHints/PublicProperties.php b/src/Symfony/Component/Serializer/Tests/Fixtures/CustomNormalizer/FullTypeHints/PublicProperties.php new file mode 100644 index 0000000000000..dfbf68ca80b11 --- /dev/null +++ b/src/Symfony/Component/Serializer/Tests/Fixtures/CustomNormalizer/FullTypeHints/PublicProperties.php @@ -0,0 +1,15 @@ +name; + } + + public function setName(string $name): void + { + $this->name = $name; + } + + public function getAge(): int + { + return $this->age; + } + + public function setAge(int $age): void + { + $this->age = $age; + } + + public function getHeight(): float + { + return $this->height; + } + + public function setHeight(float $height): void + { + $this->height = $height; + } + + public function isHandsome(): bool + { + return $this->handsome; + } + + public function setHandsome(bool $handsome): void + { + $this->handsome = $handsome; + } + + public function getNameOfFriends(): array + { + return $this->nameOfFriends; + } + + public function setNameOfFriends(array $nameOfFriends): void + { + $this->nameOfFriends = $nameOfFriends; + } + + public function getPicture() + { + return $this->picture; + } + + public function setPicture($picture): void + { + $this->picture = $picture; + } + + public function getPet(): ?string + { + return $this->pet; + } + + public function setPet(?string $pet): void + { + $this->pet = $pet; + } + + public function getRelation(): DummyObject + { + return $this->relation; + } + + public function setRelation(DummyObject $relation): void + { + $this->relation = $relation; + } + + public function getNotSet(): string + { + return $this->notSet; + } +} diff --git a/src/Symfony/Component/Serializer/Tests/Fixtures/CustomNormalizer/FullTypeHints/SmartObject.php b/src/Symfony/Component/Serializer/Tests/Fixtures/CustomNormalizer/FullTypeHints/SmartObject.php new file mode 100644 index 0000000000000..0b4dd4e26b173 --- /dev/null +++ b/src/Symfony/Component/Serializer/Tests/Fixtures/CustomNormalizer/FullTypeHints/SmartObject.php @@ -0,0 +1,8 @@ +name = $name; + $this->age = $age; + $this->picture = $picture; + $this->pet = $pet; + $this->relation = $relation; + } + + public function setHeight($height): void + { + $this->height = $height; + } + + public function setHandsome($handsome): void + { + $this->handsome = $handsome; + } + + public function setNameOfFriends($nameOfFriends): void + { + $this->nameOfFriends = $nameOfFriends; + } + + public function getName() + { + return $this->name; + } + + public function getAge() + { + return $this->age; + } + + public function getHeight() + { + return $this->height; + } + + public function getHandsome() + { + return $this->handsome; + } + + public function getNameOfFriends() + { + return $this->nameOfFriends; + } + + public function getPicture() + { + return $this->picture; + } + + public function getPet() + { + return $this->pet; + } + + public function getRelation() + { + return $this->relation; + } + + public function getNotSet() + { + return $this->notSet; + } +} diff --git a/src/Symfony/Component/Serializer/Tests/Fixtures/CustomNormalizer/NoTypeHints/ConstructorInjection.php b/src/Symfony/Component/Serializer/Tests/Fixtures/CustomNormalizer/NoTypeHints/ConstructorInjection.php new file mode 100644 index 0000000000000..3f4ca262f7eca --- /dev/null +++ b/src/Symfony/Component/Serializer/Tests/Fixtures/CustomNormalizer/NoTypeHints/ConstructorInjection.php @@ -0,0 +1,73 @@ +name = $name; + $this->age = $age; + $this->height = $height; + $this->handsome = $handsome; + $this->nameOfFriends = $nameOfFriends; + $this->picture = $picture; + $this->pet = $pet; + $this->relation = $relation; + } + + public function getName() + { + return $this->name; + } + + public function getAge() + { + return $this->age; + } + + public function getHeight() + { + return $this->height; + } + + public function getHandsome() + { + return $this->handsome; + } + + public function getNameOfFriends() + { + return $this->nameOfFriends; + } + + public function getPicture() + { + return $this->picture; + } + + public function getPet() + { + return $this->pet; + } + + public function getRelation() + { + return $this->relation; + } + + public function getNotSet() + { + return $this->notSet; + } +} diff --git a/src/Symfony/Component/Serializer/Tests/Fixtures/CustomNormalizer/NoTypeHints/ExpectedNormalizer/ConstructorAndSetterInjection.php b/src/Symfony/Component/Serializer/Tests/Fixtures/CustomNormalizer/NoTypeHints/ExpectedNormalizer/ConstructorAndSetterInjection.php new file mode 100644 index 0000000000000..ba0c353db9dbf --- /dev/null +++ b/src/Symfony/Component/Serializer/Tests/Fixtures/CustomNormalizer/NoTypeHints/ExpectedNormalizer/ConstructorAndSetterInjection.php @@ -0,0 +1,89 @@ + true]; + } + + public function supportsNormalization(mixed $data, ?string $format = NULL, array $context = []): bool + { + return $data instanceof ConstructorAndSetterInjection; + } + + public function supportsDenormalization(mixed $data, string $type, ?string $format = NULL, array $context = []): bool + { + return $type === ConstructorAndSetterInjection::class; + } + + /** + * @param ConstructorAndSetterInjection $object + */ + public function normalize(mixed $object, ?string $format = NULL, array $context = []): array|string|int|float|bool|\ArrayObject|null + { + return [ + 'name' => $this->normalizeChild($object->getName(), $format, $context, true), + 'age' => $this->normalizeChild($object->getAge(), $format, $context, true), + 'picture' => $this->normalizeChild($object->getPicture(), $format, $context, true), + 'pet' => $this->normalizeChild($object->getPet(), $format, $context, true), + 'relation' => $this->normalizeChild($object->getRelation(), $format, $context, true), + 'height' => $this->normalizeChild($object->getHeight(), $format, $context, true), + 'handsome' => $this->normalizeChild($object->getHandsome(), $format, $context, true), + 'nameOfFriends' => $this->normalizeChild($object->getNameOfFriends(), $format, $context, true), + 'notSet' => $this->normalizeChild($object->getNotSet(), $format, $context, true), + ]; + } + + public function setNormalizer(NormalizerInterface $normalizer): void + { + $this->normalizer = $normalizer; + } + + private function normalizeChild(mixed $object, ?string $format, array $context, bool $canBeIterable): mixed + { + if (is_scalar($object) || null === $object) { + return $object; + } + + if ($canBeIterable === true && is_iterable($object)) { + return array_map(fn($item) => $this->normalizeChild($item, $format, $context, true), $object); + } + + return $this->normalizer->normalize($object, $format, $context); + + } + + public function denormalize(mixed $data, string $type, ?string $format = NULL, array $context = []): mixed + { + + $output = new ConstructorAndSetterInjection( + $data['name'], + $data['age'], + $data['picture'], + $data['pet'], + $data['relation'], + ); + if (array_key_exists('height', $data)) { + $output->setHeight($data['height']); + } + if (array_key_exists('handsome', $data)) { + $output->setHandsome($data['handsome']); + } + if (array_key_exists('nameOfFriends', $data)) { + $output->setNameOfFriends($data['nameOfFriends']); + } + + return $output; + } + +} diff --git a/src/Symfony/Component/Serializer/Tests/Fixtures/CustomNormalizer/NoTypeHints/ExpectedNormalizer/ConstructorInjection.php b/src/Symfony/Component/Serializer/Tests/Fixtures/CustomNormalizer/NoTypeHints/ExpectedNormalizer/ConstructorInjection.php new file mode 100644 index 0000000000000..39389746a572e --- /dev/null +++ b/src/Symfony/Component/Serializer/Tests/Fixtures/CustomNormalizer/NoTypeHints/ExpectedNormalizer/ConstructorInjection.php @@ -0,0 +1,83 @@ + true]; + } + + public function supportsNormalization(mixed $data, ?string $format = NULL, array $context = []): bool + { + return $data instanceof ConstructorInjection; + } + + public function supportsDenormalization(mixed $data, string $type, ?string $format = NULL, array $context = []): bool + { + return $type === ConstructorInjection::class; + } + + /** + * @param ConstructorInjection $object + */ + public function normalize(mixed $object, ?string $format = NULL, array $context = []): array|string|int|float|bool|\ArrayObject|null + { + return [ + 'name' => $this->normalizeChild($object->getName(), $format, $context, true), + 'age' => $this->normalizeChild($object->getAge(), $format, $context, true), + 'height' => $this->normalizeChild($object->getHeight(), $format, $context, true), + 'handsome' => $this->normalizeChild($object->getHandsome(), $format, $context, true), + 'nameOfFriends' => $this->normalizeChild($object->getNameOfFriends(), $format, $context, true), + 'picture' => $this->normalizeChild($object->getPicture(), $format, $context, true), + 'pet' => $this->normalizeChild($object->getPet(), $format, $context, true), + 'relation' => $this->normalizeChild($object->getRelation(), $format, $context, true), + 'notSet' => $this->normalizeChild($object->getNotSet(), $format, $context, true), + ]; + } + + public function setNormalizer(NormalizerInterface $normalizer): void + { + $this->normalizer = $normalizer; + } + + private function normalizeChild(mixed $object, ?string $format, array $context, bool $canBeIterable): mixed + { + if (is_scalar($object) || null === $object) { + return $object; + } + + if ($canBeIterable === true && is_iterable($object)) { + return array_map(fn($item) => $this->normalizeChild($item, $format, $context, true), $object); + } + + return $this->normalizer->normalize($object, $format, $context); + + } + + public function denormalize(mixed $data, string $type, ?string $format = NULL, array $context = []): mixed + { + + $output = new ConstructorInjection( + $data['name'], + $data['age'], + $data['height'], + $data['handsome'], + $data['nameOfFriends'], + $data['picture'], + $data['pet'], + $data['relation'], + ); + + return $output; + } + +} diff --git a/src/Symfony/Component/Serializer/Tests/Fixtures/CustomNormalizer/NoTypeHints/ExpectedNormalizer/InheritanceChild.php b/src/Symfony/Component/Serializer/Tests/Fixtures/CustomNormalizer/NoTypeHints/ExpectedNormalizer/InheritanceChild.php new file mode 100644 index 0000000000000..dfa61a84684b2 --- /dev/null +++ b/src/Symfony/Component/Serializer/Tests/Fixtures/CustomNormalizer/NoTypeHints/ExpectedNormalizer/InheritanceChild.php @@ -0,0 +1,98 @@ + true]; + } + + public function supportsNormalization(mixed $data, ?string $format = NULL, array $context = []): bool + { + return $data instanceof InheritanceChild; + } + + public function supportsDenormalization(mixed $data, string $type, ?string $format = NULL, array $context = []): bool + { + return $type === InheritanceChild::class; + } + + /** + * @param InheritanceChild $object + */ + public function normalize(mixed $object, ?string $format = NULL, array $context = []): array|string|int|float|bool|\ArrayObject|null + { + return [ + 'childCute' => $this->normalizeChild($object->getChildCute(), $format, $context, true), + 'cute' => $this->normalizeChild($object->getCute(), $format, $context, true), + 'childName' => $this->normalizeChild($object->childName, $format, $context, true), + 'name' => $this->normalizeChild($object->name, $format, $context, true), + 'childAge' => $this->normalizeChild($object->getChildAge(), $format, $context, true), + 'childHeight' => $this->normalizeChild($object->getChildHeight(), $format, $context, true), + 'age' => $this->normalizeChild($object->getAge(), $format, $context, true), + 'height' => $this->normalizeChild($object->getHeight(), $format, $context, true), + 'handsome' => $this->normalizeChild($object->getHandsome(), $format, $context, true), + ]; + } + + public function setNormalizer(NormalizerInterface $normalizer): void + { + $this->normalizer = $normalizer; + } + + private function normalizeChild(mixed $object, ?string $format, array $context, bool $canBeIterable): mixed + { + if (is_scalar($object) || null === $object) { + return $object; + } + + if ($canBeIterable === true && is_iterable($object)) { + return array_map(fn($item) => $this->normalizeChild($item, $format, $context, true), $object); + } + + return $this->normalizer->normalize($object, $format, $context); + + } + + public function denormalize(mixed $data, string $type, ?string $format = NULL, array $context = []): mixed + { + + $output = new InheritanceChild( + $data['childCute'], + $data['cute'], + ); + if (array_key_exists('childName', $data)) { + $output->childName = $data['childName']; + } + if (array_key_exists('name', $data)) { + $output->name = $data['name']; + } + if (array_key_exists('childAge', $data)) { + $output->setChildAge($data['childAge']); + } + if (array_key_exists('childHeight', $data)) { + $output->setChildHeight($data['childHeight']); + } + if (array_key_exists('age', $data)) { + $output->setAge($data['age']); + } + if (array_key_exists('height', $data)) { + $output->setHeight($data['height']); + } + if (array_key_exists('handsome', $data)) { + $output->setHandsome($data['handsome']); + } + + return $output; + } + +} diff --git a/src/Symfony/Component/Serializer/Tests/Fixtures/CustomNormalizer/NoTypeHints/ExpectedNormalizer/PublicProperties.php b/src/Symfony/Component/Serializer/Tests/Fixtures/CustomNormalizer/NoTypeHints/ExpectedNormalizer/PublicProperties.php new file mode 100644 index 0000000000000..f587c6e599cc6 --- /dev/null +++ b/src/Symfony/Component/Serializer/Tests/Fixtures/CustomNormalizer/NoTypeHints/ExpectedNormalizer/PublicProperties.php @@ -0,0 +1,101 @@ + true]; + } + + public function supportsNormalization(mixed $data, ?string $format = NULL, array $context = []): bool + { + return $data instanceof PublicProperties; + } + + public function supportsDenormalization(mixed $data, string $type, ?string $format = NULL, array $context = []): bool + { + return $type === PublicProperties::class; + } + + /** + * @param PublicProperties $object + */ + public function normalize(mixed $object, ?string $format = NULL, array $context = []): array|string|int|float|bool|\ArrayObject|null + { + return [ + 'name' => $this->normalizeChild($object->name, $format, $context, true), + 'age' => $this->normalizeChild($object->age, $format, $context, true), + 'height' => $this->normalizeChild($object->height, $format, $context, true), + 'handsome' => $this->normalizeChild($object->handsome, $format, $context, true), + 'nameOfFriends' => $this->normalizeChild($object->nameOfFriends, $format, $context, true), + 'picture' => $this->normalizeChild($object->picture, $format, $context, true), + 'pet' => $this->normalizeChild($object->pet, $format, $context, true), + 'relation' => $this->normalizeChild($object->relation, $format, $context, true), + 'notSet' => $this->normalizeChild($object->notSet, $format, $context, true), + ]; + } + + public function setNormalizer(NormalizerInterface $normalizer): void + { + $this->normalizer = $normalizer; + } + + private function normalizeChild(mixed $object, ?string $format, array $context, bool $canBeIterable): mixed + { + if (is_scalar($object) || null === $object) { + return $object; + } + + if ($canBeIterable === true && is_iterable($object)) { + return array_map(fn($item) => $this->normalizeChild($item, $format, $context, true), $object); + } + + return $this->normalizer->normalize($object, $format, $context); + + } + + public function denormalize(mixed $data, string $type, ?string $format = NULL, array $context = []): mixed + { + + $output = new PublicProperties(); + if (array_key_exists('name', $data)) { + $output->name = $data['name']; + } + if (array_key_exists('age', $data)) { + $output->age = $data['age']; + } + if (array_key_exists('height', $data)) { + $output->height = $data['height']; + } + if (array_key_exists('handsome', $data)) { + $output->handsome = $data['handsome']; + } + if (array_key_exists('nameOfFriends', $data)) { + $output->nameOfFriends = $data['nameOfFriends']; + } + if (array_key_exists('picture', $data)) { + $output->picture = $data['picture']; + } + if (array_key_exists('pet', $data)) { + $output->pet = $data['pet']; + } + if (array_key_exists('relation', $data)) { + $output->relation = $data['relation']; + } + if (array_key_exists('notSet', $data)) { + $output->notSet = $data['notSet']; + } + + return $output; + } + +} diff --git a/src/Symfony/Component/Serializer/Tests/Fixtures/CustomNormalizer/NoTypeHints/ExpectedNormalizer/SetterInjection.php b/src/Symfony/Component/Serializer/Tests/Fixtures/CustomNormalizer/NoTypeHints/ExpectedNormalizer/SetterInjection.php new file mode 100644 index 0000000000000..71b6c35c24d0e --- /dev/null +++ b/src/Symfony/Component/Serializer/Tests/Fixtures/CustomNormalizer/NoTypeHints/ExpectedNormalizer/SetterInjection.php @@ -0,0 +1,98 @@ + true]; + } + + public function supportsNormalization(mixed $data, ?string $format = NULL, array $context = []): bool + { + return $data instanceof SetterInjection; + } + + public function supportsDenormalization(mixed $data, string $type, ?string $format = NULL, array $context = []): bool + { + return $type === SetterInjection::class; + } + + /** + * @param SetterInjection $object + */ + public function normalize(mixed $object, ?string $format = NULL, array $context = []): array|string|int|float|bool|\ArrayObject|null + { + return [ + 'name' => $this->normalizeChild($object->getName(), $format, $context, true), + 'age' => $this->normalizeChild($object->getAge(), $format, $context, true), + 'height' => $this->normalizeChild($object->getHeight(), $format, $context, true), + 'handsome' => $object->isHandsome(), + 'nameOfFriends' => $this->normalizeChild($object->getNameOfFriends(), $format, $context, true), + 'picture' => $this->normalizeChild($object->getPicture(), $format, $context, true), + 'pet' => $this->normalizeChild($object->getPet(), $format, $context, true), + 'relation' => $this->normalizeChild($object->getRelation(), $format, $context, true), + 'notSet' => $this->normalizeChild($object->getNotSet(), $format, $context, true), + ]; + } + + public function setNormalizer(NormalizerInterface $normalizer): void + { + $this->normalizer = $normalizer; + } + + private function normalizeChild(mixed $object, ?string $format, array $context, bool $canBeIterable): mixed + { + if (is_scalar($object) || null === $object) { + return $object; + } + + if ($canBeIterable === true && is_iterable($object)) { + return array_map(fn($item) => $this->normalizeChild($item, $format, $context, true), $object); + } + + return $this->normalizer->normalize($object, $format, $context); + + } + + public function denormalize(mixed $data, string $type, ?string $format = NULL, array $context = []): mixed + { + + $output = new SetterInjection(); + if (array_key_exists('name', $data)) { + $output->setName($data['name']); + } + if (array_key_exists('age', $data)) { + $output->setAge($data['age']); + } + if (array_key_exists('height', $data)) { + $output->setHeight($data['height']); + } + if (array_key_exists('handsome', $data)) { + $output->setHandsome($data['handsome']); + } + if (array_key_exists('nameOfFriends', $data)) { + $output->setNameOfFriends($data['nameOfFriends']); + } + if (array_key_exists('picture', $data)) { + $output->setPicture($data['picture']); + } + if (array_key_exists('pet', $data)) { + $output->setPet($data['pet']); + } + if (array_key_exists('relation', $data)) { + $output->setRelation($data['relation']); + } + + return $output; + } + +} diff --git a/src/Symfony/Component/Serializer/Tests/Fixtures/CustomNormalizer/NoTypeHints/InheritanceChild.php b/src/Symfony/Component/Serializer/Tests/Fixtures/CustomNormalizer/NoTypeHints/InheritanceChild.php new file mode 100644 index 0000000000000..64b585bd871c4 --- /dev/null +++ b/src/Symfony/Component/Serializer/Tests/Fixtures/CustomNormalizer/NoTypeHints/InheritanceChild.php @@ -0,0 +1,52 @@ +childCute = $childCute; + parent::__construct($cute); + } + + public function getChildCute() + { + return $this->childCute; + } + + public function getChildAge() + { + return $this->childAge; + } + + public function setChildAge($childAge): void + { + $this->childAge = $childAge; + } + + public function getChildHeight() + { + return $this->childHeight; + } + + public function setChildHeight($childHeight): void + { + $this->childHeight = $childHeight; + } + + public function getAge() + { + return $this->age; + } + + public function setAge($age): void + { + $this->age = $age; + } +} diff --git a/src/Symfony/Component/Serializer/Tests/Fixtures/CustomNormalizer/NoTypeHints/InheritanceParent.php b/src/Symfony/Component/Serializer/Tests/Fixtures/CustomNormalizer/NoTypeHints/InheritanceParent.php new file mode 100644 index 0000000000000..1cde7e76a3f21 --- /dev/null +++ b/src/Symfony/Component/Serializer/Tests/Fixtures/CustomNormalizer/NoTypeHints/InheritanceParent.php @@ -0,0 +1,45 @@ +cute = $cute; + } + + public function getCute() + { + return $this->cute; + } + + public function getHeight() + { + return $this->height; + } + + public function setHeight($height): void + { + $this->height = $height; + } + + public function getHandsome() + { + return $this->handsome; + } + + public function setHandsome($handsome): void + { + $this->handsome = $handsome; + } + + + +} diff --git a/src/Symfony/Component/Serializer/Tests/Fixtures/CustomNormalizer/NoTypeHints/PublicProperties.php b/src/Symfony/Component/Serializer/Tests/Fixtures/CustomNormalizer/NoTypeHints/PublicProperties.php new file mode 100644 index 0000000000000..9cc64412792bb --- /dev/null +++ b/src/Symfony/Component/Serializer/Tests/Fixtures/CustomNormalizer/NoTypeHints/PublicProperties.php @@ -0,0 +1,17 @@ +name; + } + + public function setName($name) + { + $this->name = $name; + } + + public function getAge() + { + return $this->age; + } + + public function setAge($age) + { + $this->age = $age; + } + + public function getHeight() + { + return $this->height; + } + + public function setHeight($height) + { + $this->height = $height; + } + + public function isHandsome() + { + return $this->handsome; + } + + public function setHandsome($handsome) + { + $this->handsome = $handsome; + } + + public function getNameOfFriends() + { + return $this->nameOfFriends; + } + + public function setNameOfFriends($nameOfFriends) + { + $this->nameOfFriends = $nameOfFriends; + } + + public function getPicture() + { + return $this->picture; + } + + public function setPicture($picture) + { + $this->picture = $picture; + } + + public function getPet() + { + return $this->pet; + } + + public function setPet($pet) + { + $this->pet = $pet; + } + + public function getRelation() + { + return $this->relation; + } + + public function setRelation($relation) + { + $this->relation = $relation; + } + + public function getNotSet() + { + return $this->notSet; + } + + + + +} From fea6eeb852c0a60cebe051afd55b390b49fe2c85 Mon Sep 17 00:00:00 2001 From: Nyholm Date: Tue, 5 Dec 2023 22:25:54 +0100 Subject: [PATCH 02/13] Fix some tests --- .../DependencyInjection/ConfigurationTest.php | 1 + .../DependencyInjection/Fixtures/php/full.php | 1 + .../Builder/CodeGenerator/AttributeTest.php | 18 +++++----- .../CodeGenerator/ClassGeneratorTest.php | 33 ++++++++++--------- .../Builder/CodeGenerator/Fixtures/Cat.php | 12 ++----- .../Builder/CodeGenerator/Fixtures/Full.php | 21 ++++++------ .../Builder/CodeGenerator/Fixtures/Person.php | 12 ++----- .../Builder/CodeGenerator/MethodTest.php | 25 +++++++------- .../Builder/CodeGenerator/PropertyTest.php | 33 ++++++++++--------- .../Tests/Builder/FixtureHelper.php | 6 +--- .../Tests/Builder/generateUpdatedFixtures.php | 2 +- 11 files changed, 74 insertions(+), 90 deletions(-) diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/ConfigurationTest.php b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/ConfigurationTest.php index d56cfa90d7f48..d8925d01062bc 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/ConfigurationTest.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/ConfigurationTest.php @@ -615,6 +615,7 @@ protected static function getBundleDefaultConfig() 'enabled' => true, 'enable_attributes' => !class_exists(FullStack::class), 'mapping' => ['paths' => []], + 'auto_normalizer' => ['paths'=>[]], ], 'property_access' => [ 'enabled' => true, diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/php/full.php b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/php/full.php index b5d8061e4d0af..481a71473eecd 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/php/full.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/php/full.php @@ -64,6 +64,7 @@ 'serializer' => [ 'enabled' => true, 'enable_attributes' => true, + 'auto_normalizer' => ['paths'=>[]], 'name_converter' => 'serializer.name_converter.camel_case_to_snake_case', 'circular_reference_handler' => 'my.circular.reference.handler', 'max_depth_handler' => 'my.max.depth.handler', diff --git a/src/Symfony/Component/Serializer/Tests/Builder/CodeGenerator/AttributeTest.php b/src/Symfony/Component/Serializer/Tests/Builder/CodeGenerator/AttributeTest.php index 878e9cb7231cf..c394c68e43940 100644 --- a/src/Symfony/Component/Serializer/Tests/Builder/CodeGenerator/AttributeTest.php +++ b/src/Symfony/Component/Serializer/Tests/Builder/CodeGenerator/AttributeTest.php @@ -11,28 +11,28 @@ namespace Symfony\Component\Serializer\Tests\Builder\CodeGenerator; -use App\CodeGenerator\_Attribute; use PHPUnit\Framework\TestCase; +use Symfony\Component\Serializer\Builder\CodeGenerator\Attribute; class AttributeTest extends TestCase { public function testNoParameters() { - $output = _Attribute::create('Foobar')->toString(); + $output = Attribute::create('Foobar')->toString(); $this->assertEquals('#[Foobar]', $output); - $output = _Attribute::create('Foo\\Bar')->toString(); + $output = Attribute::create('Foo\\Bar')->toString(); $this->assertEquals('#[Foo\\Bar]', $output); } public function testParameters() { - $output = _Attribute::create('Foobar') + $output = Attribute::create('Foobar') ->addParameter(null, 7) ->toString(); $this->assertEquals('#[Foobar(7)]', $output); - $output = _Attribute::create('Foobar') + $output = Attribute::create('Foobar') ->addParameter(null, 7) ->addParameter(null, true) ->addParameter(null, false) @@ -46,12 +46,12 @@ public function testParameters() public function testNamedParameters() { - $output = _Attribute::create('Foobar') + $output = Attribute::create('Foobar') ->addParameter('seven', 7) ->toString(); $this->assertEquals('#[Foobar(seven: 7)]', $output); - $output = _Attribute::create('Foobar') + $output = Attribute::create('Foobar') ->addParameter('seven', 7) ->addParameter('true', true) ->addParameter('false', false) @@ -65,10 +65,10 @@ public function testNamedParameters() public function testNested() { - $nested = _Attribute::create('Baz') + $nested = Attribute::create('Baz') ->addParameter('name', 'tobias'); - $output = _Attribute::create('Foobar') + $output = Attribute::create('Foobar') ->addParameter('seven', 7) ->addParameter('nested', $nested) ->toString(); diff --git a/src/Symfony/Component/Serializer/Tests/Builder/CodeGenerator/ClassGeneratorTest.php b/src/Symfony/Component/Serializer/Tests/Builder/CodeGenerator/ClassGeneratorTest.php index 8bedffb89ddd6..4d291d40febca 100644 --- a/src/Symfony/Component/Serializer/Tests/Builder/CodeGenerator/ClassGeneratorTest.php +++ b/src/Symfony/Component/Serializer/Tests/Builder/CodeGenerator/ClassGeneratorTest.php @@ -11,11 +11,12 @@ namespace Symfony\Component\Serializer\Tests\Builder\CodeGenerator; -use App\CodeGenerator\_Attribute; -use App\CodeGenerator\_Method; -use App\CodeGenerator\_Property; -use App\CodeGenerator\ClassGenerator; + use PHPUnit\Framework\TestCase; +use Symfony\Component\Serializer\Builder\CodeGenerator\Attribute; +use Symfony\Component\Serializer\Builder\CodeGenerator\ClassGenerator; +use Symfony\Component\Serializer\Builder\CodeGenerator\Method; +use Symfony\Component\Serializer\Builder\CodeGenerator\Property; class ClassGeneratorTest extends TestCase { @@ -23,16 +24,16 @@ public function testPerson() { $generator = new ClassGenerator('Person', 'Test\\CodeGenerator\\Fixtures'); - $generator->addProperty(_Property::create('name') + $generator->addProperty(Property::create('name') ->setVisibility('private') ->setType('string') ); - $generator->addProperty(_Property::create('age') + $generator->addProperty(Property::create('age') ->setVisibility('private') ->setType('int') ); - $generator->addMethod(_Method::create('__construct') + $generator->addMethod(Method::create('__construct') ->addArgument('name', 'string') ->addArgument('age', 'int') ->setBody(<<addMethod(_Method::create('getName') + $generator->addMethod(Method::create('getName') ->setReturnType('string') ->setBody('return $this->name;') ); - $generator->addMethod(_Method::create('getAge') + $generator->addMethod(Method::create('getAge') ->setReturnType('int') ->setBody('return $this->age;') ); @@ -62,17 +63,17 @@ public function testCat() { $generator = new ClassGenerator('Cat', 'Test\\CodeGenerator\\Fixtures'); - $generator->addMethod(_Method::create('__construct') + $generator->addMethod(Method::create('__construct') ->addArgument('name', 'private string') ->addArgument('age', 'private int') ); - $generator->addMethod(_Method::create('getName') + $generator->addMethod(Method::create('getName') ->setReturnType('string') ->setBody('return $this->name;') ); - $generator->addMethod(_Method::create('getAge') + $generator->addMethod(Method::create('getAge') ->setReturnType('int') ->setBody('return $this->age;') ); @@ -104,19 +105,19 @@ public function testFull() TEXT ); - $generator->addProperty(_Property::create('name') + $generator->addProperty(Property::create('name') ->setVisibility('private') ->setType('string') ); - $generator->addMethod(_Method::create('__construct') + $generator->addMethod(Method::create('__construct') ->addArgument('name', 'string', 'foobar') ->setBody(<<name = \$name; PHP )); - $generator->addMethod(_Method::create('getName') + $generator->addMethod(Method::create('getName') ->setReturnType('string') ->setBody('return $this->name;') ->setComment(<<addAttribute(_Attribute::create('MyAttribute') + $generator->addAttribute(Attribute::create('MyAttribute') ->addParameter('name', 'test') ); diff --git a/src/Symfony/Component/Serializer/Tests/Builder/CodeGenerator/Fixtures/Cat.php b/src/Symfony/Component/Serializer/Tests/Builder/CodeGenerator/Fixtures/Cat.php index 2cfff588281d3..27d606b33bbff 100644 --- a/src/Symfony/Component/Serializer/Tests/Builder/CodeGenerator/Fixtures/Cat.php +++ b/src/Symfony/Component/Serializer/Tests/Builder/CodeGenerator/Fixtures/Cat.php @@ -1,15 +1,6 @@ - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Component\Serializer\Tests\Builder\CodeGenerator\Fixtures; +namespace Test\CodeGenerator\Fixtures; class Cat { @@ -26,4 +17,5 @@ public function getAge(): int { return $this->age; } + } diff --git a/src/Symfony/Component/Serializer/Tests/Builder/CodeGenerator/Fixtures/Full.php b/src/Symfony/Component/Serializer/Tests/Builder/CodeGenerator/Fixtures/Full.php index 8c70e3367a60a..c43181e4e1d4d 100644 --- a/src/Symfony/Component/Serializer/Tests/Builder/CodeGenerator/Fixtures/Full.php +++ b/src/Symfony/Component/Serializer/Tests/Builder/CodeGenerator/Fixtures/Full.php @@ -1,27 +1,23 @@ - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. + * This is a fixture class. + * We use it for verifying the code generation. */ -namespace Symfony\Component\Serializer\Tests\Builder\CodeGenerator\Fixtures; +namespace Test\CodeGenerator\Fixtures; -use Test\CodeGenerator\Fixtures\Bar; use Test\CodeGenerator\Fixtures\Cat; use Test\CodeGenerator\Fixtures\Foo; +use Test\CodeGenerator\Fixtures\Bar; use Test\CodeGenerator\Fixtures\MyAttribute; /** * Perfect class comment. - * + * * It has some lines */ -#[MyAttribute(name: 'test')] +#[MyAttribute(name: "test")] class Full extends Cat implements Foo, Bar { private string $name; @@ -32,10 +28,13 @@ public function __construct(string $name = 'foobar') } /** - * Returns the name of the cat. + * Returns the name of the cat + * + * @return string */ public function getName(): string { return $this->name; } + } diff --git a/src/Symfony/Component/Serializer/Tests/Builder/CodeGenerator/Fixtures/Person.php b/src/Symfony/Component/Serializer/Tests/Builder/CodeGenerator/Fixtures/Person.php index 8118a2566f291..5ddaaab8684bc 100644 --- a/src/Symfony/Component/Serializer/Tests/Builder/CodeGenerator/Fixtures/Person.php +++ b/src/Symfony/Component/Serializer/Tests/Builder/CodeGenerator/Fixtures/Person.php @@ -1,15 +1,6 @@ - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Component\Serializer\Tests\Builder\CodeGenerator\Fixtures; +namespace Test\CodeGenerator\Fixtures; class Person { @@ -31,4 +22,5 @@ public function getAge(): int { return $this->age; } + } diff --git a/src/Symfony/Component/Serializer/Tests/Builder/CodeGenerator/MethodTest.php b/src/Symfony/Component/Serializer/Tests/Builder/CodeGenerator/MethodTest.php index eddf27e78cf56..5561a2400f19a 100644 --- a/src/Symfony/Component/Serializer/Tests/Builder/CodeGenerator/MethodTest.php +++ b/src/Symfony/Component/Serializer/Tests/Builder/CodeGenerator/MethodTest.php @@ -11,14 +11,15 @@ namespace Symfony\Component\Serializer\Tests\Builder\CodeGenerator; -use App\CodeGenerator\_Method; + use PHPUnit\Framework\TestCase; +use Symfony\Component\Serializer\Builder\CodeGenerator\Method; class MethodTest extends TestCase { public function testEmpty() { - $output = _Method::create('foobar')->toString(); + $output = Method::create('foobar')->toString(); $this->assertEquals('public function foobar() { }', $output); @@ -26,24 +27,24 @@ public function testEmpty() public function testAbstractMethod() { - $output = _Method::create('foobar')->setVisibility('protected')->setBody(null)->toString(); + $output = Method::create('foobar')->setVisibility('protected')->setBody(null)->toString(); $this->assertEquals('abstract protected function foobar();', $output); } public function testVisibility() { - $output = _Method::create('foobar')->setVisibility('private')->toString(); + $output = Method::create('foobar')->setVisibility('private')->toString(); $this->assertEquals('private function foobar() { }', $output); - $output = _Method::create('foobar')->setVisibility('protected')->toString(); + $output = Method::create('foobar')->setVisibility('protected')->toString(); $this->assertEquals('protected function foobar() { }', $output); // We dont care about logic - $output = _Method::create('foobar')->setVisibility('crazy')->toString(); + $output = Method::create('foobar')->setVisibility('crazy')->toString(); $this->assertEquals('crazy function foobar() { }', $output); @@ -51,17 +52,17 @@ public function testVisibility() public function testReturnType() { - $output = _Method::create('foobar')->setReturnType('int')->toString(); + $output = Method::create('foobar')->setReturnType('int')->toString(); $this->assertEquals('public function foobar(): int { }', $output); - $output = _Method::create('foobar')->setReturnType('mixed')->toString(); + $output = Method::create('foobar')->setReturnType('mixed')->toString(); $this->assertEquals('public function foobar(): mixed { }', $output); - $output = _Method::create('foobar')->setReturnType('?string')->toString(); + $output = Method::create('foobar')->setReturnType('?string')->toString(); $this->assertEquals('public function foobar(): ?string { }', $output); @@ -69,7 +70,7 @@ public function testReturnType() public function testArguments() { - $output = _Method::create('foobar') + $output = Method::create('foobar') ->addArgument('foo') ->addArgument('bar', null, 'test') ->addArgument('baz', null, null) @@ -78,7 +79,7 @@ public function testArguments() { }', $output); - $output = _Method::create('foobar') + $output = Method::create('foobar') ->addArgument('foo', 'int') ->addArgument('bar', 'string') ->addArgument('baz', '?string', null) @@ -90,7 +91,7 @@ public function testArguments() public function testBody() { - $output = _Method::create('foobar') + $output = Method::create('foobar') ->setBody('return 2;') ->toString(' '); $this->assertEquals('public function foobar() diff --git a/src/Symfony/Component/Serializer/Tests/Builder/CodeGenerator/PropertyTest.php b/src/Symfony/Component/Serializer/Tests/Builder/CodeGenerator/PropertyTest.php index f98e796d04770..158d9d17ceff7 100644 --- a/src/Symfony/Component/Serializer/Tests/Builder/CodeGenerator/PropertyTest.php +++ b/src/Symfony/Component/Serializer/Tests/Builder/CodeGenerator/PropertyTest.php @@ -11,73 +11,74 @@ namespace Symfony\Component\Serializer\Tests\Builder\CodeGenerator; -use App\CodeGenerator\_Property; + use PHPUnit\Framework\TestCase; +use Symfony\Component\Serializer\Builder\CodeGenerator\Property; class PropertyTest extends TestCase { public function testNameOnly() { - $output = _Property::create('foobar')->toString(); + $output = Property::create('foobar')->toString(); $this->assertEquals('public $foobar;', $output); } public function testVisibility() { - $output = _Property::create('foobar')->setVisibility('private')->toString(); + $output = Property::create('foobar')->setVisibility('private')->toString(); $this->assertEquals('private $foobar;', $output); - $output = _Property::create('foobar')->setVisibility('protected')->toString(); + $output = Property::create('foobar')->setVisibility('protected')->toString(); $this->assertEquals('protected $foobar;', $output); // We dont care about logic - $output = _Property::create('foobar')->setVisibility('crazy')->toString(); + $output = Property::create('foobar')->setVisibility('crazy')->toString(); $this->assertEquals('crazy $foobar;', $output); } public function testType() { - $output = _Property::create('foobar')->setType('int')->toString(); + $output = Property::create('foobar')->setType('int')->toString(); $this->assertEquals('public int $foobar;', $output); - $output = _Property::create('foobar')->setType('mixed')->toString(); + $output = Property::create('foobar')->setType('mixed')->toString(); $this->assertEquals('public mixed $foobar;', $output); - $output = _Property::create('foobar')->setType('?string')->toString(); + $output = Property::create('foobar')->setType('?string')->toString(); $this->assertEquals('public ?string $foobar;', $output); } public function testDefaultValue() { - $output = _Property::create('foobar')->setDefaultValue('2')->toString(); + $output = Property::create('foobar')->setDefaultValue('2')->toString(); $this->assertEquals('public $foobar = \'2\';', $output); - $output = _Property::create('foobar')->setDefaultValue(2)->toString(); + $output = Property::create('foobar')->setDefaultValue(2)->toString(); $this->assertEquals('public $foobar = 2;', $output); - $output = _Property::create('foobar')->setDefaultValue(null)->toString(); + $output = Property::create('foobar')->setDefaultValue(null)->toString(); $this->assertEquals('public $foobar = NULL;', $output); } public function testTypeAndDefaultValue() { - $output = _Property::create('foobar')->setType('string')->setDefaultValue('2')->toString(); + $output = Property::create('foobar')->setType('string')->setDefaultValue('2')->toString(); $this->assertEquals('public string $foobar = \'2\';', $output); - $output = _Property::create('foobar')->setType('int')->setDefaultValue(2)->toString(); + $output = Property::create('foobar')->setType('int')->setDefaultValue(2)->toString(); $this->assertEquals('public int $foobar = 2;', $output); - $output = _Property::create('foobar')->setType('?int')->setDefaultValue(null)->toString(); + $output = Property::create('foobar')->setType('?int')->setDefaultValue(null)->toString(); $this->assertEquals('public ?int $foobar = NULL;', $output); // We dont care about logic here. - $output = _Property::create('foobar')->setType('int')->setDefaultValue('test')->toString(); + $output = Property::create('foobar')->setType('int')->setDefaultValue('test')->toString(); $this->assertEquals('public int $foobar = \'test\';', $output); } public function testComment() { - $output = _Property::create('foobar') + $output = Property::create('foobar') ->setType('string') ->setDefaultValue('2') ->setComment('This is a comment') diff --git a/src/Symfony/Component/Serializer/Tests/Builder/FixtureHelper.php b/src/Symfony/Component/Serializer/Tests/Builder/FixtureHelper.php index 1e1a4bf9e6c2d..1f5ca6f4e54b1 100644 --- a/src/Symfony/Component/Serializer/Tests/Builder/FixtureHelper.php +++ b/src/Symfony/Component/Serializer/Tests/Builder/FixtureHelper.php @@ -40,7 +40,7 @@ public static function getDefinitionExtractor(): DefinitionExtractor public static function getFixturesAndResultFiles(): iterable { $rootDir = \dirname(__DIR__).'/Fixtures/CustomNormalizer'; - $data = [ + return [ NoTypeHints\PublicProperties::class => $rootDir.'/NoTypeHints/ExpectedNormalizer/PublicProperties.php', NoTypeHints\ConstructorInjection::class => $rootDir.'/NoTypeHints/ExpectedNormalizer/ConstructorInjection.php', NoTypeHints\SetterInjection::class => $rootDir.'/NoTypeHints/ExpectedNormalizer/SetterInjection.php', @@ -59,10 +59,6 @@ public static function getFixturesAndResultFiles(): iterable FullTypeHints\ExtraSetter::class => $rootDir.'/FullTypeHints/ExpectedNormalizer/ExtraSetter.php', FullTypeHints\NonReadableProperty::class => $rootDir.'/FullTypeHints/ExpectedNormalizer/NonReadableProperty.php', ]; - - foreach ($data as $class => $normalizerFile) { - yield $class => [$class, $normalizerFile]; - } } private static function getPropertyInfoExtractor(): PropertyInfoExtractor diff --git a/src/Symfony/Component/Serializer/Tests/Builder/generateUpdatedFixtures.php b/src/Symfony/Component/Serializer/Tests/Builder/generateUpdatedFixtures.php index dd4008c113a3a..6539eed7ec728 100644 --- a/src/Symfony/Component/Serializer/Tests/Builder/generateUpdatedFixtures.php +++ b/src/Symfony/Component/Serializer/Tests/Builder/generateUpdatedFixtures.php @@ -32,7 +32,7 @@ function includeIfExists(string $file): bool echo \PHP_EOL; $i = 0; -foreach (FixtureHelper::getFixturesAndResultFiles() as [$class, $outputFile]) { +foreach (FixtureHelper::getFixturesAndResultFiles() as [$class => $outputFile]) { $definition = $definitionExtractor->getDefinition($class); $result = $builder->build($definition, $outputDir); $result->loadClass(); From aaccb9d70512036e63967d5bfe6023bfb3c60b22 Mon Sep 17 00:00:00 2001 From: Nyholm Date: Tue, 5 Dec 2023 22:37:07 +0100 Subject: [PATCH 03/13] fixed more tests --- .../DependencyInjection/Compiler/UnusedTagsPass.php | 1 + .../DependencyInjection/FrameworkExtension.php | 8 ++++++-- .../FrameworkBundle/Resources/config/property_info.php | 2 +- src/Symfony/Component/Serializer/Builder/BuildResult.php | 2 +- .../Serializer/Builder/CodeGenerator/ClassGenerator.php | 4 ++-- 5 files changed, 11 insertions(+), 6 deletions(-) diff --git a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Compiler/UnusedTagsPass.php b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Compiler/UnusedTagsPass.php index 6f53e57f069c6..65123b2110844 100644 --- a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Compiler/UnusedTagsPass.php +++ b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Compiler/UnusedTagsPass.php @@ -70,6 +70,7 @@ class UnusedTagsPass implements CompilerPassInterface 'monolog.logger', 'notifier.channel', 'property_info.access_extractor', + 'property_info.constructor_argument_type_extractor', 'property_info.initializable_extractor', 'property_info.list_extractor', 'property_info.type_extractor', diff --git a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php index 924d6893348e5..269a431d9cd6d 100644 --- a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php +++ b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php @@ -390,6 +390,10 @@ public function load(array $configs, ContainerBuilder $container): void if ($propertyInfoEnabled) { $this->registerPropertyInfoConfiguration($container, $loader); + } else { + // Remove services depending on PropertyInfo + $container->removeDefinition('serializer.auto_normalizer.definition_extractor'); + $container->removeDefinition('serializer.custom_normalizer_helper'); } if ($this->readConfigEnabled('lock', $container, $config['lock'])) { @@ -1942,14 +1946,14 @@ private function registerPropertyInfoConfiguration(ContainerBuilder $container, ) { $definition = $container->register('property_info.phpstan_extractor', PhpStanExtractor::class); $definition->addTag('property_info.type_extractor', ['priority' => -1000]); - $definition->addTag('property_info.property_info.constructor_argument_type_extractor'); + $definition->addTag('property_info.constructor_argument_type_extractor'); } if (ContainerBuilder::willBeAvailable('phpdocumentor/reflection-docblock', DocBlockFactoryInterface::class, ['symfony/framework-bundle', 'symfony/property-info'], true)) { $definition = $container->register('property_info.php_doc_extractor', PhpDocExtractor::class); $definition->addTag('property_info.description_extractor', ['priority' => -1000]); $definition->addTag('property_info.type_extractor', ['priority' => -1001]); - $definition->addTag('property_info.property_info.constructor_argument_type_extractor'); + $definition->addTag('property_info.constructor_argument_type_extractor'); } if ($container->getParameter('kernel.debug')) { diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/config/property_info.php b/src/Symfony/Bundle/FrameworkBundle/Resources/config/property_info.php index cf472432df942..6f84eef1d8773 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Resources/config/property_info.php +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/config/property_info.php @@ -47,7 +47,7 @@ ->tag('property_info.type_extractor', ['priority' => -1002]) ->tag('property_info.access_extractor', ['priority' => -1000]) ->tag('property_info.initializable_extractor', ['priority' => -1000]) - ->tag('property_info.property_info.constructor_argument_type_extractor') + ->tag('property_info.constructor_argument_type_extractor') ->alias(PropertyReadInfoExtractorInterface::class, 'property_info.reflection_extractor') ->alias(PropertyWriteInfoExtractorInterface::class, 'property_info.reflection_extractor') diff --git a/src/Symfony/Component/Serializer/Builder/BuildResult.php b/src/Symfony/Component/Serializer/Builder/BuildResult.php index 7080cd45362da..badd3e62b4e16 100644 --- a/src/Symfony/Component/Serializer/Builder/BuildResult.php +++ b/src/Symfony/Component/Serializer/Builder/BuildResult.php @@ -28,7 +28,7 @@ public function __construct( ) { } - public function loadClass() + public function loadClass(): void { require_once $this->filePath; } diff --git a/src/Symfony/Component/Serializer/Builder/CodeGenerator/ClassGenerator.php b/src/Symfony/Component/Serializer/Builder/CodeGenerator/ClassGenerator.php index 776191f2e244d..631de1b40257d 100644 --- a/src/Symfony/Component/Serializer/Builder/CodeGenerator/ClassGenerator.php +++ b/src/Symfony/Component/Serializer/Builder/CodeGenerator/ClassGenerator.php @@ -40,12 +40,12 @@ public function __construct(string $name = null, string $namespace = null) $this->namespace = $namespace; } - public function setExtends(?string $class) + public function setExtends(?string $class): void { $this->extends = $class; } - public function addImplements(string $class) + public function addImplements(string $class): void { $this->implements[] = $class; } From a130539f3c20e8719e0febe7137bbb5d8a5c9fc6 Mon Sep 17 00:00:00 2001 From: Nyholm Date: Tue, 5 Dec 2023 22:39:26 +0100 Subject: [PATCH 04/13] cs --- .../Tests/DependencyInjection/ConfigurationTest.php | 2 +- .../Tests/Builder/CodeGenerator/ClassGeneratorTest.php | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/ConfigurationTest.php b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/ConfigurationTest.php index d8925d01062bc..4672735b2b496 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/ConfigurationTest.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/ConfigurationTest.php @@ -615,7 +615,7 @@ protected static function getBundleDefaultConfig() 'enabled' => true, 'enable_attributes' => !class_exists(FullStack::class), 'mapping' => ['paths' => []], - 'auto_normalizer' => ['paths'=>[]], + 'auto_normalizer' => ['paths' => []], ], 'property_access' => [ 'enabled' => true, diff --git a/src/Symfony/Component/Serializer/Tests/Builder/CodeGenerator/ClassGeneratorTest.php b/src/Symfony/Component/Serializer/Tests/Builder/CodeGenerator/ClassGeneratorTest.php index 4d291d40febca..209be714d2ced 100644 --- a/src/Symfony/Component/Serializer/Tests/Builder/CodeGenerator/ClassGeneratorTest.php +++ b/src/Symfony/Component/Serializer/Tests/Builder/CodeGenerator/ClassGeneratorTest.php @@ -11,7 +11,6 @@ namespace Symfony\Component\Serializer\Tests\Builder\CodeGenerator; - use PHPUnit\Framework\TestCase; use Symfony\Component\Serializer\Builder\CodeGenerator\Attribute; use Symfony\Component\Serializer\Builder\CodeGenerator\ClassGenerator; From 94ebc00a683c2dd2a727d0ce2de8794bb38c939b Mon Sep 17 00:00:00 2001 From: Nyholm Date: Sun, 14 Jan 2024 16:10:01 -0800 Subject: [PATCH 05/13] Use nikic/PHP-Parser --- .../Builder/CodeGenerator/Attribute.php | 91 --- .../Builder/CodeGenerator/ClassGenerator.php | 138 ---- .../Builder/CodeGenerator/Method.php | 132 ---- .../Builder/CodeGenerator/Property.php | 105 --- .../Serializer/Builder/NormalizerBuilder.php | 693 +++++++++++++----- .../Builder/CodeGenerator/AttributeTest.php | 77 -- .../CodeGenerator/ClassGeneratorTest.php | 135 ---- .../Builder/CodeGenerator/Fixtures/Cat.php | 21 - .../Builder/CodeGenerator/Fixtures/Full.php | 40 - .../Builder/CodeGenerator/Fixtures/Person.php | 26 - .../Builder/CodeGenerator/MethodTest.php | 102 --- .../Builder/CodeGenerator/PropertyTest.php | 93 --- .../Component/Serializer/composer.json | 1 + 13 files changed, 508 insertions(+), 1146 deletions(-) delete mode 100644 src/Symfony/Component/Serializer/Builder/CodeGenerator/Attribute.php delete mode 100644 src/Symfony/Component/Serializer/Builder/CodeGenerator/ClassGenerator.php delete mode 100644 src/Symfony/Component/Serializer/Builder/CodeGenerator/Method.php delete mode 100644 src/Symfony/Component/Serializer/Builder/CodeGenerator/Property.php delete mode 100644 src/Symfony/Component/Serializer/Tests/Builder/CodeGenerator/AttributeTest.php delete mode 100644 src/Symfony/Component/Serializer/Tests/Builder/CodeGenerator/ClassGeneratorTest.php delete mode 100644 src/Symfony/Component/Serializer/Tests/Builder/CodeGenerator/Fixtures/Cat.php delete mode 100644 src/Symfony/Component/Serializer/Tests/Builder/CodeGenerator/Fixtures/Full.php delete mode 100644 src/Symfony/Component/Serializer/Tests/Builder/CodeGenerator/Fixtures/Person.php delete mode 100644 src/Symfony/Component/Serializer/Tests/Builder/CodeGenerator/MethodTest.php delete mode 100644 src/Symfony/Component/Serializer/Tests/Builder/CodeGenerator/PropertyTest.php diff --git a/src/Symfony/Component/Serializer/Builder/CodeGenerator/Attribute.php b/src/Symfony/Component/Serializer/Builder/CodeGenerator/Attribute.php deleted file mode 100644 index a0a82a4f48ef3..0000000000000 --- a/src/Symfony/Component/Serializer/Builder/CodeGenerator/Attribute.php +++ /dev/null @@ -1,91 +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\Builder\CodeGenerator; - -/** - * Represents a new PHP attribute. - * - * @internal - * - * @author Tobias Nyholm - */ -class Attribute -{ - private string $name; - /** @var array */ - private array $parameters = []; - - public static function create(string $name): self - { - $method = new self(); - $method->setName($name); - - return $method; - } - - public function setName(string $name): self - { - $this->name = $name; - - return $this; - } - - public function addParameter(?string $name, self|int|string|bool|float|null|array $value): self - { - if (null === $name) { - $this->parameters[] = $value; - } else { - $this->parameters[$name] = $value; - } - - return $this; - } - - public function toString(bool $nested = false): string - { - $parameters = []; - foreach ($this->parameters as $name => $value) { - $parameter = ''; - if (\is_string($name)) { - $parameter .= $name.': '; - } - $parameters[] = $parameter.$this->getValue($value); - } - - if ([] === $parameters && !$nested) { - $output = $this->name; - } else { - $output = sprintf('%s(%s)', $this->name, implode(', ', $parameters)); - } - - if ($nested) { - return 'new '.$output; - } - - return sprintf('#[%s]', $output); - } - - private function getValue($value): string - { - if (\is_array($value)) { - $value = sprintf('[%s]', implode(', ', array_map(fn ($value) => $this->getValue($value), $value))); - } elseif ($value instanceof self) { - $value = $value->toString(true); - } elseif (\is_string($value)) { - $value = '"'.$value.'"'; - } else { - $value = var_export($value, true); - } - - return $value; - } -} diff --git a/src/Symfony/Component/Serializer/Builder/CodeGenerator/ClassGenerator.php b/src/Symfony/Component/Serializer/Builder/CodeGenerator/ClassGenerator.php deleted file mode 100644 index 631de1b40257d..0000000000000 --- a/src/Symfony/Component/Serializer/Builder/CodeGenerator/ClassGenerator.php +++ /dev/null @@ -1,138 +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\Builder\CodeGenerator; - -/** - * Generate a new PHP class. - * - * @internal - * - * @author Tobias Nyholm - */ -class ClassGenerator -{ - private ?string $name; - private ?string $namespace; - private ?string $extends = null; - private array $imports = []; - private array $implements = []; - /** @var Property[] */ - private array $properties = []; - /** @var Method[] */ - private array $methods = []; - /** @var Attribute[] */ - private array $attributes = []; - private ?string $fileComment = null; - private ?string $classComment = null; - - public function __construct(string $name = null, string $namespace = null) - { - $this->name = $name; - $this->namespace = $namespace; - } - - public function setExtends(?string $class): void - { - $this->extends = $class; - } - - public function addImplements(string $class): void - { - $this->implements[] = $class; - } - - public function addAttribute(Attribute $attribute): void - { - $this->attributes[] = $attribute; - } - - public function addMethod(Method $method): void - { - $this->methods[] = $method; - } - - public function addProperty(Property $property): void - { - $this->properties[] = $property; - } - - public function addImport(string $class): void - { - $this->imports[] = $class; - } - - public function setFileComment(?string $fileComment): void - { - $this->fileComment = $fileComment; - } - - public function setClassComment(?string $classComment): void - { - $this->classComment = $classComment; - } - - public function toString(string $indentation = ' '): string - { - $output = 'fileComment) { - $lines = explode(\PHP_EOL, $this->fileComment); - $output .= sprintf('/*'.\PHP_EOL.' * %s'.\PHP_EOL.' */'.\PHP_EOL.\PHP_EOL, implode(\PHP_EOL.' * ', $lines)); - } - - if ($this->namespace) { - $output .= 'namespace '.$this->namespace.';'.\PHP_EOL.\PHP_EOL; - } - - if ([] !== $this->imports) { - foreach ($this->imports as $import) { - $output .= 'use '.$import.';'.\PHP_EOL; - } - $output .= \PHP_EOL; - } - - if ($this->classComment) { - $lines = explode(\PHP_EOL, $this->classComment); - $output .= sprintf('/**'.\PHP_EOL.' * %s'.\PHP_EOL.' */'.\PHP_EOL, implode(\PHP_EOL.' * ', $lines)); - } - - foreach ($this->attributes as $attribute) { - $output .= $attribute->toString().\PHP_EOL; - } - - $output .= 'class '.$this->name; - if ($this->extends) { - $output .= ' extends '.$this->extends; - } - if ($this->implements) { - $output .= ' implements '.implode(', ', $this->implements); - } - $output .= \PHP_EOL.'{'.\PHP_EOL; - - if ([] !== $this->properties) { - foreach ($this->properties as $property) { - $lines = explode(\PHP_EOL, $property->toString()); - $output .= $indentation.implode(\PHP_EOL.$indentation, $lines).\PHP_EOL; - } - $output .= \PHP_EOL; - } - - foreach ($this->methods as $method) { - $lines = explode(\PHP_EOL, $method->toString($indentation)); - $output .= $indentation.implode(\PHP_EOL.$indentation, $lines).\PHP_EOL.\PHP_EOL; - } - - $output .= '}'.\PHP_EOL; - - return $output; - } -} diff --git a/src/Symfony/Component/Serializer/Builder/CodeGenerator/Method.php b/src/Symfony/Component/Serializer/Builder/CodeGenerator/Method.php deleted file mode 100644 index 63453fc7b3550..0000000000000 --- a/src/Symfony/Component/Serializer/Builder/CodeGenerator/Method.php +++ /dev/null @@ -1,132 +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\Builder\CodeGenerator; - -/** - * Represents a new PHP method. - * - * @internal - * - * @author Tobias Nyholm - */ -class Method -{ - private string $name; - private string $visibility = 'public'; - private ?string $returnType = null; - private array $arguments = []; - private ?string $body = ''; - private array $attributes = []; - private ?string $comment = null; - - public static function create(string $name): self - { - $method = new self(); - $method->setName($name); - - return $method; - } - - public function setName(string $name): self - { - $this->name = $name; - - return $this; - } - - public function setVisibility(string $visibility): self - { - $this->visibility = $visibility; - - return $this; - } - - public function setReturnType(?string $returnType): self - { - $this->returnType = $returnType; - - return $this; - } - - public function addArgument(string $name, string $type = null, $default = null): self - { - $this->arguments[$name] = [$type, $default, 3 === \func_num_args()]; - - return $this; - } - - public function setBody(?string $body): self - { - $this->body = $body; - - return $this; - } - - public function addAttribute(Attribute $attribute): self - { - $this->attributes[] = $attribute; - - return $this; - } - - public function setComment(?string $comment): self - { - $this->comment = $comment; - - return $this; - } - - public function toString(string $indentation = ''): string - { - $arguments = []; - foreach ($this->arguments as $name => [$type, $default, $hasDefault]) { - $argument = '$'.$name; - - if ($type) { - $argument = sprintf('%s %s', $type, $argument); - } - if ($hasDefault) { - $argument = sprintf('%s = %s', $argument, [] === $default ? '[]' : var_export($default, true)); - } - $arguments[] = $argument; - } - - $output = ''; - if ($this->comment) { - $lines = explode(\PHP_EOL, $this->comment); - $output .= sprintf('/**'.\PHP_EOL.' * %s'.\PHP_EOL.' */'.\PHP_EOL, implode(\PHP_EOL.' * ', $lines)); - } - - if ($this->attributes) { - foreach ($this->attributes as $attribute) { - $output .= $attribute->toString().\PHP_EOL; - } - } - - $output .= sprintf('%s function %s(%s)', $this->visibility, $this->name, implode(', ', $arguments)); - if ($this->returnType) { - $output = sprintf('%s: %s', $output, $this->returnType); - } - - if (null === $this->body) { - return sprintf('abstract %s;', $output); - } - - if ('' === $this->body) { - return sprintf('%s'.\PHP_EOL.'{'.\PHP_EOL.'}', $output); - } - - $lines = explode(\PHP_EOL, $this->body); - - return sprintf('%s'.\PHP_EOL.'{'.\PHP_EOL.'%s'.\PHP_EOL.'}', $output, $indentation.implode(\PHP_EOL.$indentation, $lines)); - } -} diff --git a/src/Symfony/Component/Serializer/Builder/CodeGenerator/Property.php b/src/Symfony/Component/Serializer/Builder/CodeGenerator/Property.php deleted file mode 100644 index deb7101eaf242..0000000000000 --- a/src/Symfony/Component/Serializer/Builder/CodeGenerator/Property.php +++ /dev/null @@ -1,105 +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\Builder\CodeGenerator; - -/** - * Represents a new PHP property. - * - * @internal - * - * @author Tobias Nyholm - */ -class Property -{ - private string $name; - private string $visibility = 'public'; - private ?string $type = null; - private $defaultValue; - private bool $hasDefaultValue = false; - private array $attributes = []; - private ?string $comment = null; - - public static function create(string $name): self - { - $property = new self(); - $property->setName($name); - - return $property; - } - - public function setName(string $name): self - { - $this->name = $name; - - return $this; - } - - public function setVisibility(string $visibility): self - { - $this->visibility = $visibility; - - return $this; - } - - public function setType(?string $type): self - { - $this->type = $type; - - return $this; - } - - public function setDefaultValue($defaultValue): self - { - $this->defaultValue = $defaultValue; - $this->hasDefaultValue = true; - - return $this; - } - - public function addAttribute(Attribute $attribute): self - { - $this->attributes[] = $attribute; - - return $this; - } - - public function setComment(?string $comment): self - { - $this->comment = $comment; - - return $this; - } - - public function toString(): string - { - $output = ''; - if ($this->comment) { - $lines = explode(\PHP_EOL, $this->comment); - $output .= sprintf('/**'.\PHP_EOL.' * %s'.\PHP_EOL.' */'.\PHP_EOL, implode(\PHP_EOL.' * ', $lines)); - } - - if ($this->attributes) { - foreach ($this->attributes as $attribute) { - $output .= $attribute->toString().\PHP_EOL; - } - } - - $lastLine = sprintf('%s $%s', $this->type, $this->name); - $lastLine = sprintf('%s %s', $this->visibility, trim($lastLine)); - if ($this->hasDefaultValue) { - $lastLine = sprintf('%s = %s', $lastLine, [] === $this->defaultValue ? '[]' : var_export($this->defaultValue, true)); - } - $lastLine .= ';'; - - return $output.$lastLine; - } -} diff --git a/src/Symfony/Component/Serializer/Builder/NormalizerBuilder.php b/src/Symfony/Component/Serializer/Builder/NormalizerBuilder.php index 473bd7dfb2a81..9c03a6af50b8f 100644 --- a/src/Symfony/Component/Serializer/Builder/NormalizerBuilder.php +++ b/src/Symfony/Component/Serializer/Builder/NormalizerBuilder.php @@ -11,14 +11,18 @@ namespace Symfony\Component\Serializer\Builder; -use Symfony\Component\Serializer\Builder\CodeGenerator\ClassGenerator; -use Symfony\Component\Serializer\Builder\CodeGenerator\Method; -use Symfony\Component\Serializer\Builder\CodeGenerator\Property; +use PhpParser\Builder\Class_; +use PhpParser\Builder\Namespace_; +use PhpParser\Node\Expr; +use PhpParser\ParserFactory; use Symfony\Component\Serializer\Exception\DenormalizingUnionFailedException; use Symfony\Component\Serializer\Normalizer\DenormalizerAwareInterface; use Symfony\Component\Serializer\Normalizer\DenormalizerInterface; use Symfony\Component\Serializer\Normalizer\NormalizerAwareInterface; use Symfony\Component\Serializer\Normalizer\NormalizerInterface; +use PhpParser\BuilderFactory; +use PhpParser\PrettyPrinter; +use PhpParser\Node; /** * The main class to create a new Normalizer from a ClassDefinition. @@ -29,18 +33,37 @@ */ class NormalizerBuilder { + private PrettyPrinter\Standard $printer; + private BuilderFactory $factory; + + public function __construct() + { + if (!class_exists(ParserFactory::class)) { + throw new \LogicException(sprintf('You cannot use "%s" as the "nikic/php-parser" package is not installed. Try running "composer require nikic/php-parser".', static::class)); + } + + $this->factory = new BuilderFactory; + $this->printer = new PrettyPrinter\Standard(); + } + public function build(ClassDefinition $definition, string $outputDir): BuildResult { - @mkdir($outputDir, 0777, true); - $generator = new ClassGenerator($definition->getNewClassName(), $definition->getNewNamespace()); + $namespace = $this->factory->namespace($definition->getNewNamespace()) + ->addStmt($this->factory->use($definition->getNamespaceAndClass())); - $generator->addImport($definition->getNamespaceAndClass()); - $this->addRequiredMethods($generator, $definition); - $this->addNormailizeMethod($generator, $definition); - $this->addDenormailizeMethod($generator, $definition); + $class = $this->factory->class($definition->getNewClassName()); + $this->addRequiredMethods($definition, $namespace, $class); + $this->addNormailizeMethod($definition, $namespace, $class); + $this->addDenormailizeMethod($definition, $namespace, $class); + // Add class to namespace + $namespace->addStmt($class); + $node = $namespace->getNode(); + + // Print + @mkdir($outputDir, 0777, true); $outputFile = $outputDir.'/'.$definition->getNewClassName().'.php'; - file_put_contents($outputFile, $generator->toString()); + file_put_contents($outputFile, $this->printer->prettyPrintFile([$node])); return new BuildResult( $outputFile, @@ -52,239 +75,462 @@ public function build(ClassDefinition $definition, string $outputDir): BuildResu /** * Generate a private helper class to normalize subtypes. */ - private function generateNormalizeChildMethod(ClassGenerator $generator): void + private function generateNormalizeChildMethod(Namespace_ $namespace, Class_ $class): void { - $generator->addImport(NormalizerAwareInterface::class); - $generator->addImplements('NormalizerAwareInterface'); - - $generator->addProperty(Property::create('normalizer')->setType('null|NormalizerInterface')->setDefaultValue(null)->setVisibility('private')); + $namespace->addStmt($this->factory->use(NormalizerAwareInterface::class)); + $class->implement('NormalizerAwareInterface'); + $class->addStmt($this->factory->property('normalizer') + ->makePrivate() + ->setType('null|NormalizerInterface') + ->setDefault(null)); // public function setNormalizer(NormalizerInterface $normalizer): void; - $generator->addMethod(Method::create('setNormalizer') - ->addArgument('normalizer', 'NormalizerInterface') + $class->addStmt($this->factory->method('setNormalizer') + ->makePublic() + ->addParam($this->factory->param('normalizer')->setType('NormalizerInterface')) ->setReturnType('void') - ->setBody('$this->normalizer = $normalizer;') + ->addStmt( + new Node\Stmt\Expression( + new Node\Expr\Assign( + $this->factory->propertyFetch(new Node\Expr\Variable('this'), 'normalizer'), + new Node\Expr\Variable('normalizer') + ) + ) + ) ); - $generator->addMethod(Method::create('normalizeChild') - ->setVisibility('private') - ->addArgument('object', 'mixed') - ->addArgument('format', '?string') - ->addArgument('context', 'array') - ->addArgument('canBeIterable', 'bool') - ->setReturnType('mixed') - ->setBody(<< \$this->normalizeChild(\$item, \$format, \$context, true), \$object); -} - -return \$this->normalizer->normalize(\$object, \$format, \$context); - -PHP - ) + // private function normalizeChild(mixed $object, ?string $format, array $context, bool $canBeIterable): mixed; + $class->addStmt($this->factory->method('normalizeChild') + ->makePrivate() + ->addParam($this->factory->param('object')->setType('mixed')) + ->addParam($this->factory->param('format')->setType('?string')) + ->addParam($this->factory->param('context')->setType('array')) + ->addParam($this->factory->param('canBeIterable')->setType('bool')) + ->setReturnType('mixed') + ->addStmts([ + new Node\Stmt\If_( + new Node\Expr\BinaryOp\BooleanOr( + $this->factory->funcCall(new Node\Name('is_scalar'), [ + new Node\Arg(new Node\Expr\Variable('object')), + ]), + new Node\Expr\BinaryOp\Identical( + new Node\Expr\ConstFetch(new Node\Name('null')), + new Node\Expr\Variable('object') + ) + ), + [ + 'stmts' => [ + new Node\Stmt\Return_(new Node\Expr\Variable('object')), + ], + ] + ), + // new line + new Node\Stmt\If_( + new Node\Expr\BinaryOp\BooleanAnd( + new Node\Expr\Variable('canBeIterable'), + $this->factory->funcCall(new Node\Name('is_iterable'), [ + new Node\Arg(new Node\Expr\Variable('object')), + ]) + ), + [ + 'stmts' => [ + new Node\Stmt\Return_( + new Node\Expr\FuncCall( + new Node\Name('array_map'), + [ + new Node\Arg( + new Node\Expr\Closure([ + 'params' => [new Node\Param(new Node\Expr\Variable('item'))], + 'stmts' => [ + new Node\Stmt\Return_( + $this->factory->methodCall( + new Node\Expr\Variable('this'), + 'normalizeChild', + [ + new Node\Arg(new Node\Expr\Variable('item')), + new Node\Arg(new Node\Expr\Variable('format')), + new Node\Arg(new Node\Expr\Variable('context')), + new Node\Arg(new Node\Expr\ConstFetch(new Node\Name('true'))), + ] + ) + ), + ], + ]) + ), + new Node\Arg(new Node\Expr\Variable('object')), + ] + ) + ), + ], + ] + ), + // new line + new Node\Stmt\Return_( + $this->factory->methodCall( + $this->factory->propertyFetch(new Node\Expr\Variable('this'), 'normalizer'), + 'normalize', + [ + new Node\Arg(new Node\Expr\Variable('object')), + new Node\Arg(new Node\Expr\Variable('format')), + new Node\Arg(new Node\Expr\Variable('context')), + ] + ) + ), + ]) ); } /** * Generate a private helper class to de-normalize subtypes. */ - private function generateDenormalizeChildMethod(ClassGenerator $generator): void + private function generateDenormalizeChildMethod(Namespace_ $namespace, Class_ $class): void { - $generator->addImport(DenormalizingUnionFailedException::class); - $generator->addImport(DenormalizerAwareInterface::class); - $generator->addImplements('DenormalizerAwareInterface'); - - $generator->addProperty(Property::create('denormalizer')->setType('null|DenormalizerInterface')->setDefaultValue(null)->setVisibility('private')); + $namespace->addStmt($this->factory->use(DenormalizingUnionFailedException::class)); + $namespace->addStmt($this->factory->use(DenormalizerAwareInterface::class)); + $class->implement('DenormalizerAwareInterface'); + $class->addStmt($this->factory->property('denormalizer') + ->makePrivate() + ->setType('null|DenormalizerInterface') + ->setDefault(null)); // public function setNormalizer(NormalizerInterface $normalizer): void; - $generator->addMethod(Method::create('setDenormalizer') - ->addArgument('denormalizer', 'DenormalizerInterface') + $class->addStmt($this->factory->method('setDenormalizer') + ->makePublic() + ->addParam($this->factory->param('denormalizer')->setType('DenormalizerInterface')) ->setReturnType('void') - ->setBody('$this->denormalizer = $denormalizer;') + ->addStmt( + new Node\Stmt\Expression( + new Node\Expr\Assign( + $this->factory->propertyFetch(new Node\Expr\Variable('this'), 'denormalizer'), + new Node\Expr\Variable('denormalizer') + ) + ) + ) ); - $generator->addMethod(Method::create('denormalizeChild') - ->setVisibility('private') - ->addArgument('data', 'mixed') - ->addArgument('type', 'string') - ->addArgument('format', '?string') - ->addArgument('context', 'array') - ->addArgument('canBeIterable', 'bool') - ->setReturnType('mixed') - ->setBody(<< \$this->denormalizeChild(\$item, \$type, \$format, \$context, true), \$data); -} - -return \$this->denormalizer->denormalize(\$data, \$type, \$format, \$context); -PHP - ) + // private function denormalizeChild(mixed $data, string $type, ?string $format, array $context, bool $canBeIterable): mixed; + $class->addStmt($this->factory->method('denormalizeChild') + ->makePrivate() + ->addParam($this->factory->param('data')->setType('mixed')) + ->addParam($this->factory->param('type')->setType('string')) + ->addParam($this->factory->param('format')->setType('?string')) + ->addParam($this->factory->param('context')->setType('array')) + ->addParam($this->factory->param('canBeIterable')->setType('bool')) + ->setReturnType('mixed') + ->addStmts([ + new Node\Stmt\If_( + new Node\Expr\BinaryOp\BooleanOr( + $this->factory->funcCall(new Node\Name('is_scalar'), [ + new Node\Arg(new Node\Expr\Variable('data')), + ]), + new Node\Expr\BinaryOp\Identical( + new Node\Expr\ConstFetch(new Node\Name('null')), + new Node\Expr\Variable('data') + ) + ), + [ + 'stmts' => [ + new Node\Stmt\Return_(new Node\Expr\Variable('data')), + ], + ] + ), + // new line + new Node\Stmt\If_( + new Node\Expr\BinaryOp\BooleanAnd( + new Node\Expr\Variable('canBeIterable'), + $this->factory->funcCall(new Node\Name('is_iterable'), [ + new Node\Arg(new Node\Expr\Variable('data')), + ]) + ), + [ + 'stmts' => [ + new Node\Stmt\Return_( + new Node\Expr\FuncCall( + new Node\Name('array_map'), + [ + new Node\Arg( + new Node\Expr\Closure([ + 'params' => [new Node\Param(new Node\Expr\Variable('item'))], + 'stmts' => [ + new Node\Stmt\Return_( + $this->factory->methodCall( + new Node\Expr\Variable('this'), + 'denormalizeChild', + [ + new Node\Arg(new Node\Expr\Variable('item')), + new Node\Arg(new Node\Expr\Variable('type')), + new Node\Arg(new Node\Expr\Variable('format')), + new Node\Arg(new Node\Expr\Variable('context')), + new Node\Arg(new Node\Expr\ConstFetch(new Node\Name('true'))), + ] + ) + ), + ], + ]) + ), + new Node\Arg(new Node\Expr\Variable('data')), + ] + ) + ), + ], + ] + ), + // new line + new Node\Stmt\Return_( + $this->factory->methodCall( + $this->factory->propertyFetch(new Node\Expr\Variable('this'), 'denormalizer'), + 'denormalize', + [ + new Node\Arg(new Node\Expr\Variable('data')), + new Node\Arg(new Node\Expr\Variable('type')), + new Node\Arg(new Node\Expr\Variable('format')), + new Node\Arg(new Node\Expr\Variable('context')), + ] + ) + ), + ]) ); } /** * Add methods required by NormalizerInterface and DenormalizerInterface. */ - private function addRequiredMethods(ClassGenerator $generator, ClassDefinition $definition): void + private function addRequiredMethods(ClassDefinition $definition, Namespace_ $namespace, Class_ $class): void { - $generator->addImport(NormalizerInterface::class); - $generator->addImport(DenormalizerInterface::class); - $generator->addImplements('NormalizerInterface'); - $generator->addImplements('DenormalizerInterface'); + $namespace + ->addStmt($this->factory->use(NormalizerInterface::class)) + ->addStmt($this->factory->use(DenormalizerInterface::class)); + + $class->implement('NormalizerInterface', 'DenormalizerInterface'); // public function getSupportedTypes(?string $format): array; - $generator->addMethod(Method::create('getSupportedTypes') - ->addArgument('format', '?string') + $class->addStmt($this->factory->method('getSupportedTypes') + ->makePublic() + ->addParam($this->factory->param('format')->setType('?string')) ->setReturnType('array') - ->setBody(sprintf('return [%s::class => true];', $definition->getSourceClassName())) + ->addStmt(new Node\Stmt\Return_(new Node\Expr\Array_([ + new Node\ArrayItem( + new Node\Expr\ConstFetch(new Node\Name('true')), + new Node\Expr\ClassConstFetch(new Node\Name($definition->getSourceClassName()), 'class') + ), + ]))) ); // public function supportsNormalization(mixed $data, string $format = null, array $context = []): bool; - $generator->addMethod(Method::create('supportsNormalization') - ->addArgument('data', 'mixed') - ->addArgument('format', '?string', null) - ->addArgument('context', 'array', []) + $class->addStmt($this->factory->method('supportsNormalization') + ->makePublic() + ->addParam($this->factory->param('data')->setType('mixed')) + ->addParam($this->factory->param('format')->setType('string')->setDefault(null)) + ->addParam($this->factory->param('context')->setType('array')->setDefault([])) ->setReturnType('bool') - ->setBody(sprintf('return $data instanceof %s;', $definition->getSourceClassName())) + ->addStmt(new Node\Stmt\Return_(new Node\Expr\Instanceof_( + new Node\Expr\Variable('data'), + new Node\Name($definition->getSourceClassName()) + ))) ); // public function supportsDenormalization(mixed $data, string $type, string $format = null, array $context = []): bool; - $generator->addMethod(Method::create('supportsDenormalization') - ->addArgument('data', 'mixed') - ->addArgument('type', 'string') - ->addArgument('format', '?string', null) - ->addArgument('context', 'array', []) + $class->addStmt($this->factory->method('supportsDenormalization') + ->makePublic() + ->addParam($this->factory->param('data')->setType('mixed')) + ->addParam($this->factory->param('type')->setType('string')) + ->addParam($this->factory->param('format')->setType('string')->setDefault(null)) + ->addParam($this->factory->param('context')->setType('array')->setDefault([])) ->setReturnType('bool') - ->setBody(sprintf('return $type === %s::class;', $definition->getSourceClassName())) + ->addStmt(new Node\Stmt\Return_(new Node\Expr\BinaryOp\Identical( + new Node\Expr\Variable('type'), + new Node\Expr\ClassConstFetch(new Node\Name($definition->getSourceClassName()), 'class') + ))) ); } - private function addDenormailizeMethod(ClassGenerator $generator, ClassDefinition $definition): void + private function addDenormailizeMethod(ClassDefinition $definition, Namespace_ $namespace, Class_ $class): void { $needsChildDenormalizer = false; - $preCreateObject = ''; + $body = []; if (ClassDefinition::CONSTRUCTOR_NONE === $definition->getConstructorType()) { - $body = sprintf('$output = new %s();', $definition->getSourceClassName()).\PHP_EOL; + $body[] = new Node\Stmt\Expression(new Node\Expr\Assign( + new Node\Expr\Variable('output'), + new Node\Expr\New_( + new Node\Name($definition->getSourceClassName()) + ) + )); } elseif (ClassDefinition::CONSTRUCTOR_PUBLIC !== $definition->getConstructorType()) { - $body = sprintf('$output = (new \\ReflectionClass(%s::class))->newInstanceWithoutConstructor();', $definition->getSourceClassName()).\PHP_EOL; + $body[] = new Node\Stmt\Expression(new Node\Expr\Assign( + new Node\Expr\Variable('output'), + $this->factory->methodCall( + new Node\Expr\New_( + new Node\Name\FullyQualified('ReflectionClass'), + [new Node\Arg(new Node\Expr\ClassConstFetch(new Node\Name($definition->getSourceClassName()), 'class'))] + ), + 'newInstanceWithoutConstructor' + ))); } else { - $body = sprintf('$output = new %s(', $definition->getSourceClassName()).\PHP_EOL; + $constructorArguments = []; foreach ($definition->getConstructorArguments() as $i => $propertyDefinition) { - $body .= ' '; - $variable = sprintf('$data[\'%s\']', $propertyDefinition->getNormalizedName()); + $variable = new Node\Expr\ArrayDimFetch(new Node\Expr\Variable('data'), new Node\Scalar\String_($propertyDefinition->getNormalizedName())); $targetClasses = $propertyDefinition->getNonPrimitiveTypes(); $canBeIterable = $propertyDefinition->isCollection(); if ([] === $targetClasses && $propertyDefinition->hasConstructorDefaultValue()) { - $variable .= ' ?? '.var_export($propertyDefinition->getConstructorDefaultValue(), true); - } elseif ([] !== $targetClasses) { - $needsChildDenormalizer = true; - $printedCanBeIterable = $canBeIterable ? 'true' : 'false'; - $tempVariableName = '$argument'.$i; - - if (\count($targetClasses) > 1) { - $variableOutput = $this->generateCodeToDeserializeMultiplePossibleClasses($targetClasses, $printedCanBeIterable, $tempVariableName, $variable, $propertyDefinition->getNormalizedName(), $definition->getNamespaceAndClass()); - } else { - $variableOutput = <<denormalizeChild($variable, \\{$targetClasses[0]}::class, \$format, \$context, $printedCanBeIterable); - -PHP; - } - - if ($propertyDefinition->hasConstructorDefaultValue()) { - $export = var_export($propertyDefinition->getConstructorDefaultValue(), true); - $variableOutput = <<getNormalizedName()}', \$data)) { - {$tempVariableName} = {$export}; -} else { - {$variableOutput} -} -PHP; - } + $constructorArguments[] = new Node\Arg(new Node\Expr\BinaryOp\Coalesce( + $variable, + $this->factory->val($propertyDefinition->getConstructorDefaultValue()) + )); + continue; + } elseif ([] === $targetClasses) { + $constructorArguments[] = new Node\Arg(new Node\Expr\Variable($variable)); + continue; + } + + $needsChildDenormalizer = true; + $tempVariableName = 'argument'.$i; + + if (\count($targetClasses) > 1) { + $variableOutput = $this->generateCodeToDeserializeMultiplePossibleClasses($targetClasses, $canBeIterable, $tempVariableName, $variable, $propertyDefinition->getNormalizedName(), $definition->getNamespaceAndClass()); + } else { + $variableOutput = [ + new Node\Stmt\Expression( + new Node\Expr\Assign( + new Node\Expr\Variable($tempVariableName), + $this->factory->methodCall( + new Node\Expr\Variable('this'), + 'denormalizeChild', + [ + new Node\Arg($variable), + new Node\Arg(new Node\Expr\ClassConstFetch(new Node\Name($targetClasses[0]), 'class')), + new Node\Arg(new Node\Expr\Variable('format')), + new Node\Arg(new Node\Expr\Variable('context')), + new Node\Arg(new Expr\ConstFetch(new Node\Name($canBeIterable ? 'true' : 'false'))), + ] + ) + ) + ), + ]; + } - // Make sure we continue to reference the temp var - $variable = $tempVariableName; - $preCreateObject .= $variableOutput; + if ($propertyDefinition->hasConstructorDefaultValue()) { + $variableOutput = [new Node\Stmt\If_(new Expr\BooleanNot( + $this->factory->funcCall('array_key_exists', [ + new Node\Arg(new Node\Scalar\String_($propertyDefinition->getNormalizedName())), + new Node\Arg(new Node\Expr\Variable('data')), + ]) + ), [ + 'stmts' => [ + new Node\Stmt\Expression(new Node\Expr\Assign( + new Node\Expr\Variable($tempVariableName), + $this->factory->val($propertyDefinition->getConstructorDefaultValue()) + )), + ], + 'else' => $variableOutput + ] + )]; } - $body .= $variable.','.\PHP_EOL; + // Add $variableOutput to the end of $body + $body = array_merge($body, $variableOutput); + + $constructorArguments[] = new Node\Arg(new Node\Expr\Variable($tempVariableName)); } - $body .= ');'.\PHP_EOL; + $body[] = new Node\Stmt\Expression(new Node\Expr\Assign( + new Node\Expr\Variable('output'), + new Node\Expr\New_( + new Node\Name($definition->getSourceClassName()), + $constructorArguments + ), + )); } + // Start working with non-constructor properties $i = 0; foreach ($definition->getDefinitions() as $propertyDefinition) { if (!$propertyDefinition->isWriteable() || $propertyDefinition->isConstructorArgument()) { continue; } - $variable = sprintf('$data[\'%s\']', $propertyDefinition->getNormalizedName()); - $accessor = ''; + $tempVariableName = null; + $variableOutput = []; + + $variable = new Node\Expr\ArrayDimFetch(new Node\Expr\Variable('data'), new Node\Scalar\String_($propertyDefinition->getNormalizedName())); $targetClasses = $propertyDefinition->getNonPrimitiveTypes(); if ([] !== $targetClasses) { $needsChildDenormalizer = true; - $printedCanBeIterable = $propertyDefinition->isCollection() ? 'true' : 'false'; - $tempVariableName = '$setter'.$i++; + $tempVariableName = 'setter'.$i++; if (\count($targetClasses) > 1) { - $accessor .= $this->generateCodeToDeserializeMultiplePossibleClasses($targetClasses, $printedCanBeIterable, $tempVariableName, $variable, $propertyDefinition->getNormalizedName(), $definition->getNamespaceAndClass()); + $variableOutput = $this->generateCodeToDeserializeMultiplePossibleClasses($targetClasses, $propertyDefinition->isCollection(), $tempVariableName, $variable, $propertyDefinition->getNormalizedName(), $definition->getNamespaceAndClass()); } else { - $accessor .= <<denormalizeChild($variable, \\{$targetClasses[0]}::class, \$format, \$context, $printedCanBeIterable); - -PHP; + $variableOutput = [ + new Node\Stmt\Expression( + new Node\Expr\Assign( + new Node\Expr\Variable($tempVariableName), + $this->factory->methodCall( + new Node\Expr\Variable('this'), + 'denormalizeChild', + [ + new Node\Arg($variable), + new Node\Arg(new Node\Expr\ClassConstFetch(new Node\Name($targetClasses[0]), 'class')), + new Node\Arg(new Node\Expr\Variable('format')), + new Node\Arg(new Node\Expr\Variable('context')), + new Node\Arg(new Expr\ConstFetch(new Node\Name($propertyDefinition->isCollection() ? 'true' : 'false'))), + ] + ) + ) + ), + ]; } - $accessor .= ' '; - // Make sure we continue to reference the temp var - $variable = $tempVariableName; + } + $result = $tempVariableName === null ? $variable : new Node\Expr\Variable($tempVariableName); if (null !== $method = $propertyDefinition->getSetterName()) { - $accessor .= sprintf('$output->%s(%s);', $method, $variable); + $variableOutput[] = new Node\Stmt\Expression(new Node\Expr\MethodCall( + new Node\Expr\Variable('output'), + $method, + [new Node\Arg($result)] + )); } else { - $accessor .= sprintf('$output->%s = %s;', $propertyDefinition->getPropertyName(), $variable); + $variableOutput[] = new Node\Stmt\Expression(new Node\Expr\Assign( + new Node\Expr\PropertyFetch(new Node\Expr\Variable('output'), $propertyDefinition->getPropertyName()), + $result + )); } - $body .= <<getNormalizedName()}', \$data)) { - $accessor -} - -PHP; + $body[] = new Node\Stmt\If_( + $this->factory->funcCall('array_key_exists', [ + new Node\Arg(new Node\Scalar\String_($propertyDefinition->getNormalizedName())), + new Node\Arg(new Node\Expr\Variable('data')), + ]), + ['stmts' => $variableOutput] + ); } - $body .= \PHP_EOL.'return $output;'; - - $generator->addMethod(Method::create('denormalize') - ->addArgument('data', 'mixed') - ->addArgument('type', 'string') - ->addArgument('format', '?string', null) - ->addArgument('context', 'array', []) + $class->addStmt($this->factory->method('denormalize') + ->makePublic() + ->addParam($this->factory->param('data')->setType('mixed')) + ->addParam($this->factory->param('type')->setType('string')) + ->addParam($this->factory->param('format')->setType('?string')->setDefault(null)) + ->addParam($this->factory->param('context')->setType('array')->setDefault([])) ->setReturnType('mixed') - ->setBody($preCreateObject.\PHP_EOL.$body)); + ->addStmts([]) + ->addStmt(new Node\Stmt\Return_(new Node\Expr\Variable('output'))) + ); if ($needsChildDenormalizer) { - $this->generateDenormalizeChildMethod($generator); + $this->generateDenormalizeChildMethod($namespace, $class); } } - private function addNormailizeMethod(ClassGenerator $generator, ClassDefinition $definition): void + private function addNormailizeMethod(ClassDefinition $definition, Namespace_ $namespace, Class_ $class): void { - $body = ''; + $bodyArrayItems = []; $needsChildNormalizer = false; foreach ($definition->getDefinitions() as $propertyDefinition) { if (!$propertyDefinition->isReadable()) { @@ -292,61 +538,136 @@ private function addNormailizeMethod(ClassGenerator $generator, ClassDefinition } if (null !== $method = $propertyDefinition->getGetterName()) { - $accessor = sprintf('$object->%s()', $method); + // $object->$method() + $accessor = $this->factory->methodCall(new Node\Expr\Variable('object'), $method); } else { - $accessor = sprintf('$object->%s', $propertyDefinition->getPropertyName()); + // $object->property + $accessor = $this->factory->propertyFetch(new Node\Expr\Variable('object'), $propertyDefinition->getPropertyName()); } if ($propertyDefinition->hasNoTypeDefinition() || [] !== $propertyDefinition->getNonPrimitiveTypes()) { $needsChildNormalizer = true; - $accessor = sprintf('$this->normalizeChild(%s, $format, $context, %s)', $accessor, $propertyDefinition->isCollection() || $propertyDefinition->hasNoTypeDefinition() ? 'true' : 'false'); + // $this->normalizeChild($accessor, $format, $context, bool); + $accessor = $this->factory->methodCall(new Node\Expr\Variable('this'), 'normalizeChild', [ + $accessor, + new Node\Arg(new Node\Expr\Variable('format')), + new Node\Arg(new Node\Expr\Variable('context')), + new Node\Arg(new Node\Expr\ConstFetch(new Node\Name($propertyDefinition->isCollection() || $propertyDefinition->hasNoTypeDefinition() ? 'true' : 'false'))), + ]); } - $body .= <<getNormalizedName()}' => $accessor, -PHP.\PHP_EOL; + $bodyArrayItems[] = new Node\ArrayItem($accessor, new Node\Scalar\String_($propertyDefinition->getNormalizedName())); } // public function normalize(mixed $object, string $format = null, array $context = []): array|string|int|float|bool|\ArrayObject|null; - $generator->addMethod(Method::create('normalize') - ->addArgument('object', 'mixed') - ->addArgument('format', '?string', null) - ->addArgument('context', 'array', []) + $class->addStmt($this->factory->method('normalize') + ->makePublic() + ->addParam($this->factory->param('object')->setType('mixed')) + ->addParam($this->factory->param('format')->setType('string')->setDefault(null)) + ->addParam($this->factory->param('context')->setType('array')->setDefault([])) ->setReturnType('array|string|int|float|bool|\ArrayObject|null') - ->setComment(sprintf('@param %s $object', $definition->getSourceClassName())) - ->setBody('return ['.\PHP_EOL.$body.'];') - ); + ->setDocComment(sprintf('/*'.PHP_EOL.'* @param %s $object'.PHP_EOL.'*/', $definition->getSourceClassName())) + ->addStmt(new Node\Stmt\Return_(new Node\Expr\Array_($bodyArrayItems)))); if ($needsChildNormalizer) { - $this->generateNormalizeChildMethod($generator); + $this->generateNormalizeChildMethod($namespace, $class); } } /** * When the type-hint has many different classes, then we need to try to denormalize them * one by one. We are happy when we dont get any exceptions thrown. + * + * @return Node\Stmt[] */ - private function generateCodeToDeserializeMultiplePossibleClasses(array $targetClasses, string $printedCanBeIterable, string $tempVariableName, string $variable, string $keyName, string $classNs): string + private function generateCodeToDeserializeMultiplePossibleClasses(array $targetClasses, bool $canBeIterable, string $tempVariableName, Expr $variable, string $keyName, string $classNs): array { - $printedArray = str_replace(\PHP_EOL, '', var_export($targetClasses, true)); - - return <<denormalizeChild($variable, \$class, \$format, \$context, $printedCanBeIterable); - {$tempVariableName}HasValue = true; - break; - } catch (\Throwable \$e) { - \$exceptions[] = \$e; - } -} -if (!{$tempVariableName}HasValue) { - throw new DenormalizingUnionFailedException('Failed to denormalize key "$keyName" of class "$classNs".', \$exceptions); -} + $arrayItems = []; + foreach ($targetClasses as $class) { + $arrayItems[] = new Node\ArrayItem(new Node\Scalar\String_($class)); + } + return [ + new Node\Stmt\Expression( + new Node\Expr\Assign( + new Node\Expr\Variable('exceptions'), + new Node\Expr\Array_() + ) + ), + new Node\Stmt\Expression( + new Node\Expr\Assign( + new Node\Expr\Variable($tempVariableName.'HasValue'), + new Node\Expr\ConstFetch(new Node\Name('false')) + ) + ), + new Node\Stmt\Foreach_( + new Node\Expr\Array_($arrayItems), + new Node\Expr\Variable('class'), + [ + 'stmts' => [ + new Node\Stmt\TryCatch( + // statements + [ + new Node\Stmt\Expression( + new Node\Expr\Assign( + new Node\Expr\Variable($tempVariableName), + $this->factory->methodCall( + new Node\Expr\Variable('this'), + 'denormalizeChild', + [ + new Node\Arg($variable), + new Node\Arg(new Node\Expr\Variable('class')), + new Node\Arg(new Node\Expr\Variable('format')), + new Node\Arg(new Node\Expr\Variable('context')), + new Node\Arg(new Expr\ConstFetch(new Node\Name($canBeIterable ? 'true' : 'false'))), + ] + ) + ) + ), + new Node\Stmt\Expression( + new Node\Expr\Assign( + new Node\Expr\Variable($tempVariableName.'HasValue'), + new Node\Expr\ConstFetch(new Node\Name('true')) + ) + ), + new Node\Stmt\Break_(), + ], + // Catches + [ + new Node\Stmt\Catch_( + [new Node\Name\FullyQualified(\Throwable::class)], + new Node\Expr\Variable('e'), + [ + new Node\Stmt\Expression( + new Node\Expr\Assign( + new Node\Expr\ArrayDimFetch(new Node\Expr\Variable('exceptions')), + new Node\Expr\Variable('e') + ) + ) + ] + ), + ], + ), + ], + ] + ), // end foreach + new Node\Stmt\If_( + new Node\Expr\BooleanNot(new Node\Expr\Variable($tempVariableName.'HasValue')), + [ + 'stmts' => [ + new Node\Expr\Throw_( + new Node\Expr\New_( + new Node\Name\FullyQualified(DenormalizingUnionFailedException::class), + [ + new Node\Arg(new Node\Scalar\String_('Failed to denormalize key "'.$keyName.'" of class "'.$classNs.'".')), + new Node\Arg(new Node\Expr\Variable('exceptions')), + ] + ) + ), + ], + ] + ), + ]; -PHP; } } diff --git a/src/Symfony/Component/Serializer/Tests/Builder/CodeGenerator/AttributeTest.php b/src/Symfony/Component/Serializer/Tests/Builder/CodeGenerator/AttributeTest.php deleted file mode 100644 index c394c68e43940..0000000000000 --- a/src/Symfony/Component/Serializer/Tests/Builder/CodeGenerator/AttributeTest.php +++ /dev/null @@ -1,77 +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\Builder\CodeGenerator; - -use PHPUnit\Framework\TestCase; -use Symfony\Component\Serializer\Builder\CodeGenerator\Attribute; - -class AttributeTest extends TestCase -{ - public function testNoParameters() - { - $output = Attribute::create('Foobar')->toString(); - $this->assertEquals('#[Foobar]', $output); - - $output = Attribute::create('Foo\\Bar')->toString(); - $this->assertEquals('#[Foo\\Bar]', $output); - } - - public function testParameters() - { - $output = Attribute::create('Foobar') - ->addParameter(null, 7) - ->toString(); - $this->assertEquals('#[Foobar(7)]', $output); - - $output = Attribute::create('Foobar') - ->addParameter(null, 7) - ->addParameter(null, true) - ->addParameter(null, false) - ->addParameter(null, null) - ->addParameter(null, 47.11) - ->addParameter(null, 'tobias') - ->addParameter(null, [47, 'test']) - ->toString(); - $this->assertEquals('#[Foobar(7, true, false, NULL, 47.11, "tobias", [47, "test"])]', $output); - } - - public function testNamedParameters() - { - $output = Attribute::create('Foobar') - ->addParameter('seven', 7) - ->toString(); - $this->assertEquals('#[Foobar(seven: 7)]', $output); - - $output = Attribute::create('Foobar') - ->addParameter('seven', 7) - ->addParameter('true', true) - ->addParameter('false', false) - ->addParameter('null', null) - ->addParameter('float', 47.11) - ->addParameter('string', 'tobias') - ->addParameter('array', [47, 'test']) - ->toString(); - $this->assertEquals('#[Foobar(seven: 7, true: true, false: false, null: NULL, float: 47.11, string: "tobias", array: [47, "test"])]', $output); - } - - public function testNested() - { - $nested = Attribute::create('Baz') - ->addParameter('name', 'tobias'); - - $output = Attribute::create('Foobar') - ->addParameter('seven', 7) - ->addParameter('nested', $nested) - ->toString(); - $this->assertEquals('#[Foobar(seven: 7, nested: new Baz(name: "tobias"))]', $output); - } -} diff --git a/src/Symfony/Component/Serializer/Tests/Builder/CodeGenerator/ClassGeneratorTest.php b/src/Symfony/Component/Serializer/Tests/Builder/CodeGenerator/ClassGeneratorTest.php deleted file mode 100644 index 209be714d2ced..0000000000000 --- a/src/Symfony/Component/Serializer/Tests/Builder/CodeGenerator/ClassGeneratorTest.php +++ /dev/null @@ -1,135 +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\Builder\CodeGenerator; - -use PHPUnit\Framework\TestCase; -use Symfony\Component\Serializer\Builder\CodeGenerator\Attribute; -use Symfony\Component\Serializer\Builder\CodeGenerator\ClassGenerator; -use Symfony\Component\Serializer\Builder\CodeGenerator\Method; -use Symfony\Component\Serializer\Builder\CodeGenerator\Property; - -class ClassGeneratorTest extends TestCase -{ - public function testPerson() - { - $generator = new ClassGenerator('Person', 'Test\\CodeGenerator\\Fixtures'); - - $generator->addProperty(Property::create('name') - ->setVisibility('private') - ->setType('string') - ); - $generator->addProperty(Property::create('age') - ->setVisibility('private') - ->setType('int') - ); - - $generator->addMethod(Method::create('__construct') - ->addArgument('name', 'string') - ->addArgument('age', 'int') - ->setBody(<<name = \$name; -\$this->age = \$age; -PHP - )); - - $generator->addMethod(Method::create('getName') - ->setReturnType('string') - ->setBody('return $this->name;') - ); - - $generator->addMethod(Method::create('getAge') - ->setReturnType('int') - ->setBody('return $this->age;') - ); - - $output = $generator->toString(); - $this->assertEquals(file_get_contents(__DIR__.'/Fixtures/Person.php'), $output); - } - - /** - * Constructor argument promotion. - */ - public function testCat() - { - $generator = new ClassGenerator('Cat', 'Test\\CodeGenerator\\Fixtures'); - - $generator->addMethod(Method::create('__construct') - ->addArgument('name', 'private string') - ->addArgument('age', 'private int') - ); - - $generator->addMethod(Method::create('getName') - ->setReturnType('string') - ->setBody('return $this->name;') - ); - - $generator->addMethod(Method::create('getAge') - ->setReturnType('int') - ->setBody('return $this->age;') - ); - - $output = $generator->toString(); - $this->assertEquals(file_get_contents(__DIR__.'/Fixtures/Cat.php'), $output); - } - - /** - * Try to flex all our features. - */ - public function testFull() - { - $generator = new ClassGenerator('Full', 'Test\\CodeGenerator\\Fixtures'); - $generator->addImport('Test\\CodeGenerator\\Fixtures\\Cat'); - $generator->addImport('Test\\CodeGenerator\\Fixtures\\Foo'); - $generator->addImport('Test\\CodeGenerator\\Fixtures\\Bar'); - $generator->addImport('Test\\CodeGenerator\\Fixtures\\MyAttribute'); - $generator->setExtends('Cat'); - $generator->addImplements('Foo'); - $generator->addImplements('Bar'); - - $generator->setFileComment('This is a fixture class. -We use it for verifying the code generation.'); - $generator->setClassComment(<<addProperty(Property::create('name') - ->setVisibility('private') - ->setType('string') - ); - - $generator->addMethod(Method::create('__construct') - ->addArgument('name', 'string', 'foobar') - ->setBody(<<name = \$name; -PHP - )); - - $generator->addMethod(Method::create('getName') - ->setReturnType('string') - ->setBody('return $this->name;') - ->setComment(<<addAttribute(Attribute::create('MyAttribute') - ->addParameter('name', 'test') - ); - - $output = $generator->toString(); - $this->assertEquals(file_get_contents(__DIR__.'/Fixtures/Full.php'), $output); - } -} diff --git a/src/Symfony/Component/Serializer/Tests/Builder/CodeGenerator/Fixtures/Cat.php b/src/Symfony/Component/Serializer/Tests/Builder/CodeGenerator/Fixtures/Cat.php deleted file mode 100644 index 27d606b33bbff..0000000000000 --- a/src/Symfony/Component/Serializer/Tests/Builder/CodeGenerator/Fixtures/Cat.php +++ /dev/null @@ -1,21 +0,0 @@ -name; - } - - public function getAge(): int - { - return $this->age; - } - -} diff --git a/src/Symfony/Component/Serializer/Tests/Builder/CodeGenerator/Fixtures/Full.php b/src/Symfony/Component/Serializer/Tests/Builder/CodeGenerator/Fixtures/Full.php deleted file mode 100644 index c43181e4e1d4d..0000000000000 --- a/src/Symfony/Component/Serializer/Tests/Builder/CodeGenerator/Fixtures/Full.php +++ /dev/null @@ -1,40 +0,0 @@ -name = $name; - } - - /** - * Returns the name of the cat - * - * @return string - */ - public function getName(): string - { - return $this->name; - } - -} diff --git a/src/Symfony/Component/Serializer/Tests/Builder/CodeGenerator/Fixtures/Person.php b/src/Symfony/Component/Serializer/Tests/Builder/CodeGenerator/Fixtures/Person.php deleted file mode 100644 index 5ddaaab8684bc..0000000000000 --- a/src/Symfony/Component/Serializer/Tests/Builder/CodeGenerator/Fixtures/Person.php +++ /dev/null @@ -1,26 +0,0 @@ -name = $name; - $this->age = $age; - } - - public function getName(): string - { - return $this->name; - } - - public function getAge(): int - { - return $this->age; - } - -} diff --git a/src/Symfony/Component/Serializer/Tests/Builder/CodeGenerator/MethodTest.php b/src/Symfony/Component/Serializer/Tests/Builder/CodeGenerator/MethodTest.php deleted file mode 100644 index 5561a2400f19a..0000000000000 --- a/src/Symfony/Component/Serializer/Tests/Builder/CodeGenerator/MethodTest.php +++ /dev/null @@ -1,102 +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\Builder\CodeGenerator; - - -use PHPUnit\Framework\TestCase; -use Symfony\Component\Serializer\Builder\CodeGenerator\Method; - -class MethodTest extends TestCase -{ - public function testEmpty() - { - $output = Method::create('foobar')->toString(); - $this->assertEquals('public function foobar() -{ -}', $output); - } - - public function testAbstractMethod() - { - $output = Method::create('foobar')->setVisibility('protected')->setBody(null)->toString(); - $this->assertEquals('abstract protected function foobar();', $output); - } - - public function testVisibility() - { - $output = Method::create('foobar')->setVisibility('private')->toString(); - $this->assertEquals('private function foobar() -{ -}', $output); - - $output = Method::create('foobar')->setVisibility('protected')->toString(); - $this->assertEquals('protected function foobar() -{ -}', $output); - - // We dont care about logic - $output = Method::create('foobar')->setVisibility('crazy')->toString(); - $this->assertEquals('crazy function foobar() -{ -}', $output); - } - - public function testReturnType() - { - $output = Method::create('foobar')->setReturnType('int')->toString(); - $this->assertEquals('public function foobar(): int -{ -}', $output); - - $output = Method::create('foobar')->setReturnType('mixed')->toString(); - $this->assertEquals('public function foobar(): mixed -{ -}', $output); - - $output = Method::create('foobar')->setReturnType('?string')->toString(); - $this->assertEquals('public function foobar(): ?string -{ -}', $output); - } - - public function testArguments() - { - $output = Method::create('foobar') - ->addArgument('foo') - ->addArgument('bar', null, 'test') - ->addArgument('baz', null, null) - ->toString(); - $this->assertEquals('public function foobar($foo, $bar = \'test\', $baz = NULL) -{ -}', $output); - - $output = Method::create('foobar') - ->addArgument('foo', 'int') - ->addArgument('bar', 'string') - ->addArgument('baz', '?string', null) - ->toString(); - $this->assertEquals('public function foobar(int $foo, string $bar, ?string $baz = NULL) -{ -}', $output); - } - - public function testBody() - { - $output = Method::create('foobar') - ->setBody('return 2;') - ->toString(' '); - $this->assertEquals('public function foobar() -{ - return 2; -}', $output); - } -} diff --git a/src/Symfony/Component/Serializer/Tests/Builder/CodeGenerator/PropertyTest.php b/src/Symfony/Component/Serializer/Tests/Builder/CodeGenerator/PropertyTest.php deleted file mode 100644 index 158d9d17ceff7..0000000000000 --- a/src/Symfony/Component/Serializer/Tests/Builder/CodeGenerator/PropertyTest.php +++ /dev/null @@ -1,93 +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\Builder\CodeGenerator; - - -use PHPUnit\Framework\TestCase; -use Symfony\Component\Serializer\Builder\CodeGenerator\Property; - -class PropertyTest extends TestCase -{ - public function testNameOnly() - { - $output = Property::create('foobar')->toString(); - $this->assertEquals('public $foobar;', $output); - } - - public function testVisibility() - { - $output = Property::create('foobar')->setVisibility('private')->toString(); - $this->assertEquals('private $foobar;', $output); - - $output = Property::create('foobar')->setVisibility('protected')->toString(); - $this->assertEquals('protected $foobar;', $output); - - // We dont care about logic - $output = Property::create('foobar')->setVisibility('crazy')->toString(); - $this->assertEquals('crazy $foobar;', $output); - } - - public function testType() - { - $output = Property::create('foobar')->setType('int')->toString(); - $this->assertEquals('public int $foobar;', $output); - - $output = Property::create('foobar')->setType('mixed')->toString(); - $this->assertEquals('public mixed $foobar;', $output); - - $output = Property::create('foobar')->setType('?string')->toString(); - $this->assertEquals('public ?string $foobar;', $output); - } - - public function testDefaultValue() - { - $output = Property::create('foobar')->setDefaultValue('2')->toString(); - $this->assertEquals('public $foobar = \'2\';', $output); - - $output = Property::create('foobar')->setDefaultValue(2)->toString(); - $this->assertEquals('public $foobar = 2;', $output); - - $output = Property::create('foobar')->setDefaultValue(null)->toString(); - $this->assertEquals('public $foobar = NULL;', $output); - } - - public function testTypeAndDefaultValue() - { - $output = Property::create('foobar')->setType('string')->setDefaultValue('2')->toString(); - $this->assertEquals('public string $foobar = \'2\';', $output); - - $output = Property::create('foobar')->setType('int')->setDefaultValue(2)->toString(); - $this->assertEquals('public int $foobar = 2;', $output); - - $output = Property::create('foobar')->setType('?int')->setDefaultValue(null)->toString(); - $this->assertEquals('public ?int $foobar = NULL;', $output); - - // We dont care about logic here. - $output = Property::create('foobar')->setType('int')->setDefaultValue('test')->toString(); - $this->assertEquals('public int $foobar = \'test\';', $output); - } - - public function testComment() - { - $output = Property::create('foobar') - ->setType('string') - ->setDefaultValue('2') - ->setComment('This is a comment') - ->toString(); - $this->assertEquals(<< Date: Sun, 14 Jan 2024 18:06:21 -0800 Subject: [PATCH 06/13] Generate new classes --- .../Serializer/Builder/NormalizerBuilder.php | 101 +++++++------- .../Tests/Builder/generateUpdatedFixtures.php | 2 +- .../ComplexTypesConstructor.php | 68 +++------- .../ComplexTypesPublicProperties.php | 124 +++++++----------- .../ExpectedNormalizer/ComplexTypesSetter.php | 124 +++++++----------- .../ConstructorInjection.php | 64 ++------- .../ConstructorWithDefaultValue.php | 80 ++++------- .../ExpectedNormalizer/ExtraSetter.php | 31 ++--- .../ExpectedNormalizer/InheritanceChild.php | 39 ++---- .../NonReadableProperty.php | 41 ++---- .../ExpectedNormalizer/PrivateConstructor.php | 26 ++-- .../ExpectedNormalizer/PublicProperties.php | 52 ++------ .../ExpectedNormalizer/SetterInjection.php | 53 ++------ .../ConstructorAndSetterInjection.php | 52 ++------ .../ConstructorInjection.php | 55 ++------ .../ExpectedNormalizer/InheritanceChild.php | 49 ++----- .../ExpectedNormalizer/PublicProperties.php | 44 ++----- .../ExpectedNormalizer/SetterInjection.php | 44 ++----- 18 files changed, 318 insertions(+), 731 deletions(-) diff --git a/src/Symfony/Component/Serializer/Builder/NormalizerBuilder.php b/src/Symfony/Component/Serializer/Builder/NormalizerBuilder.php index 9c03a6af50b8f..a3bfd5b5a0a92 100644 --- a/src/Symfony/Component/Serializer/Builder/NormalizerBuilder.php +++ b/src/Symfony/Component/Serializer/Builder/NormalizerBuilder.php @@ -140,22 +140,18 @@ private function generateNormalizeChildMethod(Namespace_ $namespace, Class_ $cla new Node\Name('array_map'), [ new Node\Arg( - new Node\Expr\Closure([ + new Node\Expr\ArrowFunction([ 'params' => [new Node\Param(new Node\Expr\Variable('item'))], - 'stmts' => [ - new Node\Stmt\Return_( - $this->factory->methodCall( - new Node\Expr\Variable('this'), - 'normalizeChild', - [ - new Node\Arg(new Node\Expr\Variable('item')), - new Node\Arg(new Node\Expr\Variable('format')), - new Node\Arg(new Node\Expr\Variable('context')), - new Node\Arg(new Node\Expr\ConstFetch(new Node\Name('true'))), - ] - ) - ), - ], + 'expr' => $this->factory->methodCall( + new Node\Expr\Variable('this'), + 'normalizeChild', + [ + new Node\Arg(new Node\Expr\Variable('item')), + new Node\Arg(new Node\Expr\Variable('format')), + new Node\Arg(new Node\Expr\Variable('context')), + new Node\Arg(new Node\Expr\ConstFetch(new Node\Name('true'))), + ] + ) ]) ), new Node\Arg(new Node\Expr\Variable('object')), @@ -251,23 +247,19 @@ private function generateDenormalizeChildMethod(Namespace_ $namespace, Class_ $c new Node\Name('array_map'), [ new Node\Arg( - new Node\Expr\Closure([ + new Node\Expr\ArrowFunction([ 'params' => [new Node\Param(new Node\Expr\Variable('item'))], - 'stmts' => [ - new Node\Stmt\Return_( - $this->factory->methodCall( - new Node\Expr\Variable('this'), - 'denormalizeChild', - [ - new Node\Arg(new Node\Expr\Variable('item')), - new Node\Arg(new Node\Expr\Variable('type')), - new Node\Arg(new Node\Expr\Variable('format')), - new Node\Arg(new Node\Expr\Variable('context')), - new Node\Arg(new Node\Expr\ConstFetch(new Node\Name('true'))), - ] - ) - ), - ], + 'expr' => $this->factory->methodCall( + new Node\Expr\Variable('this'), + 'denormalizeChild', + [ + new Node\Arg(new Node\Expr\Variable('item')), + new Node\Arg(new Node\Expr\Variable('type')), + new Node\Arg(new Node\Expr\Variable('format')), + new Node\Arg(new Node\Expr\Variable('context')), + new Node\Arg(new Node\Expr\ConstFetch(new Node\Name('true'))), + ] + ) ]) ), new Node\Arg(new Node\Expr\Variable('data')), @@ -376,14 +368,23 @@ private function addDenormailizeMethod(ClassDefinition $definition, Namespace_ $ $targetClasses = $propertyDefinition->getNonPrimitiveTypes(); $canBeIterable = $propertyDefinition->isCollection(); + $defaultValue = $propertyDefinition->getConstructorDefaultValue(); + if (is_object($defaultValue)) { + // public function __construct($foo = new \stdClass()); + // There is no support for parameters to the object. + $defaultValue = new Expr\New_(new Node\Name\FullyQualified(get_class($defaultValue))); + } else { + $defaultValue = $this->factory->val($defaultValue); + } + if ([] === $targetClasses && $propertyDefinition->hasConstructorDefaultValue()) { $constructorArguments[] = new Node\Arg(new Node\Expr\BinaryOp\Coalesce( $variable, - $this->factory->val($propertyDefinition->getConstructorDefaultValue()) + $defaultValue )); continue; } elseif ([] === $targetClasses) { - $constructorArguments[] = new Node\Arg(new Node\Expr\Variable($variable)); + $constructorArguments[] = new Node\Arg($variable); continue; } @@ -402,7 +403,7 @@ private function addDenormailizeMethod(ClassDefinition $definition, Namespace_ $ 'denormalizeChild', [ new Node\Arg($variable), - new Node\Arg(new Node\Expr\ClassConstFetch(new Node\Name($targetClasses[0]), 'class')), + new Node\Arg(new Node\Expr\ClassConstFetch(new Node\Name\FullyQualified($targetClasses[0]), 'class')), new Node\Arg(new Node\Expr\Variable('format')), new Node\Arg(new Node\Expr\Variable('context')), new Node\Arg(new Expr\ConstFetch(new Node\Name($canBeIterable ? 'true' : 'false'))), @@ -423,10 +424,10 @@ private function addDenormailizeMethod(ClassDefinition $definition, Namespace_ $ 'stmts' => [ new Node\Stmt\Expression(new Node\Expr\Assign( new Node\Expr\Variable($tempVariableName), - $this->factory->val($propertyDefinition->getConstructorDefaultValue()) + $defaultValue )), ], - 'else' => $variableOutput + 'else' => new Node\Stmt\Else_($variableOutput) ] )]; } @@ -475,7 +476,7 @@ private function addDenormailizeMethod(ClassDefinition $definition, Namespace_ $ 'denormalizeChild', [ new Node\Arg($variable), - new Node\Arg(new Node\Expr\ClassConstFetch(new Node\Name($targetClasses[0]), 'class')), + new Node\Arg(new Node\Expr\ClassConstFetch(new Node\Name\FullyQualified($targetClasses[0]), 'class')), new Node\Arg(new Node\Expr\Variable('format')), new Node\Arg(new Node\Expr\Variable('context')), new Node\Arg(new Expr\ConstFetch(new Node\Name($propertyDefinition->isCollection() ? 'true' : 'false'))), @@ -519,7 +520,7 @@ private function addDenormailizeMethod(ClassDefinition $definition, Namespace_ $ ->addParam($this->factory->param('format')->setType('?string')->setDefault(null)) ->addParam($this->factory->param('context')->setType('array')->setDefault([])) ->setReturnType('mixed') - ->addStmts([]) + ->addStmts($body) ->addStmt(new Node\Stmt\Return_(new Node\Expr\Variable('output'))) ); @@ -566,7 +567,7 @@ private function addNormailizeMethod(ClassDefinition $definition, Namespace_ $na ->addParam($this->factory->param('format')->setType('string')->setDefault(null)) ->addParam($this->factory->param('context')->setType('array')->setDefault([])) ->setReturnType('array|string|int|float|bool|\ArrayObject|null') - ->setDocComment(sprintf('/*'.PHP_EOL.'* @param %s $object'.PHP_EOL.'*/', $definition->getSourceClassName())) + ->setDocComment(sprintf('/**'.PHP_EOL.'* @param %s $object'.PHP_EOL.'*/', $definition->getSourceClassName())) ->addStmt(new Node\Stmt\Return_(new Node\Expr\Array_($bodyArrayItems)))); if ($needsChildNormalizer) { @@ -584,7 +585,7 @@ private function generateCodeToDeserializeMultiplePossibleClasses(array $targetC { $arrayItems = []; foreach ($targetClasses as $class) { - $arrayItems[] = new Node\ArrayItem(new Node\Scalar\String_($class)); + $arrayItems[] = new Node\ArrayItem(new Expr\ClassConstFetch(new Node\Name\FullyQualified($class), 'class')); } return [ @@ -655,15 +656,17 @@ private function generateCodeToDeserializeMultiplePossibleClasses(array $targetC new Node\Expr\BooleanNot(new Node\Expr\Variable($tempVariableName.'HasValue')), [ 'stmts' => [ - new Node\Expr\Throw_( - new Node\Expr\New_( - new Node\Name\FullyQualified(DenormalizingUnionFailedException::class), - [ - new Node\Arg(new Node\Scalar\String_('Failed to denormalize key "'.$keyName.'" of class "'.$classNs.'".')), - new Node\Arg(new Node\Expr\Variable('exceptions')), - ] - ) - ), + new Node\Stmt\Expression( + new Node\Expr\Throw_( + new Node\Expr\New_( + new Node\Name('DenormalizingUnionFailedException'), + [ + new Node\Arg(new Node\Scalar\String_('Failed to denormalize key "'.$keyName.'" of class "'.$classNs.'".')), + new Node\Arg(new Node\Expr\Variable('exceptions')), + ] + ) + ), + ) ], ] ), diff --git a/src/Symfony/Component/Serializer/Tests/Builder/generateUpdatedFixtures.php b/src/Symfony/Component/Serializer/Tests/Builder/generateUpdatedFixtures.php index 6539eed7ec728..bfaffd56f0cb6 100644 --- a/src/Symfony/Component/Serializer/Tests/Builder/generateUpdatedFixtures.php +++ b/src/Symfony/Component/Serializer/Tests/Builder/generateUpdatedFixtures.php @@ -32,7 +32,7 @@ function includeIfExists(string $file): bool echo \PHP_EOL; $i = 0; -foreach (FixtureHelper::getFixturesAndResultFiles() as [$class => $outputFile]) { +foreach (FixtureHelper::getFixturesAndResultFiles() as $class => $outputFile) { $definition = $definitionExtractor->getDefinition($class); $result = $builder->build($definition, $outputDir); $result->loadClass(); diff --git a/src/Symfony/Component/Serializer/Tests/Fixtures/CustomNormalizer/FullTypeHints/ExpectedNormalizer/ComplexTypesConstructor.php b/src/Symfony/Component/Serializer/Tests/Fixtures/CustomNormalizer/FullTypeHints/ExpectedNormalizer/ComplexTypesConstructor.php index 3018e085113b9..83c2c4be4ff43 100644 --- a/src/Symfony/Component/Serializer/Tests/Fixtures/CustomNormalizer/FullTypeHints/ExpectedNormalizer/ComplexTypesConstructor.php +++ b/src/Symfony/Component/Serializer/Tests/Fixtures/CustomNormalizer/FullTypeHints/ExpectedNormalizer/ComplexTypesConstructor.php @@ -8,68 +8,50 @@ use Symfony\Component\Serializer\Normalizer\NormalizerAwareInterface; use Symfony\Component\Serializer\Exception\DenormalizingUnionFailedException; use Symfony\Component\Serializer\Normalizer\DenormalizerAwareInterface; - class Symfony_Component_Serializer_Tests_Fixtures_CustomNormalizer_FullTypeHints_ComplexTypesConstructor implements NormalizerInterface, DenormalizerInterface, NormalizerAwareInterface, DenormalizerAwareInterface { - private null|NormalizerInterface $normalizer = NULL; - private null|DenormalizerInterface $denormalizer = NULL; - + private null|NormalizerInterface $normalizer = null; + private null|DenormalizerInterface $denormalizer = null; public function getSupportedTypes(?string $format): array { return [ComplexTypesConstructor::class => true]; } - - 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 ComplexTypesConstructor; } - - 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 $type === ComplexTypesConstructor::class; } - /** - * @param ComplexTypesConstructor $object - */ - public function normalize(mixed $object, ?string $format = NULL, array $context = []): array|string|int|float|bool|\ArrayObject|null + * @param ComplexTypesConstructor $object + */ + public function normalize(mixed $object, string $format = null, array $context = []): array|string|int|float|bool|\ArrayObject|null { - return [ - 'simple' => $this->normalizeChild($object->getSimple(), $format, $context, false), - 'simpleArray' => $object->getSimpleArray(), - 'array' => $this->normalizeChild($object->getArray(), $format, $context, true), - 'union' => $this->normalizeChild($object->getUnion(), $format, $context, false), - 'nested' => $this->normalizeChild($object->getNested(), $format, $context, false), - 'unionArray' => $this->normalizeChild($object->getUnionArray(), $format, $context, true), - ]; + return ['simple' => $this->normalizeChild($object->getSimple(), $format, $context, false), 'simpleArray' => $object->getSimpleArray(), 'array' => $this->normalizeChild($object->getArray(), $format, $context, true), 'union' => $this->normalizeChild($object->getUnion(), $format, $context, false), 'nested' => $this->normalizeChild($object->getNested(), $format, $context, false), 'unionArray' => $this->normalizeChild($object->getUnionArray(), $format, $context, true)]; } - public function setNormalizer(NormalizerInterface $normalizer): void { $this->normalizer = $normalizer; } - private function normalizeChild(mixed $object, ?string $format, array $context, bool $canBeIterable): mixed { if (is_scalar($object) || null === $object) { return $object; } - - if ($canBeIterable === true && is_iterable($object)) { + if ($canBeIterable && is_iterable($object)) { return array_map(fn($item) => $this->normalizeChild($item, $format, $context, true), $object); } - return $this->normalizer->normalize($object, $format, $context); - } - - public function denormalize(mixed $data, string $type, ?string $format = NULL, array $context = []): mixed + public function denormalize(mixed $data, string $type, ?string $format = null, array $context = []): mixed { $argument0 = $this->denormalizeChild($data['simple'], \Symfony\Component\Serializer\Tests\Fixtures\CustomNormalizer\FullTypeHints\DummyObject::class, $format, $context, false); $argument2 = $this->denormalizeChild($data['array'], \Symfony\Component\Serializer\Tests\Fixtures\CustomNormalizer\FullTypeHints\DummyObject::class, $format, $context, true); $exceptions = []; $argument3HasValue = false; - foreach (array ( 0 => 'Symfony\\Component\\Serializer\\Tests\\Fixtures\\CustomNormalizer\\FullTypeHints\\SmartObject', 1 => 'Symfony\\Component\\Serializer\\Tests\\Fixtures\\CustomNormalizer\\FullTypeHints\\DummyObject',) as $class) { + foreach ([\Symfony\Component\Serializer\Tests\Fixtures\CustomNormalizer\FullTypeHints\SmartObject::class, \Symfony\Component\Serializer\Tests\Fixtures\CustomNormalizer\FullTypeHints\DummyObject::class] as $class) { try { $argument3 = $this->denormalizeChild($data['union'], $class, $format, $context, false); $argument3HasValue = true; @@ -81,10 +63,9 @@ public function denormalize(mixed $data, string $type, ?string $format = NULL, a if (!$argument3HasValue) { throw new DenormalizingUnionFailedException('Failed to denormalize key "union" of class "Symfony\Component\Serializer\Tests\Fixtures\CustomNormalizer\FullTypeHints\ComplexTypesConstructor".', $exceptions); } - $exceptions = []; $argument4HasValue = false; - foreach (array ( 0 => 'Symfony\\Component\\Serializer\\Tests\\Fixtures\\CustomNormalizer\\FullTypeHints\\DummyObject', 1 => 'Symfony\\Component\\Serializer\\Tests\\Fixtures\\CustomNormalizer\\FullTypeHints\\SmartObject',) as $class) { + foreach ([\Symfony\Component\Serializer\Tests\Fixtures\CustomNormalizer\FullTypeHints\DummyObject::class, \Symfony\Component\Serializer\Tests\Fixtures\CustomNormalizer\FullTypeHints\SmartObject::class] as $class) { try { $argument4 = $this->denormalizeChild($data['nested'], $class, $format, $context, false); $argument4HasValue = true; @@ -96,10 +77,9 @@ public function denormalize(mixed $data, string $type, ?string $format = NULL, a if (!$argument4HasValue) { throw new DenormalizingUnionFailedException('Failed to denormalize key "nested" of class "Symfony\Component\Serializer\Tests\Fixtures\CustomNormalizer\FullTypeHints\ComplexTypesConstructor".', $exceptions); } - $exceptions = []; $argument5HasValue = false; - foreach (array ( 0 => 'Symfony\\Component\\Serializer\\Tests\\Fixtures\\CustomNormalizer\\FullTypeHints\\DummyObject', 1 => 'Symfony\\Component\\Serializer\\Tests\\Fixtures\\CustomNormalizer\\FullTypeHints\\SmartObject',) as $class) { + foreach ([\Symfony\Component\Serializer\Tests\Fixtures\CustomNormalizer\FullTypeHints\DummyObject::class, \Symfony\Component\Serializer\Tests\Fixtures\CustomNormalizer\FullTypeHints\SmartObject::class] as $class) { try { $argument5 = $this->denormalizeChild($data['unionArray'], $class, $format, $context, true); $argument5HasValue = true; @@ -111,37 +91,21 @@ public function denormalize(mixed $data, string $type, ?string $format = NULL, a if (!$argument5HasValue) { throw new DenormalizingUnionFailedException('Failed to denormalize key "unionArray" of class "Symfony\Component\Serializer\Tests\Fixtures\CustomNormalizer\FullTypeHints\ComplexTypesConstructor".', $exceptions); } - - - $output = new ComplexTypesConstructor( - $argument0, - $data['simpleArray'], - $argument2, - $argument3, - $argument4, - $argument5, - ); - + $output = new ComplexTypesConstructor($argument0, $data['simpleArray'], $argument2, $argument3, $argument4, $argument5); return $output; } - public function setDenormalizer(DenormalizerInterface $denormalizer): void { $this->denormalizer = $denormalizer; } - private function denormalizeChild(mixed $data, string $type, ?string $format, array $context, bool $canBeIterable): mixed { if (is_scalar($data) || null === $data) { return $data; } - - if ($canBeIterable === true && is_iterable($data)) { + if ($canBeIterable && is_iterable($data)) { return array_map(fn($item) => $this->denormalizeChild($item, $type, $format, $context, true), $data); } - return $this->denormalizer->denormalize($data, $type, $format, $context); - } - -} +} \ No newline at end of file diff --git a/src/Symfony/Component/Serializer/Tests/Fixtures/CustomNormalizer/FullTypeHints/ExpectedNormalizer/ComplexTypesPublicProperties.php b/src/Symfony/Component/Serializer/Tests/Fixtures/CustomNormalizer/FullTypeHints/ExpectedNormalizer/ComplexTypesPublicProperties.php index 9ce85cc10fe93..3e4d06998af56 100644 --- a/src/Symfony/Component/Serializer/Tests/Fixtures/CustomNormalizer/FullTypeHints/ExpectedNormalizer/ComplexTypesPublicProperties.php +++ b/src/Symfony/Component/Serializer/Tests/Fixtures/CustomNormalizer/FullTypeHints/ExpectedNormalizer/ComplexTypesPublicProperties.php @@ -8,63 +8,45 @@ use Symfony\Component\Serializer\Normalizer\NormalizerAwareInterface; use Symfony\Component\Serializer\Exception\DenormalizingUnionFailedException; use Symfony\Component\Serializer\Normalizer\DenormalizerAwareInterface; - class Symfony_Component_Serializer_Tests_Fixtures_CustomNormalizer_FullTypeHints_ComplexTypesPublicProperties implements NormalizerInterface, DenormalizerInterface, NormalizerAwareInterface, DenormalizerAwareInterface { - private null|NormalizerInterface $normalizer = NULL; - private null|DenormalizerInterface $denormalizer = NULL; - + private null|NormalizerInterface $normalizer = null; + private null|DenormalizerInterface $denormalizer = null; public function getSupportedTypes(?string $format): array { return [ComplexTypesPublicProperties::class => true]; } - - 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 ComplexTypesPublicProperties; } - - 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 $type === ComplexTypesPublicProperties::class; } - /** - * @param ComplexTypesPublicProperties $object - */ - public function normalize(mixed $object, ?string $format = NULL, array $context = []): array|string|int|float|bool|\ArrayObject|null + * @param ComplexTypesPublicProperties $object + */ + public function normalize(mixed $object, string $format = null, array $context = []): array|string|int|float|bool|\ArrayObject|null { - return [ - 'simple' => $this->normalizeChild($object->simple, $format, $context, false), - 'array' => $this->normalizeChild($object->array, $format, $context, true), - 'union' => $this->normalizeChild($object->union, $format, $context, false), - 'nested' => $this->normalizeChild($object->nested, $format, $context, false), - 'unionArray' => $this->normalizeChild($object->unionArray, $format, $context, true), - ]; + return ['simple' => $this->normalizeChild($object->simple, $format, $context, false), 'array' => $this->normalizeChild($object->array, $format, $context, true), 'union' => $this->normalizeChild($object->union, $format, $context, false), 'nested' => $this->normalizeChild($object->nested, $format, $context, false), 'unionArray' => $this->normalizeChild($object->unionArray, $format, $context, true)]; } - public function setNormalizer(NormalizerInterface $normalizer): void { $this->normalizer = $normalizer; } - private function normalizeChild(mixed $object, ?string $format, array $context, bool $canBeIterable): mixed { if (is_scalar($object) || null === $object) { return $object; } - - if ($canBeIterable === true && is_iterable($object)) { + if ($canBeIterable && is_iterable($object)) { return array_map(fn($item) => $this->normalizeChild($item, $format, $context, true), $object); } - return $this->normalizer->normalize($object, $format, $context); - } - - public function denormalize(mixed $data, string $type, ?string $format = NULL, array $context = []): mixed + public function denormalize(mixed $data, string $type, ?string $format = null, array $context = []): mixed { - $output = new ComplexTypesPublicProperties(); if (array_key_exists('simple', $data)) { $setter0 = $this->denormalizeChild($data['simple'], \Symfony\Component\Serializer\Tests\Fixtures\CustomNormalizer\FullTypeHints\DummyObject::class, $format, $context, false); @@ -76,79 +58,69 @@ public function denormalize(mixed $data, string $type, ?string $format = NULL, a } if (array_key_exists('union', $data)) { $exceptions = []; - $setter2HasValue = false; - foreach (array ( 0 => 'Symfony\\Component\\Serializer\\Tests\\Fixtures\\CustomNormalizer\\FullTypeHints\\DummyObject', 1 => 'Symfony\\Component\\Serializer\\Tests\\Fixtures\\CustomNormalizer\\FullTypeHints\\SmartObject',) as $class) { - try { - $setter2 = $this->denormalizeChild($data['union'], $class, $format, $context, false); - $setter2HasValue = true; - break; - } catch (\Throwable $e) { - $exceptions[] = $e; + $setter2HasValue = false; + foreach ([\Symfony\Component\Serializer\Tests\Fixtures\CustomNormalizer\FullTypeHints\DummyObject::class, \Symfony\Component\Serializer\Tests\Fixtures\CustomNormalizer\FullTypeHints\SmartObject::class] as $class) { + try { + $setter2 = $this->denormalizeChild($data['union'], $class, $format, $context, false); + $setter2HasValue = true; + break; + } catch (\Throwable $e) { + $exceptions[] = $e; + } + } + if (!$setter2HasValue) { + throw new DenormalizingUnionFailedException('Failed to denormalize key "union" of class "Symfony\Component\Serializer\Tests\Fixtures\CustomNormalizer\FullTypeHints\ComplexTypesPublicProperties".', $exceptions); } - } - if (!$setter2HasValue) { - throw new DenormalizingUnionFailedException('Failed to denormalize key "union" of class "Symfony\Component\Serializer\Tests\Fixtures\CustomNormalizer\FullTypeHints\ComplexTypesPublicProperties".', $exceptions); - } - $output->union = $setter2; } if (array_key_exists('nested', $data)) { $exceptions = []; - $setter3HasValue = false; - foreach (array ( 0 => 'Symfony\\Component\\Serializer\\Tests\\Fixtures\\CustomNormalizer\\FullTypeHints\\DummyObject', 1 => 'Symfony\\Component\\Serializer\\Tests\\Fixtures\\CustomNormalizer\\FullTypeHints\\SmartObject',) as $class) { - try { - $setter3 = $this->denormalizeChild($data['nested'], $class, $format, $context, false); - $setter3HasValue = true; - break; - } catch (\Throwable $e) { - $exceptions[] = $e; + $setter3HasValue = false; + foreach ([\Symfony\Component\Serializer\Tests\Fixtures\CustomNormalizer\FullTypeHints\DummyObject::class, \Symfony\Component\Serializer\Tests\Fixtures\CustomNormalizer\FullTypeHints\SmartObject::class] as $class) { + try { + $setter3 = $this->denormalizeChild($data['nested'], $class, $format, $context, false); + $setter3HasValue = true; + break; + } catch (\Throwable $e) { + $exceptions[] = $e; + } + } + if (!$setter3HasValue) { + throw new DenormalizingUnionFailedException('Failed to denormalize key "nested" of class "Symfony\Component\Serializer\Tests\Fixtures\CustomNormalizer\FullTypeHints\ComplexTypesPublicProperties".', $exceptions); } - } - if (!$setter3HasValue) { - throw new DenormalizingUnionFailedException('Failed to denormalize key "nested" of class "Symfony\Component\Serializer\Tests\Fixtures\CustomNormalizer\FullTypeHints\ComplexTypesPublicProperties".', $exceptions); - } - $output->nested = $setter3; } if (array_key_exists('unionArray', $data)) { $exceptions = []; - $setter4HasValue = false; - foreach (array ( 0 => 'Symfony\\Component\\Serializer\\Tests\\Fixtures\\CustomNormalizer\\FullTypeHints\\DummyObject', 1 => 'Symfony\\Component\\Serializer\\Tests\\Fixtures\\CustomNormalizer\\FullTypeHints\\SmartObject',) as $class) { - try { - $setter4 = $this->denormalizeChild($data['unionArray'], $class, $format, $context, true); - $setter4HasValue = true; - break; - } catch (\Throwable $e) { - $exceptions[] = $e; + $setter4HasValue = false; + foreach ([\Symfony\Component\Serializer\Tests\Fixtures\CustomNormalizer\FullTypeHints\DummyObject::class, \Symfony\Component\Serializer\Tests\Fixtures\CustomNormalizer\FullTypeHints\SmartObject::class] as $class) { + try { + $setter4 = $this->denormalizeChild($data['unionArray'], $class, $format, $context, true); + $setter4HasValue = true; + break; + } catch (\Throwable $e) { + $exceptions[] = $e; + } + } + if (!$setter4HasValue) { + throw new DenormalizingUnionFailedException('Failed to denormalize key "unionArray" of class "Symfony\Component\Serializer\Tests\Fixtures\CustomNormalizer\FullTypeHints\ComplexTypesPublicProperties".', $exceptions); } - } - if (!$setter4HasValue) { - throw new DenormalizingUnionFailedException('Failed to denormalize key "unionArray" of class "Symfony\Component\Serializer\Tests\Fixtures\CustomNormalizer\FullTypeHints\ComplexTypesPublicProperties".', $exceptions); - } - $output->unionArray = $setter4; } - return $output; } - public function setDenormalizer(DenormalizerInterface $denormalizer): void { $this->denormalizer = $denormalizer; } - private function denormalizeChild(mixed $data, string $type, ?string $format, array $context, bool $canBeIterable): mixed { if (is_scalar($data) || null === $data) { return $data; } - - if ($canBeIterable === true && is_iterable($data)) { + if ($canBeIterable && is_iterable($data)) { return array_map(fn($item) => $this->denormalizeChild($item, $type, $format, $context, true), $data); } - return $this->denormalizer->denormalize($data, $type, $format, $context); - } - -} +} \ No newline at end of file diff --git a/src/Symfony/Component/Serializer/Tests/Fixtures/CustomNormalizer/FullTypeHints/ExpectedNormalizer/ComplexTypesSetter.php b/src/Symfony/Component/Serializer/Tests/Fixtures/CustomNormalizer/FullTypeHints/ExpectedNormalizer/ComplexTypesSetter.php index 89e10a022b654..f228b332f4bba 100644 --- a/src/Symfony/Component/Serializer/Tests/Fixtures/CustomNormalizer/FullTypeHints/ExpectedNormalizer/ComplexTypesSetter.php +++ b/src/Symfony/Component/Serializer/Tests/Fixtures/CustomNormalizer/FullTypeHints/ExpectedNormalizer/ComplexTypesSetter.php @@ -8,63 +8,45 @@ use Symfony\Component\Serializer\Normalizer\NormalizerAwareInterface; use Symfony\Component\Serializer\Exception\DenormalizingUnionFailedException; use Symfony\Component\Serializer\Normalizer\DenormalizerAwareInterface; - class Symfony_Component_Serializer_Tests_Fixtures_CustomNormalizer_FullTypeHints_ComplexTypesSetter implements NormalizerInterface, DenormalizerInterface, NormalizerAwareInterface, DenormalizerAwareInterface { - private null|NormalizerInterface $normalizer = NULL; - private null|DenormalizerInterface $denormalizer = NULL; - + private null|NormalizerInterface $normalizer = null; + private null|DenormalizerInterface $denormalizer = null; public function getSupportedTypes(?string $format): array { return [ComplexTypesSetter::class => true]; } - - 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 ComplexTypesSetter; } - - 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 $type === ComplexTypesSetter::class; } - /** - * @param ComplexTypesSetter $object - */ - public function normalize(mixed $object, ?string $format = NULL, array $context = []): array|string|int|float|bool|\ArrayObject|null + * @param ComplexTypesSetter $object + */ + public function normalize(mixed $object, string $format = null, array $context = []): array|string|int|float|bool|\ArrayObject|null { - return [ - 'simple' => $this->normalizeChild($object->getSimple(), $format, $context, false), - 'array' => $this->normalizeChild($object->getArray(), $format, $context, true), - 'union' => $this->normalizeChild($object->getUnion(), $format, $context, false), - 'nested' => $this->normalizeChild($object->getNested(), $format, $context, false), - 'unionArray' => $this->normalizeChild($object->getUnionArray(), $format, $context, true), - ]; + return ['simple' => $this->normalizeChild($object->getSimple(), $format, $context, false), 'array' => $this->normalizeChild($object->getArray(), $format, $context, true), 'union' => $this->normalizeChild($object->getUnion(), $format, $context, false), 'nested' => $this->normalizeChild($object->getNested(), $format, $context, false), 'unionArray' => $this->normalizeChild($object->getUnionArray(), $format, $context, true)]; } - public function setNormalizer(NormalizerInterface $normalizer): void { $this->normalizer = $normalizer; } - private function normalizeChild(mixed $object, ?string $format, array $context, bool $canBeIterable): mixed { if (is_scalar($object) || null === $object) { return $object; } - - if ($canBeIterable === true && is_iterable($object)) { + if ($canBeIterable && is_iterable($object)) { return array_map(fn($item) => $this->normalizeChild($item, $format, $context, true), $object); } - return $this->normalizer->normalize($object, $format, $context); - } - - public function denormalize(mixed $data, string $type, ?string $format = NULL, array $context = []): mixed + public function denormalize(mixed $data, string $type, ?string $format = null, array $context = []): mixed { - $output = new ComplexTypesSetter(); if (array_key_exists('simple', $data)) { $setter0 = $this->denormalizeChild($data['simple'], \Symfony\Component\Serializer\Tests\Fixtures\CustomNormalizer\FullTypeHints\DummyObject::class, $format, $context, false); @@ -76,79 +58,69 @@ public function denormalize(mixed $data, string $type, ?string $format = NULL, a } if (array_key_exists('union', $data)) { $exceptions = []; - $setter2HasValue = false; - foreach (array ( 0 => 'Symfony\\Component\\Serializer\\Tests\\Fixtures\\CustomNormalizer\\FullTypeHints\\SmartObject', 1 => 'Symfony\\Component\\Serializer\\Tests\\Fixtures\\CustomNormalizer\\FullTypeHints\\DummyObject',) as $class) { - try { - $setter2 = $this->denormalizeChild($data['union'], $class, $format, $context, false); - $setter2HasValue = true; - break; - } catch (\Throwable $e) { - $exceptions[] = $e; + $setter2HasValue = false; + foreach ([\Symfony\Component\Serializer\Tests\Fixtures\CustomNormalizer\FullTypeHints\SmartObject::class, \Symfony\Component\Serializer\Tests\Fixtures\CustomNormalizer\FullTypeHints\DummyObject::class] as $class) { + try { + $setter2 = $this->denormalizeChild($data['union'], $class, $format, $context, false); + $setter2HasValue = true; + break; + } catch (\Throwable $e) { + $exceptions[] = $e; + } + } + if (!$setter2HasValue) { + throw new DenormalizingUnionFailedException('Failed to denormalize key "union" of class "Symfony\Component\Serializer\Tests\Fixtures\CustomNormalizer\FullTypeHints\ComplexTypesSetter".', $exceptions); } - } - if (!$setter2HasValue) { - throw new DenormalizingUnionFailedException('Failed to denormalize key "union" of class "Symfony\Component\Serializer\Tests\Fixtures\CustomNormalizer\FullTypeHints\ComplexTypesSetter".', $exceptions); - } - $output->setUnion($setter2); } if (array_key_exists('nested', $data)) { $exceptions = []; - $setter3HasValue = false; - foreach (array ( 0 => 'Symfony\\Component\\Serializer\\Tests\\Fixtures\\CustomNormalizer\\FullTypeHints\\DummyObject', 1 => 'Symfony\\Component\\Serializer\\Tests\\Fixtures\\CustomNormalizer\\FullTypeHints\\SmartObject',) as $class) { - try { - $setter3 = $this->denormalizeChild($data['nested'], $class, $format, $context, false); - $setter3HasValue = true; - break; - } catch (\Throwable $e) { - $exceptions[] = $e; + $setter3HasValue = false; + foreach ([\Symfony\Component\Serializer\Tests\Fixtures\CustomNormalizer\FullTypeHints\DummyObject::class, \Symfony\Component\Serializer\Tests\Fixtures\CustomNormalizer\FullTypeHints\SmartObject::class] as $class) { + try { + $setter3 = $this->denormalizeChild($data['nested'], $class, $format, $context, false); + $setter3HasValue = true; + break; + } catch (\Throwable $e) { + $exceptions[] = $e; + } + } + if (!$setter3HasValue) { + throw new DenormalizingUnionFailedException('Failed to denormalize key "nested" of class "Symfony\Component\Serializer\Tests\Fixtures\CustomNormalizer\FullTypeHints\ComplexTypesSetter".', $exceptions); } - } - if (!$setter3HasValue) { - throw new DenormalizingUnionFailedException('Failed to denormalize key "nested" of class "Symfony\Component\Serializer\Tests\Fixtures\CustomNormalizer\FullTypeHints\ComplexTypesSetter".', $exceptions); - } - $output->setNested($setter3); } if (array_key_exists('unionArray', $data)) { $exceptions = []; - $setter4HasValue = false; - foreach (array ( 0 => 'Symfony\\Component\\Serializer\\Tests\\Fixtures\\CustomNormalizer\\FullTypeHints\\DummyObject', 1 => 'Symfony\\Component\\Serializer\\Tests\\Fixtures\\CustomNormalizer\\FullTypeHints\\SmartObject',) as $class) { - try { - $setter4 = $this->denormalizeChild($data['unionArray'], $class, $format, $context, true); - $setter4HasValue = true; - break; - } catch (\Throwable $e) { - $exceptions[] = $e; + $setter4HasValue = false; + foreach ([\Symfony\Component\Serializer\Tests\Fixtures\CustomNormalizer\FullTypeHints\DummyObject::class, \Symfony\Component\Serializer\Tests\Fixtures\CustomNormalizer\FullTypeHints\SmartObject::class] as $class) { + try { + $setter4 = $this->denormalizeChild($data['unionArray'], $class, $format, $context, true); + $setter4HasValue = true; + break; + } catch (\Throwable $e) { + $exceptions[] = $e; + } + } + if (!$setter4HasValue) { + throw new DenormalizingUnionFailedException('Failed to denormalize key "unionArray" of class "Symfony\Component\Serializer\Tests\Fixtures\CustomNormalizer\FullTypeHints\ComplexTypesSetter".', $exceptions); } - } - if (!$setter4HasValue) { - throw new DenormalizingUnionFailedException('Failed to denormalize key "unionArray" of class "Symfony\Component\Serializer\Tests\Fixtures\CustomNormalizer\FullTypeHints\ComplexTypesSetter".', $exceptions); - } - $output->setUnionArray($setter4); } - return $output; } - public function setDenormalizer(DenormalizerInterface $denormalizer): void { $this->denormalizer = $denormalizer; } - private function denormalizeChild(mixed $data, string $type, ?string $format, array $context, bool $canBeIterable): mixed { if (is_scalar($data) || null === $data) { return $data; } - - if ($canBeIterable === true && is_iterable($data)) { + if ($canBeIterable && is_iterable($data)) { return array_map(fn($item) => $this->denormalizeChild($item, $type, $format, $context, true), $data); } - return $this->denormalizer->denormalize($data, $type, $format, $context); - } - -} +} \ No newline at end of file diff --git a/src/Symfony/Component/Serializer/Tests/Fixtures/CustomNormalizer/FullTypeHints/ExpectedNormalizer/ConstructorInjection.php b/src/Symfony/Component/Serializer/Tests/Fixtures/CustomNormalizer/FullTypeHints/ExpectedNormalizer/ConstructorInjection.php index 0c007deb82d0f..45c72101f84b8 100644 --- a/src/Symfony/Component/Serializer/Tests/Fixtures/CustomNormalizer/FullTypeHints/ExpectedNormalizer/ConstructorInjection.php +++ b/src/Symfony/Component/Serializer/Tests/Fixtures/CustomNormalizer/FullTypeHints/ExpectedNormalizer/ConstructorInjection.php @@ -8,99 +8,61 @@ use Symfony\Component\Serializer\Normalizer\NormalizerAwareInterface; use Symfony\Component\Serializer\Exception\DenormalizingUnionFailedException; use Symfony\Component\Serializer\Normalizer\DenormalizerAwareInterface; - class Symfony_Component_Serializer_Tests_Fixtures_CustomNormalizer_FullTypeHints_ConstructorInjection implements NormalizerInterface, DenormalizerInterface, NormalizerAwareInterface, DenormalizerAwareInterface { - private null|NormalizerInterface $normalizer = NULL; - private null|DenormalizerInterface $denormalizer = NULL; - + private null|NormalizerInterface $normalizer = null; + private null|DenormalizerInterface $denormalizer = null; public function getSupportedTypes(?string $format): array { return [ConstructorInjection::class => true]; } - - 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 ConstructorInjection; } - - 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 $type === ConstructorInjection::class; } - /** - * @param ConstructorInjection $object - */ - public function normalize(mixed $object, ?string $format = NULL, array $context = []): array|string|int|float|bool|\ArrayObject|null + * @param ConstructorInjection $object + */ + public function normalize(mixed $object, string $format = null, array $context = []): array|string|int|float|bool|\ArrayObject|null { - return [ - 'name' => $object->getName(), - 'age' => $object->getAge(), - 'height' => $object->getHeight(), - 'handsome' => $object->isHandsome(), - 'nameOfFriends' => $this->normalizeChild($object->getNameOfFriends(), $format, $context, true), - 'picture' => $this->normalizeChild($object->getPicture(), $format, $context, true), - 'pet' => $object->getPet(), - 'relation' => $this->normalizeChild($object->getRelation(), $format, $context, false), - 'notSet' => $object->getNotSet(), - ]; + return ['name' => $object->getName(), 'age' => $object->getAge(), 'height' => $object->getHeight(), 'handsome' => $object->isHandsome(), 'nameOfFriends' => $this->normalizeChild($object->getNameOfFriends(), $format, $context, true), 'picture' => $this->normalizeChild($object->getPicture(), $format, $context, true), 'pet' => $object->getPet(), 'relation' => $this->normalizeChild($object->getRelation(), $format, $context, false), 'notSet' => $object->getNotSet()]; } - public function setNormalizer(NormalizerInterface $normalizer): void { $this->normalizer = $normalizer; } - private function normalizeChild(mixed $object, ?string $format, array $context, bool $canBeIterable): mixed { if (is_scalar($object) || null === $object) { return $object; } - - if ($canBeIterable === true && is_iterable($object)) { + if ($canBeIterable && is_iterable($object)) { return array_map(fn($item) => $this->normalizeChild($item, $format, $context, true), $object); } - return $this->normalizer->normalize($object, $format, $context); - } - - public function denormalize(mixed $data, string $type, ?string $format = NULL, array $context = []): mixed + public function denormalize(mixed $data, string $type, ?string $format = null, array $context = []): mixed { $argument7 = $this->denormalizeChild($data['relation'], \Symfony\Component\Serializer\Tests\Fixtures\CustomNormalizer\FullTypeHints\DummyObject::class, $format, $context, false); - - $output = new ConstructorInjection( - $data['name'], - $data['age'], - $data['height'], - $data['handsome'], - $data['nameOfFriends'], - $data['picture'], - $data['pet'], - $argument7, - ); - + $output = new ConstructorInjection($data['name'], $data['age'], $data['height'], $data['handsome'], $data['nameOfFriends'], $data['picture'], $data['pet'], $argument7); return $output; } - public function setDenormalizer(DenormalizerInterface $denormalizer): void { $this->denormalizer = $denormalizer; } - private function denormalizeChild(mixed $data, string $type, ?string $format, array $context, bool $canBeIterable): mixed { if (is_scalar($data) || null === $data) { return $data; } - - if ($canBeIterable === true && is_iterable($data)) { + if ($canBeIterable && is_iterable($data)) { return array_map(fn($item) => $this->denormalizeChild($item, $type, $format, $context, true), $data); } - return $this->denormalizer->denormalize($data, $type, $format, $context); - } - -} +} \ No newline at end of file diff --git a/src/Symfony/Component/Serializer/Tests/Fixtures/CustomNormalizer/FullTypeHints/ExpectedNormalizer/ConstructorWithDefaultValue.php b/src/Symfony/Component/Serializer/Tests/Fixtures/CustomNormalizer/FullTypeHints/ExpectedNormalizer/ConstructorWithDefaultValue.php index 23e2e66792547..c1b007ceecfbc 100644 --- a/src/Symfony/Component/Serializer/Tests/Fixtures/CustomNormalizer/FullTypeHints/ExpectedNormalizer/ConstructorWithDefaultValue.php +++ b/src/Symfony/Component/Serializer/Tests/Fixtures/CustomNormalizer/FullTypeHints/ExpectedNormalizer/ConstructorWithDefaultValue.php @@ -8,106 +8,78 @@ use Symfony\Component\Serializer\Normalizer\NormalizerAwareInterface; use Symfony\Component\Serializer\Exception\DenormalizingUnionFailedException; use Symfony\Component\Serializer\Normalizer\DenormalizerAwareInterface; - class Symfony_Component_Serializer_Tests_Fixtures_CustomNormalizer_FullTypeHints_ConstructorWithDefaultValue implements NormalizerInterface, DenormalizerInterface, NormalizerAwareInterface, DenormalizerAwareInterface { - private null|NormalizerInterface $normalizer = NULL; - private null|DenormalizerInterface $denormalizer = NULL; - + private null|NormalizerInterface $normalizer = null; + private null|DenormalizerInterface $denormalizer = null; public function getSupportedTypes(?string $format): array { return [ConstructorWithDefaultValue::class => true]; } - - 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 ConstructorWithDefaultValue; } - - 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 $type === ConstructorWithDefaultValue::class; } - /** - * @param ConstructorWithDefaultValue $object - */ - public function normalize(mixed $object, ?string $format = NULL, array $context = []): array|string|int|float|bool|\ArrayObject|null + * @param ConstructorWithDefaultValue $object + */ + public function normalize(mixed $object, string $format = null, array $context = []): array|string|int|float|bool|\ArrayObject|null { - return [ - 'foo' => $object->getFoo(), - 'union' => $this->normalizeChild($object->getUnion(), $format, $context, false), - ]; + return ['foo' => $object->getFoo(), 'union' => $this->normalizeChild($object->getUnion(), $format, $context, false)]; } - public function setNormalizer(NormalizerInterface $normalizer): void { $this->normalizer = $normalizer; } - private function normalizeChild(mixed $object, ?string $format, array $context, bool $canBeIterable): mixed { if (is_scalar($object) || null === $object) { return $object; } - - if ($canBeIterable === true && is_iterable($object)) { + if ($canBeIterable && is_iterable($object)) { return array_map(fn($item) => $this->normalizeChild($item, $format, $context, true), $object); } - return $this->normalizer->normalize($object, $format, $context); - } - - public function denormalize(mixed $data, string $type, ?string $format = NULL, array $context = []): mixed + public function denormalize(mixed $data, string $type, ?string $format = null, array $context = []): mixed { if (!array_key_exists('union', $data)) { - $argument1 = NULL; + $argument1 = null; } else { $exceptions = []; - $argument1HasValue = false; - foreach (array ( 0 => 'Symfony\\Component\\Serializer\\Tests\\Fixtures\\CustomNormalizer\\FullTypeHints\\SmartObject', 1 => 'Symfony\\Component\\Serializer\\Tests\\Fixtures\\CustomNormalizer\\FullTypeHints\\DummyObject',) as $class) { - try { - $argument1 = $this->denormalizeChild($data['union'], $class, $format, $context, false); - $argument1HasValue = true; - break; - } catch (\Throwable $e) { - $exceptions[] = $e; + $argument1HasValue = false; + foreach ([\Symfony\Component\Serializer\Tests\Fixtures\CustomNormalizer\FullTypeHints\SmartObject::class, \Symfony\Component\Serializer\Tests\Fixtures\CustomNormalizer\FullTypeHints\DummyObject::class] as $class) { + try { + $argument1 = $this->denormalizeChild($data['union'], $class, $format, $context, false); + $argument1HasValue = true; + break; + } catch (\Throwable $e) { + $exceptions[] = $e; + } + } + if (!$argument1HasValue) { + throw new DenormalizingUnionFailedException('Failed to denormalize key "union" of class "Symfony\Component\Serializer\Tests\Fixtures\CustomNormalizer\FullTypeHints\ConstructorWithDefaultValue".', $exceptions); } } - if (!$argument1HasValue) { - throw new DenormalizingUnionFailedException('Failed to denormalize key "union" of class "Symfony\Component\Serializer\Tests\Fixtures\CustomNormalizer\FullTypeHints\ConstructorWithDefaultValue".', $exceptions); - } - - - } - $output = new ConstructorWithDefaultValue( - $data['foo'] ?? 4711, - $argument1, - $data['x'] ?? \Symfony\Component\Serializer\Tests\Fixtures\CustomNormalizer\FullTypeHints\SmartObject::__set_state(array( - )), - ); - + $output = new ConstructorWithDefaultValue($data['foo'] ?? 4711, $argument1, $data['x'] ?? new \Symfony\Component\Serializer\Tests\Fixtures\CustomNormalizer\FullTypeHints\SmartObject()); return $output; } - public function setDenormalizer(DenormalizerInterface $denormalizer): void { $this->denormalizer = $denormalizer; } - private function denormalizeChild(mixed $data, string $type, ?string $format, array $context, bool $canBeIterable): mixed { if (is_scalar($data) || null === $data) { return $data; } - - if ($canBeIterable === true && is_iterable($data)) { + if ($canBeIterable && is_iterable($data)) { return array_map(fn($item) => $this->denormalizeChild($item, $type, $format, $context, true), $data); } - return $this->denormalizer->denormalize($data, $type, $format, $context); - } - -} +} \ No newline at end of file diff --git a/src/Symfony/Component/Serializer/Tests/Fixtures/CustomNormalizer/FullTypeHints/ExpectedNormalizer/ExtraSetter.php b/src/Symfony/Component/Serializer/Tests/Fixtures/CustomNormalizer/FullTypeHints/ExpectedNormalizer/ExtraSetter.php index e05db9eb5edb2..b8507c6fa0649 100644 --- a/src/Symfony/Component/Serializer/Tests/Fixtures/CustomNormalizer/FullTypeHints/ExpectedNormalizer/ExtraSetter.php +++ b/src/Symfony/Component/Serializer/Tests/Fixtures/CustomNormalizer/FullTypeHints/ExpectedNormalizer/ExtraSetter.php @@ -5,46 +5,33 @@ use Symfony\Component\Serializer\Tests\Fixtures\CustomNormalizer\FullTypeHints\ExtraSetter; use Symfony\Component\Serializer\Normalizer\NormalizerInterface; use Symfony\Component\Serializer\Normalizer\DenormalizerInterface; - class Symfony_Component_Serializer_Tests_Fixtures_CustomNormalizer_FullTypeHints_ExtraSetter implements NormalizerInterface, DenormalizerInterface { public function getSupportedTypes(?string $format): array { return [ExtraSetter::class => true]; } - - 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 ExtraSetter; } - - 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 $type === ExtraSetter::class; } - /** - * @param ExtraSetter $object - */ - public function normalize(mixed $object, ?string $format = NULL, array $context = []): array|string|int|float|bool|\ArrayObject|null + * @param ExtraSetter $object + */ + public function normalize(mixed $object, string $format = null, array $context = []): array|string|int|float|bool|\ArrayObject|null { - return [ - 'name' => $object->getName(), - 'age' => $object->getAge(), - ]; + return ['name' => $object->getName(), 'age' => $object->getAge()]; } - - public function denormalize(mixed $data, string $type, ?string $format = NULL, array $context = []): mixed + public function denormalize(mixed $data, string $type, ?string $format = null, array $context = []): mixed { - - $output = new ExtraSetter( - $data['name'], - ); + $output = new ExtraSetter($data['name']); if (array_key_exists('age', $data)) { $output->setAge($data['age']); } - return $output; } - -} +} \ No newline at end of file diff --git a/src/Symfony/Component/Serializer/Tests/Fixtures/CustomNormalizer/FullTypeHints/ExpectedNormalizer/InheritanceChild.php b/src/Symfony/Component/Serializer/Tests/Fixtures/CustomNormalizer/FullTypeHints/ExpectedNormalizer/InheritanceChild.php index fd943a09954cd..c4d26d57aad66 100644 --- a/src/Symfony/Component/Serializer/Tests/Fixtures/CustomNormalizer/FullTypeHints/ExpectedNormalizer/InheritanceChild.php +++ b/src/Symfony/Component/Serializer/Tests/Fixtures/CustomNormalizer/FullTypeHints/ExpectedNormalizer/InheritanceChild.php @@ -5,49 +5,30 @@ use Symfony\Component\Serializer\Tests\Fixtures\CustomNormalizer\FullTypeHints\InheritanceChild; use Symfony\Component\Serializer\Normalizer\NormalizerInterface; use Symfony\Component\Serializer\Normalizer\DenormalizerInterface; - class Symfony_Component_Serializer_Tests_Fixtures_CustomNormalizer_FullTypeHints_InheritanceChild implements NormalizerInterface, DenormalizerInterface { public function getSupportedTypes(?string $format): array { return [InheritanceChild::class => true]; } - - 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 InheritanceChild; } - - 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 $type === InheritanceChild::class; } - /** - * @param InheritanceChild $object - */ - public function normalize(mixed $object, ?string $format = NULL, array $context = []): array|string|int|float|bool|\ArrayObject|null + * @param InheritanceChild $object + */ + public function normalize(mixed $object, string $format = null, array $context = []): array|string|int|float|bool|\ArrayObject|null { - return [ - 'childCute' => $object->getChildCute(), - 'cute' => $object->isCute(), - 'childName' => $object->childName, - 'name' => $object->name, - 'childAge' => $object->getChildAge(), - 'childHeight' => $object->getChildHeight(), - 'age' => $object->getAge(), - 'height' => $object->getHeight(), - 'handsome' => $object->isHandsome(), - ]; + return ['childCute' => $object->getChildCute(), 'cute' => $object->isCute(), 'childName' => $object->childName, 'name' => $object->name, 'childAge' => $object->getChildAge(), 'childHeight' => $object->getChildHeight(), 'age' => $object->getAge(), 'height' => $object->getHeight(), 'handsome' => $object->isHandsome()]; } - - public function denormalize(mixed $data, string $type, ?string $format = NULL, array $context = []): mixed + public function denormalize(mixed $data, string $type, ?string $format = null, array $context = []): mixed { - - $output = new InheritanceChild( - $data['childCute'], - $data['cute'], - ); + $output = new InheritanceChild($data['childCute'], $data['cute']); if (array_key_exists('childName', $data)) { $output->childName = $data['childName']; } @@ -69,8 +50,6 @@ public function denormalize(mixed $data, string $type, ?string $format = NULL, a if (array_key_exists('handsome', $data)) { $output->setHandsome($data['handsome']); } - return $output; } - -} +} \ No newline at end of file diff --git a/src/Symfony/Component/Serializer/Tests/Fixtures/CustomNormalizer/FullTypeHints/ExpectedNormalizer/NonReadableProperty.php b/src/Symfony/Component/Serializer/Tests/Fixtures/CustomNormalizer/FullTypeHints/ExpectedNormalizer/NonReadableProperty.php index be6c9d421f5be..65409567f3d2a 100644 --- a/src/Symfony/Component/Serializer/Tests/Fixtures/CustomNormalizer/FullTypeHints/ExpectedNormalizer/NonReadableProperty.php +++ b/src/Symfony/Component/Serializer/Tests/Fixtures/CustomNormalizer/FullTypeHints/ExpectedNormalizer/NonReadableProperty.php @@ -6,64 +6,45 @@ use Symfony\Component\Serializer\Normalizer\NormalizerInterface; use Symfony\Component\Serializer\Normalizer\DenormalizerInterface; use Symfony\Component\Serializer\Normalizer\NormalizerAwareInterface; - class Symfony_Component_Serializer_Tests_Fixtures_CustomNormalizer_FullTypeHints_NonReadableProperty implements NormalizerInterface, DenormalizerInterface, NormalizerAwareInterface { - private null|NormalizerInterface $normalizer = NULL; - + private null|NormalizerInterface $normalizer = null; public function getSupportedTypes(?string $format): array { return [NonReadableProperty::class => true]; } - - 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 NonReadableProperty; } - - 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 $type === NonReadableProperty::class; } - /** - * @param NonReadableProperty $object - */ - public function normalize(mixed $object, ?string $format = NULL, array $context = []): array|string|int|float|bool|\ArrayObject|null + * @param NonReadableProperty $object + */ + public function normalize(mixed $object, string $format = null, array $context = []): array|string|int|float|bool|\ArrayObject|null { - return [ - 'name' => $object->getName(), - 'funnyName' => $this->normalizeChild($object->getFunnyName(), $format, $context, true), - ]; + return ['name' => $object->getName(), 'funnyName' => $this->normalizeChild($object->getFunnyName(), $format, $context, true)]; } - public function setNormalizer(NormalizerInterface $normalizer): void { $this->normalizer = $normalizer; } - private function normalizeChild(mixed $object, ?string $format, array $context, bool $canBeIterable): mixed { if (is_scalar($object) || null === $object) { return $object; } - - if ($canBeIterable === true && is_iterable($object)) { + if ($canBeIterable && is_iterable($object)) { return array_map(fn($item) => $this->normalizeChild($item, $format, $context, true), $object); } - return $this->normalizer->normalize($object, $format, $context); - } - - public function denormalize(mixed $data, string $type, ?string $format = NULL, array $context = []): mixed + public function denormalize(mixed $data, string $type, ?string $format = null, array $context = []): mixed { - - $output = new NonReadableProperty( - $data['name'], - ); - + $output = new NonReadableProperty($data['name']); return $output; } - -} +} \ No newline at end of file diff --git a/src/Symfony/Component/Serializer/Tests/Fixtures/CustomNormalizer/FullTypeHints/ExpectedNormalizer/PrivateConstructor.php b/src/Symfony/Component/Serializer/Tests/Fixtures/CustomNormalizer/FullTypeHints/ExpectedNormalizer/PrivateConstructor.php index cc17a9c8c4e96..5c8dfc8ca9e22 100644 --- a/src/Symfony/Component/Serializer/Tests/Fixtures/CustomNormalizer/FullTypeHints/ExpectedNormalizer/PrivateConstructor.php +++ b/src/Symfony/Component/Serializer/Tests/Fixtures/CustomNormalizer/FullTypeHints/ExpectedNormalizer/PrivateConstructor.php @@ -5,43 +5,33 @@ use Symfony\Component\Serializer\Tests\Fixtures\CustomNormalizer\FullTypeHints\PrivateConstructor; use Symfony\Component\Serializer\Normalizer\NormalizerInterface; use Symfony\Component\Serializer\Normalizer\DenormalizerInterface; - class Symfony_Component_Serializer_Tests_Fixtures_CustomNormalizer_FullTypeHints_PrivateConstructor implements NormalizerInterface, DenormalizerInterface { public function getSupportedTypes(?string $format): array { return [PrivateConstructor::class => true]; } - - 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 PrivateConstructor; } - - 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 $type === PrivateConstructor::class; } - /** - * @param PrivateConstructor $object - */ - public function normalize(mixed $object, ?string $format = NULL, array $context = []): array|string|int|float|bool|\ArrayObject|null + * @param PrivateConstructor $object + */ + public function normalize(mixed $object, string $format = null, array $context = []): array|string|int|float|bool|\ArrayObject|null { - return [ - 'foo' => $object->foo, - ]; + return ['foo' => $object->foo]; } - - public function denormalize(mixed $data, string $type, ?string $format = NULL, array $context = []): mixed + public function denormalize(mixed $data, string $type, ?string $format = null, array $context = []): mixed { - $output = (new \ReflectionClass(PrivateConstructor::class))->newInstanceWithoutConstructor(); if (array_key_exists('foo', $data)) { $output->foo = $data['foo']; } - return $output; } - -} +} \ No newline at end of file diff --git a/src/Symfony/Component/Serializer/Tests/Fixtures/CustomNormalizer/FullTypeHints/ExpectedNormalizer/PublicProperties.php b/src/Symfony/Component/Serializer/Tests/Fixtures/CustomNormalizer/FullTypeHints/ExpectedNormalizer/PublicProperties.php index 3b53341a6d827..d2574a6621080 100644 --- a/src/Symfony/Component/Serializer/Tests/Fixtures/CustomNormalizer/FullTypeHints/ExpectedNormalizer/PublicProperties.php +++ b/src/Symfony/Component/Serializer/Tests/Fixtures/CustomNormalizer/FullTypeHints/ExpectedNormalizer/PublicProperties.php @@ -8,66 +8,45 @@ use Symfony\Component\Serializer\Normalizer\NormalizerAwareInterface; use Symfony\Component\Serializer\Exception\DenormalizingUnionFailedException; use Symfony\Component\Serializer\Normalizer\DenormalizerAwareInterface; - class Symfony_Component_Serializer_Tests_Fixtures_CustomNormalizer_FullTypeHints_PublicProperties implements NormalizerInterface, DenormalizerInterface, NormalizerAwareInterface, DenormalizerAwareInterface { - private null|NormalizerInterface $normalizer = NULL; - private null|DenormalizerInterface $denormalizer = NULL; - + private null|NormalizerInterface $normalizer = null; + private null|DenormalizerInterface $denormalizer = null; public function getSupportedTypes(?string $format): array { return [PublicProperties::class => true]; } - - 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 PublicProperties; } - - 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 $type === PublicProperties::class; } - /** - * @param PublicProperties $object - */ - public function normalize(mixed $object, ?string $format = NULL, array $context = []): array|string|int|float|bool|\ArrayObject|null + * @param PublicProperties $object + */ + public function normalize(mixed $object, string $format = null, array $context = []): array|string|int|float|bool|\ArrayObject|null { - return [ - 'name' => $object->name, - 'age' => $object->age, - 'height' => $object->height, - 'handsome' => $object->handsome, - 'nameOfFriends' => $this->normalizeChild($object->nameOfFriends, $format, $context, true), - 'picture' => $this->normalizeChild($object->picture, $format, $context, true), - 'pet' => $object->pet, - 'relation' => $this->normalizeChild($object->relation, $format, $context, false), - ]; + return ['name' => $object->name, 'age' => $object->age, 'height' => $object->height, 'handsome' => $object->handsome, 'nameOfFriends' => $this->normalizeChild($object->nameOfFriends, $format, $context, true), 'picture' => $this->normalizeChild($object->picture, $format, $context, true), 'pet' => $object->pet, 'relation' => $this->normalizeChild($object->relation, $format, $context, false)]; } - public function setNormalizer(NormalizerInterface $normalizer): void { $this->normalizer = $normalizer; } - private function normalizeChild(mixed $object, ?string $format, array $context, bool $canBeIterable): mixed { if (is_scalar($object) || null === $object) { return $object; } - - if ($canBeIterable === true && is_iterable($object)) { + if ($canBeIterable && is_iterable($object)) { return array_map(fn($item) => $this->normalizeChild($item, $format, $context, true), $object); } - return $this->normalizer->normalize($object, $format, $context); - } - - public function denormalize(mixed $data, string $type, ?string $format = NULL, array $context = []): mixed + public function denormalize(mixed $data, string $type, ?string $format = null, array $context = []): mixed { - $output = new PublicProperties(); if (array_key_exists('name', $data)) { $output->name = $data['name']; @@ -94,27 +73,20 @@ public function denormalize(mixed $data, string $type, ?string $format = NULL, a $setter0 = $this->denormalizeChild($data['relation'], \Symfony\Component\Serializer\Tests\Fixtures\CustomNormalizer\FullTypeHints\DummyObject::class, $format, $context, false); $output->relation = $setter0; } - return $output; } - public function setDenormalizer(DenormalizerInterface $denormalizer): void { $this->denormalizer = $denormalizer; } - private function denormalizeChild(mixed $data, string $type, ?string $format, array $context, bool $canBeIterable): mixed { if (is_scalar($data) || null === $data) { return $data; } - - if ($canBeIterable === true && is_iterable($data)) { + if ($canBeIterable && is_iterable($data)) { return array_map(fn($item) => $this->denormalizeChild($item, $type, $format, $context, true), $data); } - return $this->denormalizer->denormalize($data, $type, $format, $context); - } - -} +} \ No newline at end of file diff --git a/src/Symfony/Component/Serializer/Tests/Fixtures/CustomNormalizer/FullTypeHints/ExpectedNormalizer/SetterInjection.php b/src/Symfony/Component/Serializer/Tests/Fixtures/CustomNormalizer/FullTypeHints/ExpectedNormalizer/SetterInjection.php index 9315d0806420c..1589454f57f1c 100644 --- a/src/Symfony/Component/Serializer/Tests/Fixtures/CustomNormalizer/FullTypeHints/ExpectedNormalizer/SetterInjection.php +++ b/src/Symfony/Component/Serializer/Tests/Fixtures/CustomNormalizer/FullTypeHints/ExpectedNormalizer/SetterInjection.php @@ -8,67 +8,45 @@ use Symfony\Component\Serializer\Normalizer\NormalizerAwareInterface; use Symfony\Component\Serializer\Exception\DenormalizingUnionFailedException; use Symfony\Component\Serializer\Normalizer\DenormalizerAwareInterface; - class Symfony_Component_Serializer_Tests_Fixtures_CustomNormalizer_FullTypeHints_SetterInjection implements NormalizerInterface, DenormalizerInterface, NormalizerAwareInterface, DenormalizerAwareInterface { - private null|NormalizerInterface $normalizer = NULL; - private null|DenormalizerInterface $denormalizer = NULL; - + private null|NormalizerInterface $normalizer = null; + private null|DenormalizerInterface $denormalizer = null; public function getSupportedTypes(?string $format): array { return [SetterInjection::class => true]; } - - 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 SetterInjection; } - - 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 $type === SetterInjection::class; } - /** - * @param SetterInjection $object - */ - public function normalize(mixed $object, ?string $format = NULL, array $context = []): array|string|int|float|bool|\ArrayObject|null + * @param SetterInjection $object + */ + public function normalize(mixed $object, string $format = null, array $context = []): array|string|int|float|bool|\ArrayObject|null { - return [ - 'name' => $object->getName(), - 'age' => $object->getAge(), - 'height' => $object->getHeight(), - 'handsome' => $object->isHandsome(), - 'nameOfFriends' => $this->normalizeChild($object->getNameOfFriends(), $format, $context, true), - 'picture' => $this->normalizeChild($object->getPicture(), $format, $context, true), - 'pet' => $object->getPet(), - 'relation' => $this->normalizeChild($object->getRelation(), $format, $context, false), - 'notSet' => $object->getNotSet(), - ]; + return ['name' => $object->getName(), 'age' => $object->getAge(), 'height' => $object->getHeight(), 'handsome' => $object->isHandsome(), 'nameOfFriends' => $this->normalizeChild($object->getNameOfFriends(), $format, $context, true), 'picture' => $this->normalizeChild($object->getPicture(), $format, $context, true), 'pet' => $object->getPet(), 'relation' => $this->normalizeChild($object->getRelation(), $format, $context, false), 'notSet' => $object->getNotSet()]; } - public function setNormalizer(NormalizerInterface $normalizer): void { $this->normalizer = $normalizer; } - private function normalizeChild(mixed $object, ?string $format, array $context, bool $canBeIterable): mixed { if (is_scalar($object) || null === $object) { return $object; } - - if ($canBeIterable === true && is_iterable($object)) { + if ($canBeIterable && is_iterable($object)) { return array_map(fn($item) => $this->normalizeChild($item, $format, $context, true), $object); } - return $this->normalizer->normalize($object, $format, $context); - } - - public function denormalize(mixed $data, string $type, ?string $format = NULL, array $context = []): mixed + public function denormalize(mixed $data, string $type, ?string $format = null, array $context = []): mixed { - $output = new SetterInjection(); if (array_key_exists('name', $data)) { $output->setName($data['name']); @@ -95,27 +73,20 @@ public function denormalize(mixed $data, string $type, ?string $format = NULL, a $setter0 = $this->denormalizeChild($data['relation'], \Symfony\Component\Serializer\Tests\Fixtures\CustomNormalizer\FullTypeHints\DummyObject::class, $format, $context, false); $output->setRelation($setter0); } - return $output; } - public function setDenormalizer(DenormalizerInterface $denormalizer): void { $this->denormalizer = $denormalizer; } - private function denormalizeChild(mixed $data, string $type, ?string $format, array $context, bool $canBeIterable): mixed { if (is_scalar($data) || null === $data) { return $data; } - - if ($canBeIterable === true && is_iterable($data)) { + if ($canBeIterable && is_iterable($data)) { return array_map(fn($item) => $this->denormalizeChild($item, $type, $format, $context, true), $data); } - return $this->denormalizer->denormalize($data, $type, $format, $context); - } - -} +} \ No newline at end of file diff --git a/src/Symfony/Component/Serializer/Tests/Fixtures/CustomNormalizer/NoTypeHints/ExpectedNormalizer/ConstructorAndSetterInjection.php b/src/Symfony/Component/Serializer/Tests/Fixtures/CustomNormalizer/NoTypeHints/ExpectedNormalizer/ConstructorAndSetterInjection.php index ba0c353db9dbf..340df14b3f32f 100644 --- a/src/Symfony/Component/Serializer/Tests/Fixtures/CustomNormalizer/NoTypeHints/ExpectedNormalizer/ConstructorAndSetterInjection.php +++ b/src/Symfony/Component/Serializer/Tests/Fixtures/CustomNormalizer/NoTypeHints/ExpectedNormalizer/ConstructorAndSetterInjection.php @@ -6,73 +6,45 @@ use Symfony\Component\Serializer\Normalizer\NormalizerInterface; use Symfony\Component\Serializer\Normalizer\DenormalizerInterface; use Symfony\Component\Serializer\Normalizer\NormalizerAwareInterface; - class Symfony_Component_Serializer_Tests_Fixtures_CustomNormalizer_NoTypeHints_ConstructorAndSetterInjection implements NormalizerInterface, DenormalizerInterface, NormalizerAwareInterface { - private null|NormalizerInterface $normalizer = NULL; - + private null|NormalizerInterface $normalizer = null; public function getSupportedTypes(?string $format): array { return [ConstructorAndSetterInjection::class => true]; } - - 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 ConstructorAndSetterInjection; } - - 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 $type === ConstructorAndSetterInjection::class; } - /** - * @param ConstructorAndSetterInjection $object - */ - public function normalize(mixed $object, ?string $format = NULL, array $context = []): array|string|int|float|bool|\ArrayObject|null + * @param ConstructorAndSetterInjection $object + */ + public function normalize(mixed $object, string $format = null, array $context = []): array|string|int|float|bool|\ArrayObject|null { - return [ - 'name' => $this->normalizeChild($object->getName(), $format, $context, true), - 'age' => $this->normalizeChild($object->getAge(), $format, $context, true), - 'picture' => $this->normalizeChild($object->getPicture(), $format, $context, true), - 'pet' => $this->normalizeChild($object->getPet(), $format, $context, true), - 'relation' => $this->normalizeChild($object->getRelation(), $format, $context, true), - 'height' => $this->normalizeChild($object->getHeight(), $format, $context, true), - 'handsome' => $this->normalizeChild($object->getHandsome(), $format, $context, true), - 'nameOfFriends' => $this->normalizeChild($object->getNameOfFriends(), $format, $context, true), - 'notSet' => $this->normalizeChild($object->getNotSet(), $format, $context, true), - ]; + return ['name' => $this->normalizeChild($object->getName(), $format, $context, true), 'age' => $this->normalizeChild($object->getAge(), $format, $context, true), 'picture' => $this->normalizeChild($object->getPicture(), $format, $context, true), 'pet' => $this->normalizeChild($object->getPet(), $format, $context, true), 'relation' => $this->normalizeChild($object->getRelation(), $format, $context, true), 'height' => $this->normalizeChild($object->getHeight(), $format, $context, true), 'handsome' => $this->normalizeChild($object->getHandsome(), $format, $context, true), 'nameOfFriends' => $this->normalizeChild($object->getNameOfFriends(), $format, $context, true), 'notSet' => $this->normalizeChild($object->getNotSet(), $format, $context, true)]; } - public function setNormalizer(NormalizerInterface $normalizer): void { $this->normalizer = $normalizer; } - private function normalizeChild(mixed $object, ?string $format, array $context, bool $canBeIterable): mixed { if (is_scalar($object) || null === $object) { return $object; } - - if ($canBeIterable === true && is_iterable($object)) { + if ($canBeIterable && is_iterable($object)) { return array_map(fn($item) => $this->normalizeChild($item, $format, $context, true), $object); } - return $this->normalizer->normalize($object, $format, $context); - } - - public function denormalize(mixed $data, string $type, ?string $format = NULL, array $context = []): mixed + public function denormalize(mixed $data, string $type, ?string $format = null, array $context = []): mixed { - - $output = new ConstructorAndSetterInjection( - $data['name'], - $data['age'], - $data['picture'], - $data['pet'], - $data['relation'], - ); + $output = new ConstructorAndSetterInjection($data['name'], $data['age'], $data['picture'], $data['pet'], $data['relation']); if (array_key_exists('height', $data)) { $output->setHeight($data['height']); } @@ -82,8 +54,6 @@ public function denormalize(mixed $data, string $type, ?string $format = NULL, a if (array_key_exists('nameOfFriends', $data)) { $output->setNameOfFriends($data['nameOfFriends']); } - return $output; } - -} +} \ No newline at end of file diff --git a/src/Symfony/Component/Serializer/Tests/Fixtures/CustomNormalizer/NoTypeHints/ExpectedNormalizer/ConstructorInjection.php b/src/Symfony/Component/Serializer/Tests/Fixtures/CustomNormalizer/NoTypeHints/ExpectedNormalizer/ConstructorInjection.php index 39389746a572e..c5cae1d5672fb 100644 --- a/src/Symfony/Component/Serializer/Tests/Fixtures/CustomNormalizer/NoTypeHints/ExpectedNormalizer/ConstructorInjection.php +++ b/src/Symfony/Component/Serializer/Tests/Fixtures/CustomNormalizer/NoTypeHints/ExpectedNormalizer/ConstructorInjection.php @@ -6,78 +6,45 @@ use Symfony\Component\Serializer\Normalizer\NormalizerInterface; use Symfony\Component\Serializer\Normalizer\DenormalizerInterface; use Symfony\Component\Serializer\Normalizer\NormalizerAwareInterface; - class Symfony_Component_Serializer_Tests_Fixtures_CustomNormalizer_NoTypeHints_ConstructorInjection implements NormalizerInterface, DenormalizerInterface, NormalizerAwareInterface { - private null|NormalizerInterface $normalizer = NULL; - + private null|NormalizerInterface $normalizer = null; public function getSupportedTypes(?string $format): array { return [ConstructorInjection::class => true]; } - - 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 ConstructorInjection; } - - 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 $type === ConstructorInjection::class; } - /** - * @param ConstructorInjection $object - */ - public function normalize(mixed $object, ?string $format = NULL, array $context = []): array|string|int|float|bool|\ArrayObject|null + * @param ConstructorInjection $object + */ + public function normalize(mixed $object, string $format = null, array $context = []): array|string|int|float|bool|\ArrayObject|null { - return [ - 'name' => $this->normalizeChild($object->getName(), $format, $context, true), - 'age' => $this->normalizeChild($object->getAge(), $format, $context, true), - 'height' => $this->normalizeChild($object->getHeight(), $format, $context, true), - 'handsome' => $this->normalizeChild($object->getHandsome(), $format, $context, true), - 'nameOfFriends' => $this->normalizeChild($object->getNameOfFriends(), $format, $context, true), - 'picture' => $this->normalizeChild($object->getPicture(), $format, $context, true), - 'pet' => $this->normalizeChild($object->getPet(), $format, $context, true), - 'relation' => $this->normalizeChild($object->getRelation(), $format, $context, true), - 'notSet' => $this->normalizeChild($object->getNotSet(), $format, $context, true), - ]; + return ['name' => $this->normalizeChild($object->getName(), $format, $context, true), 'age' => $this->normalizeChild($object->getAge(), $format, $context, true), 'height' => $this->normalizeChild($object->getHeight(), $format, $context, true), 'handsome' => $this->normalizeChild($object->getHandsome(), $format, $context, true), 'nameOfFriends' => $this->normalizeChild($object->getNameOfFriends(), $format, $context, true), 'picture' => $this->normalizeChild($object->getPicture(), $format, $context, true), 'pet' => $this->normalizeChild($object->getPet(), $format, $context, true), 'relation' => $this->normalizeChild($object->getRelation(), $format, $context, true), 'notSet' => $this->normalizeChild($object->getNotSet(), $format, $context, true)]; } - public function setNormalizer(NormalizerInterface $normalizer): void { $this->normalizer = $normalizer; } - private function normalizeChild(mixed $object, ?string $format, array $context, bool $canBeIterable): mixed { if (is_scalar($object) || null === $object) { return $object; } - - if ($canBeIterable === true && is_iterable($object)) { + if ($canBeIterable && is_iterable($object)) { return array_map(fn($item) => $this->normalizeChild($item, $format, $context, true), $object); } - return $this->normalizer->normalize($object, $format, $context); - } - - public function denormalize(mixed $data, string $type, ?string $format = NULL, array $context = []): mixed + public function denormalize(mixed $data, string $type, ?string $format = null, array $context = []): mixed { - - $output = new ConstructorInjection( - $data['name'], - $data['age'], - $data['height'], - $data['handsome'], - $data['nameOfFriends'], - $data['picture'], - $data['pet'], - $data['relation'], - ); - + $output = new ConstructorInjection($data['name'], $data['age'], $data['height'], $data['handsome'], $data['nameOfFriends'], $data['picture'], $data['pet'], $data['relation']); return $output; } - -} +} \ No newline at end of file diff --git a/src/Symfony/Component/Serializer/Tests/Fixtures/CustomNormalizer/NoTypeHints/ExpectedNormalizer/InheritanceChild.php b/src/Symfony/Component/Serializer/Tests/Fixtures/CustomNormalizer/NoTypeHints/ExpectedNormalizer/InheritanceChild.php index dfa61a84684b2..81bda75d3e4d5 100644 --- a/src/Symfony/Component/Serializer/Tests/Fixtures/CustomNormalizer/NoTypeHints/ExpectedNormalizer/InheritanceChild.php +++ b/src/Symfony/Component/Serializer/Tests/Fixtures/CustomNormalizer/NoTypeHints/ExpectedNormalizer/InheritanceChild.php @@ -6,70 +6,45 @@ use Symfony\Component\Serializer\Normalizer\NormalizerInterface; use Symfony\Component\Serializer\Normalizer\DenormalizerInterface; use Symfony\Component\Serializer\Normalizer\NormalizerAwareInterface; - class Symfony_Component_Serializer_Tests_Fixtures_CustomNormalizer_NoTypeHints_InheritanceChild implements NormalizerInterface, DenormalizerInterface, NormalizerAwareInterface { - private null|NormalizerInterface $normalizer = NULL; - + private null|NormalizerInterface $normalizer = null; public function getSupportedTypes(?string $format): array { return [InheritanceChild::class => true]; } - - 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 InheritanceChild; } - - 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 $type === InheritanceChild::class; } - /** - * @param InheritanceChild $object - */ - public function normalize(mixed $object, ?string $format = NULL, array $context = []): array|string|int|float|bool|\ArrayObject|null + * @param InheritanceChild $object + */ + public function normalize(mixed $object, string $format = null, array $context = []): array|string|int|float|bool|\ArrayObject|null { - return [ - 'childCute' => $this->normalizeChild($object->getChildCute(), $format, $context, true), - 'cute' => $this->normalizeChild($object->getCute(), $format, $context, true), - 'childName' => $this->normalizeChild($object->childName, $format, $context, true), - 'name' => $this->normalizeChild($object->name, $format, $context, true), - 'childAge' => $this->normalizeChild($object->getChildAge(), $format, $context, true), - 'childHeight' => $this->normalizeChild($object->getChildHeight(), $format, $context, true), - 'age' => $this->normalizeChild($object->getAge(), $format, $context, true), - 'height' => $this->normalizeChild($object->getHeight(), $format, $context, true), - 'handsome' => $this->normalizeChild($object->getHandsome(), $format, $context, true), - ]; + return ['childCute' => $this->normalizeChild($object->getChildCute(), $format, $context, true), 'cute' => $this->normalizeChild($object->getCute(), $format, $context, true), 'childName' => $this->normalizeChild($object->childName, $format, $context, true), 'name' => $this->normalizeChild($object->name, $format, $context, true), 'childAge' => $this->normalizeChild($object->getChildAge(), $format, $context, true), 'childHeight' => $this->normalizeChild($object->getChildHeight(), $format, $context, true), 'age' => $this->normalizeChild($object->getAge(), $format, $context, true), 'height' => $this->normalizeChild($object->getHeight(), $format, $context, true), 'handsome' => $this->normalizeChild($object->getHandsome(), $format, $context, true)]; } - public function setNormalizer(NormalizerInterface $normalizer): void { $this->normalizer = $normalizer; } - private function normalizeChild(mixed $object, ?string $format, array $context, bool $canBeIterable): mixed { if (is_scalar($object) || null === $object) { return $object; } - - if ($canBeIterable === true && is_iterable($object)) { + if ($canBeIterable && is_iterable($object)) { return array_map(fn($item) => $this->normalizeChild($item, $format, $context, true), $object); } - return $this->normalizer->normalize($object, $format, $context); - } - - public function denormalize(mixed $data, string $type, ?string $format = NULL, array $context = []): mixed + public function denormalize(mixed $data, string $type, ?string $format = null, array $context = []): mixed { - - $output = new InheritanceChild( - $data['childCute'], - $data['cute'], - ); + $output = new InheritanceChild($data['childCute'], $data['cute']); if (array_key_exists('childName', $data)) { $output->childName = $data['childName']; } @@ -91,8 +66,6 @@ public function denormalize(mixed $data, string $type, ?string $format = NULL, a if (array_key_exists('handsome', $data)) { $output->setHandsome($data['handsome']); } - return $output; } - -} +} \ No newline at end of file diff --git a/src/Symfony/Component/Serializer/Tests/Fixtures/CustomNormalizer/NoTypeHints/ExpectedNormalizer/PublicProperties.php b/src/Symfony/Component/Serializer/Tests/Fixtures/CustomNormalizer/NoTypeHints/ExpectedNormalizer/PublicProperties.php index f587c6e599cc6..26840ae94ada5 100644 --- a/src/Symfony/Component/Serializer/Tests/Fixtures/CustomNormalizer/NoTypeHints/ExpectedNormalizer/PublicProperties.php +++ b/src/Symfony/Component/Serializer/Tests/Fixtures/CustomNormalizer/NoTypeHints/ExpectedNormalizer/PublicProperties.php @@ -6,66 +6,44 @@ use Symfony\Component\Serializer\Normalizer\NormalizerInterface; use Symfony\Component\Serializer\Normalizer\DenormalizerInterface; use Symfony\Component\Serializer\Normalizer\NormalizerAwareInterface; - class Symfony_Component_Serializer_Tests_Fixtures_CustomNormalizer_NoTypeHints_PublicProperties implements NormalizerInterface, DenormalizerInterface, NormalizerAwareInterface { - private null|NormalizerInterface $normalizer = NULL; - + private null|NormalizerInterface $normalizer = null; public function getSupportedTypes(?string $format): array { return [PublicProperties::class => true]; } - - 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 PublicProperties; } - - 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 $type === PublicProperties::class; } - /** - * @param PublicProperties $object - */ - public function normalize(mixed $object, ?string $format = NULL, array $context = []): array|string|int|float|bool|\ArrayObject|null + * @param PublicProperties $object + */ + public function normalize(mixed $object, string $format = null, array $context = []): array|string|int|float|bool|\ArrayObject|null { - return [ - 'name' => $this->normalizeChild($object->name, $format, $context, true), - 'age' => $this->normalizeChild($object->age, $format, $context, true), - 'height' => $this->normalizeChild($object->height, $format, $context, true), - 'handsome' => $this->normalizeChild($object->handsome, $format, $context, true), - 'nameOfFriends' => $this->normalizeChild($object->nameOfFriends, $format, $context, true), - 'picture' => $this->normalizeChild($object->picture, $format, $context, true), - 'pet' => $this->normalizeChild($object->pet, $format, $context, true), - 'relation' => $this->normalizeChild($object->relation, $format, $context, true), - 'notSet' => $this->normalizeChild($object->notSet, $format, $context, true), - ]; + return ['name' => $this->normalizeChild($object->name, $format, $context, true), 'age' => $this->normalizeChild($object->age, $format, $context, true), 'height' => $this->normalizeChild($object->height, $format, $context, true), 'handsome' => $this->normalizeChild($object->handsome, $format, $context, true), 'nameOfFriends' => $this->normalizeChild($object->nameOfFriends, $format, $context, true), 'picture' => $this->normalizeChild($object->picture, $format, $context, true), 'pet' => $this->normalizeChild($object->pet, $format, $context, true), 'relation' => $this->normalizeChild($object->relation, $format, $context, true), 'notSet' => $this->normalizeChild($object->notSet, $format, $context, true)]; } - public function setNormalizer(NormalizerInterface $normalizer): void { $this->normalizer = $normalizer; } - private function normalizeChild(mixed $object, ?string $format, array $context, bool $canBeIterable): mixed { if (is_scalar($object) || null === $object) { return $object; } - - if ($canBeIterable === true && is_iterable($object)) { + if ($canBeIterable && is_iterable($object)) { return array_map(fn($item) => $this->normalizeChild($item, $format, $context, true), $object); } - return $this->normalizer->normalize($object, $format, $context); - } - - public function denormalize(mixed $data, string $type, ?string $format = NULL, array $context = []): mixed + public function denormalize(mixed $data, string $type, ?string $format = null, array $context = []): mixed { - $output = new PublicProperties(); if (array_key_exists('name', $data)) { $output->name = $data['name']; @@ -94,8 +72,6 @@ public function denormalize(mixed $data, string $type, ?string $format = NULL, a if (array_key_exists('notSet', $data)) { $output->notSet = $data['notSet']; } - return $output; } - -} +} \ No newline at end of file diff --git a/src/Symfony/Component/Serializer/Tests/Fixtures/CustomNormalizer/NoTypeHints/ExpectedNormalizer/SetterInjection.php b/src/Symfony/Component/Serializer/Tests/Fixtures/CustomNormalizer/NoTypeHints/ExpectedNormalizer/SetterInjection.php index 71b6c35c24d0e..dfd7a9894e7ee 100644 --- a/src/Symfony/Component/Serializer/Tests/Fixtures/CustomNormalizer/NoTypeHints/ExpectedNormalizer/SetterInjection.php +++ b/src/Symfony/Component/Serializer/Tests/Fixtures/CustomNormalizer/NoTypeHints/ExpectedNormalizer/SetterInjection.php @@ -6,66 +6,44 @@ use Symfony\Component\Serializer\Normalizer\NormalizerInterface; use Symfony\Component\Serializer\Normalizer\DenormalizerInterface; use Symfony\Component\Serializer\Normalizer\NormalizerAwareInterface; - class Symfony_Component_Serializer_Tests_Fixtures_CustomNormalizer_NoTypeHints_SetterInjection implements NormalizerInterface, DenormalizerInterface, NormalizerAwareInterface { - private null|NormalizerInterface $normalizer = NULL; - + private null|NormalizerInterface $normalizer = null; public function getSupportedTypes(?string $format): array { return [SetterInjection::class => true]; } - - 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 SetterInjection; } - - 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 $type === SetterInjection::class; } - /** - * @param SetterInjection $object - */ - public function normalize(mixed $object, ?string $format = NULL, array $context = []): array|string|int|float|bool|\ArrayObject|null + * @param SetterInjection $object + */ + public function normalize(mixed $object, string $format = null, array $context = []): array|string|int|float|bool|\ArrayObject|null { - return [ - 'name' => $this->normalizeChild($object->getName(), $format, $context, true), - 'age' => $this->normalizeChild($object->getAge(), $format, $context, true), - 'height' => $this->normalizeChild($object->getHeight(), $format, $context, true), - 'handsome' => $object->isHandsome(), - 'nameOfFriends' => $this->normalizeChild($object->getNameOfFriends(), $format, $context, true), - 'picture' => $this->normalizeChild($object->getPicture(), $format, $context, true), - 'pet' => $this->normalizeChild($object->getPet(), $format, $context, true), - 'relation' => $this->normalizeChild($object->getRelation(), $format, $context, true), - 'notSet' => $this->normalizeChild($object->getNotSet(), $format, $context, true), - ]; + return ['name' => $this->normalizeChild($object->getName(), $format, $context, true), 'age' => $this->normalizeChild($object->getAge(), $format, $context, true), 'height' => $this->normalizeChild($object->getHeight(), $format, $context, true), 'handsome' => $object->isHandsome(), 'nameOfFriends' => $this->normalizeChild($object->getNameOfFriends(), $format, $context, true), 'picture' => $this->normalizeChild($object->getPicture(), $format, $context, true), 'pet' => $this->normalizeChild($object->getPet(), $format, $context, true), 'relation' => $this->normalizeChild($object->getRelation(), $format, $context, true), 'notSet' => $this->normalizeChild($object->getNotSet(), $format, $context, true)]; } - public function setNormalizer(NormalizerInterface $normalizer): void { $this->normalizer = $normalizer; } - private function normalizeChild(mixed $object, ?string $format, array $context, bool $canBeIterable): mixed { if (is_scalar($object) || null === $object) { return $object; } - - if ($canBeIterable === true && is_iterable($object)) { + if ($canBeIterable && is_iterable($object)) { return array_map(fn($item) => $this->normalizeChild($item, $format, $context, true), $object); } - return $this->normalizer->normalize($object, $format, $context); - } - - public function denormalize(mixed $data, string $type, ?string $format = NULL, array $context = []): mixed + public function denormalize(mixed $data, string $type, ?string $format = null, array $context = []): mixed { - $output = new SetterInjection(); if (array_key_exists('name', $data)) { $output->setName($data['name']); @@ -91,8 +69,6 @@ public function denormalize(mixed $data, string $type, ?string $format = NULL, a if (array_key_exists('relation', $data)) { $output->setRelation($data['relation']); } - return $output; } - -} +} \ No newline at end of file From d10be79ef4dd2325c68c6d27226ac4cd60fb055b Mon Sep 17 00:00:00 2001 From: Nyholm Date: Sun, 14 Jan 2024 18:07:48 -0800 Subject: [PATCH 07/13] cs --- .../Serializer/Builder/NormalizerBuilder.php | 33 ++++++++----------- 1 file changed, 14 insertions(+), 19 deletions(-) diff --git a/src/Symfony/Component/Serializer/Builder/NormalizerBuilder.php b/src/Symfony/Component/Serializer/Builder/NormalizerBuilder.php index a3bfd5b5a0a92..57845b7aa7724 100644 --- a/src/Symfony/Component/Serializer/Builder/NormalizerBuilder.php +++ b/src/Symfony/Component/Serializer/Builder/NormalizerBuilder.php @@ -13,16 +13,16 @@ use PhpParser\Builder\Class_; use PhpParser\Builder\Namespace_; +use PhpParser\BuilderFactory; +use PhpParser\Node; use PhpParser\Node\Expr; use PhpParser\ParserFactory; +use PhpParser\PrettyPrinter; use Symfony\Component\Serializer\Exception\DenormalizingUnionFailedException; use Symfony\Component\Serializer\Normalizer\DenormalizerAwareInterface; use Symfony\Component\Serializer\Normalizer\DenormalizerInterface; use Symfony\Component\Serializer\Normalizer\NormalizerAwareInterface; use Symfony\Component\Serializer\Normalizer\NormalizerInterface; -use PhpParser\BuilderFactory; -use PhpParser\PrettyPrinter; -use PhpParser\Node; /** * The main class to create a new Normalizer from a ClassDefinition. @@ -42,7 +42,7 @@ public function __construct() throw new \LogicException(sprintf('You cannot use "%s" as the "nikic/php-parser" package is not installed. Try running "composer require nikic/php-parser".', static::class)); } - $this->factory = new BuilderFactory; + $this->factory = new BuilderFactory(); $this->printer = new PrettyPrinter\Standard(); } @@ -99,7 +99,6 @@ private function generateNormalizeChildMethod(Namespace_ $namespace, Class_ $cla ) ); - // private function normalizeChild(mixed $object, ?string $format, array $context, bool $canBeIterable): mixed; $class->addStmt($this->factory->method('normalizeChild') ->makePrivate() @@ -151,7 +150,7 @@ private function generateNormalizeChildMethod(Namespace_ $namespace, Class_ $cla new Node\Arg(new Node\Expr\Variable('context')), new Node\Arg(new Node\Expr\ConstFetch(new Node\Name('true'))), ] - ) + ), ]) ), new Node\Arg(new Node\Expr\Variable('object')), @@ -205,7 +204,6 @@ private function generateDenormalizeChildMethod(Namespace_ $namespace, Class_ $c ) ); - // private function denormalizeChild(mixed $data, string $type, ?string $format, array $context, bool $canBeIterable): mixed; $class->addStmt($this->factory->method('denormalizeChild') ->makePrivate() @@ -259,7 +257,7 @@ private function generateDenormalizeChildMethod(Namespace_ $namespace, Class_ $c new Node\Arg(new Node\Expr\Variable('context')), new Node\Arg(new Node\Expr\ConstFetch(new Node\Name('true'))), ] - ) + ), ]) ), new Node\Arg(new Node\Expr\Variable('data')), @@ -359,7 +357,7 @@ private function addDenormailizeMethod(ClassDefinition $definition, Namespace_ $ [new Node\Arg(new Node\Expr\ClassConstFetch(new Node\Name($definition->getSourceClassName()), 'class'))] ), 'newInstanceWithoutConstructor' - ))); + ))); } else { $constructorArguments = []; @@ -369,10 +367,10 @@ private function addDenormailizeMethod(ClassDefinition $definition, Namespace_ $ $canBeIterable = $propertyDefinition->isCollection(); $defaultValue = $propertyDefinition->getConstructorDefaultValue(); - if (is_object($defaultValue)) { + if (\is_object($defaultValue)) { // public function __construct($foo = new \stdClass()); // There is no support for parameters to the object. - $defaultValue = new Expr\New_(new Node\Name\FullyQualified(get_class($defaultValue))); + $defaultValue = new Expr\New_(new Node\Name\FullyQualified($defaultValue::class)); } else { $defaultValue = $this->factory->val($defaultValue); } @@ -427,7 +425,7 @@ private function addDenormailizeMethod(ClassDefinition $definition, Namespace_ $ $defaultValue )), ], - 'else' => new Node\Stmt\Else_($variableOutput) + 'else' => new Node\Stmt\Else_($variableOutput), ] )]; } @@ -486,11 +484,9 @@ private function addDenormailizeMethod(ClassDefinition $definition, Namespace_ $ ), ]; } - - } - $result = $tempVariableName === null ? $variable : new Node\Expr\Variable($tempVariableName); + $result = null === $tempVariableName ? $variable : new Node\Expr\Variable($tempVariableName); if (null !== $method = $propertyDefinition->getSetterName()) { $variableOutput[] = new Node\Stmt\Expression(new Node\Expr\MethodCall( new Node\Expr\Variable('output'), @@ -567,7 +563,7 @@ private function addNormailizeMethod(ClassDefinition $definition, Namespace_ $na ->addParam($this->factory->param('format')->setType('string')->setDefault(null)) ->addParam($this->factory->param('context')->setType('array')->setDefault([])) ->setReturnType('array|string|int|float|bool|\ArrayObject|null') - ->setDocComment(sprintf('/**'.PHP_EOL.'* @param %s $object'.PHP_EOL.'*/', $definition->getSourceClassName())) + ->setDocComment(sprintf('/**'.\PHP_EOL.'* @param %s $object'.\PHP_EOL.'*/', $definition->getSourceClassName())) ->addStmt(new Node\Stmt\Return_(new Node\Expr\Array_($bodyArrayItems)))); if ($needsChildNormalizer) { @@ -644,7 +640,7 @@ private function generateCodeToDeserializeMultiplePossibleClasses(array $targetC new Node\Expr\ArrayDimFetch(new Node\Expr\Variable('exceptions')), new Node\Expr\Variable('e') ) - ) + ), ] ), ], @@ -666,11 +662,10 @@ private function generateCodeToDeserializeMultiplePossibleClasses(array $targetC ] ) ), - ) + ), ], ] ), ]; - } } From 9475e966296518667a302037ad46d58e373bd6d4 Mon Sep 17 00:00:00 2001 From: Nyholm Date: Sun, 14 Jan 2024 18:27:54 -0800 Subject: [PATCH 08/13] CS --- .gitattributes | 4 ++++ .../DependencyInjection/CustomNormalizerHelper.php | 4 ++-- .../Component/Serializer/Tests/Builder/FixtureHelper.php | 1 + .../Tests/Builder/NormalizerBuilderFixtureTest.php | 5 +++++ 4 files changed, 12 insertions(+), 2 deletions(-) diff --git a/.gitattributes b/.gitattributes index d1570aff1cd79..d25fa38fe3c97 100644 --- a/.gitattributes +++ b/.gitattributes @@ -5,4 +5,8 @@ /src/Symfony/Component/Notifier/Bridge export-ignore /src/Symfony/Component/Runtime export-ignore /src/Symfony/Component/Translation/Bridge export-ignore + +# Keep generated files from displaying in diffs by default +# https://docs.github.com/en/repositories/working-with-files/managing-files/customizing-how-changed-files-appear-on-github /src/Symfony/Component/Intl/Resources/data/*/* linguist-generated=true +/src/Symfony/Component/Serializer/Tests/Fixtures/CustomNormalizer/*/ExpectedNormalizer/* linguist-generated=true diff --git a/src/Symfony/Component/Serializer/DependencyInjection/CustomNormalizerHelper.php b/src/Symfony/Component/Serializer/DependencyInjection/CustomNormalizerHelper.php index 1b23803ecd719..f2dc0cbef7200 100644 --- a/src/Symfony/Component/Serializer/DependencyInjection/CustomNormalizerHelper.php +++ b/src/Symfony/Component/Serializer/DependencyInjection/CustomNormalizerHelper.php @@ -33,7 +33,7 @@ public function __construct( private DefinitionExtractor $definitionExtractor, private array $paths, private string $projectDir, - private ?LoggerInterface $logger = null + private ?LoggerInterface $logger = null, ) { } @@ -60,7 +60,7 @@ public function build(string $outputDir): iterable } $reflectionClass = new \ReflectionClass($classNs); - if ([] === $reflectionClass->getAttributes(Serializable::class)) { + if ([] === $reflectionClass->getAttributes(Serializable::class, \ReflectionAttribute::IS_INSTANCEOF)) { continue; } diff --git a/src/Symfony/Component/Serializer/Tests/Builder/FixtureHelper.php b/src/Symfony/Component/Serializer/Tests/Builder/FixtureHelper.php index 1f5ca6f4e54b1..2be4c785b62f6 100644 --- a/src/Symfony/Component/Serializer/Tests/Builder/FixtureHelper.php +++ b/src/Symfony/Component/Serializer/Tests/Builder/FixtureHelper.php @@ -40,6 +40,7 @@ public static function getDefinitionExtractor(): DefinitionExtractor public static function getFixturesAndResultFiles(): iterable { $rootDir = \dirname(__DIR__).'/Fixtures/CustomNormalizer'; + return [ NoTypeHints\PublicProperties::class => $rootDir.'/NoTypeHints/ExpectedNormalizer/PublicProperties.php', NoTypeHints\ConstructorInjection::class => $rootDir.'/NoTypeHints/ExpectedNormalizer/ConstructorInjection.php', diff --git a/src/Symfony/Component/Serializer/Tests/Builder/NormalizerBuilderFixtureTest.php b/src/Symfony/Component/Serializer/Tests/Builder/NormalizerBuilderFixtureTest.php index 3154d9103b310..f76303617135a 100644 --- a/src/Symfony/Component/Serializer/Tests/Builder/NormalizerBuilderFixtureTest.php +++ b/src/Symfony/Component/Serializer/Tests/Builder/NormalizerBuilderFixtureTest.php @@ -31,6 +31,11 @@ public static function setUpBeforeClass(): void } /** + * If one does changes to the NormalizerBuilder, this test will probably fail. + * Run `php Tests/Builder/generateUpdatedFixtures.php` to update the fixtures. + * + * This will help reviewers to see the effect of the changes in the NormalizerBuilder. + * * @dataProvider fixtureClassGenerator */ public function testBuildFixtures(string $inputClass, string $expectedOutputFile) From 9ee662b0894fd9ecc1b1d96b88b364c9273974e4 Mon Sep 17 00:00:00 2001 From: Nyholm Date: Sun, 14 Jan 2024 19:03:49 -0800 Subject: [PATCH 09/13] Only compare output on nikic/php-parser: 5 --- .../Serializer/Builder/NormalizerBuilder.php | 19 +++++++++---------- .../Builder/NormalizerBuilderFixtureTest.php | 10 +++++++++- 2 files changed, 18 insertions(+), 11 deletions(-) diff --git a/src/Symfony/Component/Serializer/Builder/NormalizerBuilder.php b/src/Symfony/Component/Serializer/Builder/NormalizerBuilder.php index 57845b7aa7724..dc2ab77a6beb9 100644 --- a/src/Symfony/Component/Serializer/Builder/NormalizerBuilder.php +++ b/src/Symfony/Component/Serializer/Builder/NormalizerBuilder.php @@ -15,7 +15,6 @@ use PhpParser\Builder\Namespace_; use PhpParser\BuilderFactory; use PhpParser\Node; -use PhpParser\Node\Expr; use PhpParser\ParserFactory; use PhpParser\PrettyPrinter; use Symfony\Component\Serializer\Exception\DenormalizingUnionFailedException; @@ -301,7 +300,7 @@ private function addRequiredMethods(ClassDefinition $definition, Namespace_ $nam ->addParam($this->factory->param('format')->setType('?string')) ->setReturnType('array') ->addStmt(new Node\Stmt\Return_(new Node\Expr\Array_([ - new Node\ArrayItem( + new Node\Expr\ArrayItem( new Node\Expr\ConstFetch(new Node\Name('true')), new Node\Expr\ClassConstFetch(new Node\Name($definition->getSourceClassName()), 'class') ), @@ -370,7 +369,7 @@ private function addDenormailizeMethod(ClassDefinition $definition, Namespace_ $ if (\is_object($defaultValue)) { // public function __construct($foo = new \stdClass()); // There is no support for parameters to the object. - $defaultValue = new Expr\New_(new Node\Name\FullyQualified($defaultValue::class)); + $defaultValue = new Node\Expr\New_(new Node\Name\FullyQualified($defaultValue::class)); } else { $defaultValue = $this->factory->val($defaultValue); } @@ -404,7 +403,7 @@ private function addDenormailizeMethod(ClassDefinition $definition, Namespace_ $ new Node\Arg(new Node\Expr\ClassConstFetch(new Node\Name\FullyQualified($targetClasses[0]), 'class')), new Node\Arg(new Node\Expr\Variable('format')), new Node\Arg(new Node\Expr\Variable('context')), - new Node\Arg(new Expr\ConstFetch(new Node\Name($canBeIterable ? 'true' : 'false'))), + new Node\Arg(new Node\Expr\ConstFetch(new Node\Name($canBeIterable ? 'true' : 'false'))), ] ) ) @@ -413,7 +412,7 @@ private function addDenormailizeMethod(ClassDefinition $definition, Namespace_ $ } if ($propertyDefinition->hasConstructorDefaultValue()) { - $variableOutput = [new Node\Stmt\If_(new Expr\BooleanNot( + $variableOutput = [new Node\Stmt\If_(new Node\Expr\BooleanNot( $this->factory->funcCall('array_key_exists', [ new Node\Arg(new Node\Scalar\String_($propertyDefinition->getNormalizedName())), new Node\Arg(new Node\Expr\Variable('data')), @@ -477,7 +476,7 @@ private function addDenormailizeMethod(ClassDefinition $definition, Namespace_ $ new Node\Arg(new Node\Expr\ClassConstFetch(new Node\Name\FullyQualified($targetClasses[0]), 'class')), new Node\Arg(new Node\Expr\Variable('format')), new Node\Arg(new Node\Expr\Variable('context')), - new Node\Arg(new Expr\ConstFetch(new Node\Name($propertyDefinition->isCollection() ? 'true' : 'false'))), + new Node\Arg(new Node\Expr\ConstFetch(new Node\Name($propertyDefinition->isCollection() ? 'true' : 'false'))), ] ) ) @@ -553,7 +552,7 @@ private function addNormailizeMethod(ClassDefinition $definition, Namespace_ $na ]); } - $bodyArrayItems[] = new Node\ArrayItem($accessor, new Node\Scalar\String_($propertyDefinition->getNormalizedName())); + $bodyArrayItems[] = new Node\Expr\ArrayItem($accessor, new Node\Scalar\String_($propertyDefinition->getNormalizedName())); } // public function normalize(mixed $object, string $format = null, array $context = []): array|string|int|float|bool|\ArrayObject|null; @@ -577,11 +576,11 @@ private function addNormailizeMethod(ClassDefinition $definition, Namespace_ $na * * @return Node\Stmt[] */ - private function generateCodeToDeserializeMultiplePossibleClasses(array $targetClasses, bool $canBeIterable, string $tempVariableName, Expr $variable, string $keyName, string $classNs): array + private function generateCodeToDeserializeMultiplePossibleClasses(array $targetClasses, bool $canBeIterable, string $tempVariableName, Node\Expr $variable, string $keyName, string $classNs): array { $arrayItems = []; foreach ($targetClasses as $class) { - $arrayItems[] = new Node\ArrayItem(new Expr\ClassConstFetch(new Node\Name\FullyQualified($class), 'class')); + $arrayItems[] = new Node\Expr\ArrayItem(new Node\Expr\ClassConstFetch(new Node\Name\FullyQualified($class), 'class')); } return [ @@ -616,7 +615,7 @@ private function generateCodeToDeserializeMultiplePossibleClasses(array $targetC new Node\Arg(new Node\Expr\Variable('class')), new Node\Arg(new Node\Expr\Variable('format')), new Node\Arg(new Node\Expr\Variable('context')), - new Node\Arg(new Expr\ConstFetch(new Node\Name($canBeIterable ? 'true' : 'false'))), + new Node\Arg(new Node\Expr\ConstFetch(new Node\Name($canBeIterable ? 'true' : 'false'))), ] ) ) diff --git a/src/Symfony/Component/Serializer/Tests/Builder/NormalizerBuilderFixtureTest.php b/src/Symfony/Component/Serializer/Tests/Builder/NormalizerBuilderFixtureTest.php index f76303617135a..78bde70217b80 100644 --- a/src/Symfony/Component/Serializer/Tests/Builder/NormalizerBuilderFixtureTest.php +++ b/src/Symfony/Component/Serializer/Tests/Builder/NormalizerBuilderFixtureTest.php @@ -11,6 +11,7 @@ namespace Symfony\Component\Serializer\Tests\Builder; +use PhpParser\ParserFactory; use PHPUnit\Framework\TestCase; use Symfony\Component\Serializer\Builder\DefinitionExtractor; use Symfony\Component\Serializer\Builder\NormalizerBuilder; @@ -20,6 +21,7 @@ class NormalizerBuilderFixtureTest extends TestCase private static NormalizerBuilder $builder; private static DefinitionExtractor $definitionExtractor; private static string $outputDir; + private static bool $compareOutput; public static function setUpBeforeClass(): void { @@ -27,6 +29,9 @@ public static function setUpBeforeClass(): void self::$outputDir = \dirname(__DIR__).'/_output/SerializerBuilderFixtureTest'; self::$builder = new NormalizerBuilder(); + // Only compare on nikic/php-parser: ^5.0 + self::$compareOutput = method_exists(ParserFactory::class, 'createForVersion'); + parent::setUpBeforeClass(); } @@ -44,7 +49,10 @@ public function testBuildFixtures(string $inputClass, string $expectedOutputFile $result = self::$builder->build($def, self::$outputDir); $result->loadClass(); $this->assertTrue(class_exists($result->classNs)); - $this->assertFileEquals($expectedOutputFile, $result->filePath); + + if (self::$compareOutput) { + $this->assertFileEquals($expectedOutputFile, $result->filePath); + } } public static function fixtureClassGenerator(): iterable From 09f9bd4144a9a656321076c6c8c5a73d362c2dcf Mon Sep 17 00:00:00 2001 From: Nyholm Date: Sun, 14 Jan 2024 19:11:00 -0800 Subject: [PATCH 10/13] Fix deps low --- src/Symfony/Component/Serializer/composer.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Symfony/Component/Serializer/composer.json b/src/Symfony/Component/Serializer/composer.json index bab2a3bf0c1ee..6b1e4a071972c 100644 --- a/src/Symfony/Component/Serializer/composer.json +++ b/src/Symfony/Component/Serializer/composer.json @@ -35,7 +35,7 @@ "symfony/messenger": "^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/property-info": "^7.1", "symfony/translation-contracts": "^2.5|^3", "symfony/uid": "^6.4|^7.0", "symfony/validator": "^6.4|^7.0", @@ -48,7 +48,7 @@ "phpdocumentor/type-resolver": "<1.4.0", "symfony/dependency-injection": "<6.4", "symfony/property-access": "<6.4", - "symfony/property-info": "<6.4", + "symfony/property-info": "<7.1", "symfony/uid": "<6.4", "symfony/validator": "<6.4", "symfony/yaml": "<6.4" From 0d224035752b43113fb168816bfdb726474a9e02 Mon Sep 17 00:00:00 2001 From: Nyholm Date: Sun, 14 Jan 2024 21:08:38 -0800 Subject: [PATCH 11/13] Added some more tests --- .../Serializer/Builder/NormalizerBuilder.php | 7 +- .../Tests/Normalizer/AutoNormalizerTest.php | 488 ++++++++++++++++++ 2 files changed, 494 insertions(+), 1 deletion(-) create mode 100644 src/Symfony/Component/Serializer/Tests/Normalizer/AutoNormalizerTest.php diff --git a/src/Symfony/Component/Serializer/Builder/NormalizerBuilder.php b/src/Symfony/Component/Serializer/Builder/NormalizerBuilder.php index dc2ab77a6beb9..ee778bbe6d629 100644 --- a/src/Symfony/Component/Serializer/Builder/NormalizerBuilder.php +++ b/src/Symfony/Component/Serializer/Builder/NormalizerBuilder.php @@ -338,7 +338,12 @@ private function addRequiredMethods(ClassDefinition $definition, Namespace_ $nam private function addDenormailizeMethod(ClassDefinition $definition, Namespace_ $namespace, Class_ $class): void { $needsChildDenormalizer = false; - $body = []; + $body = [ + new Node\Stmt\Expression(new Node\Expr\Assign( + new Node\Expr\Variable('data'), + new Node\Expr\Cast\Array_(new Node\Expr\Variable('data')) + )), + ]; if (ClassDefinition::CONSTRUCTOR_NONE === $definition->getConstructorType()) { $body[] = new Node\Stmt\Expression(new Node\Expr\Assign( diff --git a/src/Symfony/Component/Serializer/Tests/Normalizer/AutoNormalizerTest.php b/src/Symfony/Component/Serializer/Tests/Normalizer/AutoNormalizerTest.php new file mode 100644 index 0000000000000..5713def21eb95 --- /dev/null +++ b/src/Symfony/Component/Serializer/Tests/Normalizer/AutoNormalizerTest.php @@ -0,0 +1,488 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Serializer\Tests\Normalizer; + +use PhpParser\ParserFactory; +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\Builder\DefinitionExtractor; +use Symfony\Component\Serializer\Builder\NormalizerBuilder; +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; +use Symfony\Component\Serializer\Mapping\Factory\ClassMetadataFactoryInterface; +use Symfony\Component\Serializer\Mapping\Loader\AttributeLoader; +use Symfony\Component\Serializer\NameConverter\AdvancedNameConverterInterface; +use Symfony\Component\Serializer\NameConverter\CamelCaseToSnakeCaseNameConverter; +use Symfony\Component\Serializer\NameConverter\MetadataAwareNameConverter; +use Symfony\Component\Serializer\Normalizer\AbstractNormalizer; +use Symfony\Component\Serializer\Normalizer\ArrayDenormalizer; +use Symfony\Component\Serializer\Normalizer\DateTimeNormalizer; +use Symfony\Component\Serializer\Normalizer\NormalizerInterface; +use Symfony\Component\Serializer\Normalizer\xx; +use Symfony\Component\Serializer\Serializer; +use Symfony\Component\Serializer\SerializerInterface; +use Symfony\Component\Serializer\Tests\Builder\FixtureHelper; +use Symfony\Component\Serializer\Tests\Fixtures\Attributes\GroupDummy; +use Symfony\Component\Serializer\Tests\Fixtures\CircularReferenceDummy; +use Symfony\Component\Serializer\Tests\Fixtures\DummyPrivatePropertyWithoutGetter; +use Symfony\Component\Serializer\Tests\Fixtures\OtherSerializedNameDummy; +use Symfony\Component\Serializer\Tests\Fixtures\Php74Dummy; +use Symfony\Component\Serializer\Tests\Fixtures\Php74DummyPrivate; +use Symfony\Component\Serializer\Tests\Fixtures\Php80Dummy; +use Symfony\Component\Serializer\Tests\Fixtures\Sibling; +use Symfony\Component\Serializer\Tests\Fixtures\SiblingHolder; +use Symfony\Component\Serializer\Tests\Normalizer\Features\AttributesTestTrait; +use Symfony\Component\Serializer\Tests\Normalizer\Features\CacheableObjectAttributesTestTrait; +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\ContextMetadataTestTrait; +use Symfony\Component\Serializer\Tests\Normalizer\Features\GroupsTestTrait; +use Symfony\Component\Serializer\Tests\Normalizer\Features\IgnoredAttributesTestTrait; +use Symfony\Component\Serializer\Tests\Normalizer\Features\MaxDepthTestTrait; +use Symfony\Component\Serializer\Tests\Normalizer\Features\ObjectDummy; +use Symfony\Component\Serializer\Tests\Normalizer\Features\ObjectToPopulateTestTrait; +use Symfony\Component\Serializer\Tests\Normalizer\Features\SkipNullValuesTestTrait; +use Symfony\Component\Serializer\Tests\Normalizer\Features\SkipUninitializedValuesTestTrait; +use Symfony\Component\Serializer\Tests\Normalizer\Features\TypedPropertiesObject; +use Symfony\Component\Serializer\Tests\Normalizer\Features\TypedPropertiesObjectWithGetters; +use Symfony\Component\Serializer\Tests\Normalizer\Features\TypeEnforcementTestTrait; + +/** + * @author Kévin Dunglas + */ +class AutoNormalizerTest extends TestCase +{ + private static NormalizerBuilder $builder; + private static DefinitionExtractor $definitionExtractor; + private static string $outputDir; + + public static function setUpBeforeClass(): void + { + self::$definitionExtractor = FixtureHelper::getDefinitionExtractor(); + self::$outputDir = \dirname(__DIR__).'/_output/SerializerBuilderFixtureTest'; + self::$builder = new NormalizerBuilder(); + + parent::setUpBeforeClass(); + } + + private function getSerializer(string ...$inputClasses): Serializer + { + $normalizers = []; + foreach($inputClasses as $inputClass) { + $def = self::$definitionExtractor->getDefinition($inputClass); + $result = self::$builder->build($def, self::$outputDir); + $result->loadClass(); + + $normalizers[] = new $result->classNs(); + } + + return new Serializer($normalizers); + } + + public function testNormalizeObjectWithPrivatePropertyWithoutGetter() + { + $serializer = $this->getSerializer(DummyPrivatePropertyWithoutGetter::class); + $obj = new DummyPrivatePropertyWithoutGetter(); + $this->assertEquals( + ['bar' => 'bar'], + $serializer->normalize($obj, 'any') + ); + } + + public function testDenormalize() + { + $serializer = $this->getSerializer(ObjectDummy::class); + $obj = $serializer->denormalize( + ['foo' => 'foo', 'bar' => 'bar', 'baz' => true, 'fooBar' => 'foobar'], + ObjectDummy::class, + 'any' + ); + $this->assertEquals('foo', $obj->getFoo()); + $this->assertEquals('bar', $obj->bar); + $this->assertTrue($obj->isBaz()); + } + + public function testDenormalizeWithObject() + { + $serializer = $this->getSerializer(ObjectDummy::class); + $data = new \stdClass(); + $data->foo = 'foo'; + $data->bar = 'bar'; + $data->fooBar = 'foobar'; + $obj = $serializer->denormalize($data, ObjectDummy::class, 'any'); + $this->assertEquals('foo', $obj->getFoo()); + $this->assertEquals('bar', $obj->bar); + } + + public function testDenormalizeNull() + { + $serializer = $this->getSerializer(ObjectDummy::class); + $this->assertEquals(new ObjectDummy(), $serializer->denormalize(null, ObjectDummy::class)); + } + + public function testConstructorDenormalize() + { + $serializer = $this->getSerializer(ObjectConstructorDummy::class); + $obj = $serializer->denormalize( + ['foo' => 'foo', 'bar' => 'bar', 'baz' => true, 'fooBar' => 'foobar'], + ObjectConstructorDummy::class, 'any'); + $this->assertEquals('foo', $obj->getFoo()); + $this->assertEquals('bar', $obj->bar); + $this->assertTrue($obj->isBaz()); + } + + public function testConstructorDenormalizeWithNullArgument() + { + $serializer = $this->getSerializer(ObjectConstructorDummy::class); + $obj = $serializer->denormalize( + ['foo' => 'foo', 'bar' => null, 'baz' => true], + ObjectConstructorDummy::class, 'any'); + $this->assertEquals('foo', $obj->getFoo()); + $this->assertNull($obj->bar); + $this->assertTrue($obj->isBaz()); + } + + public function testConstructorDenormalizeWithMissingOptionalArgument() + { + $serializer = $this->getSerializer(ObjectConstructorOptionalArgsDummy::class); + $obj = $serializer->denormalize( + ['foo' => 'test', 'baz' => [1, 2, 3]], + ObjectConstructorOptionalArgsDummy::class, 'any'); + $this->assertEquals('test', $obj->getFoo()); + $this->assertEquals([], $obj->bar); + $this->assertEquals([1, 2, 3], $obj->getBaz()); + } + + public function testConstructorDenormalizeWithOptionalDefaultArgument() + { + $serializer = $this->getSerializer(ObjectConstructorArgsWithDefaultValueDummy::class); + $obj = $serializer->denormalize( + ['bar' => 'test'], + ObjectConstructorArgsWithDefaultValueDummy::class, 'any'); + $this->assertEquals([], $obj->getFoo()); + $this->assertEquals('test', $obj->getBar()); + } + + public function testConstructorWithObjectDenormalize() + { + $serializer = $this->getSerializer(ObjectConstructorDummy::class); + $data = new \stdClass(); + $data->foo = 'foo'; + $data->bar = 'bar'; + $data->baz = true; + $data->fooBar = 'foobar'; + $obj = $serializer->denormalize($data, ObjectConstructorDummy::class, 'any'); + $this->assertEquals('foo', $obj->getFoo()); + $this->assertEquals('bar', $obj->bar); + } + + public function testConstructorWithObjectTypeHintDenormalize() + { + $data = [ + 'id' => 10, + 'inner' => [ + 'foo' => 'oof', + 'bar' => 'rab', + ], + ]; + + + $serializer = $this->getSerializer(DummyWithConstructorObject::class, ObjectInner::class); + $obj = $serializer->denormalize($data, DummyWithConstructorObject::class); + $this->assertInstanceOf(DummyWithConstructorObject::class, $obj); + $this->assertEquals(10, $obj->getId()); + $this->assertInstanceOf(ObjectInner::class, $obj->getInner()); + $this->assertEquals('oof', $obj->getInner()->foo); + $this->assertEquals('rab', $obj->getInner()->bar); + } + + public function testConstructorWithUnconstructableNullableObjectTypeHintDenormalize() + { + $data = [ + 'id' => 10, + 'inner' => null, + ]; + + $serializer = $this->getSerializer(DummyWithNullableConstructorObject::class); + $obj = $serializer->denormalize($data, DummyWithNullableConstructorObject::class); + $this->assertInstanceOf(DummyWithNullableConstructorObject::class, $obj); + $this->assertEquals(10, $obj->getId()); + $this->assertNull($obj->getInner()); + } + + public function testConstructorWithUnknownObjectTypeHintDenormalize() + { + $data = [ + 'id' => 10, + 'unknown' => [ + 'foo' => 'oof', + 'bar' => 'rab', + ], + ]; + + $serializer = $this->getSerializer(DummyWithConstructorInexistingObject::class); + $this->expectException(NotNormalizableValueException::class); + $this->expectExceptionMessage('Could not denormalize object of type "Symfony\Component\Serializer\Tests\Normalizer\Unknown", no supporting normalizer found.'); + + $serializer->denormalize($data, DummyWithConstructorInexistingObject::class); + } + + public function testSiblingReference() + { + $serializer = $this->getSerializer(SiblingHolder::class, Sibling::class); + $siblingHolder = new SiblingHolder(); + + $expected = [ + 'sibling0' => ['coopTilleuls' => 'Les-Tilleuls.coop'], + 'sibling1' => ['coopTilleuls' => 'Les-Tilleuls.coop'], + 'sibling2' => ['coopTilleuls' => 'Les-Tilleuls.coop'], + ]; + $this->assertEquals($expected, $serializer->normalize($siblingHolder)); + } + + public function testDenormalizeNonExistingAttribute() + { + $serializer = $this->getSerializer(ObjectDummy::class); + $this->assertEquals( + new ObjectDummy(), + $serializer->denormalize(['non_existing' => true], ObjectDummy::class) + ); + } + + public function testNormalizeStatic() + { + $serializer = $this->getSerializer(ObjectWithStaticPropertiesAndMethods::class); + $this->assertEquals(['foo' => 'K'], $serializer->normalize(new ObjectWithStaticPropertiesAndMethods())); + } + + public function testNormalizeUpperCaseAttributes() + { + $serializer = $this->getSerializer(ObjectWithUpperCaseAttributeNames::class); + $this->assertEquals(['Foo' => 'Foo', 'Bar' => 'BarBar'], $serializer->normalize(new ObjectWithUpperCaseAttributeNames())); + } + + public function testThrowUnexpectedValueException() + { + $serializer = $this->getSerializer(ObjectTypeHinted::class); + $this->expectException(UnexpectedValueException::class); + $serializer->denormalize(['foo' => 'bar'], ObjectTypeHinted::class); + } + + public function testDefaultObjectClassResolver() + { + $serializer = $this->getSerializer(ObjectDummy::class); + + $obj = new ObjectDummy(); + $obj->setFoo('foo'); + $obj->bar = 'bar'; + $obj->setBaz(true); + $obj->setCamelCase('camelcase'); + $obj->unwantedProperty = 'notwanted'; + $obj->setGo(false); + + $this->assertEquals( + [ + 'foo' => 'foo', + 'bar' => 'bar', + 'baz' => true, + 'fooBar' => 'foobar', + 'camelCase' => 'camelcase', + 'object' => null, + 'go' => false, + ], + $serializer->normalize($obj, 'any') + ); + } +} + + +class ObjectConstructorDummy +{ + protected $foo; + public $bar; + private $baz; + + public function __construct($foo, $bar, $baz) + { + $this->foo = $foo; + $this->bar = $bar; + $this->baz = $baz; + } + + public function getFoo() + { + return $this->foo; + } + + public function isBaz() + { + return $this->baz; + } + + public function otherMethod() + { + throw new \RuntimeException('Dummy::otherMethod() should not be called'); + } +} + + +class ObjectConstructorOptionalArgsDummy +{ + protected $foo; + public $bar; + private $baz; + + public function __construct($foo, $bar = [], $baz = []) + { + $this->foo = $foo; + $this->bar = $bar; + $this->baz = $baz; + } + + public function getFoo() + { + return $this->foo; + } + + public function getBaz() + { + return $this->baz; + } + + public function otherMethod() + { + throw new \RuntimeException('Dummy::otherMethod() should not be called'); + } +} + +class ObjectConstructorArgsWithDefaultValueDummy +{ + protected $foo; + protected $bar; + + public function __construct($foo = [], $bar = null) + { + $this->foo = $foo; + $this->bar = $bar; + } + + public function getFoo() + { + return $this->foo; + } + + public function getBar() + { + return $this->bar; + } + + public function otherMethod() + { + throw new \RuntimeException('Dummy::otherMethod() should not be called'); + } +} + +class ObjectWithStaticPropertiesAndMethods +{ + public $foo = 'K'; + public static $bar = 'A'; + + public static function getBaz() + { + return 'L'; + } +} + +class ObjectTypeHinted +{ + public function setFoo(array $f) + { + } +} + +class ObjectInner +{ + public $foo; + public $bar; +} + +class DummyWithConstructorObject +{ + private $id; + private $inner; + + public function __construct($id, ObjectInner $inner) + { + $this->id = $id; + $this->inner = $inner; + } + + public function getId() + { + return $this->id; + } + + public function getInner() + { + return $this->inner; + } +} + +class DummyWithConstructorInexistingObject +{ + public function __construct($id, Unknown $unknown) + { + } +} + +class ObjectWithUpperCaseAttributeNames +{ + private $Foo = 'Foo'; + public $Bar = 'BarBar'; + + public function getFoo() + { + return $this->Foo; + } +} + +class DummyWithNullableConstructorObject +{ + private $id; + private $inner; + + public function __construct($id, ?ObjectConstructorDummy $inner) + { + $this->id = $id; + $this->inner = $inner; + } + + public function getId() + { + return $this->id; + } + + public function getInner() + { + return $this->inner; + } +} From 606e24e554ed78dcf117ffbc3dccbc6eeb99000b Mon Sep 17 00:00:00 2001 From: Nyholm Date: Sun, 14 Jan 2024 21:19:33 -0800 Subject: [PATCH 12/13] Removed some tests --- .../Tests/Normalizer/AutoNormalizerTest.php | 13 ------------- 1 file changed, 13 deletions(-) diff --git a/src/Symfony/Component/Serializer/Tests/Normalizer/AutoNormalizerTest.php b/src/Symfony/Component/Serializer/Tests/Normalizer/AutoNormalizerTest.php index 5713def21eb95..2a8bdbd1cd56b 100644 --- a/src/Symfony/Component/Serializer/Tests/Normalizer/AutoNormalizerTest.php +++ b/src/Symfony/Component/Serializer/Tests/Normalizer/AutoNormalizerTest.php @@ -268,25 +268,12 @@ public function testDenormalizeNonExistingAttribute() ); } - public function testNormalizeStatic() - { - $serializer = $this->getSerializer(ObjectWithStaticPropertiesAndMethods::class); - $this->assertEquals(['foo' => 'K'], $serializer->normalize(new ObjectWithStaticPropertiesAndMethods())); - } - public function testNormalizeUpperCaseAttributes() { $serializer = $this->getSerializer(ObjectWithUpperCaseAttributeNames::class); $this->assertEquals(['Foo' => 'Foo', 'Bar' => 'BarBar'], $serializer->normalize(new ObjectWithUpperCaseAttributeNames())); } - public function testThrowUnexpectedValueException() - { - $serializer = $this->getSerializer(ObjectTypeHinted::class); - $this->expectException(UnexpectedValueException::class); - $serializer->denormalize(['foo' => 'bar'], ObjectTypeHinted::class); - } - public function testDefaultObjectClassResolver() { $serializer = $this->getSerializer(ObjectDummy::class); From 476baa02a28b9a1e9757e9c82f4d5f9b4576590b Mon Sep 17 00:00:00 2001 From: Nyholm Date: Sun, 14 Jan 2024 21:29:35 -0800 Subject: [PATCH 13/13] CS --- .../ComplexTypesConstructor.php | 1 + .../ComplexTypesPublicProperties.php | 1 + .../ExpectedNormalizer/ComplexTypesSetter.php | 1 + .../ConstructorInjection.php | 1 + .../ConstructorWithDefaultValue.php | 1 + .../ExpectedNormalizer/ExtraSetter.php | 1 + .../ExpectedNormalizer/InheritanceChild.php | 1 + .../NonReadableProperty.php | 1 + .../ExpectedNormalizer/PrivateConstructor.php | 1 + .../ExpectedNormalizer/PublicProperties.php | 1 + .../ExpectedNormalizer/SetterInjection.php | 1 + .../ConstructorAndSetterInjection.php | 1 + .../ConstructorInjection.php | 1 + .../ExpectedNormalizer/InheritanceChild.php | 1 + .../ExpectedNormalizer/PublicProperties.php | 1 + .../ExpectedNormalizer/SetterInjection.php | 1 + .../Tests/Normalizer/AutoNormalizerTest.php | 219 +----------------- 17 files changed, 18 insertions(+), 217 deletions(-) diff --git a/src/Symfony/Component/Serializer/Tests/Fixtures/CustomNormalizer/FullTypeHints/ExpectedNormalizer/ComplexTypesConstructor.php b/src/Symfony/Component/Serializer/Tests/Fixtures/CustomNormalizer/FullTypeHints/ExpectedNormalizer/ComplexTypesConstructor.php index 83c2c4be4ff43..61cee1d250e0d 100644 --- a/src/Symfony/Component/Serializer/Tests/Fixtures/CustomNormalizer/FullTypeHints/ExpectedNormalizer/ComplexTypesConstructor.php +++ b/src/Symfony/Component/Serializer/Tests/Fixtures/CustomNormalizer/FullTypeHints/ExpectedNormalizer/ComplexTypesConstructor.php @@ -47,6 +47,7 @@ private function normalizeChild(mixed $object, ?string $format, array $context, } public function denormalize(mixed $data, string $type, ?string $format = null, array $context = []): mixed { + $data = (array) $data; $argument0 = $this->denormalizeChild($data['simple'], \Symfony\Component\Serializer\Tests\Fixtures\CustomNormalizer\FullTypeHints\DummyObject::class, $format, $context, false); $argument2 = $this->denormalizeChild($data['array'], \Symfony\Component\Serializer\Tests\Fixtures\CustomNormalizer\FullTypeHints\DummyObject::class, $format, $context, true); $exceptions = []; diff --git a/src/Symfony/Component/Serializer/Tests/Fixtures/CustomNormalizer/FullTypeHints/ExpectedNormalizer/ComplexTypesPublicProperties.php b/src/Symfony/Component/Serializer/Tests/Fixtures/CustomNormalizer/FullTypeHints/ExpectedNormalizer/ComplexTypesPublicProperties.php index 3e4d06998af56..b2046cef874e8 100644 --- a/src/Symfony/Component/Serializer/Tests/Fixtures/CustomNormalizer/FullTypeHints/ExpectedNormalizer/ComplexTypesPublicProperties.php +++ b/src/Symfony/Component/Serializer/Tests/Fixtures/CustomNormalizer/FullTypeHints/ExpectedNormalizer/ComplexTypesPublicProperties.php @@ -47,6 +47,7 @@ private function normalizeChild(mixed $object, ?string $format, array $context, } public function denormalize(mixed $data, string $type, ?string $format = null, array $context = []): mixed { + $data = (array) $data; $output = new ComplexTypesPublicProperties(); if (array_key_exists('simple', $data)) { $setter0 = $this->denormalizeChild($data['simple'], \Symfony\Component\Serializer\Tests\Fixtures\CustomNormalizer\FullTypeHints\DummyObject::class, $format, $context, false); diff --git a/src/Symfony/Component/Serializer/Tests/Fixtures/CustomNormalizer/FullTypeHints/ExpectedNormalizer/ComplexTypesSetter.php b/src/Symfony/Component/Serializer/Tests/Fixtures/CustomNormalizer/FullTypeHints/ExpectedNormalizer/ComplexTypesSetter.php index f228b332f4bba..a3e2855adead6 100644 --- a/src/Symfony/Component/Serializer/Tests/Fixtures/CustomNormalizer/FullTypeHints/ExpectedNormalizer/ComplexTypesSetter.php +++ b/src/Symfony/Component/Serializer/Tests/Fixtures/CustomNormalizer/FullTypeHints/ExpectedNormalizer/ComplexTypesSetter.php @@ -47,6 +47,7 @@ private function normalizeChild(mixed $object, ?string $format, array $context, } public function denormalize(mixed $data, string $type, ?string $format = null, array $context = []): mixed { + $data = (array) $data; $output = new ComplexTypesSetter(); if (array_key_exists('simple', $data)) { $setter0 = $this->denormalizeChild($data['simple'], \Symfony\Component\Serializer\Tests\Fixtures\CustomNormalizer\FullTypeHints\DummyObject::class, $format, $context, false); diff --git a/src/Symfony/Component/Serializer/Tests/Fixtures/CustomNormalizer/FullTypeHints/ExpectedNormalizer/ConstructorInjection.php b/src/Symfony/Component/Serializer/Tests/Fixtures/CustomNormalizer/FullTypeHints/ExpectedNormalizer/ConstructorInjection.php index 45c72101f84b8..33e700d531965 100644 --- a/src/Symfony/Component/Serializer/Tests/Fixtures/CustomNormalizer/FullTypeHints/ExpectedNormalizer/ConstructorInjection.php +++ b/src/Symfony/Component/Serializer/Tests/Fixtures/CustomNormalizer/FullTypeHints/ExpectedNormalizer/ConstructorInjection.php @@ -47,6 +47,7 @@ private function normalizeChild(mixed $object, ?string $format, array $context, } public function denormalize(mixed $data, string $type, ?string $format = null, array $context = []): mixed { + $data = (array) $data; $argument7 = $this->denormalizeChild($data['relation'], \Symfony\Component\Serializer\Tests\Fixtures\CustomNormalizer\FullTypeHints\DummyObject::class, $format, $context, false); $output = new ConstructorInjection($data['name'], $data['age'], $data['height'], $data['handsome'], $data['nameOfFriends'], $data['picture'], $data['pet'], $argument7); return $output; diff --git a/src/Symfony/Component/Serializer/Tests/Fixtures/CustomNormalizer/FullTypeHints/ExpectedNormalizer/ConstructorWithDefaultValue.php b/src/Symfony/Component/Serializer/Tests/Fixtures/CustomNormalizer/FullTypeHints/ExpectedNormalizer/ConstructorWithDefaultValue.php index c1b007ceecfbc..7dfdac5f09492 100644 --- a/src/Symfony/Component/Serializer/Tests/Fixtures/CustomNormalizer/FullTypeHints/ExpectedNormalizer/ConstructorWithDefaultValue.php +++ b/src/Symfony/Component/Serializer/Tests/Fixtures/CustomNormalizer/FullTypeHints/ExpectedNormalizer/ConstructorWithDefaultValue.php @@ -47,6 +47,7 @@ private function normalizeChild(mixed $object, ?string $format, array $context, } public function denormalize(mixed $data, string $type, ?string $format = null, array $context = []): mixed { + $data = (array) $data; if (!array_key_exists('union', $data)) { $argument1 = null; } else { diff --git a/src/Symfony/Component/Serializer/Tests/Fixtures/CustomNormalizer/FullTypeHints/ExpectedNormalizer/ExtraSetter.php b/src/Symfony/Component/Serializer/Tests/Fixtures/CustomNormalizer/FullTypeHints/ExpectedNormalizer/ExtraSetter.php index b8507c6fa0649..e7018f526b216 100644 --- a/src/Symfony/Component/Serializer/Tests/Fixtures/CustomNormalizer/FullTypeHints/ExpectedNormalizer/ExtraSetter.php +++ b/src/Symfony/Component/Serializer/Tests/Fixtures/CustomNormalizer/FullTypeHints/ExpectedNormalizer/ExtraSetter.php @@ -28,6 +28,7 @@ public function normalize(mixed $object, string $format = null, array $context = } public function denormalize(mixed $data, string $type, ?string $format = null, array $context = []): mixed { + $data = (array) $data; $output = new ExtraSetter($data['name']); if (array_key_exists('age', $data)) { $output->setAge($data['age']); diff --git a/src/Symfony/Component/Serializer/Tests/Fixtures/CustomNormalizer/FullTypeHints/ExpectedNormalizer/InheritanceChild.php b/src/Symfony/Component/Serializer/Tests/Fixtures/CustomNormalizer/FullTypeHints/ExpectedNormalizer/InheritanceChild.php index c4d26d57aad66..6f6db40dec057 100644 --- a/src/Symfony/Component/Serializer/Tests/Fixtures/CustomNormalizer/FullTypeHints/ExpectedNormalizer/InheritanceChild.php +++ b/src/Symfony/Component/Serializer/Tests/Fixtures/CustomNormalizer/FullTypeHints/ExpectedNormalizer/InheritanceChild.php @@ -28,6 +28,7 @@ public function normalize(mixed $object, string $format = null, array $context = } public function denormalize(mixed $data, string $type, ?string $format = null, array $context = []): mixed { + $data = (array) $data; $output = new InheritanceChild($data['childCute'], $data['cute']); if (array_key_exists('childName', $data)) { $output->childName = $data['childName']; diff --git a/src/Symfony/Component/Serializer/Tests/Fixtures/CustomNormalizer/FullTypeHints/ExpectedNormalizer/NonReadableProperty.php b/src/Symfony/Component/Serializer/Tests/Fixtures/CustomNormalizer/FullTypeHints/ExpectedNormalizer/NonReadableProperty.php index 65409567f3d2a..e2a8da8759c1f 100644 --- a/src/Symfony/Component/Serializer/Tests/Fixtures/CustomNormalizer/FullTypeHints/ExpectedNormalizer/NonReadableProperty.php +++ b/src/Symfony/Component/Serializer/Tests/Fixtures/CustomNormalizer/FullTypeHints/ExpectedNormalizer/NonReadableProperty.php @@ -44,6 +44,7 @@ private function normalizeChild(mixed $object, ?string $format, array $context, } public function denormalize(mixed $data, string $type, ?string $format = null, array $context = []): mixed { + $data = (array) $data; $output = new NonReadableProperty($data['name']); return $output; } diff --git a/src/Symfony/Component/Serializer/Tests/Fixtures/CustomNormalizer/FullTypeHints/ExpectedNormalizer/PrivateConstructor.php b/src/Symfony/Component/Serializer/Tests/Fixtures/CustomNormalizer/FullTypeHints/ExpectedNormalizer/PrivateConstructor.php index 5c8dfc8ca9e22..3f37ee2546446 100644 --- a/src/Symfony/Component/Serializer/Tests/Fixtures/CustomNormalizer/FullTypeHints/ExpectedNormalizer/PrivateConstructor.php +++ b/src/Symfony/Component/Serializer/Tests/Fixtures/CustomNormalizer/FullTypeHints/ExpectedNormalizer/PrivateConstructor.php @@ -28,6 +28,7 @@ public function normalize(mixed $object, string $format = null, array $context = } public function denormalize(mixed $data, string $type, ?string $format = null, array $context = []): mixed { + $data = (array) $data; $output = (new \ReflectionClass(PrivateConstructor::class))->newInstanceWithoutConstructor(); if (array_key_exists('foo', $data)) { $output->foo = $data['foo']; diff --git a/src/Symfony/Component/Serializer/Tests/Fixtures/CustomNormalizer/FullTypeHints/ExpectedNormalizer/PublicProperties.php b/src/Symfony/Component/Serializer/Tests/Fixtures/CustomNormalizer/FullTypeHints/ExpectedNormalizer/PublicProperties.php index d2574a6621080..c7d69b2229bf8 100644 --- a/src/Symfony/Component/Serializer/Tests/Fixtures/CustomNormalizer/FullTypeHints/ExpectedNormalizer/PublicProperties.php +++ b/src/Symfony/Component/Serializer/Tests/Fixtures/CustomNormalizer/FullTypeHints/ExpectedNormalizer/PublicProperties.php @@ -47,6 +47,7 @@ private function normalizeChild(mixed $object, ?string $format, array $context, } public function denormalize(mixed $data, string $type, ?string $format = null, array $context = []): mixed { + $data = (array) $data; $output = new PublicProperties(); if (array_key_exists('name', $data)) { $output->name = $data['name']; diff --git a/src/Symfony/Component/Serializer/Tests/Fixtures/CustomNormalizer/FullTypeHints/ExpectedNormalizer/SetterInjection.php b/src/Symfony/Component/Serializer/Tests/Fixtures/CustomNormalizer/FullTypeHints/ExpectedNormalizer/SetterInjection.php index 1589454f57f1c..f33937186f73b 100644 --- a/src/Symfony/Component/Serializer/Tests/Fixtures/CustomNormalizer/FullTypeHints/ExpectedNormalizer/SetterInjection.php +++ b/src/Symfony/Component/Serializer/Tests/Fixtures/CustomNormalizer/FullTypeHints/ExpectedNormalizer/SetterInjection.php @@ -47,6 +47,7 @@ private function normalizeChild(mixed $object, ?string $format, array $context, } public function denormalize(mixed $data, string $type, ?string $format = null, array $context = []): mixed { + $data = (array) $data; $output = new SetterInjection(); if (array_key_exists('name', $data)) { $output->setName($data['name']); diff --git a/src/Symfony/Component/Serializer/Tests/Fixtures/CustomNormalizer/NoTypeHints/ExpectedNormalizer/ConstructorAndSetterInjection.php b/src/Symfony/Component/Serializer/Tests/Fixtures/CustomNormalizer/NoTypeHints/ExpectedNormalizer/ConstructorAndSetterInjection.php index 340df14b3f32f..db3c3d7557cbf 100644 --- a/src/Symfony/Component/Serializer/Tests/Fixtures/CustomNormalizer/NoTypeHints/ExpectedNormalizer/ConstructorAndSetterInjection.php +++ b/src/Symfony/Component/Serializer/Tests/Fixtures/CustomNormalizer/NoTypeHints/ExpectedNormalizer/ConstructorAndSetterInjection.php @@ -44,6 +44,7 @@ private function normalizeChild(mixed $object, ?string $format, array $context, } public function denormalize(mixed $data, string $type, ?string $format = null, array $context = []): mixed { + $data = (array) $data; $output = new ConstructorAndSetterInjection($data['name'], $data['age'], $data['picture'], $data['pet'], $data['relation']); if (array_key_exists('height', $data)) { $output->setHeight($data['height']); diff --git a/src/Symfony/Component/Serializer/Tests/Fixtures/CustomNormalizer/NoTypeHints/ExpectedNormalizer/ConstructorInjection.php b/src/Symfony/Component/Serializer/Tests/Fixtures/CustomNormalizer/NoTypeHints/ExpectedNormalizer/ConstructorInjection.php index c5cae1d5672fb..47ad282494dad 100644 --- a/src/Symfony/Component/Serializer/Tests/Fixtures/CustomNormalizer/NoTypeHints/ExpectedNormalizer/ConstructorInjection.php +++ b/src/Symfony/Component/Serializer/Tests/Fixtures/CustomNormalizer/NoTypeHints/ExpectedNormalizer/ConstructorInjection.php @@ -44,6 +44,7 @@ private function normalizeChild(mixed $object, ?string $format, array $context, } public function denormalize(mixed $data, string $type, ?string $format = null, array $context = []): mixed { + $data = (array) $data; $output = new ConstructorInjection($data['name'], $data['age'], $data['height'], $data['handsome'], $data['nameOfFriends'], $data['picture'], $data['pet'], $data['relation']); return $output; } diff --git a/src/Symfony/Component/Serializer/Tests/Fixtures/CustomNormalizer/NoTypeHints/ExpectedNormalizer/InheritanceChild.php b/src/Symfony/Component/Serializer/Tests/Fixtures/CustomNormalizer/NoTypeHints/ExpectedNormalizer/InheritanceChild.php index 81bda75d3e4d5..9f092cd53de56 100644 --- a/src/Symfony/Component/Serializer/Tests/Fixtures/CustomNormalizer/NoTypeHints/ExpectedNormalizer/InheritanceChild.php +++ b/src/Symfony/Component/Serializer/Tests/Fixtures/CustomNormalizer/NoTypeHints/ExpectedNormalizer/InheritanceChild.php @@ -44,6 +44,7 @@ private function normalizeChild(mixed $object, ?string $format, array $context, } public function denormalize(mixed $data, string $type, ?string $format = null, array $context = []): mixed { + $data = (array) $data; $output = new InheritanceChild($data['childCute'], $data['cute']); if (array_key_exists('childName', $data)) { $output->childName = $data['childName']; diff --git a/src/Symfony/Component/Serializer/Tests/Fixtures/CustomNormalizer/NoTypeHints/ExpectedNormalizer/PublicProperties.php b/src/Symfony/Component/Serializer/Tests/Fixtures/CustomNormalizer/NoTypeHints/ExpectedNormalizer/PublicProperties.php index 26840ae94ada5..71b298280e1eb 100644 --- a/src/Symfony/Component/Serializer/Tests/Fixtures/CustomNormalizer/NoTypeHints/ExpectedNormalizer/PublicProperties.php +++ b/src/Symfony/Component/Serializer/Tests/Fixtures/CustomNormalizer/NoTypeHints/ExpectedNormalizer/PublicProperties.php @@ -44,6 +44,7 @@ private function normalizeChild(mixed $object, ?string $format, array $context, } public function denormalize(mixed $data, string $type, ?string $format = null, array $context = []): mixed { + $data = (array) $data; $output = new PublicProperties(); if (array_key_exists('name', $data)) { $output->name = $data['name']; diff --git a/src/Symfony/Component/Serializer/Tests/Fixtures/CustomNormalizer/NoTypeHints/ExpectedNormalizer/SetterInjection.php b/src/Symfony/Component/Serializer/Tests/Fixtures/CustomNormalizer/NoTypeHints/ExpectedNormalizer/SetterInjection.php index dfd7a9894e7ee..6b74afcd9d6d5 100644 --- a/src/Symfony/Component/Serializer/Tests/Fixtures/CustomNormalizer/NoTypeHints/ExpectedNormalizer/SetterInjection.php +++ b/src/Symfony/Component/Serializer/Tests/Fixtures/CustomNormalizer/NoTypeHints/ExpectedNormalizer/SetterInjection.php @@ -44,6 +44,7 @@ private function normalizeChild(mixed $object, ?string $format, array $context, } public function denormalize(mixed $data, string $type, ?string $format = null, array $context = []): mixed { + $data = (array) $data; $output = new SetterInjection(); if (array_key_exists('name', $data)) { $output->setName($data['name']); diff --git a/src/Symfony/Component/Serializer/Tests/Normalizer/AutoNormalizerTest.php b/src/Symfony/Component/Serializer/Tests/Normalizer/AutoNormalizerTest.php index 2a8bdbd1cd56b..b234de6e71f1b 100644 --- a/src/Symfony/Component/Serializer/Tests/Normalizer/AutoNormalizerTest.php +++ b/src/Symfony/Component/Serializer/Tests/Normalizer/AutoNormalizerTest.php @@ -11,60 +11,18 @@ namespace Symfony\Component\Serializer\Tests\Normalizer; -use PhpParser\ParserFactory; -use PHPStan\PhpDocParser\Parser\PhpDocParser; -use PHPUnit\Framework\MockObject\MockObject; +require_once __DIR__.'/ObjectNormalizerTest.php'; + 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\Builder\DefinitionExtractor; use Symfony\Component\Serializer\Builder\NormalizerBuilder; -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; -use Symfony\Component\Serializer\Mapping\Factory\ClassMetadataFactoryInterface; -use Symfony\Component\Serializer\Mapping\Loader\AttributeLoader; -use Symfony\Component\Serializer\NameConverter\AdvancedNameConverterInterface; -use Symfony\Component\Serializer\NameConverter\CamelCaseToSnakeCaseNameConverter; -use Symfony\Component\Serializer\NameConverter\MetadataAwareNameConverter; -use Symfony\Component\Serializer\Normalizer\AbstractNormalizer; -use Symfony\Component\Serializer\Normalizer\ArrayDenormalizer; -use Symfony\Component\Serializer\Normalizer\DateTimeNormalizer; -use Symfony\Component\Serializer\Normalizer\NormalizerInterface; -use Symfony\Component\Serializer\Normalizer\xx; use Symfony\Component\Serializer\Serializer; -use Symfony\Component\Serializer\SerializerInterface; use Symfony\Component\Serializer\Tests\Builder\FixtureHelper; -use Symfony\Component\Serializer\Tests\Fixtures\Attributes\GroupDummy; -use Symfony\Component\Serializer\Tests\Fixtures\CircularReferenceDummy; use Symfony\Component\Serializer\Tests\Fixtures\DummyPrivatePropertyWithoutGetter; -use Symfony\Component\Serializer\Tests\Fixtures\OtherSerializedNameDummy; -use Symfony\Component\Serializer\Tests\Fixtures\Php74Dummy; -use Symfony\Component\Serializer\Tests\Fixtures\Php74DummyPrivate; -use Symfony\Component\Serializer\Tests\Fixtures\Php80Dummy; use Symfony\Component\Serializer\Tests\Fixtures\Sibling; use Symfony\Component\Serializer\Tests\Fixtures\SiblingHolder; -use Symfony\Component\Serializer\Tests\Normalizer\Features\AttributesTestTrait; -use Symfony\Component\Serializer\Tests\Normalizer\Features\CacheableObjectAttributesTestTrait; -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\ContextMetadataTestTrait; -use Symfony\Component\Serializer\Tests\Normalizer\Features\GroupsTestTrait; -use Symfony\Component\Serializer\Tests\Normalizer\Features\IgnoredAttributesTestTrait; -use Symfony\Component\Serializer\Tests\Normalizer\Features\MaxDepthTestTrait; use Symfony\Component\Serializer\Tests\Normalizer\Features\ObjectDummy; -use Symfony\Component\Serializer\Tests\Normalizer\Features\ObjectToPopulateTestTrait; -use Symfony\Component\Serializer\Tests\Normalizer\Features\SkipNullValuesTestTrait; -use Symfony\Component\Serializer\Tests\Normalizer\Features\SkipUninitializedValuesTestTrait; -use Symfony\Component\Serializer\Tests\Normalizer\Features\TypedPropertiesObject; -use Symfony\Component\Serializer\Tests\Normalizer\Features\TypedPropertiesObjectWithGetters; -use Symfony\Component\Serializer\Tests\Normalizer\Features\TypeEnforcementTestTrait; /** * @author Kévin Dunglas @@ -300,176 +258,3 @@ public function testDefaultObjectClassResolver() ); } } - - -class ObjectConstructorDummy -{ - protected $foo; - public $bar; - private $baz; - - public function __construct($foo, $bar, $baz) - { - $this->foo = $foo; - $this->bar = $bar; - $this->baz = $baz; - } - - public function getFoo() - { - return $this->foo; - } - - public function isBaz() - { - return $this->baz; - } - - public function otherMethod() - { - throw new \RuntimeException('Dummy::otherMethod() should not be called'); - } -} - - -class ObjectConstructorOptionalArgsDummy -{ - protected $foo; - public $bar; - private $baz; - - public function __construct($foo, $bar = [], $baz = []) - { - $this->foo = $foo; - $this->bar = $bar; - $this->baz = $baz; - } - - public function getFoo() - { - return $this->foo; - } - - public function getBaz() - { - return $this->baz; - } - - public function otherMethod() - { - throw new \RuntimeException('Dummy::otherMethod() should not be called'); - } -} - -class ObjectConstructorArgsWithDefaultValueDummy -{ - protected $foo; - protected $bar; - - public function __construct($foo = [], $bar = null) - { - $this->foo = $foo; - $this->bar = $bar; - } - - public function getFoo() - { - return $this->foo; - } - - public function getBar() - { - return $this->bar; - } - - public function otherMethod() - { - throw new \RuntimeException('Dummy::otherMethod() should not be called'); - } -} - -class ObjectWithStaticPropertiesAndMethods -{ - public $foo = 'K'; - public static $bar = 'A'; - - public static function getBaz() - { - return 'L'; - } -} - -class ObjectTypeHinted -{ - public function setFoo(array $f) - { - } -} - -class ObjectInner -{ - public $foo; - public $bar; -} - -class DummyWithConstructorObject -{ - private $id; - private $inner; - - public function __construct($id, ObjectInner $inner) - { - $this->id = $id; - $this->inner = $inner; - } - - public function getId() - { - return $this->id; - } - - public function getInner() - { - return $this->inner; - } -} - -class DummyWithConstructorInexistingObject -{ - public function __construct($id, Unknown $unknown) - { - } -} - -class ObjectWithUpperCaseAttributeNames -{ - private $Foo = 'Foo'; - public $Bar = 'BarBar'; - - public function getFoo() - { - return $this->Foo; - } -} - -class DummyWithNullableConstructorObject -{ - private $id; - private $inner; - - public function __construct($id, ?ObjectConstructorDummy $inner) - { - $this->id = $id; - $this->inner = $inner; - } - - public function getId() - { - return $this->id; - } - - public function getInner() - { - return $this->inner; - } -}