Passed
Push — master ( c94b56...122110 )
by Brian
12:02
created

Parser::resolveTagClass()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 19
Code Lines 10

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 11
CRAP Score 3

Importance

Changes 0
Metric Value
cc 3
eloc 10
nc 3
nop 1
dl 0
loc 19
ccs 11
cts 11
cp 1
crap 3
rs 9.9332
c 0
b 0
f 0
1
<?php
2
3
namespace Bmatovu\Ussd;
4
5
use Bmatovu\Ussd\Contracts\Tag;
6
use Bmatovu\Ussd\Support\Arr;
7
use Illuminate\Container\Container;
8
use Illuminate\Contracts\Cache\Repository as CacheContract;
9
use Illuminate\Support\Str;
10
11
class Parser
12
{
13
    protected \DOMXPath $xpath;
14
    protected CacheContract $cache;
15
    protected string $prefix;
16
    protected int $ttl;
17
18 6
    public function __construct(\DOMXPath $xpath, array $options, CacheContract $cache, ?int $ttl = null)
19
    {
20 6
        $this->xpath = $xpath;
21 6
        $this->cache = $cache;
22 6
        $this->ttl = $ttl;
23
24 6
        $this->bootstrap($options);
25
    }
26
27 5
    public function parse(?string $answer): string
28
    {
29 5
        $this->processResponse($answer);
30
31 5
        $exp = $this->cache->get("{$this->prefix}_exp");
32
33 5
        $node = $this->xpath->query($exp)->item(0);
34
35 5
        if (! $node) {
36 1
            $this->setBreakpoint();
37
        }
38
39 5
        $output = $this->renderNext();
40
41 3
        if (! $output) {
42 1
            return $this->parse($answer);
43
        }
44
45 3
        return $output;
46
    }
47
48 5
    protected function sessionExists(string $sessionId): bool
49
    {
50 5
        $preSessionId = $this->cache->get("{$this->prefix}_session_id", '');
51
52 5
        return $preSessionId === $sessionId;
53
    }
54
55 6
    protected function bootstrap(array $options)
56
    {
57 6
        $required = ['session_id', 'phone_number', 'service_code', 'expression'];
58
59 6
        if ($missing = Arr::keysDiff($required, $options)) {
60 1
            $msg = array_pop($missing);
61
62 1
            if ($missing) {
63 1
                $msg = implode(', ', $missing).', and '.$msg;
64
            }
65
66 1
            throw new \Exception('Missing parser options: '.$msg);
67
        }
68
69
        [
70
            'session_id' => $session_id,
71
            'phone_number' => $phone_number,
72
            'service_code' => $service_code,
73
            'expression' => $expression,
74
        ] = $options;
75
76 5
        $this->prefix = "{$phone_number}{$service_code}";
77
78
        // ...
79
80 5
        $preSessionId = $this->cache->get("{$this->prefix}_session_id", '');
0 ignored issues
show
Unused Code introduced by
The assignment to $preSessionId is dead and can be removed.
Loading history...
81
82 5
        if ($this->sessionExists($session_id)) {
83 2
            return;
84
        }
85
86 3
        $this->cache->put("{$this->prefix}_session_id", $session_id, $this->ttl);
87 3
        $this->cache->put("{$this->prefix}_service_code", $service_code, $this->ttl);
88 3
        $this->cache->put("{$this->prefix}_phone_number", $phone_number, $this->ttl);
89
90 3
        $this->cache->put("{$this->prefix}_pre", '', $this->ttl);
91 3
        $this->cache->put("{$this->prefix}_exp", $expression, $this->ttl);
92 3
        $this->cache->put("{$this->prefix}_breakpoints", '[]', $this->ttl);
93
    }
94
95 5
    protected function processResponse(?string $answer): void
96
    {
97 5
        $pre = $this->cache->get("{$this->prefix}_pre");
98
99 5
        if (! $pre) {
100 4
            return;
101
        }
102
103 2
        $preNode = $this->xpath->query($pre)->item(0);
104
105 2
        $tagName = $this->resolveTagName($preNode);
106 2
        $tag = $this->createTag($tagName, [$preNode, $this->cache, $this->prefix, $this->ttl]);
107 2
        $tag->process($answer);
108
    }
109
110 1
    protected function setBreakpoint(): void
111
    {
112 1
        $exp = $this->cache->get("{$this->prefix}_exp");
113
114 1
        $breakpoints = (array) json_decode((string) $this->cache->get("{$this->prefix}_breakpoints"), true);
115
116 1
        if (! $breakpoints || ! isset($breakpoints[0][$exp])) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $breakpoints 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...
117
            throw new \Exception('Missing tag');
118
        }
119
120 1
        $breakpoint = array_shift($breakpoints);
121 1
        $this->cache->put("{$this->prefix}_exp", $breakpoint[$exp], $this->ttl);
122 1
        $this->cache->put("{$this->prefix}_breakpoints", json_encode($breakpoints), $this->ttl);
123
    }
