Passed
Pull Request — main (#22)
by Andrey
28:59 queued 16:23
created

HttpBuilder::__call()   A

Complexity

Conditions 6
Paths 5

Size

Total Lines 19
Code Lines 11

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 6
eloc 11
nc 5
nop 2
dl 0
loc 19
rs 9.2222
c 0
b 0
f 0
1
<?php
2
3
namespace Helldar\Support\Helpers;
4
5
use ArgumentCountError;
6
use Helldar\Support\Facades\Helpers\Arr;
0 ignored issues
show
Bug introduced by
This use statement conflicts with another class in this namespace, Helldar\Support\Helpers\Arr. Consider defining an alias.

Let?s assume that you have a directory layout like this:

.
|-- OtherDir
|   |-- Bar.php
|   `-- Foo.php
`-- SomeDir
    `-- Foo.php

and let?s assume the following content of Bar.php:

// Bar.php
namespace OtherDir;

use SomeDir\Foo; // This now conflicts the class OtherDir\Foo

If both files OtherDir/Foo.php and SomeDir/Foo.php are loaded in the same runtime, you will see a PHP error such as the following:

PHP Fatal error:  Cannot use SomeDir\Foo as Foo because the name is already in use in OtherDir/Foo.php

However, as OtherDir/Foo.php does not necessarily have to be loaded and the error is only triggered if it is loaded before OtherDir/Bar.php, this problem might go unnoticed for a while. In order to prevent this error from surfacing, you must import the namespace with a different alias:

// Bar.php
namespace OtherDir;

use SomeDir\Foo as SomeDirFoo; // There is no conflict anymore.
Loading history...
7
use Helldar\Support\Facades\Helpers\Str;
0 ignored issues
show
Bug introduced by
This use statement conflicts with another class in this namespace, Helldar\Support\Helpers\Str. Consider defining an alias.

Let?s assume that you have a directory layout like this:

.
|-- OtherDir
|   |-- Bar.php
|   `-- Foo.php
`-- SomeDir
    `-- Foo.php

and let?s assume the following content of Bar.php:

// Bar.php
namespace OtherDir;

use SomeDir\Foo; // This now conflicts the class OtherDir\Foo

If both files OtherDir/Foo.php and SomeDir/Foo.php are loaded in the same runtime, you will see a PHP error such as the following:

PHP Fatal error:  Cannot use SomeDir\Foo as Foo because the name is already in use in OtherDir/Foo.php

However, as OtherDir/Foo.php does not necessarily have to be loaded and the error is only triggered if it is loaded before OtherDir/Bar.php, this problem might go unnoticed for a while. In order to prevent this error from surfacing, you must import the namespace with a different alias:

// Bar.php
namespace OtherDir;

use SomeDir\Foo as SomeDirFoo; // There is no conflict anymore.
Loading history...
8
use RuntimeException;
9
10
/**
11
 * Based on code by Maksim (Ellrion) Platonov.
12
 *
13
 * @see https://gist.github.com/Ellrion/f51ba0d40ae1d62eeae44fd1adf7b704
14
 *
15
 * @method static HttpBuilder setFragment(array|string $value)
16
 * @method static HttpBuilder setHost(string $value)
17
 * @method static HttpBuilder setPass(string $value)
18
 * @method static HttpBuilder setPath(string $value)
19
 * @method static HttpBuilder setPort(string $value)
20
 * @method static HttpBuilder setQuery(array|string $value)
21
 * @method static HttpBuilder setScheme(string $value)
22
 * @method static HttpBuilder setUser(string $value)
23
 * @method static string|null getFragment()
24
 * @method static string|null getHost()
25
 * @method static string|null getPass()
26
 * @method static string|null getPath()
27
 * @method static string|null getPort()
28
 * @method static string|null getQuery()
29
 * @method static string|null getScheme()
30
 * @method static string|null getUser()
31
 */
32
final class HttpBuilder
33
{
34
    protected $parsed = [];
35
36
    protected $components = [
37
        PHP_URL_SCHEME   => 'scheme',
38
        PHP_URL_HOST     => 'host',
39
        PHP_URL_PORT     => 'port',
40
        PHP_URL_USER     => 'user',
41
        PHP_URL_PASS     => 'pass',
42
        PHP_URL_QUERY    => 'query',
43
        PHP_URL_PATH     => 'path',
44
        PHP_URL_FRAGMENT => 'fragment',
45
    ];
46
47
    /**
48
     * Calling magic methods.
49
     *
50
     * @param  string  $method
51
     * @param  mixed  $args
52
     *
53
     * @return $this|string|null
54
     */
55
    public function __call($method, $args)
56
    {
57
        if ($this->isGetter($method) || $this->isSetter($method)) {
58
            $key = $this->parseKey($method);
59
60
            if (! $this->allowKey($key)) {
61
                throw new RuntimeException($method . ' method not defined.');
62
            }
63
64
            switch (true) {
65
                case $this->isGetter($method):
66
                    $this->validateArgumentsCount($method, $args, 0);
67
68
                    return $this->get($key);
69
70
                case $this->isSetter($method):
71
                    $this->validateArgumentsCount($method, $args);
72
73
                    return $this->set($key, ...$args);
0 ignored issues
show
Bug introduced by
$args is expanded, but the parameter $value of Helldar\Support\Helpers\HttpBuilder::set() does not expect variable arguments. ( Ignorable by Annotation )

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

73
                    return $this->set($key, /** @scrutinizer ignore-type */ ...$args);
Loading history...
74
            }
75
        }
76
    }
77
78
    /**
79
     * Gets the current instance of the object.
80
     *
81
     * @return $this
82
     */
83
    public function same(): self
84
    {
85
        return $this;
86
    }
87
88
    /**
89
     * Parse a URL.
90
     *
91
     * @param  string  $url
92
     * @param  int  $component
93
     *
94
     * @return $this
95
     */
96
    public function parse(string $url, int $component = -1): self
97
    {
98
        $component = $this->componentIndex($component);
99
        $key       = $this->componentKey($component);
100
101
        $component === -1 || empty($key)
102
            ? $this->parsed       = parse_url($url)
103
            : $this->parsed[$key] = parse_url($url, $component);
104
105
        return $this;
106
    }
107
108
    /**
109
     * Compiles parameters to URL.
110
     *
111
     * @return string
112
     */
113
    public function compile(): string
114
    {
115
        return implode('', array_filter($this->prepare()));
116
    }
117
118
    /**
119
     * Prepares data for compilation.
120
     *
121
     * @return array
122
     */
123
    protected function prepare(): array
124
    {
125
        return [
126
            $this->getScheme() ? $this->getScheme() . '://' : '',
127
            $this->getUser(),
128
            $this->getPass() ? ':' . $this->getPass() : '',
129
            $this->getUser() || $this->getPass() ? '@' : '',
130
            $this->getHost(),
131
            $this->getPort() ? ':' . $this->getPort() : '',
132
            $this->getPath() ? '/' . ltrim($this->getPath(), '/') : '',
133
            $this->getQuery() ? '?' . $this->getQuery() : '',
134
            $this->getFragment() ? '#' . $this->getFragment() : '',
135
        ];
136
    }
137
138
    /**
139
     * Gets the value by key.
140
     *
141
     * @param  string  $key
142
     *
143
     * @return string|null
144
     */
145
    protected function value(string $key): ?string
146
    {
147
        return Arr::get($this->parsed, $key);
148
    }
149
150
    /**
151
     * Gets the index of the component.
152
     *
153
     * @param  int  $component
154
     *
155
     * @return int
156
     */
157
    protected function componentIndex(int $component = -1): int
158
    {
159
        return Arr::getKey($this->components, $component, -1);
160
    }
161
162
    /**
163
     * Gets the key for the component.
164
     *
165
     * @param  int  $component
166
     *
167
     * @return string|null
168
     */
169
    protected function componentKey(int $component = -1): ?string
170
    {
171
        return Arr::get($this->components, $component);
172
    }
173
174
    /**
175
     * Checks if calling the requested key is allowed.
176
     *
177
     * @param  string|null  $key
178
     *
179
     * @return bool
180
     */
181
    protected function allowKey(?string $key): bool
182
    {
183
        return in_array($key, $this->components);
184
    }
185
186
    /**
187
     * Checks if the method is a request for information.
188
     *
189
     * @param  string  $method
190
     *
191
     * @return bool
192
     */
193
    protected function isGetter(string $method): bool
194
    {
195
        return Str::startsWith($method, 'get');
196
    }
197
198
    /**
199
     * Checks if the method is a request to fill information.
200
     *
201
     * @param  string  $method
202
     *
203
     * @return bool
204
     */
205
    protected function isSetter(string $method): bool
206
    {
207
        return Str::startsWith($method, 'set');
208
    }
209
210
    /**
211
     * Gets the key of the component from the name of the magic method.
212
     *
213
     * @param  string  $method
214
     *
215
     * @return string|null
216
     */
217
    protected function parseKey(string $method): ?string
218
    {
219
        $search = Str::startsWith($method, 'get') ? 'get' : 'set';
220
221
        return Str::lower(Str::after($method, $search));
222
    }
223
224
    /**
225
     * Set the component key with a value.
226
     *
227
     * @param  string  $key
228
     * @param  mixed  $value
229
     *
230
     * @return $this
231
     */
232
    protected function set(string $key, $value): self
233
    {
234
        $this->parsed[$key] = is_array($value) ? http_build_query($value) : $value;
235
236
        return $this;
237
    }
238
239
    /**
240
     * Gets the value of the component.
241
     *
242
     * @param  string  $key
243
     *
244
     * @return string|null
245
     */
246
    protected function get(string $key): ?string
247
    {
248
        return $this->parsed[$key] ?? null;
249
    }
250
251
    /**
252
     * Checks the number of arguments passed.
253
     *
254
     * @param  string  $method
255
     * @param  array  $args
256
     * @param  int  $need
257
     */
258
    protected function validateArgumentsCount(string $method, array $args, int $need = 1): void
259
    {
260
        if (count($args) > 1) {
261
            throw new ArgumentCountError($method . ' expects at most ' . $need . ' parameter, ' . count($args) . ' given.');
262
        }
263
    }
264
}
265