From abb534ab566363bec1461e2edf01a056eeb29307 Mon Sep 17 00:00:00 2001 From: Jesse Rushlow Date: Fri, 26 Feb 2021 18:06:15 -0500 Subject: [PATCH] implement twig serialize filter --- src/Symfony/Bridge/Twig/CHANGELOG.md | 5 ++ .../Twig/Extension/SerializerExtension.php | 28 ++++++++ .../Twig/Extension/SerializerRuntime.php | 33 +++++++++ .../Fixtures/SerializerModelFixture.php | 18 +++++ .../Extension/SerializerExtensionTest.php | 70 +++++++++++++++++++ src/Symfony/Bridge/Twig/composer.json | 1 + src/Symfony/Bundle/TwigBundle/CHANGELOG.md | 7 +- .../Compiler/ExtensionPass.php | 5 ++ .../TwigBundle/Resources/config/twig.php | 7 ++ 9 files changed, 173 insertions(+), 1 deletion(-) create mode 100644 src/Symfony/Bridge/Twig/Extension/SerializerExtension.php create mode 100644 src/Symfony/Bridge/Twig/Extension/SerializerRuntime.php create mode 100644 src/Symfony/Bridge/Twig/Tests/Extension/Fixtures/SerializerModelFixture.php create mode 100644 src/Symfony/Bridge/Twig/Tests/Extension/SerializerExtensionTest.php diff --git a/src/Symfony/Bridge/Twig/CHANGELOG.md b/src/Symfony/Bridge/Twig/CHANGELOG.md index d44edb22fc..b2d25bce4f 100644 --- a/src/Symfony/Bridge/Twig/CHANGELOG.md +++ b/src/Symfony/Bridge/Twig/CHANGELOG.md @@ -1,6 +1,11 @@ CHANGELOG ========= +5.3.0 +----- + +* Added a new `serialize` filter to serialize objects using the Serializer component + 5.2.0 ----- diff --git a/src/Symfony/Bridge/Twig/Extension/SerializerExtension.php b/src/Symfony/Bridge/Twig/Extension/SerializerExtension.php new file mode 100644 index 0000000000..f38571efaa --- /dev/null +++ b/src/Symfony/Bridge/Twig/Extension/SerializerExtension.php @@ -0,0 +1,28 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bridge\Twig\Extension; + +use Twig\Extension\AbstractExtension; +use Twig\TwigFilter; + +/** + * @author Jesse Rushlow + */ +final class SerializerExtension extends AbstractExtension +{ + public function getFilters(): array + { + return [ + new TwigFilter('serialize', [SerializerRuntime::class, 'serialize']), + ]; + } +} diff --git a/src/Symfony/Bridge/Twig/Extension/SerializerRuntime.php b/src/Symfony/Bridge/Twig/Extension/SerializerRuntime.php new file mode 100644 index 0000000000..3a4087aa79 --- /dev/null +++ b/src/Symfony/Bridge/Twig/Extension/SerializerRuntime.php @@ -0,0 +1,33 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bridge\Twig\Extension; + +use Symfony\Component\Serializer\SerializerInterface; +use Twig\Extension\RuntimeExtensionInterface; + +/** + * @author Jesse Rushlow + */ +final class SerializerRuntime implements RuntimeExtensionInterface +{ + private $serializer; + + public function __construct(SerializerInterface $serializer) + { + $this->serializer = $serializer; + } + + public function serialize($data, string $format = 'json', array $context = []): string + { + return $this->serializer->serialize($data, $format, $context); + } +} diff --git a/src/Symfony/Bridge/Twig/Tests/Extension/Fixtures/SerializerModelFixture.php b/src/Symfony/Bridge/Twig/Tests/Extension/Fixtures/SerializerModelFixture.php new file mode 100644 index 0000000000..07493ea9d8 --- /dev/null +++ b/src/Symfony/Bridge/Twig/Tests/Extension/Fixtures/SerializerModelFixture.php @@ -0,0 +1,18 @@ + + */ +class SerializerModelFixture +{ + /** + * @Groups({"read"}) + */ + public $name = 'howdy'; + + public $title = 'fixture'; +} diff --git a/src/Symfony/Bridge/Twig/Tests/Extension/SerializerExtensionTest.php b/src/Symfony/Bridge/Twig/Tests/Extension/SerializerExtensionTest.php new file mode 100644 index 0000000000..ef54ee2775 --- /dev/null +++ b/src/Symfony/Bridge/Twig/Tests/Extension/SerializerExtensionTest.php @@ -0,0 +1,70 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bridge\Twig\Tests\Extension; + +use Doctrine\Common\Annotations\AnnotationReader; +use PHPUnit\Framework\TestCase; +use Symfony\Bridge\Twig\Extension\SerializerExtension; +use Symfony\Bridge\Twig\Extension\SerializerRuntime; +use Symfony\Bridge\Twig\Tests\Extension\Fixtures\SerializerModelFixture; +use Symfony\Component\Serializer\Encoder\JsonEncoder; +use Symfony\Component\Serializer\Encoder\YamlEncoder; +use Symfony\Component\Serializer\Mapping\Factory\ClassMetadataFactory; +use Symfony\Component\Serializer\Mapping\Loader\AnnotationLoader; +use Symfony\Component\Serializer\Normalizer\ObjectNormalizer; +use Symfony\Component\Serializer\Serializer; +use Twig\Environment; +use Twig\Loader\ArrayLoader; +use Twig\RuntimeLoader\RuntimeLoaderInterface; + +/** + * @author Jesse Rushlow + */ +class SerializerExtensionTest extends TestCase +{ + /** + * @dataProvider serializerDataProvider + */ + public function testSerializeFilter(string $template, string $expectedResult) + { + $twig = $this->getTwig($template); + + self::assertSame($expectedResult, $twig->render('template', ['object' => new SerializerModelFixture()])); + } + + public function serializerDataProvider(): \Generator + { + yield ['{{ object|serialize }}', '{"name":"howdy","title":"fixture"}']; + yield ['{{ object|serialize(\'yaml\') }}', '{ name: howdy, title: fixture }']; + yield ['{{ object|serialize(\'yaml\', {groups: \'read\'}) }}', '{ name: howdy }']; + } + + private function getTwig(string $template): Environment + { + $meta = new ClassMetadataFactory(new AnnotationLoader(new AnnotationReader())); + $runtime = new SerializerRuntime(new Serializer([new ObjectNormalizer($meta)], [new JsonEncoder(), new YamlEncoder()])); + + $mockRuntimeLoader = $this->createMock(RuntimeLoaderInterface::class); + $mockRuntimeLoader + ->method('load') + ->willReturnMap([ + ['Symfony\Bridge\Twig\Extension\SerializerRuntime', $runtime], + ]) + ; + + $twig = new Environment(new ArrayLoader(['template' => $template])); + $twig->addExtension(new SerializerExtension()); + $twig->addRuntimeLoader($mockRuntimeLoader); + + return $twig; + } +} diff --git a/src/Symfony/Bridge/Twig/composer.json b/src/Symfony/Bridge/Twig/composer.json index 44fb474553..29dc959e5c 100644 --- a/src/Symfony/Bridge/Twig/composer.json +++ b/src/Symfony/Bridge/Twig/composer.json @@ -22,6 +22,7 @@ "twig/twig": "^2.13|^3.0.4" }, "require-dev": { + "doctrine/annotations": "^1.12", "egulias/email-validator": "^2.1.10", "phpdocumentor/reflection-docblock": "^3.0|^4.0|^5.0", "symfony/asset": "^4.4|^5.0", diff --git a/src/Symfony/Bundle/TwigBundle/CHANGELOG.md b/src/Symfony/Bundle/TwigBundle/CHANGELOG.md index be47f246de..b1642ea1af 100644 --- a/src/Symfony/Bundle/TwigBundle/CHANGELOG.md +++ b/src/Symfony/Bundle/TwigBundle/CHANGELOG.md @@ -1,6 +1,11 @@ CHANGELOG ========= +5.3.0 +----- + +* Added support for the new `serialize` filter (from Twig Bridge) + 5.2.0 ----- @@ -33,7 +38,7 @@ CHANGELOG 4.1.0 ----- - * added priority to Twig extensions + * added priority to Twig extensions * deprecated relying on the default value (`false`) of the `twig.strict_variables` configuration option. The `%kernel.debug%` parameter will be the new default in 5.0 4.0.0 diff --git a/src/Symfony/Bundle/TwigBundle/DependencyInjection/Compiler/ExtensionPass.php b/src/Symfony/Bundle/TwigBundle/DependencyInjection/Compiler/ExtensionPass.php index ae3f9df3be..a18de86e7b 100644 --- a/src/Symfony/Bundle/TwigBundle/DependencyInjection/Compiler/ExtensionPass.php +++ b/src/Symfony/Bundle/TwigBundle/DependencyInjection/Compiler/ExtensionPass.php @@ -120,5 +120,10 @@ class ExtensionPass implements CompilerPassInterface } else { $container->getDefinition('workflow.twig_extension')->addTag('twig.extension'); } + + if ($container->has('serializer')) { + $container->getDefinition('twig.runtime.serializer')->addTag('twig.runtime'); + $container->getDefinition('twig.extension.serializer')->addTag('twig.extension'); + } } } diff --git a/src/Symfony/Bundle/TwigBundle/Resources/config/twig.php b/src/Symfony/Bundle/TwigBundle/Resources/config/twig.php index a7124a30c2..aa71e0de86 100644 --- a/src/Symfony/Bundle/TwigBundle/Resources/config/twig.php +++ b/src/Symfony/Bundle/TwigBundle/Resources/config/twig.php @@ -23,6 +23,8 @@ use Symfony\Bridge\Twig\Extension\HttpKernelExtension; use Symfony\Bridge\Twig\Extension\HttpKernelRuntime; use Symfony\Bridge\Twig\Extension\ProfilerExtension; use Symfony\Bridge\Twig\Extension\RoutingExtension; +use Symfony\Bridge\Twig\Extension\SerializerExtension; +use Symfony\Bridge\Twig\Extension\SerializerRuntime; use Symfony\Bridge\Twig\Extension\StopwatchExtension; use Symfony\Bridge\Twig\Extension\TranslationExtension; use Symfony\Bridge\Twig\Extension\WebLinkExtension; @@ -160,5 +162,10 @@ return static function (ContainerConfigurator $container) { ->factory([TwigErrorRenderer::class, 'isDebug']) ->args([service('request_stack'), param('kernel.debug')]), ]) + + ->set('twig.runtime.serializer', SerializerRuntime::class) + ->args([service('serializer')]) + + ->set('twig.extension.serializer', SerializerExtension::class) ; };