Passed
Push — master ( 2197c3...f519e1 )
by Brian
02:30
created

Parser::determineAnswer()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 20
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 6

Importance

Changes 1
Bugs 1 Features 0
Metric Value
eloc 6
c 1
b 1
f 0
dl 0
loc 20
ccs 0
cts 7
cp 0
rs 10
cc 2
nc 2
nop 1
crap 6
1
<?php
2
3
namespace Bmatovu\Ussd;
4
5
use Bmatovu\Ussd\Contracts\AnswerableTag;
6
use Bmatovu\Ussd\Contracts\RenderableTag;
7
use Bmatovu\Ussd\Support\Arr;
8
use Illuminate\Container\Container;
9
use Illuminate\Contracts\Cache\Repository as CacheContract;
10
use Illuminate\Support\Facades\Log;
11
use Illuminate\Support\Str;
12
13
class Parser
14
{
15
    protected \DOMXPath $xpath;
16
    protected CacheContract $cache;
17
    protected string $prefix;
18
    protected int $ttl;
19
    // protected array $answers = [''];
20
21 6
    public function __construct(\DOMXPath $xpath, array $options, CacheContract $cache, ?int $ttl = null)
22
    {
23 6
        $this->xpath = $xpath;
24 6
        $this->cache = $cache;
25 6
        $this->ttl = $ttl;
26
27 6
        $this->bootstrap($options);
28
    }
29
30 5
    public function parse(?string $userInput): string
31
    {
32
        // $preAnswer = $this->cache->get("{$this->prefix}_answer");
33
34
        // $answer = $this->clean($userInput);
35
36
        // if ($preAnswer) {
37
        //     $answer = $this->clean(str_replace($preAnswer, '', $userInput));
38
        // }
39
40
        // $this->answers = explode('*', $answer);
41
42
        // $this->cache->put("{$this->prefix}_answer", $userInput, $this->ttl);
43
44
        // .......................
45
46 5
        $this->processResponse($userInput);
47
48 5
        $exp = $this->cache->get("{$this->prefix}_exp");
49
50 5
        $node = $this->xpath->query($exp)->item(0);
51
52 5
        if (! $node) {
53 1
            $this->setBreakpoint();
54
        }
55
56 5
        $output = $this->renderNext();
57
58 3
        if (! $output) {
59 1
            return $this->parse($userInput);
60
        }
61
62
        // $answer = array_shift($this->answers);
63
        // if($answer) {
64
        //     return $this->parse($answer);
65
        // }
66
67 3
        return $output;
68
    }
69
70 3
    protected function clean(string $code = ''): string
71
    {
72 3
        if (! $code) {
73
            return $code;
74
        }
75
76 3
        return rtrim(ltrim($code, '*'), '#');
77
    }
78
79 5
    protected function sessionExists(string $sessionId): bool
80
    {
81 5
        $preSessionId = $this->cache->get("{$this->prefix}_session_id", '');
82
83 5
        return $preSessionId === $sessionId;
84
    }
85
86 6
    protected function bootstrap(array $options)
87
    {
88 6
        $required = ['session_id', 'phone_number', 'service_code', 'expression'];
89
90 6
        if ($missing = Arr::keysDiff($required, $options)) {
91 1
            $msg = array_pop($missing);
92
93 1
            if ($missing) {
94 1
                $msg = implode(', ', $missing).', and '.$msg;
95
            }
96
97 1
            throw new \Exception('Missing parser options: '.$msg);
98
        }
99
100
        [
101
            'session_id' => $session_id,
102
            'phone_number' => $phone_number,
103
            'service_code' => $service_code,
104
            'expression' => $expression,
105
        ] = $options;
106
107 5
        $this->prefix = "{$phone_number}{$service_code}";
108
109
        // ...
110
111 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...
112
113 5
        if ($this->sessionExists($session_id)) {
114 2
            return;
115
        }
116
117 3
        $this->cache->put("{$this->prefix}_session_id", $session_id, $this->ttl);
118 3
        $this->cache->put("{$this->prefix}_service_code", $this->clean($service_code), $this->ttl);
119 3
        $this->cache->put("{$this->prefix}_answer", $this->clean($service_code), $this->ttl);
120 3
        $this->cache->put("{$this->prefix}_phone_number", $phone_number, $this->ttl);
121
122 3
        $this->cache->put("{$this->prefix}_pre", '', $this->ttl);
123 3
        $this->cache->put("{$this->prefix}_exp", $expression, $this->ttl);
124 3
        $this->cache->put("{$this->prefix}_breakpoints", '[]', $this->ttl);
125
    }
126
127 5
    protected function processResponse(?string $userInput): void
128
    {
129 5
        $pre = $this->cache->get("{$this->prefix}_pre");
130
131 5
        if (! $pre) {
132 4
            return;
133
        }
134
135 2
        $preNode = $this->xpath->query($pre)->item(0);
136
137 2
        $tagName = $this->resolveTagName($preNode);
138 2
        $tag = $this->createTag($tagName, [$preNode, $this->cache, $this->prefix, $this->ttl]);
139
140 2
        if (! $tag instanceof AnswerableTag) {
141 2
            return;
142
        }
143
144
        $answer = $this->determineAnswer($userInput);
145
146
        // $answer = array_shift($this->answers);
147
        // $this->cache->put("{$this->prefix}_answer", "{$userInput}*{$answer}", $this->ttl);
148
149
        $tag->process($answer);
150
    }
151
152
    protected function determineAnswer(?string $userInput): ?string
153
    {
154
        // $serviceCode = $this->cache->get("{$this->prefix}_service_code");
155
        $preAnswer = $this->cache->get("{$this->prefix}_answer");
156
157
        if (! $preAnswer) {
158
            return $userInput;
159
        }
160
161
        $answer = $this->clean(str_replace($preAnswer, '', $userInput));
162
163
        $this->cache->put("{$this->prefix}_answer", $userInput, $this->ttl);
164
165
        // Log::debug('answers ---', [
166
        //     'pre_answer' => $preAnswer,
167
        //     'input' => $userInput,
168
        //     'answer' => $answer,
169
        // ]);
170
171
        return $answer;
172
    }
173
174 1
    protected function setBreakpoint(): void
175
    {
176 1
        $exp = $this->cache->get("{$this->prefix}_exp");
177
178 1
        $breakpoints = (array) json_decode((string) $this->cache->get("{$this->prefix}_breakpoints"), true);
179
180 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...
181
            throw new \Exception('Missing tag');
182
        }
183
184 1
        $breakpoint = array_shift($breakpoints);
185 1
        $this->cache->put("{$this->prefix}_exp", $breakpoint[$exp], $this->ttl);
186 1
        $this->cache->put("{$this->prefix}_breakpoints", json_encode($breakpoints), $this->ttl);
187
    }
