--- /dev/null
+<?php
+
+/*
+ * This file is part of Twig.
+ *
+ * (c) 2010 Fabien Potencier
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+class Twig_Extension_Optimizer extends Twig_Extension
+{
+ protected $optimizers;
+
+ public function __construct($optimizers = -1)
+ {
+ $this->optimizers = $optimizers;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getNodeVisitors()
+ {
+ return array(new Twig_NodeVisitor_Optimizer($this->optimizers));
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getName()
+ {
+ return 'optimizer';
+ }
+}
--- /dev/null
+<?php
+
+/*
+ * This file is part of Twig.
+ *
+ * (c) 2010 Fabien Potencier
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+/**
+ * Twig_NodeVisitor_Optimizer tries to optimizes the AST.
+ *
+ * This visitor is always the last registered one.
+ *
+ * You can configure which optimizations you want to activate via the
+ * optimizer mode.
+ *
+ * @package twig
+ * @author Fabien Potencier <fabien.potencier@symfony-project.com>
+ */
+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;
+ }
+}
--- /dev/null
+<?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.
+ */
+class Twig_Tests_NodeVisitor_OptimizerTest extends PHPUnit_Framework_TestCase
+{
+ /**
+ * @dataProvider getTestsForForOptimizer
+ */
+ public function testForOptimizer($template, $expected)
+ {
+ $env = new Twig_Environment(new Twig_Loader_String(), array('cache' => 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;
+ }
+ }
+ }
+}