Project

General

Profile

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
}
(2-2/4)