optimized variable access when possible

This commit is contained in:
Fabien Potencier
2011-09-30 17:04:06 +02:00
parent 4a91f4ff34
commit a68425118d
10 changed files with 154 additions and 47 deletions
+20
View File
@@ -0,0 +1,20 @@
<?php
/*
* This file is part of Twig.
*
* (c) 2011 Fabien Potencier
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
/**
* Represents a body node.
*
* @package twig
* @author Fabien Potencier <fabien@symfony.com>
*/
class Twig_Node_Body extends Twig_Node
{
}
+20 -23
View File
@@ -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');
}
}
+22
View File
@@ -0,0 +1,22 @@
<?php
/*
* This file is part of Twig.
*
* (c) 2011 Fabien Potencier
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
class Twig_Node_Expression_TempName extends Twig_Node_Expression
{
public function __construct($name, $lineno)
{
parent::__construct(array(), array('name' => $name), $lineno);
}
public function compile(Twig_Compiler $compiler)
{
$compiler->raw('$_')->raw($this->getAttribute('name'))->raw('_');
}
}
+8 -2
View File
@@ -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) {
+35
View File
@@ -0,0 +1,35 @@
<?php
/*
* This file is part of Twig.
*
* (c) 2011 Fabien Potencier
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
class Twig_Node_SetTemp extends Twig_Node
{
public function __construct($name, $lineno)
{
parent::__construct(array(), array('name' => $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")
;
}
}
+42 -3
View File
@@ -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);
+2 -2
View File
@@ -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)
+1 -1
View File
@@ -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;
}
+1 -1
View File
@@ -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'));
}
/**
+3 -15
View File
@@ -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
*/