From e21388408e1d8899fb6d523623032c32ba7ede69 Mon Sep 17 00:00:00 2001 From: Mathias Arlaud Date: Wed, 9 Oct 2024 18:56:29 +0200 Subject: [PATCH] [FrameworkBundle] [JsonEncoder] Wire services --- .../Bundle/FrameworkBundle/CHANGELOG.md | 1 + .../Compiler/UnusedTagsPass.php | 3 + .../DependencyInjection/Configuration.php | 24 ++++ .../FrameworkExtension.php | 46 ++++++- .../Resources/config/json_encoder.php | 126 ++++++++++++++++++ .../Resources/config/schema/symfony-1.0.xsd | 10 ++ .../DependencyInjection/ConfigurationTest.php | 4 + .../Fixtures/php/json_encoder.php | 14 ++ .../DependencyInjection/Fixtures/xml/full.xml | 1 + .../Fixtures/xml/json_encoder.xml | 14 ++ .../DependencyInjection/Fixtures/yml/full.yml | 1 + .../Fixtures/yml/json_encoder.yml | 10 ++ .../FrameworkExtensionTestCase.php | 6 + .../Tests/Functional/JsonEncoderTest.php | 47 +++++++ .../Functional/app/JsonEncoder/Dto/Dummy.php | 32 +++++ .../app/JsonEncoder/RangeNormalizer.php | 38 ++++++ .../Functional/app/JsonEncoder/bundles.php | 18 +++ .../Functional/app/JsonEncoder/config.yml | 24 ++++ 18 files changed, 418 insertions(+), 1 deletion(-) create mode 100644 src/Symfony/Bundle/FrameworkBundle/Resources/config/json_encoder.php create mode 100644 src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/php/json_encoder.php create mode 100644 src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/json_encoder.xml create mode 100644 src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/yml/json_encoder.yml create mode 100644 src/Symfony/Bundle/FrameworkBundle/Tests/Functional/JsonEncoderTest.php create mode 100644 src/Symfony/Bundle/FrameworkBundle/Tests/Functional/app/JsonEncoder/Dto/Dummy.php create mode 100644 src/Symfony/Bundle/FrameworkBundle/Tests/Functional/app/JsonEncoder/RangeNormalizer.php create mode 100644 src/Symfony/Bundle/FrameworkBundle/Tests/Functional/app/JsonEncoder/bundles.php create mode 100644 src/Symfony/Bundle/FrameworkBundle/Tests/Functional/app/JsonEncoder/config.yml diff --git a/src/Symfony/Bundle/FrameworkBundle/CHANGELOG.md b/src/Symfony/Bundle/FrameworkBundle/CHANGELOG.md index 3f05ad7a59030..d63b0172335d1 100644 --- a/src/Symfony/Bundle/FrameworkBundle/CHANGELOG.md +++ b/src/Symfony/Bundle/FrameworkBundle/CHANGELOG.md @@ -6,6 +6,7 @@ CHANGELOG * Add support for assets pre-compression * Rename `TranslationUpdateCommand` to `TranslationExtractCommand` + * Add JsonEncoder services and configuration 7.2 --- diff --git a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Compiler/UnusedTagsPass.php b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Compiler/UnusedTagsPass.php index ae2523e515d0c..45d08a975bd83 100644 --- a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Compiler/UnusedTagsPass.php +++ b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Compiler/UnusedTagsPass.php @@ -53,6 +53,9 @@ class UnusedTagsPass implements CompilerPassInterface 'form.type_guesser', 'html_sanitizer', 'http_client.client', + 'json_encoder.denormalizer', + 'json_encoder.encodable', + 'json_encoder.normalizer', 'kernel.cache_clearer', 'kernel.cache_warmer', 'kernel.event_listener', diff --git a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Configuration.php b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Configuration.php index 372da4a5ee8e5..99592fe4989c9 100644 --- a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Configuration.php +++ b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Configuration.php @@ -31,6 +31,7 @@ use Symfony\Component\HttpClient\HttpClient; use Symfony\Component\HttpFoundation\Cookie; use Symfony\Component\HttpFoundation\IpUtils; +use Symfony\Component\JsonEncoder\EncoderInterface; use Symfony\Component\Lock\Lock; use Symfony\Component\Lock\Store\SemaphoreStore; use Symfony\Component\Mailer\Mailer; @@ -181,6 +182,7 @@ public function getConfigTreeBuilder(): TreeBuilder $this->addHtmlSanitizerSection($rootNode, $enableIfStandalone); $this->addWebhookSection($rootNode, $enableIfStandalone); $this->addRemoteEventSection($rootNode, $enableIfStandalone); + $this->addJsonEncoderSection($rootNode, $enableIfStandalone); return $treeBuilder; } @@ -2570,4 +2572,26 @@ private function addHtmlSanitizerSection(ArrayNodeDefinition $rootNode, callable ->end() ; } + + private function addJsonEncoderSection(ArrayNodeDefinition $rootNode, callable $enableIfStandalone): void + { + $rootNode + ->children() + ->arrayNode('json_encoder') + ->info('JSON encoder configuration') + ->{$enableIfStandalone('symfony/json-encoder', EncoderInterface::class)}() + ->fixXmlConfig('path') + ->children() + ->arrayNode('paths') + ->info('Namespaces and paths of encodable/decodable classes.') + ->normalizeKeys(false) + ->useAttributeAsKey('namespace') + ->scalarPrototype()->end() + ->defaultValue([]) + ->end() + ->end() + ->end() + ->end() + ; + } } diff --git a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php index 3f51575820ae2..862abe3ca5942 100644 --- a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php +++ b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php @@ -99,6 +99,11 @@ use Symfony\Component\HttpKernel\DataCollector\DataCollectorInterface; use Symfony\Component\HttpKernel\DependencyInjection\Extension; use Symfony\Component\HttpKernel\Log\DebugLoggerConfigurator; +use Symfony\Component\JsonEncoder\Decode\Denormalizer\DenormalizerInterface as JsonEncoderDenormalizerInterface; +use Symfony\Component\JsonEncoder\DecoderInterface as JsonEncoderDecoderInterface; +use Symfony\Component\JsonEncoder\Encode\Normalizer\NormalizerInterface as JsonEncoderNormalizerInterface; +use Symfony\Component\JsonEncoder\EncoderInterface as JsonEncoderEncoderInterface; +use Symfony\Component\JsonEncoder\JsonEncoder; use Symfony\Component\Lock\LockFactory; use Symfony\Component\Lock\LockInterface; use Symfony\Component\Lock\PersistingStoreInterface; @@ -176,6 +181,7 @@ use Symfony\Component\TypeInfo\Type; use Symfony\Component\TypeInfo\TypeResolver\PhpDocAwareReflectionTypeResolver; use Symfony\Component\TypeInfo\TypeResolver\StringTypeResolver; +use Symfony\Component\TypeInfo\TypeResolver\TypeResolverInterface; use Symfony\Component\Uid\Factory\UuidFactory; use Symfony\Component\Uid\UuidV4; use Symfony\Component\Validator\Constraints\ExpressionLanguageProvider; @@ -414,7 +420,7 @@ public function load(array $configs, ContainerBuilder $container): void $container->removeDefinition('console.command.serializer_debug'); } - if ($this->readConfigEnabled('type_info', $container, $config['type_info'])) { + if ($typeInfoEnabled = $this->readConfigEnabled('type_info', $container, $config['type_info'])) { $this->registerTypeInfoConfiguration($container, $loader); } @@ -422,6 +428,14 @@ public function load(array $configs, ContainerBuilder $container): void $this->registerPropertyInfoConfiguration($container, $loader); } + if ($this->readConfigEnabled('json_encoder', $container, $config['json_encoder'])) { + if (!$typeInfoEnabled) { + throw new LogicException('JsonEncoder support cannot be enabled as the TypeInfo component is not '.(interface_exists(TypeResolverInterface::class) ? 'enabled.' : 'installed. Try running "composer require symfony/type-info".')); + } + + $this->registerJsonEncoderConfiguration($config['json_encoder'], $container, $loader); + } + if ($this->readConfigEnabled('lock', $container, $config['lock'])) { $this->registerLockConfiguration($config['lock'], $container, $loader); } @@ -1990,6 +2004,36 @@ private function registerSerializerConfiguration(array $config, ContainerBuilder $container->setParameter('.serializer.named_serializers', $config['named_serializers'] ?? []); } + private function registerJsonEncoderConfiguration(array $config, ContainerBuilder $container, PhpFileLoader $loader): void + { + if (!class_exists(JsonEncoder::class)) { + throw new LogicException('JsonEncoder support cannot be enabled as the JsonEncoder component is not installed. Try running "composer require symfony/json-encoder".'); + } + + $container->registerForAutoconfiguration(JsonEncoderNormalizerInterface::class) + ->addTag('json_encoder.normalizer'); + $container->registerForAutoconfiguration(JsonEncoderDenormalizerInterface::class) + ->addTag('json_encoder.denormalizer'); + + $loader->load('json_encoder.php'); + + $container->registerAliasForArgument('json_encoder.encoder', JsonEncoderEncoderInterface::class, 'json.encoder'); + $container->registerAliasForArgument('json_encoder.decoder', JsonEncoderDecoderInterface::class, 'json.decoder'); + + $container->setParameter('.json_encoder.encoders_dir', '%kernel.cache_dir%/json_encoder/encoder'); + $container->setParameter('.json_encoder.decoders_dir', '%kernel.cache_dir%/json_encoder/decoder'); + $container->setParameter('.json_encoder.lazy_ghosts_dir', '%kernel.cache_dir%/json_encoder/lazy_ghost'); + + $encodableDefinition = (new Definition()) + ->setAbstract(true) + ->addTag('container.excluded') + ->addTag('json_encoder.encodable'); + + foreach ($config['paths'] as $namespace => $path) { + $loader->registerClasses($encodableDefinition, $namespace, $path); + } + } + private function registerPropertyInfoConfiguration(ContainerBuilder $container, PhpFileLoader $loader): void { if (!interface_exists(PropertyInfoExtractorInterface::class)) { diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/config/json_encoder.php b/src/Symfony/Bundle/FrameworkBundle/Resources/config/json_encoder.php new file mode 100644 index 0000000000000..b864ed0f9a893 --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/config/json_encoder.php @@ -0,0 +1,126 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection\Loader\Configurator; + +use Symfony\Component\JsonEncoder\CacheWarmer\EncoderDecoderCacheWarmer; +use Symfony\Component\JsonEncoder\CacheWarmer\LazyGhostCacheWarmer; +use Symfony\Component\JsonEncoder\Decode\Denormalizer\DateTimeDenormalizer; +use Symfony\Component\JsonEncoder\Encode\Normalizer\DateTimeNormalizer; +use Symfony\Component\JsonEncoder\JsonDecoder; +use Symfony\Component\JsonEncoder\JsonEncoder; +use Symfony\Component\JsonEncoder\Mapping\Decode\AttributePropertyMetadataLoader as DecodeAttributePropertyMetadataLoader; +use Symfony\Component\JsonEncoder\Mapping\Decode\DateTimeTypePropertyMetadataLoader as DecodeDateTimeTypePropertyMetadataLoader; +use Symfony\Component\JsonEncoder\Mapping\Encode\AttributePropertyMetadataLoader as EncodeAttributePropertyMetadataLoader; +use Symfony\Component\JsonEncoder\Mapping\Encode\DateTimeTypePropertyMetadataLoader as EncodeDateTimeTypePropertyMetadataLoader; +use Symfony\Component\JsonEncoder\Mapping\GenericTypePropertyMetadataLoader; +use Symfony\Component\JsonEncoder\Mapping\PropertyMetadataLoader; + +return static function (ContainerConfigurator $container) { + $container->services() + // encoder/decoder + ->set('json_encoder.encoder', JsonEncoder::class) + ->args([ + tagged_locator('json_encoder.normalizer'), + service('json_encoder.encode.property_metadata_loader'), + param('.json_encoder.encoders_dir'), + false, + ]) + ->set('json_encoder.decoder', JsonDecoder::class) + ->args([ + tagged_locator('json_encoder.denormalizer'), + service('json_encoder.decode.property_metadata_loader'), + param('.json_encoder.decoders_dir'), + param('.json_encoder.lazy_ghosts_dir'), + ]) + ->alias(JsonEncoder::class, 'json_encoder.encoder') + ->alias(JsonDecoder::class, 'json_encoder.decoder') + + // metadata + ->stack('json_encoder.encode.property_metadata_loader', [ + inline_service(EncodeAttributePropertyMetadataLoader::class) + ->args([ + service('.inner'), + tagged_locator('json_encoder.normalizer'), + service('type_info.resolver'), + ]), + inline_service(EncodeDateTimeTypePropertyMetadataLoader::class) + ->args([ + service('.inner'), + ]), + inline_service(GenericTypePropertyMetadataLoader::class) + ->args([ + service('.inner'), + service('type_info.type_context_factory'), + ]), + inline_service(PropertyMetadataLoader::class) + ->args([ + service('type_info.resolver'), + ]), + ]) + + ->stack('json_encoder.decode.property_metadata_loader', [ + inline_service(DecodeAttributePropertyMetadataLoader::class) + ->args([ + service('.inner'), + tagged_locator('json_encoder.denormalizer'), + service('type_info.resolver'), + ]), + inline_service(DecodeDateTimeTypePropertyMetadataLoader::class) + ->args([ + service('.inner'), + ]), + inline_service(GenericTypePropertyMetadataLoader::class) + ->args([ + service('.inner'), + service('type_info.type_context_factory'), + ]), + inline_service(PropertyMetadataLoader::class) + ->args([ + service('type_info.resolver'), + ]), + ]) + + // normalizers/denormalizers + ->set('json_encoder.normalizer.date_time', DateTimeNormalizer::class) + ->tag('json_encoder.normalizer') + ->set('json_encoder.denormalizer.date_time', DateTimeDenormalizer::class) + ->args([ + false, + ]) + ->tag('json_encoder.denormalizer') + ->set('json_encoder.denormalizer.date_time_immutable', DateTimeDenormalizer::class) + ->args([ + true, + ]) + ->tag('json_encoder.denormalizer') + + // cache + ->set('.json_encoder.cache_warmer.encoder_decoder', EncoderDecoderCacheWarmer::class) + ->args([ + tagged_iterator('json_encoder.encodable'), + service('json_encoder.encode.property_metadata_loader'), + service('json_encoder.decode.property_metadata_loader'), + param('.json_encoder.encoders_dir'), + param('.json_encoder.decoders_dir'), + false, + service('logger')->ignoreOnInvalid(), + ]) + ->tag('kernel.cache_warmer') + + ->set('.json_encoder.cache_warmer.lazy_ghost', LazyGhostCacheWarmer::class) + ->args([ + tagged_iterator('json_encoder.encodable'), + param('.json_encoder.lazy_ghosts_dir'), + ]) + ->tag('kernel.cache_warmer') + ; +}; diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/config/schema/symfony-1.0.xsd b/src/Symfony/Bundle/FrameworkBundle/Resources/config/schema/symfony-1.0.xsd index 91528b60f3de6..9cb89207ddade 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Resources/config/schema/symfony-1.0.xsd +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/config/schema/symfony-1.0.xsd @@ -46,6 +46,7 @@ + @@ -1003,4 +1004,13 @@ + + + + + + + + + diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/ConfigurationTest.php b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/ConfigurationTest.php index c7113cfb47d45..963cac6386c57 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/ConfigurationTest.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/ConfigurationTest.php @@ -970,6 +970,10 @@ class_exists(SemaphoreStore::class) && SemaphoreStore::isSupported() ? 'semaphor 'remote-event' => [ 'enabled' => !class_exists(FullStack::class) && class_exists(RemoteEvent::class), ], + 'json_encoder' => [ + 'enabled' => false, + 'paths' => [], + ], ]; } diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/php/json_encoder.php b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/php/json_encoder.php new file mode 100644 index 0000000000000..42204b2cbb1dd --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/php/json_encoder.php @@ -0,0 +1,14 @@ +loadFromExtension('framework', [ + 'annotations' => false, + 'http_method_override' => false, + 'handle_all_throwables' => true, + 'php_errors' => ['log' => true], + 'type_info' => [ + 'enabled' => true, + ], + 'json_encoder' => [ + 'enabled' => true, + ], +]); diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/full.xml b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/full.xml index c01e857838bc3..a3e5cfd88b5ff 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/full.xml +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/full.xml @@ -46,5 +46,6 @@ + diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/json_encoder.xml b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/json_encoder.xml new file mode 100644 index 0000000000000..a20f98567581a --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/json_encoder.xml @@ -0,0 +1,14 @@ + + + + + + + + + + diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/yml/full.yml b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/yml/full.yml index 7550749eb1a1e..8e272d11bfb47 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/yml/full.yml +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/yml/full.yml @@ -70,3 +70,4 @@ framework: formats: csv: ['text/csv', 'text/plain'] pdf: 'application/pdf' + json_encoder: ~ diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/yml/json_encoder.yml b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/yml/json_encoder.yml new file mode 100644 index 0000000000000..e09f7c7d368b0 --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/yml/json_encoder.yml @@ -0,0 +1,10 @@ +framework: + annotations: false + http_method_override: false + handle_all_throwables: true + php_errors: + log: true + type_info: + enabled: true + json_encoder: + enabled: true diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/FrameworkExtensionTestCase.php b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/FrameworkExtensionTestCase.php index 798217191e7c0..0446eb5d2e7c6 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/FrameworkExtensionTestCase.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/FrameworkExtensionTestCase.php @@ -2497,6 +2497,12 @@ public function testSemaphoreWithService() self::assertEquals(new Reference('my_service'), $storeDef->getArgument(0)); } + public function testJsonEncoderEnabled() + { + $container = $this->createContainerFromFile('json_encoder'); + $this->assertTrue($container->has('json_encoder.encoder')); + } + protected function createContainer(array $data = []) { return new ContainerBuilder(new EnvPlaceholderParameterBag(array_merge([ diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/JsonEncoderTest.php b/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/JsonEncoderTest.php new file mode 100644 index 0000000000000..0ab66e6c1830f --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/JsonEncoderTest.php @@ -0,0 +1,47 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bundle\FrameworkBundle\Tests\Functional; + +use Symfony\Bundle\FrameworkBundle\Tests\Functional\app\JsonEncoder\Dto\Dummy; +use Symfony\Component\JsonEncoder\DecoderInterface; +use Symfony\Component\JsonEncoder\EncoderInterface; +use Symfony\Component\TypeInfo\Type; + +/** + * @author Mathias Arlaud + */ +class JsonEncoderTest extends AbstractWebTestCase +{ + public function testEncode() + { + static::bootKernel(['test_case' => 'JsonEncoder']); + + /** @var EncoderInterface $encoder */ + $encoder = static::getContainer()->get('json_encoder.encoder.alias'); + + $this->assertSame('{"@name":"DUMMY","range":"10..20"}', (string) $encoder->encode(new Dummy(), Type::object(Dummy::class))); + } + + public function testDecode() + { + static::bootKernel(['test_case' => 'JsonEncoder']); + + /** @var DecoderInterface $decoder */ + $decoder = static::getContainer()->get('json_encoder.decoder.alias'); + + $expected = new Dummy(); + $expected->name = 'dummy'; + $expected->range = [0, 1]; + + $this->assertEquals($expected, $decoder->decode('{"@name": "DUMMY", "range": "0..1"}', Type::object(Dummy::class))); + } +} diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/app/JsonEncoder/Dto/Dummy.php b/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/app/JsonEncoder/Dto/Dummy.php new file mode 100644 index 0000000000000..344b9d11cba03 --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/app/JsonEncoder/Dto/Dummy.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\Bundle\FrameworkBundle\Tests\Functional\app\JsonEncoder\Dto; + +use Symfony\Bundle\FrameworkBundle\Tests\Functional\app\JsonEncoder\RangeNormalizer; +use Symfony\Component\JsonEncoder\Attribute\Denormalizer; +use Symfony\Component\JsonEncoder\Attribute\EncodedName; +use Symfony\Component\JsonEncoder\Attribute\Normalizer; + +/** + * @author Mathias Arlaud + */ +class Dummy +{ + #[EncodedName('@name')] + #[Normalizer('strtoupper')] + #[Denormalizer('strtolower')] + public string $name = 'dummy'; + + #[Normalizer(RangeNormalizer::class)] + #[Denormalizer(RangeNormalizer::class)] + public array $range = [10, 20]; +} diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/app/JsonEncoder/RangeNormalizer.php b/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/app/JsonEncoder/RangeNormalizer.php new file mode 100644 index 0000000000000..beb9e81888ce4 --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/app/JsonEncoder/RangeNormalizer.php @@ -0,0 +1,38 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bundle\FrameworkBundle\Tests\Functional\app\JsonEncoder; + +use Symfony\Component\JsonEncoder\Decode\Denormalizer\DenormalizerInterface; +use Symfony\Component\JsonEncoder\Encode\Normalizer\NormalizerInterface; +use Symfony\Component\TypeInfo\Type; +use Symfony\Component\TypeInfo\Type\BuiltinType; + +/** + * @author Mathias Arlaud + */ +class RangeNormalizer implements NormalizerInterface, DenormalizerInterface +{ + public function normalize(mixed $denormalized, array $options = []): string + { + return $denormalized[0].'..'.$denormalized[1]; + } + + public function denormalize(mixed $normalized, array $options = []): array + { + return array_map(static fn (string $v): int => (int) $v, explode('..', $normalized)); + } + + public static function getNormalizedType(): BuiltinType + { + return Type::string(); + } +} diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/app/JsonEncoder/bundles.php b/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/app/JsonEncoder/bundles.php new file mode 100644 index 0000000000000..15ff182c6fed5 --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/app/JsonEncoder/bundles.php @@ -0,0 +1,18 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +use Symfony\Bundle\FrameworkBundle\FrameworkBundle; +use Symfony\Bundle\FrameworkBundle\Tests\Functional\Bundle\TestBundle\TestBundle; + +return [ + new FrameworkBundle(), + new TestBundle(), +]; diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/app/JsonEncoder/config.yml b/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/app/JsonEncoder/config.yml new file mode 100644 index 0000000000000..55fdf53f5c2fd --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/app/JsonEncoder/config.yml @@ -0,0 +1,24 @@ +imports: + - { resource: ../config/default.yml } + +framework: + http_method_override: false + type_info: ~ + json_encoder: + enabled: true + paths: + Symfony\Bundle\FrameworkBundle\Tests\Functional\app\JsonEncoder\Dto\: '../../Tests/Functional/app/JsonEncoder/Dto/*' + +services: + _defaults: + autoconfigure: true + + json_encoder.encoder.alias: + alias: json_encoder.encoder + public: true + + json_encoder.decoder.alias: + alias: json_encoder.decoder + public: true + + Symfony\Bundle\FrameworkBundle\Tests\Functional\app\JsonEncoder\RangeNormalizer: ~