Passed
Push — master ( a5fbdb...d8d104 )
by Brian
15:30
created

Parser::createTag()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 5
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

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