Twig_NodeVisitor_Optimizer::leaveNode()   C
last analyzed

Complexity

Conditions 12
Paths 20

Size

Total Lines 32
Code Lines 18

Duplication

Lines 0
Ratio 0 %
Metric Value
dl 0
loc 32
rs 5.1612
cc 12
eloc 18
nc 20
nop 2

How to fix   Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

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 <[email protected]>
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 $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
    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(Twig_NodeInterface $node, Twig_Environment $env)
0 ignored issues
show
Unused Code introduced by
The parameter $env is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
113
    {
114
        if ('Twig_Node_Expression_Name' === get_class($node) && $node->isSimple()) {
0 ignored issues
show
Bug introduced by
It seems like you code against a concrete implementation and not the interface Twig_NodeInterface as the method isSimple() does only exist in the following implementations of said interface: Twig_Node_Expression_AssignName, Twig_Node_Expression_AssignName, Twig_Node_Expression_Name, Twig_Node_Expression_Name.

Let’s take a look at an example:

interface User
{
    /** @return string */
    public function getPassword();
}

class MyUser implements User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different implementation of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the interface:

    interface User
    {
        /** @return string */
        public function getPassword();
    
        /** @return string */
        public function getDisplayName();
    }
    
Loading history...
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(Twig_NodeInterface $node, Twig_Environment $env)
0 ignored issues
show
Unused Code introduced by
The parameter $env is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
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(Twig_NodeInterface $node, Twig_Environment $env)
0 ignored issues
show
Unused Code introduced by
The parameter $env is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
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(Twig_NodeInterface $node, Twig_Environment $env)
0 ignored issues
show
Unused Code introduced by
The parameter $env is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
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
            array_unshift($this->loopsTargets, $node->getNode('value_target')->getAttribute('name'));
179
            array_unshift($this->loopsTargets, $node->getNode('key_target')->getAttribute('name'));
180
        } elseif (!$this->loops) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->loops of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
181
            // we are outside a loop
182
            return;
183
        }
184
185
        // when do we need to add the loop variable back?
186
187
        // the loop variable is referenced for the current loop
188
        elseif ($node instanceof Twig_Node_Expression_Name && 'loop' === $node->getAttribute('name')) {
189
            $node->setAttribute('always_defined', true);
190
            $this->addLoopToCurrent();
191
        }
192
193
        // optimize access to loop targets
194
        elseif ($node instanceof Twig_Node_Expression_Name && in_array($node->getAttribute('name'), $this->loopsTargets)) {
195
            $node->setAttribute('always_defined', true);
196
        }
197
198
        // block reference
199
        elseif ($node instanceof Twig_Node_BlockReference || $node instanceof Twig_Node_Expression_BlockReference) {
200
            $this->addLoopToCurrent();
201
        }
202
203
        // include without the only attribute
204
        elseif ($node instanceof Twig_Node_Include && !$node->getAttribute('only')) {
205
            $this->addLoopToAll();
206
        }
207
208
        // include function without the with_context=false parameter
209
        elseif ($node instanceof Twig_Node_Expression_Function
210
            && 'include' === $node->getAttribute('name')
211
            && (!$node->getNode('arguments')->hasNode('with_context')
212
                 || false !== $node->getNode('arguments')->getNode('with_context')->getAttribute('value')
213
               )
214
        ) {
215
            $this->addLoopToAll();
216
        }
217
218
        // the loop variable is referenced via an attribute
219
        elseif ($node instanceof Twig_Node_Expression_GetAttr
220
            && (!$node->getNode('attribute') instanceof Twig_Node_Expression_Constant
221
                || 'parent' === $node->getNode('attribute')->getAttribute('value')
222
               )
223
            && (true === $this->loops[0]->getAttribute('with_loop')
224
                || ($node->getNode('node') instanceof Twig_Node_Expression_Name
225
                    && 'loop' === $node->getNode('node')->getAttribute('name')
226
                   )
227
               )
228
        ) {
229
            $this->addLoopToAll();
230
        }
231
    }
232
233
    /**
234
     * Optimizes "for" tag by removing the "loop" variable creation whenever possible.
235
     *
236
     * @param Twig_NodeInterface $node A Node
237
     * @param Twig_Environment   $env  The current Twig environment
238
     */
239
    protected function leaveOptimizeFor(Twig_NodeInterface $node, Twig_Environment $env)
0 ignored issues
show
Unused Code introduced by
The parameter $env is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
240
    {
241
        if ($node instanceof Twig_Node_For) {
242
            array_shift($this->loops);
243
            array_shift($this->loopsTargets);
244
            array_shift($this->loopsTargets);
245
        }
246
    }
247
248
    protected function addLoopToCurrent()
249
    {
250
        $this->loops[0]->setAttribute('with_loop', true);
251
    }
252
253
    protected function addLoopToAll()
254
    {
255
        foreach ($this->loops as $loop) {
256
            $loop->setAttribute('with_loop', true);
257
        }
258
    }
259
260
    /**
261
     * {@inheritdoc}
262
     */
263
    public function getPriority()
264
    {
265
        return 255;
266
    }
267
}
268