From a68425118d03090fdceb9e2769ea88496c0905d9 Mon Sep 17 00:00:00 2001 From: Fabien Potencier Date: Fri, 30 Sep 2011 17:04:06 +0200 Subject: [PATCH] optimized variable access when possible --- lib/Twig/Node/Body.php | 20 +++++++++++ lib/Twig/Node/Expression/Name.php | 43 +++++++++++------------- lib/Twig/Node/Expression/TempName.php | 22 ++++++++++++ lib/Twig/Node/Module.php | 10 ++++- lib/Twig/Node/SetTemp.php | 35 +++++++++++++++++++ lib/Twig/NodeVisitor/Optimizer.php | 45 +++++++++++++++++++++++-- lib/Twig/Parser.php | 4 +- lib/Twig/TokenParser/Macro.php | 2 +- test/Twig/Tests/ExpressionParserTest.php | 2 +- test/Twig/Tests/NodeVisitor/OptimizerTest.php | 18 ++-------- 10 files changed, 154 insertions(+), 47 deletions(-) create mode 100644 lib/Twig/Node/Body.php create mode 100644 lib/Twig/Node/Expression/TempName.php create mode 100644 lib/Twig/Node/SetTemp.php diff --git a/lib/Twig/Node/Body.php b/lib/Twig/Node/Body.php new file mode 100644 index 0000000..f72bf50 --- /dev/null +++ b/lib/Twig/Node/Body.php @@ -0,0 +1,20 @@ + + */ +class Twig_Node_Body extends Twig_Node +{ +} diff --git a/lib/Twig/Node/Expression/Name.php b/lib/Twig/Node/Expression/Name.php index 1dacc87..94d6135 100644 --- a/lib/Twig/Node/Expression/Name.php +++ b/lib/Twig/Node/Expression/Name.php @@ -11,42 +11,29 @@ */ class Twig_Node_Expression_Name extends Twig_Node_Expression { + protected $specialVars = array( + '_self' => '$this', + '_context' => '$context', + '_charset' => '$this->env->getCharset()', + ); + public function __construct($name, $lineno) { - parent::__construct(array(), array('name' => $name, 'output' => false), $lineno); + parent::__construct(array(), array('name' => $name), $lineno); } public function compile(Twig_Compiler $compiler) { - static $specialVars = array( - '_self' => '$this', - '_context' => '$context', - '_charset' => '$this->env->getCharset()', - ); - $name = $this->getAttribute('name'); if ($this->hasAttribute('is_defined_test')) { - if (isset($specialVars[$name])) { + if ($this->isSpecial()) { $compiler->repr(true); } else { $compiler->raw('array_key_exists(')->repr($name)->raw(', $context)'); } - } elseif (isset($specialVars[$name])) { - $compiler->raw($specialVars[$name]); - } elseif ($this->getAttribute('output')) { - $compiler - ->addDebugInfo($this) - ->write('if (isset($context[') - ->string($name) - ->raw("])) {\n") - ->indent() - ->write('echo $context[') - ->string($name) - ->raw("];\n") - ->outdent() - ->write("}\n") - ; + } elseif ($this->isSpecial()) { + $compiler->raw($this->specialVars[$name]); } else { $compiler ->raw('$this->getContext($context, ') @@ -55,4 +42,14 @@ class Twig_Node_Expression_Name extends Twig_Node_Expression ; } } + + public function isSpecial() + { + return isset($this->specialVars[$this->getAttribute('name')]); + } + + public function isSimple() + { + return !$this->isSpecial() && !$this->hasAttribute('is_defined_test'); + } } diff --git a/lib/Twig/Node/Expression/TempName.php b/lib/Twig/Node/Expression/TempName.php new file mode 100644 index 0000000..eea9d47 --- /dev/null +++ b/lib/Twig/Node/Expression/TempName.php @@ -0,0 +1,22 @@ + $name), $lineno); + } + + public function compile(Twig_Compiler $compiler) + { + $compiler->raw('$_')->raw($this->getAttribute('name'))->raw('_'); + } +} diff --git a/lib/Twig/Node/Module.php b/lib/Twig/Node/Module.php index bf7d4c1..7321e06 100644 --- a/lib/Twig/Node/Module.php +++ b/lib/Twig/Node/Module.php @@ -257,8 +257,14 @@ class Twig_Node_Module extends Twig_Node // only contains blocks and use statements. $traitable = null === $this->getNode('parent') && 0 === count($this->getNode('macros')); if ($traitable) { - if (!count($nodes = $this->getNode('body'))) { - $nodes = new Twig_Node(array($this->getNode('body'))); + if ($this->getNode('body') instanceof Twig_Node_Body) { + $nodes = $this->getNode('body')->getNode(0); + } else { + $nodes = $this->getNode('body'); + } + + if (!count($nodes)) { + $nodes = new Twig_Node(array($nodes)); } foreach ($nodes as $node) { diff --git a/lib/Twig/Node/SetTemp.php b/lib/Twig/Node/SetTemp.php new file mode 100644 index 0000000..3bdd1cb --- /dev/null +++ b/lib/Twig/Node/SetTemp.php @@ -0,0 +1,35 @@ + $name), $lineno); + } + + public function compile(Twig_Compiler $compiler) + { + $name = $this->getAttribute('name'); + $compiler + ->addDebugInfo($this) + ->write('if (isset($context[') + ->string($name) + ->raw('])) { $_') + ->raw($name) + ->raw('_ = $context[') + ->repr($name) + ->raw(']; } else { $_') + ->raw($name) + ->raw("_ = null; }\n") + ; + } +} diff --git a/lib/Twig/NodeVisitor/Optimizer.php b/lib/Twig/NodeVisitor/Optimizer.php index 0d0ff84..196c5d7 100644 --- a/lib/Twig/NodeVisitor/Optimizer.php +++ b/lib/Twig/NodeVisitor/Optimizer.php @@ -26,9 +26,12 @@ class Twig_NodeVisitor_Optimizer implements Twig_NodeVisitorInterface const OPTIMIZE_NONE = 0; const OPTIMIZE_FOR = 2; const OPTIMIZE_RAW_FILTER = 4; + const OPTIMIZE_VAR_ACCESS = 8; protected $loops = array(); protected $optimizers; + protected $prependedNodes = array(); + protected $inABody = false; /** * Constructor. @@ -53,6 +56,20 @@ class Twig_NodeVisitor_Optimizer implements Twig_NodeVisitorInterface $this->enterOptimizeFor($node, $env); } + if (self::OPTIMIZE_VAR_ACCESS === (self::OPTIMIZE_VAR_ACCESS & $this->optimizers) && !$env->isStrictVariables() && !$env->hasExtension('sandbox')) { + if ($this->inABody) { + if (!$node instanceof Twig_Node_Expression) { + if (get_class($node) !== 'Twig_Node') { + array_unshift($this->prependedNodes, array()); + } + } else { + $node = $this->optimizeVariables($node, $env); + } + } elseif ($node instanceof Twig_Node_Body) { + $this->inABody = true; + } + } + return $node; } @@ -61,6 +78,8 @@ class Twig_NodeVisitor_Optimizer implements Twig_NodeVisitorInterface */ public function leaveNode(Twig_NodeInterface $node, Twig_Environment $env) { + $expression = $node instanceof Twig_Node_Expression; + if (self::OPTIMIZE_FOR === (self::OPTIMIZE_FOR & $this->optimizers)) { $this->leaveOptimizeFor($node, $env); } @@ -71,6 +90,28 @@ class Twig_NodeVisitor_Optimizer implements Twig_NodeVisitorInterface $node = $this->optimizePrintNode($node, $env); + if (self::OPTIMIZE_VAR_ACCESS === (self::OPTIMIZE_VAR_ACCESS & $this->optimizers) && !$env->isStrictVariables() && !$env->hasExtension('sandbox')) { + if ($node instanceof Twig_Node_Body) { + $this->inABody = false; + } elseif ($this->inABody) { + if (!$expression && get_class($node) !== 'Twig_Node' && $prependedNodes = array_shift($this->prependedNodes)) { + $prependedNodes[] = $node; + $node = new Twig_Node($prependedNodes); + } + } + } + + return $node; + } + + protected function optimizeVariables($node, $env) + { + if ('Twig_Node_Expression_Name' === get_class($node) && $node->isSimple()) { + $this->prependedNodes[0][] = new Twig_Node_SetTemp($node->getAttribute('name'), $node->getLine()); + + return new Twig_Node_Expression_TempName($node->getAttribute('name'), $node->getLine()); + } + return $node; } @@ -80,7 +121,6 @@ class Twig_NodeVisitor_Optimizer implements Twig_NodeVisitorInterface * It replaces: * * * "echo $this->render(Parent)Block()" with "$this->display(Parent)Block()" - * * "echo $this->getContext('...')" with "if (isset('...')) { echo '...' }" * * @param Twig_NodeInterface $node A Node * @param Twig_Environment $env The current Twig environment @@ -93,8 +133,7 @@ class Twig_NodeVisitor_Optimizer implements Twig_NodeVisitorInterface if ( $node->getNode('expr') instanceof Twig_Node_Expression_BlockReference || - $node->getNode('expr') instanceof Twig_Node_Expression_Parent || - ($node->getNode('expr') instanceof Twig_Node_Expression_Name && !$env->hasExtension('sandbox') && !$env->isStrictVariables()) + $node->getNode('expr') instanceof Twig_Node_Expression_Parent ) { $node->getNode('expr')->setAttribute('output', true); diff --git a/lib/Twig/Parser.php b/lib/Twig/Parser.php index dc5b42c..0fc11cb 100644 --- a/lib/Twig/Parser.php +++ b/lib/Twig/Parser.php @@ -93,7 +93,7 @@ class Twig_Parser implements Twig_ParserInterface throw $e; } - $node = new Twig_Node_Module($body, $this->parent, new Twig_Node($this->blocks), new Twig_Node($this->macros), new Twig_Node($this->traits), $this->stream->getFilename()); + $node = new Twig_Node_Module(new Twig_Node_Body(array($body)), $this->parent, new Twig_Node($this->blocks), new Twig_Node($this->macros), new Twig_Node($this->traits), $this->stream->getFilename()); $traverser = new Twig_NodeTraverser($this->env, $this->visitors); @@ -204,7 +204,7 @@ class Twig_Parser implements Twig_ParserInterface public function setBlock($name, $value) { - $this->blocks[$name] = $value; + $this->blocks[$name] = new Twig_Node_Body(array($value)); } public function hasMacro($name) diff --git a/lib/Twig/TokenParser/Macro.php b/lib/Twig/TokenParser/Macro.php index da1ac55..64f2ea5 100644 --- a/lib/Twig/TokenParser/Macro.php +++ b/lib/Twig/TokenParser/Macro.php @@ -47,7 +47,7 @@ class Twig_TokenParser_Macro extends Twig_TokenParser $this->parser->popLocalScope(); $this->parser->getStream()->expect(Twig_Token::BLOCK_END_TYPE); - $this->parser->setMacro($name, new Twig_Node_Macro($name, $body, $arguments, $lineno, $this->getTag())); + $this->parser->setMacro($name, new Twig_Node_Macro($name, new Twig_Node_Body(array($body)), $arguments, $lineno, $this->getTag())); return null; } diff --git a/test/Twig/Tests/ExpressionParserTest.php b/test/Twig/Tests/ExpressionParserTest.php index 4e43652..d36c9fd 100644 --- a/test/Twig/Tests/ExpressionParserTest.php +++ b/test/Twig/Tests/ExpressionParserTest.php @@ -45,7 +45,7 @@ class Twig_Tests_ExpressionParserTest extends PHPUnit_Framework_TestCase $stream = $env->tokenize($template, 'index'); $parser = new Twig_Parser($env); - $this->assertEquals($expected, $parser->parse($stream)->getNode('body')->getNode('expr')); + $this->assertEquals($expected, $parser->parse($stream)->getNode('body')->getNode(0)->getNode('expr')); } /** diff --git a/test/Twig/Tests/NodeVisitor/OptimizerTest.php b/test/Twig/Tests/NodeVisitor/OptimizerTest.php index b6aa979..edf6c51 100644 --- a/test/Twig/Tests/NodeVisitor/OptimizerTest.php +++ b/test/Twig/Tests/NodeVisitor/OptimizerTest.php @@ -17,7 +17,7 @@ class Twig_Tests_NodeVisitor_OptimizerTest extends PHPUnit_Framework_TestCase $stream = $env->parse($env->tokenize('{{ block("foo") }}', 'index')); - $node = $stream->getNode('body'); + $node = $stream->getNode('body')->getNode(0); $this->assertInstanceOf('Twig_Node_Expression_BlockReference', $node); $this->assertTrue($node->getAttribute('output')); @@ -30,7 +30,7 @@ class Twig_Tests_NodeVisitor_OptimizerTest extends PHPUnit_Framework_TestCase $stream = $env->parse($env->tokenize('{% extends "foo" %}{% block content %}{{ parent() }}{% endblock %}', 'index')); - $node = $stream->getNode('blocks')->getNode('content')->getNode('body'); + $node = $stream->getNode('blocks')->getNode('content')->getNode(0)->getNode('body'); $this->assertInstanceOf('Twig_Node_Expression_Parent', $node); $this->assertTrue($node->getAttribute('output')); @@ -42,24 +42,12 @@ class Twig_Tests_NodeVisitor_OptimizerTest extends PHPUnit_Framework_TestCase $env->addExtension(new Twig_Extension_Optimizer()); $stream = $env->parse($env->tokenize('{{ block(name|lower) }}', 'index')); - $node = $stream->getNode('body'); + $node = $stream->getNode('body')->getNode(0)->getNode(1); $this->assertInstanceOf('Twig_Node_Expression_BlockReference', $node); $this->assertTrue($node->getAttribute('output')); } - public function testRenderNameOptimizer() - { - $env = new Twig_Environment(new Twig_Loader_String(), array('cache' => false, 'autoescape' => false)); - $env->addExtension(new Twig_Extension_Optimizer()); - $stream = $env->parse($env->tokenize('{{ name }}', 'index')); - - $node = $stream->getNode('body'); - - $this->assertInstanceOf('Twig_Node_Expression_Name', $node); - $this->assertTrue($node->getAttribute('output')); - } - /** * @dataProvider getTestsForForOptimizer */ -- 1.7.2.5