Passed
Push — main ( a01d42...f042b8 )
by Andrey
13:24 queued 11:56
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\Http\Uri;
11
use Helldar\Support\Tools\HttpBuilderPrepare;
12
use Psr\Http\Message\UriInterface;
13
use RuntimeException;
14
15
/**
16
 * Based on code by Maksim (Ellrion) Platonov.
17
 *
18
 * @see https://gist.github.com/Ellrion/f51ba0d40ae1d62eeae44fd1adf7b704
19
 *
20
 * @method HttpBuilder setFragment(array|string $value)
21
 * @method HttpBuilder setHost(string $value)
22
 * @method HttpBuilder setPass(string $value)
23
 * @method HttpBuilder setPath(string $value)
24
 * @method HttpBuilder setPort(string $value)
25
 * @method HttpBuilder setQuery(array|string $value)
26
 * @method HttpBuilder setScheme(string $value)
27
 * @method HttpBuilder setUser(string $value)
28
 * @method HttpBuilder putQuery(string $key, $value)
29
 * @method HttpBuilder removeQuery(string $key)
30
 * @method string|null getFragment()
31
 * @method string|null getHost()
32
 * @method string|null getPass()
33
 * @method string|null getPath()
34
 * @method string|null getPort()
35
 * @method string|null getQuery()
36
 * @method string|null getScheme()
37
 * @method string|null getUser()
38
 */
