This commit is contained in:
2021-12-06 13:54:48 +01:00
parent cfd6acbbb8
commit f5cb936c97
111 changed files with 2189 additions and 858 deletions

View File

@@ -0,0 +1,43 @@
<?php
/**
* This file is part of the rybakit/twig-deferred-extension package.
*
* (c) Eugene Leonovich <gen.work@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
declare(strict_types=1);
namespace Twig\DeferredExtension;
use Twig\Compiler;
use Twig\Node\BlockNode;
final class DeferredBlockNode extends BlockNode
{
public function compile(Compiler $compiler) : void
{
$name = $this->getAttribute('name');
$compiler
->write("public function block_$name(\$context, array \$blocks = [])\n", "{\n")
->indent()
->write("\$this->deferred->defer(\$this, '$name');\n")
->outdent()
->write("}\n\n")
;
$compiler
->addDebugInfo($this)
->write("public function block_{$name}_deferred(\$context, array \$blocks = [])\n", "{\n")
->indent()
->subcompile($this->getNode('body'))
->write("\$this->deferred->resolve(\$this, \$context, \$blocks);\n")
->outdent()
->write("}\n\n")
;
}
}

View File

@@ -0,0 +1,72 @@
<?php
/**
* This file is part of the rybakit/twig-deferred-extension package.
*
* (c) Eugene Leonovich <gen.work@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
declare(strict_types=1);
namespace Twig\DeferredExtension;
use Twig\Environment;
use Twig\Extension\AbstractExtension;
use Twig\Template;
final class DeferredExtension extends AbstractExtension
{
private $blocks = [];
public function getTokenParsers() : array
{
return [new DeferredTokenParser()];
}
public function getNodeVisitors() : array
{
if (Environment::VERSION_ID < 20000) {
// Twig 1.x support
return [new DeferredNodeVisitorCompat()];
}
return [new DeferredNodeVisitor()];
}
public function defer(Template $template, string $blockName) : void
{
$templateName = $template->getTemplateName();
$this->blocks[$templateName][] = $blockName;
$index = \count($this->blocks[$templateName]) - 1;
\ob_start(function (string $buffer) use ($index, $templateName) {
unset($this->blocks[$templateName][$index]);
return $buffer;
});
}
public function resolve(Template $template, array $context, array $blocks) : void
{
$templateName = $template->getTemplateName();
if (empty($this->blocks[$templateName])) {
return;
}
while ($blockName = \array_pop($this->blocks[$templateName])) {
$buffer = \ob_get_clean();
$blocks[$blockName] = [$template, 'block_'.$blockName.'_deferred'];
$template->displayBlock($blockName, $context, $blocks);
echo $buffer;
}
if ($parent = $template->getParent($context)) {
$this->resolve($parent, $context, $blocks);
}
}
}

View File

@@ -0,0 +1,27 @@
<?php
/**
* This file is part of the rybakit/twig-deferred-extension package.
*
* (c) Eugene Leonovich <gen.work@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
declare(strict_types=1);
namespace Twig\DeferredExtension;
use Twig\Compiler;
use Twig\Node\Node;
final class DeferredExtensionNode extends Node
{
public function compile(Compiler $compiler) : void
{
$compiler
->write("\$this->deferred = \$this->env->getExtension('".DeferredExtension::class."');\n")
;
}
}

View File

@@ -0,0 +1,27 @@
<?php
/**
* This file is part of the rybakit/twig-deferred-extension package.
*
* (c) Eugene Leonovich <gen.work@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
declare(strict_types=1);
namespace Twig\DeferredExtension;
use Twig\Compiler;
use Twig\Node\Node;
final class DeferredNode extends Node
{
public function compile(Compiler $compiler) : void
{
$compiler
->write("\$this->deferred->resolve(\$this, \$context, \$blocks);\n")
;
}
}

View File

@@ -0,0 +1,49 @@
<?php
/**
* This file is part of the rybakit/twig-deferred-extension package.
*
* (c) Eugene Leonovich <gen.work@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
declare(strict_types=1);
namespace Twig\DeferredExtension;
use Twig\Environment;
use Twig\Node\ModuleNode;
use Twig\Node\Node;
use Twig\NodeVisitor\NodeVisitorInterface;
final class DeferredNodeVisitor implements NodeVisitorInterface
{
private $hasDeferred = false;
public function enterNode(Node $node, Environment $env) : Node
{
if (!$this->hasDeferred && $node instanceof DeferredBlockNode) {
$this->hasDeferred = true;
}
return $node;
}
public function leaveNode(Node $node, Environment $env) : ?Node
{
if ($this->hasDeferred && $node instanceof ModuleNode) {
$node->setNode('constructor_end', new Node([new DeferredExtensionNode(), $node->getNode('constructor_end')]));
$node->setNode('display_end', new Node([new DeferredNode(), $node->getNode('display_end')]));
$this->hasDeferred = false;
}
return $node;
}
public function getPriority() : int
{
return 0;
}
}

View File

@@ -0,0 +1,49 @@
<?php
/**
* This file is part of the rybakit/twig-deferred-extension package.
*
* (c) Eugene Leonovich <gen.work@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
declare(strict_types=1);
namespace Twig\DeferredExtension;
use Twig\Environment;
use Twig\Node\ModuleNode;
use Twig\Node\Node;
use Twig\NodeVisitor\NodeVisitorInterface;
final class DeferredNodeVisitorCompat implements NodeVisitorInterface
{
private $hasDeferred = false;
public function enterNode(\Twig_NodeInterface $node, Environment $env) : Node
{
if (!$this->hasDeferred && $node instanceof DeferredBlockNode) {
$this->hasDeferred = true;
}
return $node;
}
public function leaveNode(\Twig_NodeInterface $node, Environment $env) : ?Node
{
if ($this->hasDeferred && $node instanceof ModuleNode) {
$node->setNode('constructor_end', new Node([new DeferredExtensionNode(), $node->getNode('constructor_end')]));
$node->setNode('display_end', new Node([new DeferredNode(), $node->getNode('display_end')]));
$this->hasDeferred = false;
}
return $node;
}
public function getPriority() : int
{
return 0;
}
}

View File

@@ -0,0 +1,77 @@
<?php
/**
* This file is part of the rybakit/twig-deferred-extension package.
*
* (c) Eugene Leonovich <gen.work@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
declare(strict_types=1);
namespace Twig\DeferredExtension;
use Twig\Node\BlockNode;
use Twig\Node\Node;
use Twig\Parser;
use Twig\Token;
use Twig\TokenParser\AbstractTokenParser;
use Twig\TokenParser\BlockTokenParser;
final class DeferredTokenParser extends AbstractTokenParser
{
private $blockTokenParser;
public function setParser(Parser $parser) : void
{
parent::setParser($parser);
$this->blockTokenParser = new BlockTokenParser();
$this->blockTokenParser->setParser($parser);
}
public function parse(Token $token) : Node
{
$stream = $this->parser->getStream();
$nameToken = $stream->next();
$deferredToken = $stream->nextIf(Token::NAME_TYPE, 'deferred');
$stream->injectTokens([$nameToken]);
$node = $this->blockTokenParser->parse($token);
if ($deferredToken) {
$this->replaceBlockNode($nameToken->getValue());
}
return $node;
}
public function getTag() : string
{
return 'block';
}
private function replaceBlockNode(string $name) : void
{
$block = $this->parser->getBlock($name)->getNode('0');
$this->parser->setBlock($name, $this->createDeferredBlockNode($block));
}
private function createDeferredBlockNode(BlockNode $block) : DeferredBlockNode
{
$name = $block->getAttribute('name');
$deferredBlock = new DeferredBlockNode($name, new Node([]), $block->getTemplateLine());
foreach ($block as $nodeName => $node) {
$deferredBlock->setNode($nodeName, $node);
}
if ($sourceContext = $block->getSourceContext()) {
$deferredBlock->setSourceContext($sourceContext);
}
return $deferredBlock;
}
}