Completed
Push — master ( 0ed20a...7babd4 )
by Nikola
03:05
created

NodeVisitor::hasBufferizingNode()   B

Complexity

Conditions 6
Paths 4

Size

Total Lines 21
Code Lines 11

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 9
CRAP Score 6

Importance

Changes 0
Metric Value
dl 0
loc 21
ccs 9
cts 9
cp 1
rs 8.7624
c 0
b 0
f 0
cc 6
eloc 11
nc 4
nop 1
crap 6
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\BufferBreakPoint;
14
use RunOpenCode\Twig\BufferizedTemplate\Tag\TemplateBuffer\Initialize;
15
use RunOpenCode\Twig\BufferizedTemplate\Tag\TemplateBuffer\Terminate;
16
17
/**
18
 * Class NodeVisitor
19
 *
20
 * Parses AST adding buffering tags on required templates.
21
 *
22
 * @package RunOpenCode\Twig\BufferizedTemplate
23
 *
24
 * @internal
25
 */
26
final class NodeVisitor extends \Twig_BaseNodeVisitor
27
{
28
    /**
29
     * @var array
30
     */
31
    private $settings;
32
33
    /**
34
     * @var string Current template name.
35
     */
36
    protected $templateName;
37
38
    /**
39
     * @var bool Denotes if current template body should be bufferized.
40
     */
41
    private $shouldBufferize = false;
42
43
    /**
44
     * @var string Denotes current scope of AST (block or body).
45
     */
46
    private $currentScope;
47
48
    /**
49
     * @var \Twig_Node[] List of blocks for current template.
50
     */
51
    private $blocks;
52
53 6
    public function __construct(array $settings)
54
    {
55 6
        $this->settings = $settings;
56 6
    }
57
58
    /**
59
     * {@inheritdoc}
60
     */
61 5
    protected function doEnterNode(\Twig_Node $node, \Twig_Environment $env)
62
    {
63 5
        if ($node instanceof \Twig_Node_Module) {
64 5
            $this->templateName = $node->getTemplateName();
65
        }
66
67 5
        if ($this->shouldProcess()) {
68
69 3
            if ($this->isBufferizingNode($node)) {
70 3
                $this->shouldBufferize = true;
71
            }
72
73 3
            if ($node instanceof \Twig_Node_Module) {
74 3
                $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...
75
            }
76
77 3
            if ($node instanceof \Twig_Node_Body) {
78 3
                $this->currentScope = null;
79 3
            } elseif ($node instanceof \Twig_Node_Block) {
80 2
                $this->currentScope = $node->getAttribute('name');
81
            }
82
        }
83
84 5
        return $node;
85
    }
86
87
    /**
88
     * {@inheritdoc}
89
     */
90 5
    protected function doLeaveNode(\Twig_Node $node, \Twig_Environment $env)
91
    {
92 5
        if ($node instanceof \Twig_Node_Module) {
93 5
            $this->templateName = null;
94
        }
95
96 5
        $defaultExecutionPriority = $this->settings['default_execution_priority'];
97
98 5
        if ($this->shouldProcess()) {
99
100 4
            if ($node instanceof \Twig_Node_Module) {
101
102 4
                if ($this->shouldBufferize) {
103
104 3
                    $node->setNode('body', new \Twig_Node([
105 3
                        new Initialize($defaultExecutionPriority),
106 3
                        $node->getNode('body'),
107 3
                        new Terminate($defaultExecutionPriority)
108
                    ]));
109
                }
110
111 4
                $this->shouldBufferize = false;
112 4
                $this->blocks = [];
113
            }
114
115 4
            if ($this->isBufferizingNode($node) || ($node instanceof \Twig_Node_BlockReference && $this->hasBufferizingNode($this->blocks[$node->getAttribute('name')]))) {
116
117 3
                return new \Twig_Node([
118 3
                    new BufferBreakPoint($defaultExecutionPriority),
119 3
                    $node,
120 3
                    new BufferBreakPoint($defaultExecutionPriority, [], ['bufferized_execution_priority' => $this->getNodeExecutionPriority($node)])
121
                ]);
122
123 4
            } elseif ($this->currentScope && $node instanceof \Twig_Node_Block && $this->hasBufferizingNode($node)) {
124
125 1
                $node->setNode('body', new \Twig_Node([
126 1
                    new Initialize($defaultExecutionPriority),
127 1
                    $node->getNode('body'),
128 1
                    new Terminate($defaultExecutionPriority)
129
                ]));
130
131 1
                return $node;
132
            }
133
134
        }
135
136 5
        return $node;
137
    }
138
139
    /**
140
     * {@inheritdoc}
141
     */
142 5
    public function getPriority()
143
    {
144 5
        return $this->settings['node_visitor_priority'];
145
    }
146
147
    /**
148
     * Check if current template should be processed with node visitor based on whitelist or blacklist.
149
     *
150
     * @return bool
151
     */
152 5
    private function shouldProcess()
153
    {
154 5
        if (count($this->settings['whitelist']) > 0) {
155 1
            return in_array($this->templateName, $this->settings['whitelist'], true);
156
        }
157
158 4
        if (count($this->settings['blacklist']) > 0) {
159 1
            return !in_array($this->templateName, $this->settings['blacklist'], true);
160
        }
161
162 3
        return true;
163
    }
164
165
    /**
166
     * Check if provided node is node for bufferizing.
167
     *
168
     * @param \Twig_Node $node
169
     * @return bool
170
     */
171 4
    private function isBufferizingNode(\Twig_Node $node)
172
    {
173 4
        foreach ($this->settings['nodes'] as $nodeClass => $priority) {
174
175 4
            if ($node instanceof $nodeClass) {
176 3
                return true;
177
            }
178
        }
179
180 4
        return false;
181
    }
182
183
    /**
184
     * Checks if current node is asset injection node, or if such node exists in its sub-tree.
185
     *
186
     * @param \Twig_Node $node Node to check.
187
     * @return bool TRUE if this subtree has bufferizing node.
188
     */
189 2
    private function hasBufferizingNode(\Twig_Node $node)
190
    {
191 2
        if ($this->isBufferizingNode($node)) {
192 1
            return true;
193
        }
194
195 2
        $has = false;
196
197 2
        foreach ($node as $n) {
198
199
            if (
200 2
                ($has |= $this->hasBufferizingNode($n))
201
                ||
202 2
                ($n instanceof \Twig_Node_BlockReference && $this->hasBufferizingNode($this->blocks[$n->getAttribute('name')]))
203
            ) {
204 1
                return true;
205
            }
206
        }
207
208 2
        return $has;
209
    }
210
211
    /**
212
     * Get execution priority of bufferized node.
213
     *
214
     * Get execution priority of bufferized node based on the node settings or configuration of the extension.
215
     *
216
     * @param \Twig_Node $node
217
     * @return mixed
218
     */
219 3
    private function getNodeExecutionPriority(\Twig_Node $node)
220
    {
221 3
        if ($node instanceof BufferizeNode && null !== $node->getPriority()) {
222 3
            return $node->getPriority();
223
        }
224
225 2
        foreach ($this->settings['nodes'] as $nodeClass => $priority) {
226
227 2
            if (null !== $priority && $node instanceof $nodeClass) {
228 2
                return $priority;
229
            }
230
        }
231
232
        return $this->settings['default_execution_priority'];
233
    }
234
}
235