Passed
Push — main ( 1a8771...d7bb24 )
by Andrey
11:52 queued 10:31
created

HttpBuilder::__call()   B

Complexity

Conditions 11
Paths 7

Size

Total Lines 33
Code Lines 18

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 17
CRAP Score 11.0207

Importance

Changes 0
Metric Value
cc 11
eloc 18
c 0
b 0
f 0
nc 7
nop 2
dl 0
loc 33
ccs 17
cts 18
cp 0.9444
crap 11.0207
rs 7.3166

How to fix   Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
3
namespace Helldar\Support\Helpers;
4
5
use ArgumentCountError;
6
use Helldar\Support\Facades\Helpers\Arr as ArrFacade;
7
use Helldar\Support\Facades\Helpers\Http as HttpHelper;
8
use Helldar\Support\Facades\Helpers\Instance as InstanceHelper;
9
use Helldar\Support\Facades\Helpers\Str as StrFacade;
10
use Helldar\Support\Tools\HttpBuilderPrepare;
11
use RuntimeException;
12
13
/**
14
 * Based on code by Maksim (Ellrion) Platonov.
15
 *
16
 * @see https://gist.github.com/Ellrion/f51ba0d40ae1d62eeae44fd1adf7b704
17
 *
18
 * @method HttpBuilder setFragment(array|string $value)
19
 * @method HttpBuilder setHost(string $value)
20
 * @method HttpBuilder setPass(string $value)
21
 * @method HttpBuilder setPath(string $value)
22
 * @method HttpBuilder setPort(string $value)
23
 * @method HttpBuilder setQuery(array|string $value)
24
 * @method HttpBuilder setScheme(string $value)
25
 * @method HttpBuilder setUser(string $value)
26
 * @method HttpBuilder putQuery(string $key, $value)
27
 * @method HttpBuilder removeQuery(string $key)
28
 * @method string|null getFragment()
29
 * @method string|null getHost()
30
 * @method string|null getPass()
31
 * @method string|null getPath()
32
 * @method string|null getPort()
33
 * @method string|null getQuery()
34
 * @method string|null getScheme()
35
 * @method string|null getUser()
36
 */
