From 1ac8a2b9becfa4f6b45980826fbf2998526cc053 Mon Sep 17 00:00:00 2001 From: "hubert.lenoir" Date: Tue, 2 May 2023 13:55:02 +0200 Subject: [PATCH] [FrameworkBundle][Serializer] Add TranslatableNormalizer --- .../FrameworkExtension.php | 4 ++ .../Resources/config/serializer.php | 5 ++ .../FrameworkExtensionTestCase.php | 13 ++++ .../Tests/Functional/SerializerTest.php | 1 + .../Functional/app/Serializer/config.yml | 4 ++ .../Normalizer/TranslatableNormalizer.php | 54 ++++++++++++++++ .../Normalizer/TranslatableNormalizerTest.php | 63 +++++++++++++++++++ 7 files changed, 144 insertions(+) create mode 100644 src/Symfony/Component/Serializer/Normalizer/TranslatableNormalizer.php create mode 100644 src/Symfony/Component/Serializer/Tests/Normalizer/TranslatableNormalizerTest.php diff --git a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php index 56242bf318de7..e2d93d365f986 100644 --- a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php +++ b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php @@ -1879,6 +1879,10 @@ private function registerSerializerConfiguration(array $config, ContainerBuilder $container->removeDefinition('serializer.normalizer.mime_message'); } + if (!class_exists(Translator::class)) { + $container->removeDefinition('serializer.normalizer.translatable'); + } + // compat with Symfony < 6.3 if (!is_subclass_of(ProblemNormalizer::class, SerializerAwareInterface::class)) { $container->getDefinition('serializer.normalizer.problem') diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/config/serializer.php b/src/Symfony/Bundle/FrameworkBundle/Resources/config/serializer.php index 6459dfa4442bd..ba8d5c6ee2d83 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Resources/config/serializer.php +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/config/serializer.php @@ -47,6 +47,7 @@ use Symfony\Component\Serializer\Normalizer\ObjectNormalizer; use Symfony\Component\Serializer\Normalizer\ProblemNormalizer; use Symfony\Component\Serializer\Normalizer\PropertyNormalizer; +use Symfony\Component\Serializer\Normalizer\TranslatableNormalizer; use Symfony\Component\Serializer\Normalizer\UidNormalizer; use Symfony\Component\Serializer\Normalizer\UnwrappingDenormalizer; use Symfony\Component\Serializer\Serializer; @@ -113,6 +114,10 @@ ->set('serializer.normalizer.uid', UidNormalizer::class) ->tag('serializer.normalizer', ['priority' => -890]) + ->set('serializer.normalizer.translatable', TranslatableNormalizer::class) + ->args(['$translator' => service('translator')]) + ->tag('serializer.normalizer', ['priority' => -890]) + ->set('serializer.normalizer.form_error', FormErrorNormalizer::class) ->tag('serializer.normalizer', ['priority' => -915]) diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/FrameworkExtensionTestCase.php b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/FrameworkExtensionTestCase.php index 0b34bcd9f7f9f..8aa91a0616a96 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/FrameworkExtensionTestCase.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/FrameworkExtensionTestCase.php @@ -75,6 +75,7 @@ use Symfony\Component\Serializer\Normalizer\FormErrorNormalizer; use Symfony\Component\Serializer\Normalizer\JsonSerializableNormalizer; use Symfony\Component\Serializer\Normalizer\ObjectNormalizer; +use Symfony\Component\Serializer\Normalizer\TranslatableNormalizer; use Symfony\Component\Serializer\Serializer; use Symfony\Component\Translation\DependencyInjection\TranslatorPass; use Symfony\Component\Translation\LocaleSwitcher; @@ -1586,6 +1587,18 @@ public function testConstraintViolationListNormalizerRegistered() $this->assertEquals(new Reference('serializer.name_converter.metadata_aware'), $definition->getArgument(1)); } + public function testTranslatableNormalizerRegistered() + { + $container = $this->createContainerFromFile('full'); + + $definition = $container->getDefinition('serializer.normalizer.translatable'); + $tag = $definition->getTag('serializer.normalizer'); + + $this->assertSame(TranslatableNormalizer::class, $definition->getClass()); + $this->assertSame(-890, $tag[0]['priority']); + $this->assertEquals(new Reference('translator'), $definition->getArgument('$translator')); + } + public function testSerializerCacheActivated() { $container = $this->createContainerFromFile('serializer_enabled'); diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/SerializerTest.php b/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/SerializerTest.php index 0793769a1a506..630bfb7cd3004 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/SerializerTest.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/SerializerTest.php @@ -60,6 +60,7 @@ public static function provideNormalizersAndEncodersWithDefaultContextOption(): ['serializer.normalizer.json_serializable.alias'], ['serializer.normalizer.problem.alias'], ['serializer.normalizer.uid.alias'], + ['serializer.normalizer.translatable.alias'], ['serializer.normalizer.object.alias'], ['serializer.encoder.xml.alias'], ['serializer.encoder.yaml.alias'], diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/app/Serializer/config.yml b/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/app/Serializer/config.yml index e51b738580255..016e41291fdbb 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/app/Serializer/config.yml +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/app/Serializer/config.yml @@ -39,6 +39,10 @@ services: alias: serializer.normalizer.uid public: true + serializer.normalizer.translatable.alias: + alias: serializer.normalizer.translatable + public: true + serializer.normalizer.property.alias: alias: serializer.normalizer.property public: true diff --git a/src/Symfony/Component/Serializer/Normalizer/TranslatableNormalizer.php b/src/Symfony/Component/Serializer/Normalizer/TranslatableNormalizer.php new file mode 100644 index 0000000000000..b79a0ffb99488 --- /dev/null +++ b/src/Symfony/Component/Serializer/Normalizer/TranslatableNormalizer.php @@ -0,0 +1,54 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Serializer\Normalizer; + +use Symfony\Component\Serializer\Exception\InvalidArgumentException; +use Symfony\Contracts\Translation\TranslatableInterface; +use Symfony\Contracts\Translation\TranslatorInterface; + +final class TranslatableNormalizer implements NormalizerInterface +{ + public const NORMALIZATION_LOCALE_KEY = 'translatable_normalization_locale'; + + private array $defaultContext = [ + self::NORMALIZATION_LOCALE_KEY => null, + ]; + + public function __construct( + private readonly TranslatorInterface $translator, + array $defaultContext = [], + ) { + $this->defaultContext = array_merge($this->defaultContext, $defaultContext); + } + + /** + * @throws InvalidArgumentException + */ + public function normalize(mixed $object, string $format = null, array $context = []): string + { + if (!$object instanceof TranslatableInterface) { + throw new InvalidArgumentException(sprintf('The object must implement the "%s".', TranslatableInterface::class)); + } + + return $object->trans($this->translator, $context[self::NORMALIZATION_LOCALE_KEY] ?? $this->defaultContext[self::NORMALIZATION_LOCALE_KEY]); + } + + public function supportsNormalization(mixed $data, string $format = null, array $context = []): bool + { + return $data instanceof TranslatableInterface; + } + + public function getSupportedTypes(?string $format): array + { + return [TranslatableInterface::class => true]; + } +} diff --git a/src/Symfony/Component/Serializer/Tests/Normalizer/TranslatableNormalizerTest.php b/src/Symfony/Component/Serializer/Tests/Normalizer/TranslatableNormalizerTest.php new file mode 100644 index 0000000000000..54338b07e55be --- /dev/null +++ b/src/Symfony/Component/Serializer/Tests/Normalizer/TranslatableNormalizerTest.php @@ -0,0 +1,63 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Serializer\Tests\Normalizer; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\Serializer\Normalizer\TranslatableNormalizer; +use Symfony\Contracts\Translation\TranslatableInterface; +use Symfony\Contracts\Translation\TranslatorInterface; + +class TranslatableNormalizerTest extends TestCase +{ + private readonly TranslatableNormalizer $normalizer; + + protected function setUp(): void + { + $this->normalizer = new TranslatableNormalizer($this->createMock(TranslatorInterface::class)); + } + + public function testSupportsNormalization() + { + $this->assertTrue($this->normalizer->supportsNormalization(new TestMessage())); + $this->assertFalse($this->normalizer->supportsNormalization(new \stdClass())); + } + + public function testNormalize() + { + $message = new TestMessage(); + + $this->assertSame('key_null', $this->normalizer->normalize($message)); + $this->assertSame('key_fr', $this->normalizer->normalize($message, context: ['translatable_normalization_locale' => 'fr'])); + $this->assertSame('key_en', $this->normalizer->normalize($message, context: ['translatable_normalization_locale' => 'en'])); + } + + public function testNormalizeWithNormalizationLocalePassedInConstructor() + { + $normalizer = new TranslatableNormalizer( + $this->createMock(TranslatorInterface::class), + ['translatable_normalization_locale' => 'es'], + ); + $message = new TestMessage(); + + $this->assertSame('key_es', $normalizer->normalize($message)); + $this->assertSame('key_fr', $normalizer->normalize($message, context: ['translatable_normalization_locale' => 'fr'])); + $this->assertSame('key_en', $normalizer->normalize($message, context: ['translatable_normalization_locale' => 'en'])); + } +} + +class TestMessage implements TranslatableInterface +{ + public function trans(TranslatorInterface $translator, string $locale = null): string + { + return 'key_'.($locale ?? 'null'); + } +}