Passed
Push — master ( d8d104...a380f8 )
by Brian
02:35
created

Parser::resolveTagClass()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 16
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 9
CRAP Score 3

Importance

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