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 implements Twig_NodeVisitorInterface
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 $optimizers;
32
    protected $prependedNodes = array();
33
    protected $inABody = false;
34

    
35
    /**
36
     * Constructor.
37
     *
38
     * @param integer $optimizers The optimizer mode
39
     */
40
    public function __construct($optimizers = -1)
41
    {
42
        if (!is_int($optimizers) || $optimizers > (self::OPTIMIZE_FOR | self::OPTIMIZE_RAW_FILTER | self::OPTIMIZE_VAR_ACCESS)) {
43
            throw new InvalidArgumentException(sprintf('Optimizer mode "%s" is not valid.', $optimizers));
44
        }
45

    
46
        $this->optimizers = $optimizers;
47
    }
48

    
49
    /**
50
     * {@inheritdoc}
51
     */
52
    public function enterNode(Twig_NodeInterface $node, Twig_Environment $env)
53
    {
54
        if (self::OPTIMIZE_FOR === (self::OPTIMIZE_FOR & $this->optimizers)) {
55
            $this->enterOptimizeFor($node, $env);
56
        }
57

    
58
        if (!version_compare(phpversion(), '5.4.0RC1', '>=') && self::OPTIMIZE_VAR_ACCESS === (self::OPTIMIZE_VAR_ACCESS & $this->optimizers) && !$env->isStrictVariables() && !$env->hasExtension('sandbox')) {
59
            if ($this->inABody) {
60
                if (!$node instanceof Twig_Node_Expression) {
61
                    if (get_class($node) !== 'Twig_Node') {
62
                        array_unshift($this->prependedNodes, array());
63
                    }
64
                } else {
65
                    $node = $this->optimizeVariables($node, $env);
66
                }
67
            } elseif ($node instanceof Twig_Node_Body) {
68
                $this->inABody = true;
69
            }
70
        }
71

    
72
        return $node;
73
    }
74

    
75
    /**
76
     * {@inheritdoc}
77
     */
78
    public function leaveNode(Twig_NodeInterface $node, Twig_Environment $env)
79
    {
80
        $expression = $node instanceof Twig_Node_Expression;
81

    
82
        if (self::OPTIMIZE_FOR === (self::OPTIMIZE_FOR & $this->optimizers)) {
83
            $this->leaveOptimizeFor($node, $env);
84
        }
85

    
86
        if (self::OPTIMIZE_RAW_FILTER === (self::OPTIMIZE_RAW_FILTER & $this->optimizers)) {
87
            $node = $this->optimizeRawFilter($node, $env);
88
        }
89

    
90
        $node = $this->optimizePrintNode($node, $env);
91

    
92
        if (self::OPTIMIZE_VAR_ACCESS === (self::OPTIMIZE_VAR_ACCESS & $this->optimizers) && !$env->isStrictVariables() && !$env->hasExtension('sandbox')) {
93
            if ($node instanceof Twig_Node_Body) {
94
                $this->inABody = false;
95
            } elseif ($this->inABody) {
96
                if (!$expression && get_class($node) !== 'Twig_Node' && $prependedNodes = array_shift($this->prependedNodes)) {
97
                    $nodes = array();
98
                    foreach (array_unique($prependedNodes) as $name) {
99
                        $nodes[] = new Twig_Node_SetTemp($name, $node->getLine());
100
                    }
101

    
102
                    $nodes[] = $node;
103
                    $node = new Twig_Node($nodes);
104
                }
105
            }
106
        }
107

    
108
        return $node;
109
    }
110

    
111
    protected function optimizeVariables(Twig_NodeInterface $node, Twig_Environment $env)
112
    {
113
        if ('Twig_Node_Expression_Name' === get_class($node) && $node->isSimple()) {
114
            $this->prependedNodes[0][] = $node->getAttribute('name');
115

    
116
            return new Twig_Node_Expression_TempName($node->getAttribute('name'), $node->getLine());
117
        }
118

    
119
        return $node;
120
    }
121

    
122
    /**
123
     * Optimizes print nodes.
124
     *
125
     * It replaces:
126
     *
127
     *   * "echo $this->render(Parent)Block()" with "$this->display(Parent)Block()"
128
     *
129
     * @param Twig_NodeInterface $node A Node
130
     * @param Twig_Environment   $env  The current Twig environment
131
     */
132
    protected function optimizePrintNode(Twig_NodeInterface $node, Twig_Environment $env)
133
    {
134
        if (!$node instanceof Twig_Node_Print) {
135
            return $node;
136
        }
137

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

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

    
147
        return $node;
148
    }
149

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

    
162
        return $node;
163
    }
164

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

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

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

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

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

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

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

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

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

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