mirror of
https://github.com/twigphp/Twig.git
synced 2026-09-13 02:46:29 +00:00
Abstract node capture in its own Node
This commit is contained in:
@@ -12,13 +12,17 @@
|
|||||||
namespace Twig\Extra\Cache\Node;
|
namespace Twig\Extra\Cache\Node;
|
||||||
|
|
||||||
use Twig\Compiler;
|
use Twig\Compiler;
|
||||||
|
use Twig\Node\CaptureNode;
|
||||||
use Twig\Node\Expression\AbstractExpression;
|
use Twig\Node\Expression\AbstractExpression;
|
||||||
use Twig\Node\Node;
|
use Twig\Node\Node;
|
||||||
|
|
||||||
class CacheNode extends Node
|
class CacheNode extends AbstractExpression
|
||||||
{
|
{
|
||||||
public function __construct(AbstractExpression $key, ?AbstractExpression $ttl, ?AbstractExpression $tags, Node $body, int $lineno, string $tag)
|
public function __construct(AbstractExpression $key, ?AbstractExpression $ttl, ?AbstractExpression $tags, Node $body, int $lineno, string $tag)
|
||||||
{
|
{
|
||||||
|
$body = new CaptureNode($body, $lineno);
|
||||||
|
$body->setAttribute('raw', true);
|
||||||
|
|
||||||
$nodes = ['key' => $key, 'body' => $body];
|
$nodes = ['key' => $key, 'body' => $body];
|
||||||
if (null !== $ttl) {
|
if (null !== $ttl) {
|
||||||
$nodes['ttl'] = $ttl;
|
$nodes['ttl'] = $ttl;
|
||||||
@@ -34,7 +38,7 @@ class CacheNode extends Node
|
|||||||
{
|
{
|
||||||
$compiler
|
$compiler
|
||||||
->addDebugInfo($this)
|
->addDebugInfo($this)
|
||||||
->write('$cached = $this->env->getRuntime(\'Twig\Extra\Cache\CacheRuntime\')->getCache()->get(')
|
->raw('$this->env->getRuntime(\'Twig\Extra\Cache\CacheRuntime\')->getCache()->get(')
|
||||||
->subcompile($this->getNode('key'))
|
->subcompile($this->getNode('key'))
|
||||||
->raw(", function (\Symfony\Contracts\Cache\ItemInterface \$item) use (\$context, \$macros) {\n")
|
->raw(", function (\Symfony\Contracts\Cache\ItemInterface \$item) use (\$context, \$macros) {\n")
|
||||||
->indent()
|
->indent()
|
||||||
@@ -57,13 +61,11 @@ class CacheNode extends Node
|
|||||||
}
|
}
|
||||||
|
|
||||||
$compiler
|
$compiler
|
||||||
->write("ob_start(function () { return ''; });\n")
|
->write('return ')
|
||||||
->subcompile($this->getNode('body'))
|
->subcompile($this->getNode('body'))
|
||||||
->write("\n")
|
->raw(";\n")
|
||||||
->write("return ob_get_clean();\n")
|
|
||||||
->outdent()
|
->outdent()
|
||||||
->write("});\n")
|
->write("})\n")
|
||||||
->write("echo '' === \$cached ? '' : new Markup(\$cached, \$this->env->getCharset());\n")
|
|
||||||
;
|
;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,7 +13,10 @@ namespace Twig\Extra\Cache\TokenParser;
|
|||||||
|
|
||||||
use Twig\Error\SyntaxError;
|
use Twig\Error\SyntaxError;
|
||||||
use Twig\Extra\Cache\Node\CacheNode;
|
use Twig\Extra\Cache\Node\CacheNode;
|
||||||
|
use Twig\Node\Expression\ConstantExpression;
|
||||||
|
use Twig\Node\Expression\FilterExpression;
|
||||||
use Twig\Node\Node;
|
use Twig\Node\Node;
|
||||||
|
use Twig\Node\PrintNode;
|
||||||
use Twig\Token;
|
use Twig\Token;
|
||||||
use Twig\TokenParser\AbstractTokenParser;
|
use Twig\TokenParser\AbstractTokenParser;
|
||||||
|
|
||||||
@@ -54,7 +57,10 @@ class CacheTokenParser extends AbstractTokenParser
|
|||||||
$body = $this->parser->subparse([$this, 'decideCacheEnd'], true);
|
$body = $this->parser->subparse([$this, 'decideCacheEnd'], true);
|
||||||
$stream->expect(Token::BLOCK_END_TYPE);
|
$stream->expect(Token::BLOCK_END_TYPE);
|
||||||
|
|
||||||
return new CacheNode($key, $ttl, $tags, $body, $token->getLine(), $this->getTag());
|
$body = new CacheNode($key, $ttl, $tags, $body, $token->getLine(), $this->getTag());
|
||||||
|
$body = new FilterExpression($body, new ConstantExpression('raw', $token->getLine()), new Node(), $token->getLine());
|
||||||
|
|
||||||
|
return new PrintNode($body, $token->getLine(), $this->getTag());
|
||||||
}
|
}
|
||||||
|
|
||||||
public function decideCacheEnd(Token $token): bool
|
public function decideCacheEnd(Token $token): bool
|
||||||
|
|||||||
@@ -17,7 +17,7 @@
|
|||||||
"require": {
|
"require": {
|
||||||
"php": ">=7.2.5",
|
"php": ">=7.2.5",
|
||||||
"symfony/cache": "^5.0|^6.0|^7.0",
|
"symfony/cache": "^5.0|^6.0|^7.0",
|
||||||
"twig/twig": "^3.0"
|
"twig/twig": "^3.9"
|
||||||
},
|
},
|
||||||
"require-dev": {
|
"require-dev": {
|
||||||
"symfony/phpunit-bridge": "^6.4|^7.0"
|
"symfony/phpunit-bridge": "^6.4|^7.0"
|
||||||
|
|||||||
@@ -0,0 +1,63 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This file is part of Twig.
|
||||||
|
*
|
||||||
|
* (c) Fabien Potencier
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please view the LICENSE
|
||||||
|
* file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Twig\Node;
|
||||||
|
|
||||||
|
use Twig\Compiler;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents a node for which we need to capture the output.
|
||||||
|
*
|
||||||
|
* @author Fabien Potencier <fabien@symfony.com>
|
||||||
|
*/
|
||||||
|
class CaptureNode extends Node
|
||||||
|
{
|
||||||
|
public function __construct(Node $body, int $lineno, string $tag = null)
|
||||||
|
{
|
||||||
|
parent::__construct(['body' => $body], ['raw' => false, 'with_blocks' => false], $lineno, $tag);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function compile(Compiler $compiler): void
|
||||||
|
{
|
||||||
|
if ($this->getAttribute('with_blocks')) {
|
||||||
|
$compiler->raw("(function () use (&\$context, \$macros, \$blocks) {\n");
|
||||||
|
} else {
|
||||||
|
$compiler->raw("(function () use (&\$context, \$macros) {\n");
|
||||||
|
}
|
||||||
|
$compiler->indent();
|
||||||
|
if ($compiler->getEnvironment()->isDebug()) {
|
||||||
|
$compiler->write("ob_start();\n");
|
||||||
|
} else {
|
||||||
|
$compiler->write("ob_start(function () { return ''; });\n");
|
||||||
|
}
|
||||||
|
$compiler
|
||||||
|
->write("try {\n")
|
||||||
|
->indent()
|
||||||
|
->subcompile($this->getNode('body'))
|
||||||
|
->raw("\n")
|
||||||
|
;
|
||||||
|
if ($this->getAttribute('raw')) {
|
||||||
|
$compiler->write("return ob_get_contents();\n");
|
||||||
|
} else {
|
||||||
|
$compiler->write("return ('' === \$tmp = ob_get_contents()) ? '' : new Markup(\$tmp, \$this->env->getCharset());\n");
|
||||||
|
}
|
||||||
|
$compiler
|
||||||
|
->outdent()
|
||||||
|
->write("} finally {\n")
|
||||||
|
->indent()
|
||||||
|
->write("ob_end_clean();\n")
|
||||||
|
->outdent()
|
||||||
|
->write("}\n")
|
||||||
|
->outdent()
|
||||||
|
->write('})()')
|
||||||
|
;
|
||||||
|
}
|
||||||
|
}
|
||||||
+4
-20
@@ -31,6 +31,8 @@ class MacroNode extends Node
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$body = new CaptureNode($body, $lineno, $tag);
|
||||||
|
|
||||||
parent::__construct(['body' => $body, 'arguments' => $arguments], ['name' => $name], $lineno, $tag);
|
parent::__construct(['body' => $body, 'arguments' => $arguments], ['name' => $name], $lineno, $tag);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -81,31 +83,13 @@ class MacroNode extends Node
|
|||||||
->write('')
|
->write('')
|
||||||
->string(self::VARARGS_NAME)
|
->string(self::VARARGS_NAME)
|
||||||
->raw(' => ')
|
->raw(' => ')
|
||||||
;
|
|
||||||
|
|
||||||
$compiler
|
|
||||||
->raw("\$__varargs__,\n")
|
->raw("\$__varargs__,\n")
|
||||||
->outdent()
|
->outdent()
|
||||||
->write("]);\n\n")
|
->write("]);\n\n")
|
||||||
->write("\$blocks = [];\n\n")
|
->write("\$blocks = [];\n\n")
|
||||||
;
|
->write('return ')
|
||||||
if ($compiler->getEnvironment()->isDebug()) {
|
|
||||||
$compiler->write("ob_start();\n");
|
|
||||||
} else {
|
|
||||||
$compiler->write("ob_start(function () { return ''; });\n");
|
|
||||||
}
|
|
||||||
$compiler
|
|
||||||
->write("try {\n")
|
|
||||||
->indent()
|
|
||||||
->subcompile($this->getNode('body'))
|
->subcompile($this->getNode('body'))
|
||||||
->raw("\n")
|
->raw(";\n")
|
||||||
->write("return ('' === \$tmp = ob_get_contents()) ? '' : new Markup(\$tmp, \$this->env->getCharset());\n")
|
|
||||||
->outdent()
|
|
||||||
->write("} finally {\n")
|
|
||||||
->indent()
|
|
||||||
->write("ob_end_clean();\n")
|
|
||||||
->outdent()
|
|
||||||
->write("}\n")
|
|
||||||
->outdent()
|
->outdent()
|
||||||
->write("}\n\n")
|
->write("}\n\n")
|
||||||
;
|
;
|
||||||
|
|||||||
+14
-26
@@ -23,22 +23,24 @@ class SetNode extends Node implements NodeCaptureInterface
|
|||||||
{
|
{
|
||||||
public function __construct(bool $capture, Node $names, Node $values, int $lineno, string $tag = null)
|
public function __construct(bool $capture, Node $names, Node $values, int $lineno, string $tag = null)
|
||||||
{
|
{
|
||||||
parent::__construct(['names' => $names, 'values' => $values], ['capture' => $capture, 'safe' => false], $lineno, $tag);
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Optimizes the node when capture is used for a large block of text.
|
* Optimizes the node when capture is used for a large block of text.
|
||||||
*
|
*
|
||||||
* {% set foo %}foo{% endset %} is compiled to $context['foo'] = new Twig\Markup("foo");
|
* {% set foo %}foo{% endset %} is compiled to $context['foo'] = new Twig\Markup("foo");
|
||||||
*/
|
*/
|
||||||
if ($this->getAttribute('capture')) {
|
$safe = false;
|
||||||
$this->setAttribute('safe', true);
|
if ($capture) {
|
||||||
|
$safe = true;
|
||||||
$values = $this->getNode('values');
|
|
||||||
if ($values instanceof TextNode) {
|
if ($values instanceof TextNode) {
|
||||||
$this->setNode('values', new ConstantExpression($values->getAttribute('data'), $values->getTemplateLine()));
|
$values = new ConstantExpression($values->getAttribute('data'), $values->getTemplateLine());
|
||||||
$this->setAttribute('capture', false);
|
$capture = false;
|
||||||
|
} else {
|
||||||
|
$values = new CaptureNode($values, $values->getTemplateLine());
|
||||||
|
$values->setAttribute('with_blocks', true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
parent::__construct(['names' => $names, 'values' => $values], ['capture' => $capture, 'safe' => $safe], $lineno, $tag);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function compile(Compiler $compiler): void
|
public function compile(Compiler $compiler): void
|
||||||
@@ -56,27 +58,13 @@ class SetNode extends Node implements NodeCaptureInterface
|
|||||||
}
|
}
|
||||||
$compiler->raw(')');
|
$compiler->raw(')');
|
||||||
} else {
|
} else {
|
||||||
if ($this->getAttribute('capture')) {
|
|
||||||
if ($compiler->getEnvironment()->isDebug()) {
|
|
||||||
$compiler->write("ob_start();\n");
|
|
||||||
} else {
|
|
||||||
$compiler->write("ob_start(function () { return ''; });\n");
|
|
||||||
}
|
|
||||||
$compiler
|
|
||||||
->subcompile($this->getNode('values'))
|
|
||||||
;
|
|
||||||
}
|
|
||||||
|
|
||||||
$compiler->subcompile($this->getNode('names'), false);
|
$compiler->subcompile($this->getNode('names'), false);
|
||||||
|
|
||||||
if ($this->getAttribute('capture')) {
|
|
||||||
$compiler->raw(" = ('' === \$tmp = ob_get_clean()) ? '' : new Markup(\$tmp, \$this->env->getCharset())");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
$compiler->raw(' = ');
|
||||||
|
|
||||||
if (!$this->getAttribute('capture')) {
|
if ($this->getAttribute('capture')) {
|
||||||
$compiler->raw(' = ');
|
$compiler->subcompile($this->getNode('values'));
|
||||||
|
} else {
|
||||||
if (\count($this->getNode('names')) > 1) {
|
if (\count($this->getNode('names')) > 1) {
|
||||||
$compiler->write('[');
|
$compiler->write('[');
|
||||||
foreach ($this->getNode('values') as $idx => $value) {
|
foreach ($this->getNode('values') as $idx => $value) {
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ class MacroTest extends NodeTestCase
|
|||||||
$arguments = new Node([new NameExpression('foo', 1)], [], 1);
|
$arguments = new Node([new NameExpression('foo', 1)], [], 1);
|
||||||
$node = new MacroNode('foo', $body, $arguments, 1);
|
$node = new MacroNode('foo', $body, $arguments, 1);
|
||||||
|
|
||||||
$this->assertEquals($body, $node->getNode('body'));
|
$this->assertEquals($body, $node->getNode('body')->getNode('body'));
|
||||||
$this->assertEquals($arguments, $node->getNode('arguments'));
|
$this->assertEquals($arguments, $node->getNode('arguments'));
|
||||||
$this->assertEquals('foo', $node->getAttribute('name'));
|
$this->assertEquals('foo', $node->getAttribute('name'));
|
||||||
}
|
}
|
||||||
@@ -54,14 +54,16 @@ public function macro_foo(\$__foo__ = null, \$__bar__ = "Foo", ...\$__varargs__)
|
|||||||
|
|
||||||
\$blocks = [];
|
\$blocks = [];
|
||||||
|
|
||||||
ob_start(function () { return ''; });
|
return (function () use (&\$context, \$macros) {
|
||||||
try {
|
ob_start(function () { return ''; });
|
||||||
echo "foo";
|
try {
|
||||||
|
echo "foo";
|
||||||
|
|
||||||
return ('' === \$tmp = ob_get_contents()) ? '' : new Markup(\$tmp, \$this->env->getCharset());
|
return ('' === \$tmp = ob_get_contents()) ? '' : new Markup(\$tmp, \$this->env->getCharset());
|
||||||
} finally {
|
} finally {
|
||||||
ob_end_clean();
|
ob_end_clean();
|
||||||
}
|
}
|
||||||
|
})();
|
||||||
}
|
}
|
||||||
EOF
|
EOF
|
||||||
],
|
],
|
||||||
|
|||||||
+10
-3
@@ -51,9 +51,16 @@ EOF
|
|||||||
$node = new SetNode(true, $names, $values, 1);
|
$node = new SetNode(true, $names, $values, 1);
|
||||||
$tests[] = [$node, <<<EOF
|
$tests[] = [$node, <<<EOF
|
||||||
// line 1
|
// line 1
|
||||||
ob_start(function () { return ''; });
|
\$context["foo"] = (function () use (&\$context, \$macros, \$blocks) {
|
||||||
echo "foo";
|
ob_start(function () { return ''; });
|
||||||
\$context["foo"] = ('' === \$tmp = ob_get_clean()) ? '' : new Markup(\$tmp, \$this->env->getCharset());
|
try {
|
||||||
|
echo "foo";
|
||||||
|
|
||||||
|
return ('' === \$tmp = ob_get_contents()) ? '' : new Markup(\$tmp, \$this->env->getCharset());
|
||||||
|
} finally {
|
||||||
|
ob_end_clean();
|
||||||
|
}
|
||||||
|
})();
|
||||||
EOF
|
EOF
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user