Completed
Push — master ( 547cab...942530 )
by Nikola
02:51
created

NodeVisitor   B

Complexity

Total Complexity 42

Size/Duplication

Total Lines 216
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 9

Test Coverage

Coverage 90.54%

Importance

Changes 0
Metric Value
wmc 42
lcom 1
cbo 9
dl 0
loc 216
ccs 67
cts 74
cp 0.9054
rs 8.295
c 0
b 0
f 0

8 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 4 1
C doLeaveNode() 0 46 11
A getPriority() 0 4 1
A shouldProcess() 0 10 4
A isBufferizingNode() 0 19 4
C hasBufferizingNode() 0 23 8
B getNodeExecutionPriority() 0 15 6
C doEnterNode() 0 25 7

How to fix   Complexity   

Complex Class

Complex classes like NodeVisitor often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use NodeVisitor, and based on these observations, apply Extract Interface, too.

1
<?php
2
/*
3
 * This file is part of the Twig Bufferized Template package, an RunOpenCode project.
4
 *
5
 * (c) 2017 RunOpenCode
6
 *
7
 * For the full copyright and license information, please view the LICENSE
8
 * file that was distributed with this source code.
9
 */
10
namespace RunOpenCode\Twig\BufferizedTemplate;
11
12
use RunOpenCode\Twig\BufferizedTemplate\Tag\Bufferize\Node as BufferizeNode;
13
use RunOpenCode\Twig\BufferizedTemplate\Tag\TemplateBuffer\BaseBufferNode;
14
use RunOpenCode\Twig\BufferizedTemplate\Tag\TemplateBuffer\BufferBreakPoint;
15
use RunOpenCode\Twig\BufferizedTemplate\Tag\TemplateBuffer\Initialize;
16
use RunOpenCode\Twig\BufferizedTemplate\Tag\TemplateBuffer\Terminate;
17
18
/**
19
 * Class NodeVisitor
20
 *
21
 * Parses AST adding buffering tags on required templates.
22
 *
23
 * @package RunOpenCode\Twig\BufferizedTemplate
24
 */
