Twig-1.3.0/lib/Twig/NodeVisitor/Optimizer.php
changeset 4 9a001a04b634
equal deleted inserted replaced
3:6d109e3804ac 4:9a001a04b634
       
     1 <?php
       
     2 
       
     3 /*
       
     4  * This file is part of Twig.
       
     5  *
       
     6  * (c) 2010 Fabien Potencier
       
     7  *
       
     8  * For the full copyright and license information, please view the LICENSE
       
     9  * file that was distributed with this source code.
       
    10  */
       
    11 
       
    12 /**
       
    13  * Twig_NodeVisitor_Optimizer tries to optimizes the AST.
       
    14  *
       
    15  * This visitor is always the last registered one.
       
    16  *
       
    17  * You can configure which optimizations you want to activate via the
       
    18  * optimizer mode.
       
    19  *
       
    20  * @package twig
       
    21  * @author  Fabien Potencier <fabien@symfony.com>
       
    22  */
       
    23 class Twig_NodeVisitor_Optimizer implements Twig_NodeVisitorInterface
       
    24 {
       
    25     const OPTIMIZE_ALL         = -1;
       
    26     const OPTIMIZE_NONE        = 0;
       
    27     const OPTIMIZE_FOR         = 2;
       
    28     const OPTIMIZE_RAW_FILTER  = 4;
       
    29 
       
    30     protected $loops = array();
       
    31     protected $optimizers;
       
    32 
       
    33     /**
       
    34      * Constructor.
       
    35      *
       
    36      * @param integer $optimizers The optimizer mode
       
    37      */
       
    38     public function __construct($optimizers = -1)
       
    39     {
       
    40         if (!is_int($optimizers) || $optimizers > 2) {
       
    41             throw new InvalidArgumentException(sprintf('Optimizer mode "%s" is not valid.', $optimizers));
       
    42         }
       
    43 
       
    44         $this->optimizers = $optimizers;
       
    45     }
       
    46 
       
    47     /**
       
    48      * {@inheritdoc}
       
    49      */
       
    50     public function enterNode(Twig_NodeInterface $node, Twig_Environment $env)
       
    51     {
       
    52         if (self::OPTIMIZE_FOR === (self::OPTIMIZE_FOR & $this->optimizers)) {
       
    53             $this->enterOptimizeFor($node, $env);
       
    54         }
       
    55 
       
    56         return $node;
       
    57     }
       
    58 
       
    59     /**
       
    60      * {@inheritdoc}
       
    61      */
       
    62     public function leaveNode(Twig_NodeInterface $node, Twig_Environment $env)
       
    63     {
       
    64         if (self::OPTIMIZE_FOR === (self::OPTIMIZE_FOR & $this->optimizers)) {
       
    65             $this->leaveOptimizeFor($node, $env);
       
    66         }
       
    67 
       
    68         if (self::OPTIMIZE_RAW_FILTER === (self::OPTIMIZE_RAW_FILTER & $this->optimizers)) {
       
    69             $node = $this->optimizeRawFilter($node, $env);
       
    70         }
       
    71 
       
    72         $node = $this->optimizePrintNode($node, $env);
       
    73 
       
    74         return $node;
       
    75     }
       
    76 
       
    77     /**
       
    78      * Optimizes print nodes.
       
    79      *
       
    80      * It replaces:
       
    81      *
       
    82      *   * "echo $this->render(Parent)Block()" with "$this->display(Parent)Block()"
       
    83      *   * "echo $this->getContext('...')" with "if (isset('...')) { echo '...' }"
       
    84      *
       
    85      * @param Twig_NodeInterface $node A Node
       
    86      * @param Twig_Environment   $env  The current Twig environment
       
    87      */
       
    88     protected function optimizePrintNode($node, $env)
       
    89     {
       
    90         if (!$node instanceof Twig_Node_Print) {
       
    91             return $node;
       
    92         }
       
    93 
       
    94         if (
       
    95             $node->getNode('expr') instanceof Twig_Node_Expression_BlockReference ||
       
    96             $node->getNode('expr') instanceof Twig_Node_Expression_Parent ||
       
    97             ($node->getNode('expr') instanceof Twig_Node_Expression_Name && !$env->hasExtension('sandbox') && !$env->isStrictVariables())
       
    98         ) {
       
    99             $node->getNode('expr')->setAttribute('output', true);
       
   100 
       
   101             return $node->getNode('expr');
       
   102         }
       
   103 
       
   104         return $node;
       
   105     }
       
   106 
       
   107     /**
       
   108      * Removes "raw" filters.
       
   109      *
       
   110      * @param Twig_NodeInterface $node A Node
       
   111      * @param Twig_Environment   $env  The current Twig environment
       
   112      */
       
   113     protected function optimizeRawFilter($node, $env)
       
   114     {
       
   115         if ($node instanceof Twig_Node_Expression_Filter && 'raw' == $node->getNode('filter')->getAttribute('value')) {
       
   116             return $node->getNode('node');
       
   117         }
       
   118 
       
   119         return $node;
       
   120     }
       
   121 
       
   122     /**
       
   123      * Optimizes "for" tag by removing the "loop" variable creation whenever possible.
       
   124      *
       
   125      * @param Twig_NodeInterface $node A Node
       
   126      * @param Twig_Environment   $env  The current Twig environment
       
   127      */
       
   128     protected function enterOptimizeFor($node, $env)
       
   129     {
       
   130         if ($node instanceof Twig_Node_For) {
       
   131             // disable the loop variable by default
       
   132             $node->setAttribute('with_loop', false);
       
   133             array_unshift($this->loops, $node);
       
   134         } elseif (!$this->loops) {
       
   135             // we are outside a loop
       
   136             return;
       
   137         }
       
   138 
       
   139         // when do we need to add the loop variable back?
       
   140 
       
   141         // the loop variable is referenced for the current loop
       
   142         elseif ($node instanceof Twig_Node_Expression_Name && 'loop' === $node->getAttribute('name')) {
       
   143             $this->addLoopToCurrent();
       
   144         }
       
   145 
       
   146         // block reference
       
   147         elseif ($node instanceof Twig_Node_BlockReference || $node instanceof Twig_Node_Expression_BlockReference) {
       
   148             $this->addLoopToCurrent();
       
   149         }
       
   150 
       
   151         // include without the only attribute
       
   152         elseif ($node instanceof Twig_Node_Include && !$node->getAttribute('only')) {
       
   153             $this->addLoopToAll();
       
   154         }
       
   155 
       
   156         // the loop variable is referenced via an attribute
       
   157         elseif ($node instanceof Twig_Node_Expression_GetAttr
       
   158             && (!$node->getNode('attribute') instanceof Twig_Node_Expression_Constant
       
   159                 || 'parent' === $node->getNode('attribute')->getAttribute('value')
       
   160                )
       
   161             && (true === $this->loops[0]->getAttribute('with_loop')
       
   162                 || ($node->getNode('node') instanceof Twig_Node_Expression_Name
       
   163                     && 'loop' === $node->getNode('node')->getAttribute('name')
       
   164                    )
       
   165                )
       
   166         ) {
       
   167             $this->addLoopToAll();
       
   168         }
       
   169     }
       
   170 
       
   171     /**
       
   172      * Optimizes "for" tag by removing the "loop" variable creation whenever possible.
       
   173      *
       
   174      * @param Twig_NodeInterface $node A Node
       
   175      * @param Twig_Environment   $env  The current Twig environment
       
   176      */
       
   177     protected function leaveOptimizeFor($node, $env)
       
   178     {
       
   179         if ($node instanceof Twig_Node_For) {
       
   180             array_shift($this->loops);
       
   181         }
       
   182     }
       
   183 
       
   184     protected function addLoopToCurrent()
       
   185     {
       
   186         $this->loops[0]->setAttribute('with_loop', true);
       
   187     }
       
   188 
       
   189     protected function addLoopToAll()
       
   190     {
       
   191         foreach ($this->loops as $loop) {
       
   192             $loop->setAttribute('with_loop', true);
       
   193         }
       
   194     }
       
   195 
       
   196     /**
       
   197      * {@inheritdoc}
       
   198      */
       
   199     public function getPriority()
       
   200     {
       
   201         return 255;
       
   202     }
       
   203 }