188
189 5
    protected function renderNext(): ?string
190
    {
191 5
        $exp = $this->cache->get("{$this->prefix}_exp");
192
193 5
        $node = $this->xpath->query($exp)->item(0);
194
195 5
        $tagName = $this->resolveTagName($node);
196 5
        $tag = $this->createTag($tagName, [$node, $this->cache, $this->prefix, $this->ttl]);
197 4
        $output = $tag->handle();
198
199 3
        $exp = $this->cache->get("{$this->prefix}_exp");
200 3
        $breakpoints = (array) json_decode((string) $this->cache->get("{$this->prefix}_breakpoints"), true);
201
202 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...
203
            $breakpoint = array_shift($breakpoints);
204
            $this->cache->put("{$this->prefix}_exp", $breakpoint[$exp], $this->ttl);
205
            $this->cache->put("{$this->prefix}_breakpoints", json_encode($breakpoints), $this->ttl);
206
        }
207
208 3
        return $output;
209
    }
210
211 5
    protected function resolveTagName(\DOMNode $node): string
212
    {
213 5
        $tagName = $node->tagName;
214
215 5
        if ('action' !== strtolower($tagName)) {
216 5
            return Str::studly("{$tagName}Tag");
217
        }
218
219
        $tagName = $node->attributes->getNamedItem('name')->nodeValue;
0 ignored issues
show
Bug introduced by
The method getNamedItem() does not exist on null. ( Ignorable by Annotation )

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

219
        $tagName = $node->attributes->/** @scrutinizer ignore-call */ getNamedItem('name')->nodeValue;

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
220
221
        return Str::studly("{$tagName}Action");
222
    }
223
224 5
    protected function resolveTagClass(string $tagName): string
225
    {
226 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...
227
228 5
        $tagNs = config('ussd.tag-ns', []);
229 5
        $actionNs = config('ussd.action-ns', []);
230
231 5
        $namespaces = array_merge($tagNs, $actionNs);
232
233 5
        $fqcn = $tagName;
0 ignored issues
show
Unused Code introduced by
The assignment to $fqcn is dead and can be removed.
Loading history...
234
235 5
        foreach ($namespaces as $ns) {
236 5
            $fqcn = "{$ns}\\{$tagName}";
237 5
            if (class_exists($fqcn)) {
238 4
                return $fqcn;
239
            }
240
        }
241
242 1
        throw new \Exception("Missing class: {$tagName}");
243
    }
244
245 5
    protected function createTag(string $tagName, array $args = []): RenderableTag
246
    {
247 5
        $fqcn = $this->resolveTagClass($tagName);
248
249 4
        return \call_user_func_array([new \ReflectionClass($fqcn), 'newInstance'], $args);
250
    }
251
}
252