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

BetaParser   A

Complexity

Total Complexity 18

Size/Duplication

Total Lines 127
Duplicated Lines 0 %

Test Coverage

Coverage 0%

Importance

Changes 1
Bugs 0 Features 0
Metric Value
wmc 18
eloc 63
c 1
b 0
f 0
dl 0
loc 127
ccs 0
cts 65
cp 0
rs 10

7 Methods

Rating   Name   Duplication   Size   Complexity  
A parse() 0 9 2
A doRender() 0 20 3
A setOptions() 0 7 2
A doParse() 0 18 3
A doProcess() 0 20 3
A __construct() 0 20 2
A doBreak() 0 13 3
1
<?php
2
3
namespace Bmatovu\Ussd;
4
5
use Bmatovu\Ussd\Contracts\AnswerableTag;
6
use Bmatovu\Ussd\Contracts\RenderableTag;
7
use Illuminate\Container\Container;
8
use Illuminate\Contracts\Cache\Repository as CacheContract;
9
use Illuminate\Support\Facades\Cache;
10
use Illuminate\Support\Str;
11
12
class Record
13
{
14
    protected CacheContract $cache;
15
    protected int $ttl;
16
    protected string $prefix;
17
18
    public function __construct(string $store, int $ttl, string $prefix)
19
    {
20
        $this->cache = Cache::store($store);
21
        $this->ttl = $ttl;
22
        $this->prefix = $prefix;
23
    }
24
25
    public function get(string $key, $default)
26
    {
27
        return $this->cache->get("{$this->prefix}{$key}", $default);
28
    }
29
30
    public function pull(string $key)
31
    {
32
        return $this->cache->pull("{$this->prefix}{$key}");
33
    }
34
35
    public function put(string $key, $value): void
36
    {
37
        $this->cache->put("{$this->prefix}{$key}", $value, $this->ttl);
38
    }
39
40
    public function append(string $key, string $extra): void
41
    {
42
        $value = $this->cache->get("{$this->prefix}{$key}");
43
44
        $this->cache->put("{$this->prefix}{$key}", "{$value}{$extra}", $this->ttl);
45
    }
46
}
47
48
trait ParserUtils
49
{
50
    protected function sessionExists(string $sessionId): bool
51
    {
52
        $preSessionId = $this->record->get('_session_id', '');
53
54
        return $preSessionId === $sessionId;
55
    }
56
57
    protected function clean(string $code = ''): string
58
    {
59
        if (! $code) {
60
            return $code;
61
        }
62
63
        return rtrim(ltrim($code, '*'), '#');
64
    }
65
66
    protected function getAnswer(?string $userInput): ?string
67
    {
68
        if (! $userInput) {
69
            return '';
70
        }
71
72
        $preAnswer = $this->record->get('_answer');
73
        if (! $preAnswer) {
74
            return (string) $userInput;
75
        }
76
77
        $answer = $this->clean(str_replace($preAnswer, '', $userInput));
78
79
        $this->record->put('_answer', $userInput);
80
81
        return $answer;
82
    }
83
84
    protected function resolveTagName(\DOMNode $node): string
85
    {
86
        $tagName = $node->tagName;
87
88
        if ('action' !== strtolower($tagName)) {
89
            return Str::studly("{$tagName}Tag");
90
        }
91
92
        $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

92
        $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...
93
94
        return Str::studly("{$tagName}Action");
95
    }
96
97
    protected function resolveTagClass(string $tagName): string
98
    {
99
        // $config = Container::getInstance()->make('config');
100
        $tagNs = config('ussd.tag-ns', []);
101
        $actionNs = config('ussd.action-ns', []);
102
103
        $namespaces = array_merge($tagNs, $actionNs);
104
105
        $fqcn = $tagName;
0 ignored issues
show
Unused Code introduced by
The assignment to $fqcn is dead and can be removed.
Loading history...
106
107
        foreach ($namespaces as $ns) {
108
            $fqcn = "{$ns}\\{$tagName}";
109
            if (class_exists($fqcn)) {
110
                return $fqcn;
111
            }
112
        }
113
114
        throw new \Exception("Missing class: {$tagName}");
115
    }
116
117
    protected function instantiateTag(string $tagName, array $args = []): RenderableTag
118
    {
119
        $fqcn = $this->resolveTagClass($tagName);
120
121
        return \call_user_func_array([new \ReflectionClass($fqcn), 'newInstance'], $args);
122
    }
123
}
124
125
class BetaParser
126
{
127
    use ParserUtils;
0 ignored issues
show
introduced by
The trait Bmatovu\Ussd\ParserUtils requires some properties which are not provided by Bmatovu\Ussd\BetaParser: $tagName, $attributes, $nodeValue
Loading history...
128
129
    protected \DOMXPath $xpath;
130
    protected string $sessionId;
131
    protected Record $record;
132
133
    public function __construct(\DOMXPath $xpath, string $expression, string $sessionId, string $serviceCode = '')
134
    {
135
        $this->xpath = $xpath;
136
137
        // $config = Container::getInstance()->make('config');
138
        $store = config('ussd.cache.store');
139
        $ttl = config('ussd.cache.ttl');
140
        $this->record = new Record($store, $ttl, $sessionId);
141
142
        if ($this->sessionExists($sessionId)) {
143
            return;
144
        }
145
146
        $serviceCode = $this->cleanup($serviceCode);
0 ignored issues
show
Bug introduced by
The method cleanup() does not exist on Bmatovu\Ussd\BetaParser. Did you maybe mean clean()? ( Ignorable by Annotation )

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

146
        /** @scrutinizer ignore-call */ 
147
        $serviceCode = $this->cleanup($serviceCode);

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...
147
        $this->record->put('_session_id', $sessionId);
148
        $this->record->put('_service_code', $serviceCode);
149
        $this->record->put('_answer', $serviceCode);
150
        $this->record->put('_pre', '');
151
        $this->record->put('_exp', $expression);
152
        $this->record->put('_breakpoints', '[]');
153
    }
154
155
    public function setOptions(array $options): self
156
    {
157
        foreach ($options as $key => $value) {
158
            $this->record->put($key, $value);
159
        }
160
161
        return self;
0 ignored issues
show
Bug introduced by
The constant self was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
162
    }
163
164
    public function parse(?string $userInput): string
165
    {
166
        $answers = explode('*', $this->getAnswer($userInput));
167
168
        foreach ($answers as $answer) {
169
            $output = $this->doParse($answer);
170
        }
171
172
        return $output;
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $output seems to be defined by a foreach iteration on line 168. Are you sure the iterator is never empty, otherwise this variable is not defined?
Loading history...
173
    }
174
175
    protected function doParse(?string $answer): ?string
176
    {
177
        $this->doProcess($answer);
178
179
        $exp = $this->record->get('_exp');
0 ignored issues
show
Bug introduced by
The call to Bmatovu\Ussd\Record::get() has too few arguments starting with default. ( Ignorable by Annotation )

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

179
        /** @scrutinizer ignore-call */ 
180
        $exp = $this->record->get('_exp');

This check compares calls to functions or methods with their respective definitions. If the call has less arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress. Please note the @ignore annotation hint above.

Loading history...
180
        $node = $this->xpath->query($exp)->item(0);
181
182
        if (! $node) {
183
            $this->doBreak();
184
        }
185
186
        $output = $this->doRender();
187
188
        if (! $output) {
189
            return $this->doParse($answer);
190
        }
191
192
        return $output;
193
    }
194
195
    protected function doProcess(?string $answer): void
196
    {
197
        $pre = $this->record->get('_pre');
0 ignored issues
show
Bug introduced by
The call to Bmatovu\Ussd\Record::get() has too few arguments starting with default. ( Ignorable by Annotation )

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

197
        /** @scrutinizer ignore-call */ 
198
        $pre = $this->record->get('_pre');

This check compares calls to functions or methods with their respective definitions. If the call has less arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress. Please note the @ignore annotation hint above.

Loading history...
198
199
        if (! $pre) {
200
            return;
201
        }
202
203
        $preNode = $this->xpath->query($pre)->item(0);
204
205
        $tagName = $this->resolveTagName($preNode);
206
        $tag = $this->instantiateTag($tagName, [$preNode, $this->record]);
207
208
        if (! $tag instanceof AnswerableTag) {
209
            return;
210
        }
211
212
        $this->record->append('_answer', "*{$answer}");
213
214
        $tag->process($answer);
215
    }
216
217
    protected function doBreak(): void
218
    {
219
        $exp = $this->record->get('_exp');
0 ignored issues
show
Bug introduced by
The call to Bmatovu\Ussd\Record::get() has too few arguments starting with default. ( Ignorable by Annotation )

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

219
        /** @scrutinizer ignore-call */ 
220
        $exp = $this->record->get('_exp');

This check compares calls to functions or methods with their respective definitions. If the call has less arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress. Please note the @ignore annotation hint above.

Loading history...
220
221
        $breakpoints = (array) json_decode((string) $this->record->get('_breakpoints'), true);
222
223
        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...
224
            throw new \Exception('Missing tag');
225
        }
226
227
        $breakpoint = array_shift($breakpoints);
228
        $this->record->put('_exp', $breakpoint[$exp]);
229
        $this->record->put('_breakpoints', json_encode($breakpoints));
230
    }