39
class HttpBuilder
40
{
41
    protected $parsed = [];
42
43
    protected $components = [
44
        PHP_URL_SCHEME   => 'scheme',
45
        PHP_URL_HOST     => 'host',
46
        PHP_URL_PORT     => 'port',
47
        PHP_URL_USER     => 'user',
48
        PHP_URL_PASS     => 'pass',
49
        PHP_URL_QUERY    => 'query',
50
        PHP_URL_PATH     => 'path',
51
        PHP_URL_FRAGMENT => 'fragment',
52
    ];
53
54
    protected $allow_put_remove = ['query'];
55
56
    protected $casts = [
57
        'query' => 'array',
58
    ];
59
60
    /**
61
     * Calling magic methods.
62
     *
63
     * @param  string  $method
64
     * @param  mixed  $args
65
     *
66
     * @return $this|string|null
67
     */
68 92
    public function __call(string $method, $args)
69
    {
70 92
        if ($this->isGetter($method) || $this->isSetter($method) || $this->isPutter($method) || $this->isRemover($method)) {
71 92
            $key = $this->parseKey($method);
72
73 92
            if (! $this->allowKey($key) || ! $this->allowArrayable($method, $key)) {
74 32
                throw new RuntimeException($method . ' method not defined.');
75
            }
76
77
            switch (true) {
78 60
                case $this->isGetter($method):
79 56
                    $this->validateArgumentsCount($method, $args, 0);
80
81 56
                    return $this->get($key);
0 ignored issues
show
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...
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

81
                    return $this->get(/** @scrutinizer ignore-type */ $key);
Loading history...
82
83 28
                case $this->isSetter($method):
84 24
                    $this->validateArgumentsCount($method, $args);
85
86 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

86
                    return $this->set(/** @scrutinizer ignore-type */ $key, $args[0]);
Loading history...
87
88 4
                case $this->isPutter($method):
89 2
                    $this->validateArgumentsCount($method, $args, 2);
90
91 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

91
                    return $this->put(/** @scrutinizer ignore-type */ $key, $args[0], $args[1]);
Loading history...
92
93 2
                case $this->isRemover($method):
94 2
                    $this->validateArgumentsCount($method, $args);
95
96 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

96
                    return $this->remove(/** @scrutinizer ignore-type */ $key, $args[0]);
Loading history...
97
            }
98
        }
99
100
        throw new RuntimeException("Using an unknown method: \"{$method}\"");
101
    }
102
103
    /**
104
     * Gets the current instance of the object.
105
     *
106
     * @return $this
107
     */
108 8
    public function same(): self
109
    {
110 8
        return $this;
111
    }
112
113
    /**
114
     * Parse a URL.
115
     *
116
     * @param  string|null  $url
117
     * @param  int  $component
118
     *
119
     * @return $this
120
     */
121 40
    public function parse(?string $url, int $component = -1): self
122
    {
123 40
        if ($component === -1) {
124 36
            HttpHelper::validateUrl($url);
125
        }
126
127 34
        $component = $this->componentIndex($component);
128 34
        $key       = $this->componentKey($component);
129
130 34
        $component === -1 || empty($key)
131 30
            ? $this->parsed       = parse_url($url)
132 4
            : $this->parsed[$key] = parse_url($url, $component);
133
134 34
        $this->cast();
135
136 34
        return $this;
137
    }
138
139
    /**
140
     * Filling the builder with parsed data.
141
     *
142
     * @param  array  $parsed
143
     *
144
     * @return $this
145
     */
146 6
    public function raw(array $parsed): self
147
    {
148 6
        foreach ($parsed as $key => $value) {
149 6
            if (! $this->allowKey($key)) {
150 2
                throw new RuntimeException('Filling in the "' . $key . '" key is prohibited.');
151
            }
152
153 4
            $this->set($key, $value);
154
        }
155
156 4
        $this->cast();
157
158 4
        return $this;
159
    }
160
161
    /**
162
     * Compiles parameters to URL.
163
     *
164
     * @return string
165
     */
166 20
    public function compile(): string
167
    {
168 20
        return implode('', array_filter(array_map(function ($value) {
169 20
            return InstanceHelper::of($value, HttpBuilderPrepare::class) ? $value->get() : $value;
170 20
        }, $this->prepare())));
171
    }
172
173
    /**
174
     * Returns parsed data.
175
     *
176
     * @return null[]|string[]
177
     */
178 4
    public function toArray(): array
179
    {
180
        return [
181 4
            'scheme'   => $this->getScheme(),
182 4
            'host'     => $this->getHost(),
183 4
            'port'     => $this->getPort(),
184 4
            'user'     => $this->getUser(),
185 4
            'pass'     => $this->getPass(),
186 4
            'query'    => $this->getQuery(),
187 4
            'path'     => $this->getPath(),
188 4
            'fragment' => $this->getFragment(),
189
        ];
190
    }
191
192
    /**
193
     * Converts HttpBuilder from `\Psr\Http\Message\UriInterface` interface.
194
     *
195
     * @param  \Psr\Http\Message\UriInterface  $uri
196
     *
197
     * @return $this
198
     */
199 2
    public function fromUriInterface(UriInterface $uri): self
200
    {
201 2
        $this->parse((string) $uri);
202
203 2
        return $this;
204
    }
205
206
    /**
207
     * Converts `\Psr\Http\Message\UriInterface` from `HttpBuilder` instance.
208
     *
209
     * @return \Psr\Http\Message\UriInterface
210
     */
211 2
    public function toUriInterface(): UriInterface
212
    {
213 2
        return Uri::make($this);
214
    }
215
216
    /**
217
     * Prepares data for compilation.
218
     *
219
     * @return array
220
     */
221 20
    protected function prepare(): array
222
    {
223
        return [
224 20
            HttpBuilderPrepare::make()->of($this->getScheme())->suffix('://'),
225 20
            HttpBuilderPrepare::make()->of($this->getUser()),
226 20
            HttpBuilderPrepare::make()->of($this->getPass())->prefix(':'),
227
228 20
            $this->getUser() || $this->getPass() ? '@' : '',
229
230 20
            HttpBuilderPrepare::make()->of($this->getHost()),
231 20
            HttpBuilderPrepare::make()->of($this->getPort())->prefix(':'),
232 20
            HttpBuilderPrepare::make()->of($this->getPath())->prefix('/'),
233 20
            HttpBuilderPrepare::make()->of($this->getQuery())->prefix('?'),
234 20
            HttpBuilderPrepare::make()->of($this->getFragment())->prefix('#'),
235
        ];
236
    }
237
238
    /**
239
     * Gets the index of the component.
240
     *
241
     * @param  int  $component
242
     *
243
     * @return int
244
     */
245 34
    protected function componentIndex(int $component = -1): int
246
    {
247 34
        return ArrFacade::getKey($this->components, $component, -1);
248
    }
249
250
    /**
251
     * Gets the key for the component.
252
     *
253
     * @param  int  $component
254
     *
255
     * @return string|null
256
     */
257 34
    protected function componentKey(int $component = -1): ?string
258
    {
259 34
        return ArrFacade::get($this->components, $component);
260
    }
261
262
    /**
263
     * Checks if calling the requested key is allowed.
264
     *
265
     * @param  string|null  $key
266
     *
267
     * @return bool
268
     */
269 94
    protected function allowKey(?string $key): bool
270
    {
271 94
        return in_array($key, $this->components);
272
    }
273
274 88
    protected function allowArrayable(?string $method, ?string $key): bool
275
    {
276 88
        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::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

276
        if ($this->isPutter(/** @scrutinizer ignore-type */ $method) || $this->isRemover($method)) {
Loading history...
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

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