From 6e4feb517f5861fa37a39dc756a7c65e5c542d42 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Edi=20Modri=C4=87?= Date: Tue, 21 May 2024 14:01:24 +0200 Subject: [PATCH] Support yield rendering strategy in Twig 3.9+ --- .../Templating/Twig/Node/DefaultContext.php | 2 + .../Templating/Twig/Node/RenderZone.php | 17 +++++- .../Twig/Runtime/HelpersRuntime.php | 8 ++- .../Twig/Runtime/RenderingRuntime.php | 6 +-- composer.json | 2 +- phpstan.neon | 5 +- .../Extension/RenderingExtensionTwigTest.php | 6 +-- .../Templating/Twig/Node/RenderZoneTest.php | 9 ++-- .../Twig/Runtime/RenderingRuntimeTest.php | 53 +++++++++++++++++++ 9 files changed, 90 insertions(+), 18 deletions(-) diff --git a/bundles/LayoutsBundle/Templating/Twig/Node/DefaultContext.php b/bundles/LayoutsBundle/Templating/Twig/Node/DefaultContext.php index 4f688e000..2110eeeac 100644 --- a/bundles/LayoutsBundle/Templating/Twig/Node/DefaultContext.php +++ b/bundles/LayoutsBundle/Templating/Twig/Node/DefaultContext.php @@ -4,10 +4,12 @@ namespace Netgen\Bundle\LayoutsBundle\Templating\Twig\Node; +use Twig\Attribute\YieldReady; use Twig\Compiler; use Twig\Node\Expression\AbstractExpression; use Twig\Node\Node; +#[YieldReady] final class DefaultContext extends Node { public function __construct(AbstractExpression $expr, int $line = 0, ?string $tag = null) diff --git a/bundles/LayoutsBundle/Templating/Twig/Node/RenderZone.php b/bundles/LayoutsBundle/Templating/Twig/Node/RenderZone.php index bd721e308..3ddeca6b1 100644 --- a/bundles/LayoutsBundle/Templating/Twig/Node/RenderZone.php +++ b/bundles/LayoutsBundle/Templating/Twig/Node/RenderZone.php @@ -8,12 +8,15 @@ use Netgen\Layouts\API\Values\Layout\Zone; use Netgen\Layouts\View\Twig\ContextualizedTwigTemplate; use Netgen\Layouts\View\ViewInterface; +use Twig\Attribute\YieldReady; use Twig\Compiler; +use Twig\Environment; use Twig\Node\Expression\AbstractExpression; use Twig\Node\Node; use const PHP_EOL; +#[YieldReady] final class RenderZone extends Node { public function __construct(AbstractExpression $zone, ?AbstractExpression $context = null, int $line = 0, ?string $tag = null) @@ -36,9 +39,8 @@ public function compile(Compiler $compiler): void ->write('$nglZoneIdentifier = $nglZone instanceof ' . Zone::class . ' ? $nglZone->getIdentifier() : $nglZone;' . PHP_EOL); $this->compileContextNode($compiler); - $compiler->write('$nglTemplate = new ' . ContextualizedTwigTemplate::class . '($this, $context, $blocks);' . PHP_EOL); - $compiler->write('$this->env->getRuntime("' . RenderingRuntime::class . '")->displayZone($context["nglayouts"]->getLayout(), $nglZoneIdentifier, $nglContext, $nglTemplate);' . PHP_EOL); + $this->compileRenderNode($compiler); } /** @@ -62,4 +64,15 @@ private function compileContextNode(Compiler $compiler): void $compiler->write('$nglContext = ' . ViewInterface::class . '::CONTEXT_DEFAULT;' . PHP_EOL); } + + private function compileRenderNode(Compiler $compiler): void + { + if (Environment::VERSION_ID >= 30900) { + $compiler->write('yield $this->env->getRuntime("' . RenderingRuntime::class . '")->renderZone($context["nglayouts"]->getLayout(), $nglZoneIdentifier, $nglContext, $nglTemplate);' . PHP_EOL); + + return; + } + + $compiler->write('echo $this->env->getRuntime("' . RenderingRuntime::class . '")->renderZone($context["nglayouts"]->getLayout(), $nglZoneIdentifier, $nglContext, $nglTemplate);' . PHP_EOL); + } } diff --git a/bundles/LayoutsBundle/Templating/Twig/Runtime/HelpersRuntime.php b/bundles/LayoutsBundle/Templating/Twig/Runtime/HelpersRuntime.php index 0a3a906e3..1cac5a80a 100644 --- a/bundles/LayoutsBundle/Templating/Twig/Runtime/HelpersRuntime.php +++ b/bundles/LayoutsBundle/Templating/Twig/Runtime/HelpersRuntime.php @@ -19,9 +19,11 @@ use Ramsey\Uuid\Uuid; use Throwable; use Twig\Environment; +use Twig\Extension\CoreExtension; use function array_unshift; use function is_string; +use function method_exists; use function twig_date_converter; final class HelpersRuntime @@ -121,7 +123,11 @@ public function getCountryFlag(string $countryCode): string */ public function formatDateTime(Environment $twig, $dateTime, string $dateFormat = 'medium', string $timeFormat = 'medium'): string { - $dateTime = twig_date_converter($twig, $dateTime); + $coreExtension = $twig->getExtension(CoreExtension::class); + + $dateTime = method_exists($coreExtension, 'convertDate') ? + $coreExtension->convertDate($dateTime) : + twig_date_converter($twig, $dateTime); $formatValues = [ 'none' => IntlDateFormatter::NONE, diff --git a/bundles/LayoutsBundle/Templating/Twig/Runtime/RenderingRuntime.php b/bundles/LayoutsBundle/Templating/Twig/Runtime/RenderingRuntime.php index 44b874700..4603ea469 100644 --- a/bundles/LayoutsBundle/Templating/Twig/Runtime/RenderingRuntime.php +++ b/bundles/LayoutsBundle/Templating/Twig/Runtime/RenderingRuntime.php @@ -159,9 +159,9 @@ public function renderValue(array $context, $value, array $parameters = [], ?str } /** - * Displays the provided zone. + * Renders the provided zone. */ - public function displayZone(Layout $layout, string $zoneIdentifier, string $viewContext, ContextualizedTwigTemplate $twigTemplate): void + public function renderZone(Layout $layout, string $zoneIdentifier, string $viewContext, ContextualizedTwigTemplate $twigTemplate): string { $locales = null; @@ -178,7 +178,7 @@ public function displayZone(Layout $layout, string $zoneIdentifier, string $view $locales, ); - echo $this->renderValue( + return $this->renderValue( [], new ZoneReference($layout, $zoneIdentifier), [ diff --git a/composer.json b/composer.json index 02f5e0f03..6f44a617a 100644 --- a/composer.json +++ b/composer.json @@ -44,7 +44,7 @@ "symfony/twig-bundle": "^3.4.47 || ^5.4 || ^6.2", "symfony/validator": "^3.4.47 || ^5.4 || ^6.2", "symfony/yaml": "^3.4.47 || ^5.4 || ^6.2", - "twig/twig": "^2.15 || ^3.4", + "twig/twig": "^2.15 || ^3.9", "sensio/framework-extra-bundle": "^5.4 || ^6.2", "doctrine/dbal": "^2.13 || ^3.5", "doctrine/doctrine-bundle": "^1.12 || ^2.7", diff --git a/phpstan.neon b/phpstan.neon index 559780df3..d92fd4f74 100644 --- a/phpstan.neon +++ b/phpstan.neon @@ -7,6 +7,7 @@ parameters: treatPhpDocTypesAsCertain: false dynamicConstantNames: - Symfony\Component\HttpKernel\Kernel::VERSION_ID + - Twig\Environment::VERSION_ID ignoreErrors: # Doctrine DBAL @@ -49,10 +50,6 @@ parameters: - "#Call to function method_exists\\(\\) with Symfony\\\\Component\\\\HttpFoundation\\\\Request and 'getContentTypeFormat' will always evaluate to true.#" - "#Call to function method_exists\\(\\) with Symfony\\\\Component\\\\DependencyInjection\\\\ContainerBuilder and 'registerAttributeFo…' will always evaluate to true.#" - - - message: '#twig_date_converter not found.#' - path: bundles/LayoutsBundle/Templating/Twig/Runtime/HelpersRuntime.php - # Netgen Layouts specifics - "#Call to function method_exists\\(\\) with Netgen\\\\Layouts\\\\Layout\\\\Resolver\\\\(Condition|Target)TypeInterface and '(export|import)' will always evaluate to true.#" diff --git a/tests/bundles/LayoutsBundle/Templating/Twig/Extension/RenderingExtensionTwigTest.php b/tests/bundles/LayoutsBundle/Templating/Twig/Extension/RenderingExtensionTwigTest.php index 7a51ff55e..8a69f56bc 100644 --- a/tests/bundles/LayoutsBundle/Templating/Twig/Extension/RenderingExtensionTwigTest.php +++ b/tests/bundles/LayoutsBundle/Templating/Twig/Extension/RenderingExtensionTwigTest.php @@ -54,7 +54,7 @@ protected function setUp(): void } /** - * @covers \Netgen\Bundle\LayoutsBundle\Templating\Twig\Runtime\RenderingRuntime::displayZone + * @covers \Netgen\Bundle\LayoutsBundle\Templating\Twig\Runtime\RenderingRuntime::renderZone * * @dataProvider getTests * @@ -74,7 +74,7 @@ public function testIntegration($file, $message, $condition, $templates, $except } /** - * @covers \Netgen\Bundle\LayoutsBundle\Templating\Twig\Runtime\RenderingRuntime::displayZone + * @covers \Netgen\Bundle\LayoutsBundle\Templating\Twig\Runtime\RenderingRuntime::renderZone * * @dataProvider getTests * @@ -97,7 +97,7 @@ public function testIntegrationWithLocale($file, $message, $condition, $template } /** - * @covers \Netgen\Bundle\LayoutsBundle\Templating\Twig\Runtime\RenderingRuntime::displayZone + * @covers \Netgen\Bundle\LayoutsBundle\Templating\Twig\Runtime\RenderingRuntime::renderZone * * @dataProvider getLegacyTests * diff --git a/tests/bundles/LayoutsBundle/Templating/Twig/Node/RenderZoneTest.php b/tests/bundles/LayoutsBundle/Templating/Twig/Node/RenderZoneTest.php index fe2eedc2a..6127e2bc1 100644 --- a/tests/bundles/LayoutsBundle/Templating/Twig/Node/RenderZoneTest.php +++ b/tests/bundles/LayoutsBundle/Templating/Twig/Node/RenderZoneTest.php @@ -15,6 +15,7 @@ /** * @covers \Netgen\Bundle\LayoutsBundle\Templating\Twig\Node\RenderZone::compile * @covers \Netgen\Bundle\LayoutsBundle\Templating\Twig\Node\RenderZone::compileContextNode + * @covers \Netgen\Bundle\LayoutsBundle\Templating\Twig\Node\RenderZone::compileRenderNode */ final class RenderZoneTest extends NodeTestBase { @@ -69,7 +70,7 @@ public static function getTests(): array \$nglZoneIdentifier = \$nglZone instanceof {$zoneClass} ? \$nglZone->getIdentifier() : \$nglZone; \$nglContext = {$contextNodeGetter}; \$nglTemplate = new {$templateClass}(\$this, \$context, \$blocks); - \$this->env->getRuntime("{$runtimeClass}")->displayZone(\$context["nglayouts"]->getLayout(), \$nglZoneIdentifier, \$nglContext, \$nglTemplate); + yield \$this->env->getRuntime("{$runtimeClass}")->renderZone(\$context["nglayouts"]->getLayout(), \$nglZoneIdentifier, \$nglContext, \$nglTemplate); EOT , $environment, @@ -82,7 +83,7 @@ public static function getTests(): array \$nglZoneIdentifier = \$nglZone instanceof {$zoneClass} ? \$nglZone->getIdentifier() : \$nglZone; \$nglContext = {$contextNodeGetter}; \$nglTemplate = new {$templateClass}(\$this, \$context, \$blocks); - \$this->env->getRuntime("{$runtimeClass}")->displayZone(\$context["nglayouts"]->getLayout(), \$nglZoneIdentifier, \$nglContext, \$nglTemplate); + yield \$this->env->getRuntime("{$runtimeClass}")->renderZone(\$context["nglayouts"]->getLayout(), \$nglZoneIdentifier, \$nglContext, \$nglTemplate); EOT , $environment, @@ -95,7 +96,7 @@ public static function getTests(): array \$nglZoneIdentifier = \$nglZone instanceof {$zoneClass} ? \$nglZone->getIdentifier() : \$nglZone; \$nglContext = {$viewInterface}::CONTEXT_DEFAULT; \$nglTemplate = new {$templateClass}(\$this, \$context, \$blocks); - \$this->env->getRuntime("{$runtimeClass}")->displayZone(\$context["nglayouts"]->getLayout(), \$nglZoneIdentifier, \$nglContext, \$nglTemplate); + yield \$this->env->getRuntime("{$runtimeClass}")->renderZone(\$context["nglayouts"]->getLayout(), \$nglZoneIdentifier, \$nglContext, \$nglTemplate); EOT , $environment, @@ -108,7 +109,7 @@ public static function getTests(): array \$nglZoneIdentifier = \$nglZone instanceof {$zoneClass} ? \$nglZone->getIdentifier() : \$nglZone; \$nglContext = {$viewInterface}::CONTEXT_DEFAULT; \$nglTemplate = new {$templateClass}(\$this, \$context, \$blocks); - \$this->env->getRuntime("{$runtimeClass}")->displayZone(\$context["nglayouts"]->getLayout(), \$nglZoneIdentifier, \$nglContext, \$nglTemplate); + yield \$this->env->getRuntime("{$runtimeClass}")->renderZone(\$context["nglayouts"]->getLayout(), \$nglZoneIdentifier, \$nglContext, \$nglTemplate); EOT , $environment, diff --git a/tests/bundles/LayoutsBundle/Templating/Twig/Runtime/RenderingRuntimeTest.php b/tests/bundles/LayoutsBundle/Templating/Twig/Runtime/RenderingRuntimeTest.php index 338f6cebe..3082447f2 100644 --- a/tests/bundles/LayoutsBundle/Templating/Twig/Runtime/RenderingRuntimeTest.php +++ b/tests/bundles/LayoutsBundle/Templating/Twig/Runtime/RenderingRuntimeTest.php @@ -4,13 +4,17 @@ namespace Netgen\Bundle\LayoutsBundle\Tests\Templating\Twig\Runtime; +use Doctrine\Common\Collections\ArrayCollection; use Exception; use Netgen\Bundle\LayoutsBundle\Templating\Twig\Runtime\RenderingRuntime; use Netgen\Layouts\API\Service\BlockService; use Netgen\Layouts\API\Values\Block\Block; +use Netgen\Layouts\API\Values\Block\BlockList; use Netgen\Layouts\API\Values\Block\Placeholder; use Netgen\Layouts\API\Values\Collection\Item; use Netgen\Layouts\API\Values\Collection\Slot; +use Netgen\Layouts\API\Values\Layout\Layout; +use Netgen\Layouts\API\Values\Layout\Zone; use Netgen\Layouts\API\Values\LayoutResolver\RuleCondition; use Netgen\Layouts\Block\BlockDefinition; use Netgen\Layouts\Collection\Result\ManualItem; @@ -21,6 +25,7 @@ use Netgen\Layouts\Tests\Stubs\ErrorHandler; use Netgen\Layouts\View\RendererInterface; use Netgen\Layouts\View\Twig\ContextualizedTwigTemplate; +use Netgen\Layouts\View\View\ZoneView\ZoneReference; use Netgen\Layouts\View\ViewInterface; use PHPUnit\Framework\MockObject\MockObject; use PHPUnit\Framework\TestCase; @@ -60,6 +65,54 @@ protected function setUp(): void ); } + /** + * @covers \Netgen\Bundle\LayoutsBundle\Templating\Twig\Runtime\RenderingRuntime::__construct + * @covers \Netgen\Bundle\LayoutsBundle\Templating\Twig\Runtime\RenderingRuntime::getViewContext + * @covers \Netgen\Bundle\LayoutsBundle\Templating\Twig\Runtime\RenderingRuntime::renderZone + */ + public function testRenderZone(): void + { + $zone = Zone::fromArray([]); + $blocks = new BlockList(); + $layout = Layout::fromArray(['zones' => new ArrayCollection(['zone' => $zone])]); + + $twigTemplate = new ContextualizedTwigTemplate($this->createMock(Template::class)); + + $this->blockServiceMock + ->expects(self::once()) + ->method('loadZoneBlocks') + ->with( + self::identicalTo($zone), + self::isNull(), + ) + ->willReturn($blocks); + + $this->rendererMock + ->expects(self::once()) + ->method('renderValue') + ->with( + self::isInstanceOf(ZoneReference::class), + self::identicalTo(ViewInterface::CONTEXT_DEFAULT), + self::identicalTo( + [ + 'blocks' => $blocks, + 'twig_template' => $twigTemplate, + ], + ), + ) + ->willReturn('rendered zone'); + + self::assertSame( + 'rendered zone', + $this->runtime->renderZone( + $layout, + 'zone', + ViewInterface::CONTEXT_DEFAULT, + $twigTemplate, + ), + ); + } + /** * @covers \Netgen\Bundle\LayoutsBundle\Templating\Twig\Runtime\RenderingRuntime::__construct * @covers \Netgen\Bundle\LayoutsBundle\Templating\Twig\Runtime\RenderingRuntime::getViewContext