25
class NodeVisitor extends \Twig_BaseNodeVisitor
26
{
27
    /**
28
     * @var array
29
     */
30
    private $settings;
31
32
    /**
33
     * @var string Current template name.
34
     */
35
    protected $templateName;
36
37
    /**
38
     * @var bool Denotes if current template body should be bufferized.
39
     */
40
    private $shouldBufferize = false;
41
42
    /**
43
     * @var string Denotes current scope of AST (block or body).
44
     */
45
    private $currentScope;
46
47
    /**
48
     * @var \Twig_Node[] List of blocks for current template.
49
     */
50
    private $blocks;
51
52 2
    public function __construct(array $settings)
53
    {
54 2
        $this->settings = $settings;
55 2
    }
56
57
    /**
58
     * {@inheritdoc}
59
     */
60 2
    protected function doEnterNode(\Twig_Node $node, \Twig_Environment $env)
61
    {
62 2
        if ($node instanceof \Twig_Node_Module) {
63 2
            $this->templateName = $node->getTemplateName();
64
        }
65
66 2
        if ($this->shouldProcess()) {
67
68 2
            if ($this->isBufferizingNode($node)) {
69 2
                $this->shouldBufferize = true;
70
            }
71
72 2
            if ($node instanceof \Twig_Node_Module) {
73 2
                $this->blocks = $node->getNode('blocks')->getIterator()->getArrayCopy();
0 ignored issues
show
Bug introduced by
It seems like you code against a concrete implementation and not the interface Traversable as the method getArrayCopy() does only exist in the following implementations of said interface: ArrayIterator, ArrayObject, DoctrineTest\Instantiato...tAsset\ArrayObjectAsset, DoctrineTest\Instantiato...lizableArrayObjectAsset, DoctrineTest\Instantiato...ceptionArrayObjectAsset, DoctrineTest\Instantiato...sset\WakeUpNoticesAsset, Issue523, RecursiveArrayIterator, Symfony\Component\Finder...rator\InnerNameIterator, Symfony\Component\Finder...rator\InnerSizeIterator, Symfony\Component\Finder...rator\InnerTypeIterator, Symfony\Component\Finder...or\MockFileListIterator.

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...
74
            }
75
76 2
            if ($node instanceof \Twig_Node_Body) {
77 2
                $this->currentScope = null;
78 2
            } elseif ($node instanceof \Twig_Node_Block) {
79 2
                $this->currentScope = $node->getAttribute('name');
80
            }
81
        }
82
83 2
        return $node;
84
    }
85
86
    /**
87
     * {@inheritdoc}
88
     */
89 2
    protected function doLeaveNode(\Twig_Node $node, \Twig_Environment $env)
90
    {
91 2
        if ($node instanceof \Twig_Node_Module) {
92 2
            $this->templateName = null;
93
        }
94
95 2
        if ($this->shouldProcess()) {
96
97 2
            if ($node instanceof \Twig_Node_Module) {
98
99 2
                if ($this->shouldBufferize) {
100
101 2
                    $node->setNode('body', new \Twig_Node(array(
102 2
                        new Initialize($this->settings['defaultExecutionPriority']),
103 2
                        $node->getNode('body'),
104 2
                        new Terminate($this->settings['defaultExecutionPriority'])
105
                    )));
106
                }
107
108 2
                $this->shouldBufferize = false;
109 2
                $this->blocks = array();
110
            }
111
112 2
            if ($this->isBufferizingNode($node) || ($node instanceof \Twig_Node_BlockReference && $this->hasBufferizingNode($this->blocks[$node->getAttribute('name')]))) {
113
114 2
                return new \Twig_Node(array(
115 2
                    new BufferBreakPoint($this->settings['defaultExecutionPriority']),
116 2
                    $node,
117 2
                    new BufferBreakPoint($this->settings['defaultExecutionPriority'], array(), array(BaseBufferNode::BUFFERIZED_EXECUTION_PRIORITY_ATTRIBUTE_NAME => $this->getNodeExecutionPriority($node)))
118
                ));
119
120 2
            } elseif ($this->currentScope && $node instanceof \Twig_Node_Block && $this->hasBufferizingNode($node)) {
121
122 1
                $node->setNode('body', new \Twig_Node(array(
123 1
                    new Initialize($this->settings['defaultExecutionPriority']),
124 1
                    $node->getNode('body'),
125 1
                    new Terminate($this->settings['defaultExecutionPriority'])
126
                )));
127
128 1
                return $node;
129
            }
130
131
        }
132
133 2
        return $node;
134
    }
135
136
    /**
137
     * {@inheritdoc}
138
     */
139 2
    public function getPriority()
140
    {
141 2
        return $this->settings['nodeVisitorPriority'];
142
    }
143
144
145
    /**
146
     * Check if current template should be processed with node visitor based on whitelist or blacklist.
147
     *
148
     * @return bool
149
     */
150 2
    protected function shouldProcess()
151
    {
152 2
        if (count($this->settings['whitelist']) == 0 && count($this->settings['blacklist']) == 0) {
153 2
            return true;
154
        } elseif (count($this->settings['whitelist']) > 0) {
155
            return in_array($this->templateName, $this->settings['whitelist']);
156
        } else {
157
            return !in_array($this->templateName, $this->settings['blacklist']);
158
        }
159
    }
160
161
    /**
162
     * Check if provided node is node for bufferizing.
163
     *
164
     * @param \Twig_Node $node
165
     * @return bool
166
     */
167 2
    protected function isBufferizingNode(\Twig_Node $node = null)
168
    {
169 2
        if (is_null($node)) {
170
171
            return false;
172
173
        } else {
174
175 2
            foreach ($this->settings['nodes'] as $nodeClass => $priority) {
176
177 2
                if (is_a($node, $nodeClass)) {
178 2
                    return true;
179
                }
180
            }
181
182
        }
183
184 2
        return false;
185
    }
186
187
    /**
188
     * Checks if current node is asset injection node, or if such node exists in its sub-tree.
189
     *
190
     * @param \Twig_Node $node Node to check.
191
     * @return bool TRUE if this subtree has bufferizing node.
192
     */
193 2
    protected function hasBufferizingNode(\Twig_Node $node = null)
194
    {
195 2
        if (is_null($node)) {
196
            return false;
197
        }
198
199 2
        if ($this->isBufferizingNode($node)) {
200
            return true;
201
        }
202
203 2
        $has = false;
204
205 2
        foreach ($node as $k => $n) {
206
207 2
            if ($this->isBufferizingNode($n) || ($n instanceof \Twig_Node_BlockReference && $this->hasBufferizingNode($this->blocks[$n->getAttribute('name')]))) {
208 1
                return true;
209 2
            } elseif (($has |= $this->hasBufferizingNode($n))) {
210 1
                return true;
211
            }
212
        }
213
214 2
        return $has;
215
    }
216
217
    /**
218
     * Get execution priority of bufferized node.
219
     *
220
     * Get execution priority of bufferized node based on the node settings or configuration of the extension.
221
     *
222
     * @param \Twig_Node $node
223
     * @return mixed
224
     */
225 2
    protected function getNodeExecutionPriority(\Twig_Node $node)
226
    {
227 2
        if ($node instanceof BufferizeNode && !is_null($node->getPriority())) {
228 2
            return $node->getPriority();
229
        }
230
231 2
        foreach ($this->settings['nodes'] as $nodeClass => $priority) {
232
233 2
            if (is_a($node, $nodeClass) && !is_null($priority)) {
234 2
                return $priority;
235
            }
236
        }
237
238
        return $this->settings['defaultExecutionPriority'];
239
    }
240
}
241