124
125 5
    protected function renderNext(): ?string
126
    {
127 5
        $exp = $this->cache->get("{$this->prefix}_exp");
128
129 5
        $node = $this->xpath->query($exp)->item(0);
130
131 5
        $tagName = $this->resolveTagName($node);
132 5
        $tag = $this->createTag($tagName, [$node, $this->cache, $this->prefix, $this->ttl]);
133 4
        $output = $tag->handle();
134
135 3
        $exp = $this->cache->get("{$this->prefix}_exp");
136 3
        $breakpoints = (array) json_decode((string) $this->cache->get("{$this->prefix}_breakpoints"), true);
137
138 3
        if ($breakpoints && isset($breakpoints[0][$exp])) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $breakpoints 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...
139
            $breakpoint = array_shift($breakpoints);
140
            $this->cache->put("{$this->prefix}_exp", $breakpoint[$exp], $this->ttl);
141
            $this->cache->put("{$this->prefix}_breakpoints", json_encode($breakpoints), $this->ttl);
142
        }
143
144 3
        return $output;
145
    }
146
147 5
    protected function resolveTagName(\DOMNode $node): string
148
    {
149 5
        $tagName = $node->tagName;
150
151 5
        if ('action' !== strtolower($tagName)) {
152 5
            return Str::studly("{$tagName}Tag");
153
        }
154
155
        $tagName = $this->node->attributes->getNamedItem($name)->nodeValue;
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $name seems to be never defined.
Loading history...
Bug Best Practice introduced by
The property node does not exist on Bmatovu\Ussd\Parser. Did you maybe forget to declare it?
Loading history...
156
157
        return Str::studly("{$tagName}Action");
158
    }
159
160 5
    protected function resolveTagClass(string $tagName): string
161
    {
162 5
        $config = Container::getInstance()->make('config');
0 ignored issues
show
Unused Code introduced by
The assignment to $config is dead and can be removed.
Loading history...
163
164 5
        $tagNs = config('ussd.tag-ns', []);
165 5
        $actionNs = config('ussd.action-ns', []);
166
167 5
        $namespaces = array_merge($tagNs, $actionNs);
168
169 5
        $fqcn = $tagName;
0 ignored issues
show
Unused Code introduced by
The assignment to $fqcn is dead and can be removed.
Loading history...
170
171 5
        foreach ($namespaces as $ns) {
172 5
            $fqcn = "{$ns}\\{$tagName}";
173 5
            if (class_exists($fqcn)) {
174 4
                return $fqcn;
175
            }
176
        }
177
178 1
        throw new \Exception("Missing class: {$tagName}");
179
    }
180
181 5
    protected function createTag(string $tagName, array $args = []): Tag
182
    {
183 5
        $fqcn = $this->resolveTagClass($tagName);
184
185 4
        return \call_user_func_array([new \ReflectionClass($fqcn), 'newInstance'], $args);
186
    }
187
}
188