vendor/twig/lib/Twig/NodeVisitor/Optimizer.php
changeset 0 7f95f8617b0b
equal deleted inserted replaced
-1:000000000000 0:7f95f8617b0b
       
     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->optimizeRenderBlock($node, $env);
       
    73 
       
    74         return $node;
       
    75     }
       
    76 
       
    77     /**
       
    78      * Replaces "echo $this->renderBlock()" with "$this->displayBlock()".
       
    79      *
       
    80      * @param Twig_NodeInterface $node A Node
       
    81      * @param Twig_Environment   $env  The current Twig environment
       
    82      */
       
    83     protected function optimizeRenderBlock($node, $env)
       
    84     {
       
    85         if ($node instanceof Twig_Node_Print && $node->getNode('expr') instanceof Twig_Node_Expression_BlockReference) {
       
    86             $node->getNode('expr')->setAttribute('output', true);
       
    87 
       
    88             return $node->getNode('expr');
       
    89         }
       
    90 
       
    91         return $node;
       
    92     }
       
    93 
       
    94     /**
       
    95      * Removes "raw" filters.
       
    96      *
       
    97      * @param Twig_NodeInterface $node A Node
       
    98      * @param Twig_Environment   $env  The current Twig environment
       
    99      */
       
   100     protected function optimizeRawFilter($node, $env)
       
   101     {
       
   102         if ($node instanceof Twig_Node_Expression_Filter && 'raw' == $node->getNode('filter')->getAttribute('value')) {
       
   103             return $node->getNode('node');
       
   104         }
       
   105 
       
   106         return $node;
       
   107     }
       
   108 
       
   109     /**
       
   110      * Optimizes "for" tag by removing the "loop" variable creation whenever possible.
       
   111      *
       
   112      * @param Twig_NodeInterface $node A Node
       
   113      * @param Twig_Environment   $env  The current Twig environment
       
   114      */
       
   115     protected function enterOptimizeFor($node, $env)
       
   116     {
       
   117         if ($node instanceof Twig_Node_For) {
       
   118             // disable the loop variable by default
       
   119             $node->setAttribute('with_loop', false);
       
   120             array_unshift($this->loops, $node);
       
   121         } elseif (!$this->loops) {
       
   122             // we are outside a loop
       
   123             return;
       
   124         }
       
   125 
       
   126         // when do we need to add the loop variable back?
       
   127 
       
   128         // the loop variable is referenced for the current loop
       
   129         elseif ($node instanceof Twig_Node_Expression_Name && 'loop' === $node->getAttribute('name')) {
       
   130             $this->addLoopToCurrent();
       
   131         }
       
   132 
       
   133         // block reference
       
   134         elseif ($node instanceof Twig_Node_BlockReference || $node instanceof Twig_Node_Expression_BlockReference) {
       
   135             $this->addLoopToCurrent();
       
   136         }
       
   137 
       
   138         // include without the only attribute
       
   139         elseif ($node instanceof Twig_Node_Include && !$node->getAttribute('only')) {
       
   140             $this->addLoopToAll();
       
   141         }
       
   142 
       
   143         // the loop variable is referenced via an attribute
       
   144         elseif ($node instanceof Twig_Node_Expression_GetAttr
       
   145             && (!$node->getNode('attribute') instanceof Twig_Node_Expression_Constant
       
   146                 || 'parent' === $node->getNode('attribute')->getAttribute('value')
       
   147                )
       
   148             && (true === $this->loops[0]->getAttribute('with_loop')
       
   149                 || ($node->getNode('node') instanceof Twig_Node_Expression_Name
       
   150                     && 'loop' === $node->getNode('node')->getAttribute('name')
       
   151                    )
       
   152                )
       
   153         ) {
       
   154             $this->addLoopToAll();
       
   155         }
       
   156     }
       
   157 
       
   158     /**
       
   159      * Optimizes "for" tag by removing the "loop" variable creation whenever possible.
       
   160      *
       
   161      * @param Twig_NodeInterface $node A Node
       
   162      * @param Twig_Environment   $env  The current Twig environment
       
   163      */
       
   164     protected function leaveOptimizeFor($node, $env)
       
   165     {
       
   166         if ($node instanceof Twig_Node_For) {
       
   167             array_shift($this->loops);
       
   168         }
       
   169     }
       
   170 
       
   171     protected function addLoopToCurrent()
       
   172     {
       
   173         $this->loops[0]->setAttribute('with_loop', true);
       
   174     }
       
   175 
       
   176     protected function addLoopToAll()
       
   177     {
       
   178         foreach ($this->loops as $loop) {
       
   179             $loop->setAttribute('with_loop', true);
       
   180         }
       
   181     }
       
   182 
       
   183     /**
       
   184      * {@inheritdoc}
       
   185      */
       
   186     public function getPriority()
       
   187     {
       
   188         return 255;
       
   189     }
       
   190 }