37
final class HttpBuilder
38
{
39
    protected $parsed = [];
40
41
    protected $components = [
42
        PHP_URL_SCHEME   => 'scheme',
43
        PHP_URL_HOST     => 'host',
44
        PHP_URL_PORT     => 'port',
45
        PHP_URL_USER     => 'user',
46
        PHP_URL_PASS     => 'pass',
47
        PHP_URL_QUERY    => 'query',
48
        PHP_URL_PATH     => 'path',
49
        PHP_URL_FRAGMENT => 'fragment',
50
    ];
51
52
    protected $allow_put_remove = ['query'];
53
54
    protected $casts = [
55
        'query' => 'array',
56
    ];
57
58
    /**
59
     * Calling magic methods.
60
     *
61
     * @param  string  $method
62
     * @param  mixed  $args
63
     *
64
     * @return $this|string|null
65
     */
66 88
    public function __call(string $method, $args)
67
    {
68 88
        if ($this->isGetter($method) || $this->isSetter($method) || $this->isPutter($method) || $this->isRemover($method)) {
69 88
            $key = $this->parseKey($method);
70
71 88
            if (! $this->allowKey($key) || ! $this->allowArrayable($method, $key)) {
72 32
                throw new RuntimeException($method . ' method not defined.');
73
            }
74
75
            switch (true) {
76 56
                case $this->isGetter($method):
77 52
                    $this->validateArgumentsCount($method, $args, 0);
78
79 52
                    return $this->get($key);
0 ignored issues
show
Bug introduced by
It seems like $key can also be of type null; however, parameter $key of Helldar\Support\Helpers\HttpBuilder::get() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

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

79
                    return $this->get(/** @scrutinizer ignore-type */ $key);
Loading history...
Bug Best Practice introduced by
The expression return $this->get($key) also could return the type array which is incompatible with the documented return type Helldar\Support\Helpers\HttpBuilder|null|string.
Loading history...
80
81 28
                case $this->isSetter($method):
82 24
                    $this->validateArgumentsCount($method, $args);
83
84 20
                    return $this->set($key, $args[0]);
0 ignored issues
show
Bug introduced by
It seems like $key can also be of type null; however, parameter $key of Helldar\Support\Helpers\HttpBuilder::set() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

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

84
                    return $this->set(/** @scrutinizer ignore-type */ $key, $args[0]);
Loading history...
85
86 4
                case $this->isPutter($method):
87 2
                    $this->validateArgumentsCount($method, $args, 2);
88
89 2
                    return $this->put($key, $args[0], $args[1]);
0 ignored issues
show
Bug introduced by
It seems like $key can also be of type null; however, parameter $key of Helldar\Support\Helpers\HttpBuilder::put() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

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

89
                    return $this->put(/** @scrutinizer ignore-type */ $key, $args[0], $args[1]);
Loading history...
90
91 2
                case $this->isRemover($method):
92 2
                    $this->validateArgumentsCount($method, $args);
93
94 2
                    return $this->remove($key, $args[0]);
0 ignored issues
show
Bug introduced by
It seems like $key can also be of type null; however, parameter $key of Helldar\Support\Helpers\HttpBuilder::remove() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

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

94
                    return $this->remove(/** @scrutinizer ignore-type */ $key, $args[0]);
Loading history...
95
            }
96
        }
97
98
        throw new RuntimeException("Using an unknown method: \"{$method}\"");
99
    }
100
101
    /**
102
     * Gets the current instance of the object.
103
     *
104
     * @return $this
105
     */
106 8
    public function same(): self
107
    {
108 8
        return $this;
109
    }
110
111
    /**
112
     * Parse a URL.
113
     *
114
     * @param  string|null  $url
115
     * @param  int  $component
116
     *
117
     * @return $this
118
     */
119 36
    public function parse(?string $url, int $component = -1): self
120
    {
121 36
        if ($component === -1) {
122 32
            HttpHelper::validateUrl($url);
123
        }
124
125 30
        $component = $this->componentIndex($component);
126 30
        $key       = $this->componentKey($component);
127
128 30
        $component === -1 || empty($key)
129 26
            ? $this->parsed       = parse_url($url)
130 4
            : $this->parsed[$key] = parse_url($url, $component);
131
132 30
        $this->cast();
133
134 30
        return $this;
135
    }
136
137
    /**
138
     * Filling the builder with parsed data.
139
     *
140
     * @param  array  $parsed
141
     *
142
     * @return $this
143
     */
144 6
    public function raw(array $parsed): self
145
    {
146 6
        foreach ($parsed as $key => $value) {
147 6
            if (! $this->allowKey($key)) {
148 2
                throw new RuntimeException('Filling in the "' . $key . '" key is prohibited.');
149
            }
150
151 4
            $this->set($key, $value);
152
        }
153
154 4
        $this->cast();
155
156 4
        return $this;
157
    }
158
159
    /**
160
     * Compiles parameters to URL.
161
     *
162
     * @return string
163
     */
164 16
    public function compile(): string
165
    {
166 16
        return implode('', array_filter(array_map(function ($value) {
167 16
            return InstanceHelper::of($value, HttpBuilderPrepare::class) ? $value->get() : $value;
168 16
        }, $this->prepare())));
169
    }
170
171
    /**
172
     * Returns parsed data.
173
     *
174
     * @return null[]|string[]
175
     */
176 4
    public function toArray(): array
177
    {
178
        return [
179 4
            'scheme'   => $this->getScheme(),
180 4
            'host'     => $this->getHost(),
181 4
            'port'     => $this->getPort(),
182 4
            'user'     => $this->getUser(),
183 4
            'pass'     => $this->getPass(),
184 4
            'query'    => $this->getQuery(),
185 4
            'path'     => $this->getPath(),
186 4
            'fragment' => $this->getFragment(),
187
        ];
188
    }
189
190
    /**
191
     * Prepares data for compilation.
192
     *
193
     * @return array
194
     */
195 16
    protected function prepare(): array
196
    {
197
        return [
198 16
            HttpBuilderPrepare::make()->of($this->getScheme())->suffix('://'),
199 16
            HttpBuilderPrepare::make()->of($this->getUser()),
200 16
            HttpBuilderPrepare::make()->of($this->getPass())->prefix(':'),
201
202 16
            $this->getUser() || $this->getPass() ? '@' : '',
203
204 16
            HttpBuilderPrepare::make()->of($this->getHost()),
205 16
            HttpBuilderPrepare::make()->of($this->getPort())->prefix(':'),
206 16
            HttpBuilderPrepare::make()->of($this->getPath())->prefix('/'),
207 16
            HttpBuilderPrepare::make()->of($this->getQuery())->prefix('?'),
208 16
            HttpBuilderPrepare::make()->of($this->getFragment())->prefix('#'),
209
        ];
210
    }
211
212
    /**
213
     * Gets the index of the component.
214
     *
215
     * @param  int  $component
216
     *
217
     * @return int
218
     */
219 30
    protected function componentIndex(int $component = -1): int
220
    {
221 30
        return ArrFacade::getKey($this->components, $component, -1);
222
    }
223
224
    /**
225
     * Gets the key for the component.
226
     *
227
     * @param  int  $component
228
     *
229
     * @return string|null
230
     */
231 30
    protected function componentKey(int $component = -1): ?string
232
    {
233 30
        return ArrFacade::get($this->components, $component);
234
    }
235
236
    /**
237
     * Checks if calling the requested key is allowed.
238
     *
239
     * @param  string|null  $key
240
     *
241
     * @return bool
242
     */
243 90
    protected function allowKey(?string $key): bool
244
    {
245 90
        return in_array($key, $this->components);
246
    }
247
248 84
    protected function allowArrayable(?string $method, ?string $key): bool
249
    {
250 84
        if ($this->isPutter($method) || $this->isRemover($method)) {
0 ignored issues
show
Bug introduced by
It seems like $method can also be of type null; however, parameter $method of Helldar\Support\Helpers\HttpBuilder::isRemover() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

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

250
        if ($this->isPutter($method) || $this->isRemover(/** @scrutinizer ignore-type */ $method)) {
Loading history...
Bug introduced by
It seems like $method can also be of type null; however, parameter $method of Helldar\Support\Helpers\HttpBuilder::isPutter() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

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

250
        if ($this->isPutter(/** @scrutinizer ignore-type */ $method) || $this->isRemover($method)) {
Loading history...
251 32
            return in_array($key, $this->allow_put_remove);
252
        }
253
254 56
        return true;
255
    }
256
257
    /**
258
     * Checks if the method is a request for information.
259
     *
260
     * @param  string  $method
261
     *
262
     * @return bool
263
     */
264 88
    protected function isGetter(string $method): bool
265
    {
266 88
        return StrFacade::startsWith($method, 'get');
267
    }
268
269
    /**
270
     * Checks if the method is a request to fill information.
271
     *
272
     * @param  string  $method
273
     *
274
     * @return bool
275
     */
276 58
    protected function isSetter(string $method): bool
277
    {
278 58
        return StrFacade::startsWith($method, 'set');
279
    }
280
281
    /**
282
     * Checks if the method is a request to put value.
283
     *
284
     * @param  string  $method
285
     *
286
     * @return bool
287
     */
288 84
    protected function isPutter(string $method): bool
289
    {
290 84
        return StrFacade::startsWith($method, 'put');
291
    }
292
293
    /**
294
     * Checks if the method is a request to remove value.
295
     *
296
     * @param  string  $method
297
     *
298
     * @return bool
299
     */
300 70
    protected function isRemover(string $method): bool
301
    {
302 70
        return StrFacade::startsWith($method, 'remove');
303
    }
304
305
    /**
306
     * Gets the key of the component from the name of the magic method.
307
     *
308
     * @param  string  $method
309
     *
310
     * @return string|null
311
     */
312 88
    protected function parseKey(string $method): ?string
313
    {
314 88
        $search = 'unknown';
315
316
        switch (true) {
317 88
            case $this->isGetter($method):
318 54
                $search = 'get';
319 54
                break;
320
321 58
            case $this->isSetter($method):
322 26
                $search = 'set';
323 26
                break;
324
325 32
            case $this->isPutter($method):
326 16
                $search = 'put';
327 16
                break;
328
329 16
            case $this->isRemover($method):
330 16
                $search = 'remove';
331 16
                break;
332
        }
333
334 88
        return StrFacade::lower(StrFacade::after($method, $search));
335
    }
336
337
    /**
338
     * Set the component key with a value.
339
     *
340
     * @param  string  $key
341
     * @param  mixed  $value
342
     *
343
     * @return $this
344
     */
345 24
    protected function set(string $key, $value): self
346
    {
347
        switch (true) {
348 24
            case $this->hasCastArray($key):
349 8
                $value = $this->castToArray($value);
350 8
                break;
351
        }
352
353 24
        $this->parsed[$key] = $value;
354
355 24
        return $this;
356
    }
357
358
    /**
359
     * Gets the value of the component.
360
     *
361
     * @param  string  $key
362
     *
363
     * @return array|string|null
364
     */
365 52
    protected function get(string $key)
366
    {
367 52
        if ($value = $this->parsed[$key] ?? null) {
368 52
            return $this->hasCastArray($key) ? http_build_query($value) : $value;
369
        }
370
371 42
        return null;
372
    }
373
374
    /**
375
     * Adds a key-value to an array.
376
     *
377
     * @param  string  $key
378
     * @param  string  $parameter
379
     * @param  mixed  $value
380
     *
381
     * @return $this
382
     */
383 2
    protected function put(string $key, string $parameter, $value): self
384
    {
385 2
        $this->parsed[$key][$parameter] = $value;
386
387 2
        return $this;
388
    }
389
390
    /**
391
     * Removes a key from a variable.
392
     *
393
     * @param  string  $key
394
     * @param  string  $parameter
395
     *
396
     * @return $this
397
     */
398 2
    protected function remove(string $key, string $parameter): self
399
    {
400 2
        unset($this->parsed[$key][$parameter]);
401
402 2
        return $this;
403
    }
404
405 52
    protected function hasCastArray(string $key): bool
406
    {
407 52
        return ($this->casts[$key] ?? null) === 'array';
408
    }
409
410 34
    protected function cast(): void
411
    {
412 34
        foreach ($this->casts as $key => $cast) {
413 34
            $value = $this->parsed[$key] ?? null;
414
415
            switch ($cast) {
416 34
                case 'array':
417 34
                    $value = $this->castToArray($value);
418 34
                    break;
419
            }
420
421 34
            $this->parsed[$key] = $value;
422
        }
423 34
    }
424
425 40
    protected function castToArray($value): array
426
    {
427 40
        if (empty($value)) {
428 20
            return [];
429
        }
430
431 24
        if (is_array($value)) {
432 4
            return $value;
433
        }
434
435 22
        $items = [];
436
437 22
        foreach (explode('&', $value) as $item) {
438 22
            [$key, $value] = StrFacade::contains($item, '=') ? explode('=', $item) : [0, $item];
439
440 22
            $items[$key] = $value;
441
        }
442
443 22
        return $items;
444
    }
445
446
    /**
447
     * Checks the number of arguments passed.
448
     *
449
     * @param  string  $method
450
     * @param  array  $args
451
     * @param  int  $need
452
     */
453 56
    protected function validateArgumentsCount(string $method, array $args, int $need = 1): void
454
    {
455 56
        if (count($args) > $need) {
456 4
            throw new ArgumentCountError($method . ' expects at most ' . $need . ' parameter, ' . count($args) . ' given.');
457
        }
458 52
    }
459
}
460