From bd5629b3f24d47463691afa22a6da5cad0b1a108 Mon Sep 17 00:00:00 2001 From: Fabien Potencier Date: Sun, 28 Nov 2010 15:30:05 +0100 Subject: [PATCH] added an Optimizer extension --- CHANGELOG | 1 + lib/Twig/Extension/Optimizer.php | 35 ++++++++ lib/Twig/NodeVisitor/Optimizer.php | 114 +++++++++++++++++++++++++ test/Twig/Tests/NodeVisitor/OptimizerTest.php | 59 +++++++++++++ 4 files changed, 209 insertions(+), 0 deletions(-) create mode 100644 lib/Twig/Extension/Optimizer.php create mode 100644 lib/Twig/NodeVisitor/Optimizer.php create mode 100644 test/Twig/Tests/NodeVisitor/OptimizerTest.php diff --git a/CHANGELOG b/CHANGELOG index f19d607..492e832 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,5 +1,6 @@ * 0.9.10 + * added an Optimizer extension * added priority to node visitors * 0.9.9 (2010-11-28) diff --git a/lib/Twig/Extension/Optimizer.php b/lib/Twig/Extension/Optimizer.php new file mode 100644 index 0000000..013fcb6 --- /dev/null +++ b/lib/Twig/Extension/Optimizer.php @@ -0,0 +1,35 @@ +optimizers = $optimizers; + } + + /** + * {@inheritdoc} + */ + public function getNodeVisitors() + { + return array(new Twig_NodeVisitor_Optimizer($this->optimizers)); + } + + /** + * {@inheritdoc} + */ + public function getName() + { + return 'optimizer'; + } +} diff --git a/lib/Twig/NodeVisitor/Optimizer.php b/lib/Twig/NodeVisitor/Optimizer.php new file mode 100644 index 0000000..e94d3c9 --- /dev/null +++ b/lib/Twig/NodeVisitor/Optimizer.php @@ -0,0 +1,114 @@ + + */ +class Twig_NodeVisitor_Optimizer implements Twig_NodeVisitorInterface +{ + const OPTIMIZE_ALL = -1; + const OPTIMIZE_NONE = 0; + const OPTIMIZE_FOR = 2; + + protected $loops = array(); + protected $optimizers; + + /** + * Constructor. + * + * @param integer $optimizers The optimizer mode + */ + public function __construct($optimizers = -1) + { + if (null === $optimizers) { + $mode = self::OPTIMIZE_ALL; + } elseif (!is_int($optimizers) || $optimizers > 2) { + throw new \InvalidArgumentException(sprintf('Optimizer mode "%s" is not valid.', $optimizers)); + } + + $this->optimizers = $optimizers; + } + + /** + * {@inheritdoc} + */ + public function enterNode(Twig_NodeInterface $node, Twig_Environment $env) + { + if (self::OPTIMIZE_FOR === (self::OPTIMIZE_FOR & $this->optimizers)) { + $this->enterOptimizeFor($node, $env); + } + + return $node; + } + + /** + * {@inheritdoc} + */ + public function leaveNode(Twig_NodeInterface $node, Twig_Environment $env) + { + if (self::OPTIMIZE_FOR === (self::OPTIMIZE_FOR & $this->optimizers)) { + $this->leaveOptimizeFor($node, $env); + } + + return $node; + } + + /** + * Optimizes "for" tag. + * + * This method removes the creation of the "loop" variable when: + * + * * "loop" is not used in the "for" tag + * * and there is no include tag without the "only" attribute + * * and there is inner-for tag (in which case we would need to check parent.loop usage) + * + * This method should be able to optimize for with inner-for tags. + */ + protected function enterOptimizeFor($node, $env) + { + if ($node instanceof Twig_Node_For) { + $node->setAttribute('with_loop', false); + + if ($this->loops) { + $this->loops[0]->setAttribute('with_loop', true); + } + + array_unshift($this->loops, $node); + } elseif ($this->loops && $node instanceof Twig_Node_Expression_Name && 'loop' === $node->getAttribute('name')) { + $this->loops[0]->setAttribute('with_loop', true); + } elseif ($this->loops && $node instanceof Twig_Node_Include && !$node->getAttribute('only')) { + $this->loops[0]->setAttribute('with_loop', true); + } + } + + protected function leaveOptimizeFor($node, $env) + { + if ($node instanceof Twig_Node_For) { + array_shift($this->loops); + } + } + + /** + * {@inheritdoc} + */ + public function getPriority() + { + return 255; + } +} diff --git a/test/Twig/Tests/NodeVisitor/OptimizerTest.php b/test/Twig/Tests/NodeVisitor/OptimizerTest.php new file mode 100644 index 0000000..b3a9a6f --- /dev/null +++ b/test/Twig/Tests/NodeVisitor/OptimizerTest.php @@ -0,0 +1,59 @@ + false)); + $env->addExtension(new Twig_Extension_Optimizer()); + + $stream = $env->parse($env->tokenize($template, 'index')); + + foreach ($expected as $target => $withLoop) { + $this->assertTrue($this->checkForConfiguration($stream, $target, $withLoop)); + } + } + + public function getTestsForForOptimizer() + { + return array( + array('{% for i in foo %}{% endfor %}', array('i' => false)), + array('{% for i in foo %}{{ loop.index }}{% endfor %}', array('i' => true)), + array('{% for i in foo %}{% for j in foo %}{% endfor %}{% endfor %}', array('i' => true, 'j' => false)), + array('{% for i in foo %}{% include "foo" %}{% endfor %}', array('i' => true)), + array('{% for i in foo %}{% include "foo" only %}{% endfor %}', array('i' => false)), + array('{% for i in foo %}{% for j in foo %}{{ loop.index }}{% endfor %}{% endfor %}', array('i' => true, 'j' => true)), + ); + } + + public function checkForConfiguration(Twig_NodeInterface $node = null, $target, $withLoop) + { + if (null === $node) { + return; + } + + foreach ($node as $n) { + if ($n instanceof Twig_Node_For) { + if ($target === $n->getNode('value_target')->getAttribute('name')) { + return $withLoop == $n->getAttribute('with_loop'); + } + } + + $ret = $this->checkForConfiguration($n, $target, $withLoop); + if (null !== $ret) { + return $ret; + } + } + } +} -- 1.7.2.5