Completed
Push — master ( ebbaf0...77ab2d )
by Nikola
04:22
created

NodeVisitor::isBufferizingNode()   A

Complexity

Conditions 4
Paths 4

Size

Total Lines 15
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 6
CRAP Score 4.0466

Importance

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