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
 * @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
    const OPTIMIZE_VAR_ACCESS  = 8;
30

    
31
    protected $loops = array();
32
    protected $optimizers;
33
    protected $prependedNodes = array();
34
    protected $inABody = false;
35

    
36
    /**
37
     * Constructor.
38
     *
39
     * @param integer $optimizers The optimizer mode
40
     */
41
    public function __construct($optimizers = -1)
42
    {
43
        if (!is_int($optimizers) || $optimizers > 2) {
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
    public function enterNode(Twig_NodeInterface $node, Twig_Environment $env)
54
    {
55
        if (self::OPTIMIZE_FOR === (self::OPTIMIZE_FOR & $this->optimizers)) {
56
            $this->enterOptimizeFor($node, $env);
57
        }
58

    
59
        if (!version_compare(phpversion(), '5.4.0RC1', '>=')  && 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
    public function leaveNode(Twig_NodeInterface $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($node, $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
    protected function optimizePrintNode($node, $env)
134
    {
135
        if (!$node instanceof Twig_Node_Print) {
136
            return $node;
137
        }
138

    
139
        if (
140
            $node->getNode('expr') instanceof Twig_Node_Expression_BlockReference ||
141
            $node->getNode('expr') instanceof Twig_Node_Expression_Parent
142
        ) {
143
            $node->getNode('expr')->setAttribute('output', true);
144

    
145
            return $node->getNode('expr');
146
        }
147

    
148
        return $node;
149
    }
150

    
151
    /**
152
     * Removes "raw" filters.
153
     *
154
     * @param Twig_NodeInterface $node A Node
155
     * @param Twig_Environment   $env  The current Twig environment
156
     */
157
    protected function optimizeRawFilter($node, $env)
158
    {
159
        if ($node instanceof Twig_Node_Expression_Filter && 'raw' == $node->getNode('filter')->getAttribute('value')) {
160
            return $node->getNode('node');
161
        }
162

    
163
        return $node;
164
    }
165

    
166
    /**
167
     * Optimizes "for" tag by removing the "loop" variable creation whenever possible.
168
     *
169
     * @param Twig_NodeInterface $node A Node
170
     * @param Twig_Environment   $env  The current Twig environment
171
     */
172
    protected function enterOptimizeFor($node, $env)
173
    {
174
        if ($node instanceof Twig_Node_For) {
175
            // disable the loop variable by default
176
            $node->setAttribute('with_loop', false);
177
            array_unshift($this->loops, $node);
178
        } elseif (!$this->loops) {
179
            // we are outside a loop
180
            return;
181
        }
182

    
183
        // when do we need to add the loop variable back?
184

    
185
        // the loop variable is referenced for the current loop
186
        elseif ($node instanceof Twig_Node_Expression_Name && 'loop' === $node->getAttribute('name')) {
187
            $this->addLoopToCurrent();
188
        }
189

    
190
        // block reference
191
        elseif ($node instanceof Twig_Node_BlockReference || $node instanceof Twig_Node_Expression_BlockReference) {
192
            $this->addLoopToCurrent();
193
        }
194

    
195
        // include without the only attribute
196
        elseif ($node instanceof Twig_Node_Include && !$node->getAttribute('only')) {
197
            $this->addLoopToAll();
198
        }
199

    
200
        // the loop variable is referenced via an attribute
201
        elseif ($node instanceof Twig_Node_Expression_GetAttr
202
            && (!$node->getNode('attribute') instanceof Twig_Node_Expression_Constant
203
                || 'parent' === $node->getNode('attribute')->getAttribute('value')
204
               )
205
            && (true === $this->loops[0]->getAttribute('with_loop')
206
                || ($node->getNode('node') instanceof Twig_Node_Expression_Name
207
                    && 'loop' === $node->getNode('node')->getAttribute('name')
208
                   )
209
               )
210
        ) {
211
            $this->addLoopToAll();
212
        }
213
    }
214

    
215
    /**
216
     * Optimizes "for" tag by removing the "loop" variable creation whenever possible.
217
     *
218
     * @param Twig_NodeInterface $node A Node
219
     * @param Twig_Environment   $env  The current Twig environment
220
     */
221
    protected function leaveOptimizeFor($node, $env)
222
    {
223
        if ($node instanceof Twig_Node_For) {
224
            array_shift($this->loops);
225
        }
226
    }
227

    
228
    protected function addLoopToCurrent()
229
    {
230
        $this->loops[0]->setAttribute('with_loop', true);
231
    }
232

    
233
    protected function addLoopToAll()
234
    {
235
        foreach ($this->loops as $loop) {
236
            $loop->setAttribute('with_loop', true);
237
        }
238
    }
239

    
240
    /**
241
     * {@inheritdoc}
242
     */
243
    public function getPriority()
244
    {
245
        return 255;
246
    }
247
}
(2-2/5)