shop.balmet.com

Unnamed repository; edit this file 'description' to name the repository.
Log | Files | Refs | README

Optimizer.php (9329B)


      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  * @author Fabien Potencier <fabien@symfony.com>
     21  */
     22 class Twig_NodeVisitor_Optimizer extends Twig_BaseNodeVisitor
     23 {
     24     const OPTIMIZE_ALL = -1;
     25     const OPTIMIZE_NONE = 0;
     26     const OPTIMIZE_FOR = 2;
     27     const OPTIMIZE_RAW_FILTER = 4;
     28     const OPTIMIZE_VAR_ACCESS = 8;
     29 
     30     protected $loops = array();
     31     protected $loopsTargets = array();
     32     protected $optimizers;
     33     protected $prependedNodes = array();
     34     protected $inABody = false;
     35 
     36     /**
     37      * Constructor.
     38      *
     39      * @param int $optimizers The optimizer mode
     40      */
     41     public function __construct($optimizers = -1)
     42     {
     43         if (!is_int($optimizers) || $optimizers > (self::OPTIMIZE_FOR | self::OPTIMIZE_RAW_FILTER | self::OPTIMIZE_VAR_ACCESS)) {
     44             throw new InvalidArgumentException(sprintf('Optimizer mode "%s" is not valid.', $optimizers));
     45         }
     46 
     47         $this->optimizers = $optimizers;
     48     }
     49 
     50     /**
     51      * {@inheritdoc}
     52      */
     53     protected function doEnterNode(Twig_Node $node, Twig_Environment $env)
     54     {
     55         if (self::OPTIMIZE_FOR === (self::OPTIMIZE_FOR & $this->optimizers)) {
     56             $this->enterOptimizeFor($node, $env);
     57         }
     58 
     59         if (PHP_VERSION_ID < 50400 && self::OPTIMIZE_VAR_ACCESS === (self::OPTIMIZE_VAR_ACCESS & $this->optimizers) && !$env->isStrictVariables() && !$env->hasExtension('sandbox')) {
     60             if ($this->inABody) {
     61                 if (!$node instanceof Twig_Node_Expression) {
     62                     if (get_class($node) !== 'Twig_Node') {
     63                         array_unshift($this->prependedNodes, array());
     64                     }
     65                 } else {
     66                     $node = $this->optimizeVariables($node, $env);
     67                 }
     68             } elseif ($node instanceof Twig_Node_Body) {
     69                 $this->inABody = true;
     70             }
     71         }
     72 
     73         return $node;
     74     }
     75 
     76     /**
     77      * {@inheritdoc}
     78      */
     79     protected function doLeaveNode(Twig_Node $node, Twig_Environment $env)
     80     {
     81         $expression = $node instanceof Twig_Node_Expression;
     82 
     83         if (self::OPTIMIZE_FOR === (self::OPTIMIZE_FOR & $this->optimizers)) {
     84             $this->leaveOptimizeFor($node, $env);
     85         }
     86 
     87         if (self::OPTIMIZE_RAW_FILTER === (self::OPTIMIZE_RAW_FILTER & $this->optimizers)) {
     88             $node = $this->optimizeRawFilter($node, $env);
     89         }
     90 
     91         $node = $this->optimizePrintNode($node, $env);
     92 
     93         if (self::OPTIMIZE_VAR_ACCESS === (self::OPTIMIZE_VAR_ACCESS & $this->optimizers) && !$env->isStrictVariables() && !$env->hasExtension('sandbox')) {
     94             if ($node instanceof Twig_Node_Body) {
     95                 $this->inABody = false;
     96             } elseif ($this->inABody) {
     97                 if (!$expression && get_class($node) !== 'Twig_Node' && $prependedNodes = array_shift($this->prependedNodes)) {
     98                     $nodes = array();
     99                     foreach (array_unique($prependedNodes) as $name) {
    100                         $nodes[] = new Twig_Node_SetTemp($name, $node->getLine());
    101                     }
    102 
    103                     $nodes[] = $node;
    104                     $node = new Twig_Node($nodes);
    105                 }
    106             }
    107         }
    108 
    109         return $node;
    110     }
    111 
    112     protected function optimizeVariables(Twig_NodeInterface $node, Twig_Environment $env)
    113     {
    114         if ('Twig_Node_Expression_Name' === get_class($node) && $node->isSimple()) {
    115             $this->prependedNodes[0][] = $node->getAttribute('name');
    116 
    117             return new Twig_Node_Expression_TempName($node->getAttribute('name'), $node->getLine());
    118         }
    119 
    120         return $node;
    121     }
    122 
    123     /**
    124      * Optimizes print nodes.
    125      *
    126      * It replaces:
    127      *
    128      *   * "echo $this->render(Parent)Block()" with "$this->display(Parent)Block()"
    129      *
    130      * @param Twig_NodeInterface $node A Node
    131      * @param Twig_Environment   $env  The current Twig environment
    132      *
    133      * @return Twig_NodeInterface
    134      */
    135     protected function optimizePrintNode(Twig_NodeInterface $node, Twig_Environment $env)
    136     {
    137         if (!$node instanceof Twig_Node_Print) {
    138             return $node;
    139         }
    140 
    141         if (
    142             $node->getNode('expr') instanceof Twig_Node_Expression_BlockReference ||
    143             $node->getNode('expr') instanceof Twig_Node_Expression_Parent
    144         ) {
    145             $node->getNode('expr')->setAttribute('output', true);
    146 
    147             return $node->getNode('expr');
    148         }
    149 
    150         return $node;
    151     }
    152 
    153     /**
    154      * Removes "raw" filters.
    155      *
    156      * @param Twig_NodeInterface $node A Node
    157      * @param Twig_Environment   $env  The current Twig environment
    158      *
    159      * @return Twig_NodeInterface
    160      */
    161     protected function optimizeRawFilter(Twig_NodeInterface $node, Twig_Environment $env)
    162     {
    163         if ($node instanceof Twig_Node_Expression_Filter && 'raw' == $node->getNode('filter')->getAttribute('value')) {
    164             return $node->getNode('node');
    165         }
    166 
    167         return $node;
    168     }
    169 
    170     /**
    171      * Optimizes "for" tag by removing the "loop" variable creation whenever possible.
    172      *
    173      * @param Twig_NodeInterface $node A Node
    174      * @param Twig_Environment   $env  The current Twig environment
    175      */
    176     protected function enterOptimizeFor(Twig_NodeInterface $node, Twig_Environment $env)
    177     {
    178         if ($node instanceof Twig_Node_For) {
    179             // disable the loop variable by default
    180             $node->setAttribute('with_loop', false);
    181             array_unshift($this->loops, $node);
    182             array_unshift($this->loopsTargets, $node->getNode('value_target')->getAttribute('name'));
    183             array_unshift($this->loopsTargets, $node->getNode('key_target')->getAttribute('name'));
    184         } elseif (!$this->loops) {
    185             // we are outside a loop
    186             return;
    187         }
    188 
    189         // when do we need to add the loop variable back?
    190 
    191         // the loop variable is referenced for the current loop
    192         elseif ($node instanceof Twig_Node_Expression_Name && 'loop' === $node->getAttribute('name')) {
    193             $node->setAttribute('always_defined', true);
    194             $this->addLoopToCurrent();
    195         }
    196 
    197         // optimize access to loop targets
    198         elseif ($node instanceof Twig_Node_Expression_Name && in_array($node->getAttribute('name'), $this->loopsTargets)) {
    199             $node->setAttribute('always_defined', true);
    200         }
    201 
    202         // block reference
    203         elseif ($node instanceof Twig_Node_BlockReference || $node instanceof Twig_Node_Expression_BlockReference) {
    204             $this->addLoopToCurrent();
    205         }
    206 
    207         // include without the only attribute
    208         elseif ($node instanceof Twig_Node_Include && !$node->getAttribute('only')) {
    209             $this->addLoopToAll();
    210         }
    211 
    212         // include function without the with_context=false parameter
    213         elseif ($node instanceof Twig_Node_Expression_Function
    214             && 'include' === $node->getAttribute('name')
    215             && (!$node->getNode('arguments')->hasNode('with_context')
    216                  || false !== $node->getNode('arguments')->getNode('with_context')->getAttribute('value')
    217                )
    218         ) {
    219             $this->addLoopToAll();
    220         }
    221 
    222         // the loop variable is referenced via an attribute
    223         elseif ($node instanceof Twig_Node_Expression_GetAttr
    224             && (!$node->getNode('attribute') instanceof Twig_Node_Expression_Constant
    225                 || 'parent' === $node->getNode('attribute')->getAttribute('value')
    226                )
    227             && (true === $this->loops[0]->getAttribute('with_loop')
    228                 || ($node->getNode('node') instanceof Twig_Node_Expression_Name
    229                     && 'loop' === $node->getNode('node')->getAttribute('name')
    230                    )
    231                )
    232         ) {
    233             $this->addLoopToAll();
    234         }
    235     }
    236 
    237     /**
    238      * Optimizes "for" tag by removing the "loop" variable creation whenever possible.
    239      *
    240      * @param Twig_NodeInterface $node A Node
    241      * @param Twig_Environment   $env  The current Twig environment
    242      */
    243     protected function leaveOptimizeFor(Twig_NodeInterface $node, Twig_Environment $env)
    244     {
    245         if ($node instanceof Twig_Node_For) {
    246             array_shift($this->loops);
    247             array_shift($this->loopsTargets);
    248             array_shift($this->loopsTargets);
    249         }
    250     }
    251 
    252     protected function addLoopToCurrent()
    253     {
    254         $this->loops[0]->setAttribute('with_loop', true);
    255     }
    256 
    257     protected function addLoopToAll()
    258     {
    259         foreach ($this->loops as $loop) {
    260             $loop->setAttribute('with_loop', true);
    261         }
    262     }
    263 
    264     /**
    265      * {@inheritdoc}
    266      */
    267     public function getPriority()
    268     {
    269         return 255;
    270     }
    271 }