Completed
Branch master (c0d8ef)
by Yaroslav
06:36
created

DiscoverRoutingFunctionNodeVisitor::doLeaveNode()   B

Complexity

Conditions 6
Paths 5

Size

Total Lines 38
Code Lines 20

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 21
CRAP Score 6

Importance

Changes 0
Metric Value
eloc 20
dl 0
loc 38
rs 8.9777
c 0
b 0
f 0
ccs 21
cts 21
cp 1
cc 6
nc 5
nop 2
crap 6
1
<?php
2
3
/*
4
 *
5
 * (c) Yaroslav Honcharuk <[email protected]>
6
 *
7
 * For the full copyright and license information, please view the LICENSE
8
 * file that was distributed with this source code.
9
 */
10
11
namespace Yarhon\RouteGuardBundle\Twig\NodeVisitor;
12
13
use Twig\Node\Node;
14
use Twig\Environment;
15
use Twig\NodeVisitor\AbstractNodeVisitor;
16
use Twig\NodeVisitor\NodeVisitorInterface;
17
use Twig\Node\Expression\FunctionExpression;
18
use Twig\Node\Expression\NameExpression;
19
use Twig\Error\SyntaxError;
20
use Yarhon\RouteGuardBundle\Twig\Node\RouteNode;
21
use Yarhon\RouteGuardBundle\Twig\Node\RouteExpression;
22
23
/**
24
 * @author Yaroslav Honcharuk <[email protected]>
25
 *
26
 * Note: we extend from AbstractNodeVisitor just for compatibility with Twig 1.x NodeVisitorInterface.
27
 * When this compatibility would no longer be needed, we could drop usage of AbstractNodeVisitor
28
 * and implement NodeVisitorInterface directly.
29
 *
30
 * TODO: find a way to set source context for thrown exceptions (see \Twig_Parser::parse)
31
 */
32
class DiscoverRoutingFunctionNodeVisitor extends AbstractNodeVisitor
33
{
34
    /**
35
     * @var DiscoveredFunctions
36
     */
37
    private $discoveredFunctions;
38
39
    /**
40
     * @var string
41
     */
42
    private $tagName;
43
44
    /**
45
     * @var string
46
     */
47
    private $tagVariableName;
48
49
    /**
50
     * @var Scope
51
     */
52
    private $scope;
53
54
    /**
55
     * @param string $tagName
56
     * @param string $tagVariableName
57
     */
58 7
    public function __construct($tagName, $tagVariableName)
59
    {
60 7
        $this->tagName = $tagName;
61 7
        $this->tagVariableName = $tagVariableName;
62 7
        $this->discoveredFunctions = new DiscoveredFunctions();
63 7
        $this->scope = new Scope();
64 7
    }
65
66
    /**
67
     * {@inheritdoc}
68
     */
69 6
    protected function doEnterNode(Node $node, Environment $env)
70
    {
71 6
        if ($this->isTargetNode($node)) {
72 5
            $this->scope = $this->scope->enter();
73 5
            $this->scope->set('insideTargetNode', true);
74
        }
75
76 6
        return $node;
77
    }
78
79
    /**
80
     * {@inheritdoc}
81
     *
82
     * @throws SyntaxError If zero / more than one routing function calls was found inside node
83
     */
84 6
    protected function doLeaveNode(Node $node, Environment $env)
85
    {
86 6
        if ($this->isTargetNode($node)) {
87
            /* @var RouteNode $node */
88
89 4
            if (!$this->scope->has('routingFunction')) {
90 1
                throw new SyntaxError(
91 1
                    sprintf('"%s" tag with discover option must contain one %s call.', $this->tagName, $this->createDiscoveredFunctionsString()),
92 1
                    $node->getTemplateLine()
93
                );
94
            }
95
96 3
            $condition = $this->createRouteExpression($this->scope->get('routingFunction'), $node->getTemplateLine());
0 ignored issues
show
Bug introduced by
It seems like $this->scope->get('routingFunction') can also be of type null; however, parameter $function of Yarhon\RouteGuardBundle\...createRouteExpression() does only seem to accept Twig\Node\Expression\FunctionExpression, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

96
            $condition = $this->createRouteExpression(/** @scrutinizer ignore-type */ $this->scope->get('routingFunction'), $node->getTemplateLine());
Loading history...
97 3
            $node->setNode('condition', $condition);
98
99 3
            $this->scope->set('insideTargetNode', false);
100 3
            $this->scope = $this->scope->leave();
101
102 3
            return $node;
103
        }
104
105 6
        if ($this->scope->get('insideTargetNode') && $this->isDiscoveredNode($node)) {
106 4
            if ($this->scope->has('routingFunction')) {
107 1
                throw new SyntaxError(
108 1
                    sprintf('"%s" tag with discover option must contain only one %s call.', $this->tagName, $this->createDiscoveredFunctionsString()),
109 1
                    $node->getTemplateLine()
110
                );
111
            }
112
113 4
            $this->scope->set('routingFunction', $node);
114
115 4
            $newNode = new NameExpression($this->tagVariableName, $node->getTemplateLine());
116 4
            $newNode->setAttribute('always_defined', true);
117
118 4
            return $newNode;
119
        }
120
121 6
        return $node;
122
    }
123
124
    /**
125
     * {@inheritdoc}
126
     */
127 6
    public function getPriority()
128
    {
129 6
        return 0;
130
    }
131
132
    /**
133
     * @param Node $node
134
     *
135
     * @return bool
136
     */
137 6
    private function isTargetNode(Node $node)
138
    {
139 6
        return $node instanceof RouteNode && !$node->hasNode('condition');
140
    }
141
142
    /**
143
     * @param Node $node
144
     *
145
     * @return bool
146
     */
147 5
    private function isDiscoveredNode(Node $node)
148
    {
149 5
        return $node instanceof FunctionExpression && $this->discoveredFunctions->has($node->getAttribute('name'));
150
    }
151
152
    /**
153
     * @param FunctionExpression $function
154
     * @param int                $line
155
     *
156
     * @return RouteExpression
157
     *
158
     * @throws SyntaxError
159
     */
160 3
    private function createRouteExpression(FunctionExpression $function, $line)
161
    {
162 3
        $functionName = $function->getAttribute('name');
163 3
        $functionArguments = [];
164 3
        foreach ($function->getNode('arguments') as $argument) {
165 3
            $functionArguments[] = $argument;
166
        }
167
168 3
        list($arguments, $generateAs) = $this->discoveredFunctions->resolveArguments($functionName, $functionArguments);
169
170 3
        $expression = new RouteExpression(new Node($arguments), $line);
171 3
        $expression->setGenerateAs(...$generateAs);
0 ignored issues
show
Bug introduced by
$generateAs is expanded, but the parameter $referenceType of Yarhon\RouteGuardBundle\...ession::setGenerateAs() does not expect variable arguments. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

171
        $expression->setGenerateAs(/** @scrutinizer ignore-type */ ...$generateAs);
Loading history...
172
173 3
        return $expression;
174
    }
175
176
    /**
177
     * @return string
178
     */
179 2
    private function createDiscoveredFunctionsString()
180
    {
181 2
        $functions = $this->discoveredFunctions->getFunctions();
182
        $functions = array_map(function ($name) {
183 2
            return '"'.$name.'()"';
184 2
        }, $functions);
185
186 2
        return implode(' / ', $functions);
187
    }
188
}
189