231
232
    protected function doRender(): ?string
233
    {
234
        $exp = $this->record->get('_exp');
0 ignored issues
show
Bug introduced by
The call to Bmatovu\Ussd\Record::get() has too few arguments starting with default. ( Ignorable by Annotation )

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

234
        /** @scrutinizer ignore-call */ 
235
        $exp = $this->record->get('_exp');

This check compares calls to functions or methods with their respective definitions. If the call has less arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress. Please note the @ignore annotation hint above.

Loading history...
235
236
        $node = $this->xpath->query($exp)->item(0);
237
238
        $tagName = $this->resolveTagName($node);
239
        $tag = $this->instantiateTag($tagName, [$node, $this->record]);
240
        $output = $tag->handle();
241
242
        $exp = $this->record->get('_exp');
243
        $breakpoints = (array) json_decode((string) $this->record->get('_breakpoints'), true);
244
245
        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...
246
            $breakpoint = array_shift($breakpoints);
247
            $this->record->put('_exp', $breakpoint[$exp]);
248
            $this->record->put('_breakpoints', json_encode($breakpoints));
249
        }
250
251
        return $output;
252
    }
253
}
254
255
/*
256
$parser = new Parser($xpath, $exp, $request->session_id, $request->serviceCode);
257
258
$parser = new Parser($xpath, $exp, $request->session_id, $request->serviceCode)
259
    ->setOptions([
260
        'phone_number' => $request->phone_number,
261
    ]);
262
263
$output = $parser->parse($request->input);
264
*/
265