Passed
Push — main ( b1bb1e...a5e312 )
by Andrey
03:20 queued 02:09
created

HttpBuilder::raw()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 11
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 6
CRAP Score 3

Importance

Changes 0
Metric Value
cc 3
eloc 5
nc 3
nop 1
dl 0
loc 11
ccs 6
cts 6
cp 1
crap 3
rs 10
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 52
    public function __call($method, $args)
56
    {
57 52
        if ($this->isGetter($method) || $this->isSetter($method)) {
58 52
            $key = $this->parseKey($method);
59
60 52
            if (! $this->allowKey($key)) {
61 4
                throw new RuntimeException($method . ' method not defined.');
62
            }
63
64
            switch (true) {
65 48
                case $this->isGetter($method):
66 44
                    $this->validateArgumentsCount($method, $args, 0);
67
68 44
                    return $this->get($key);
69
70 24
                case $this->isSetter($method):
71 24
                    $this->validateArgumentsCount($method, $args);
72
73 20
                    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
        throw new RuntimeException("Using an unknown method: \"{$method}\"");
78
    }
79
80
    /**
81
     * Gets the current instance of the object.
82
     *
83
     * @return $this
84
     */
85 8
    public function same(): self
86
    {
87 8
        return $this;
88
    }
89
90
    /**
91
     * Parse a URL.
92
     *
93
     * @param  string  $url
94
     * @param  int  $component
95
     *
96
     * @return $this
97
     */
98 22
    public function parse(string $url, int $component = -1): self
99
    {
100 22
        $component = $this->componentIndex($component);
101 22
        $key       = $this->componentKey($component);
102
103 22
        $component === -1 || empty($key)
104 18
            ? $this->parsed = parse_url($url)
105 4
            : $this->parsed[$key] = parse_url($url, $component);
106
107 22
        return $this;
108
    }
109
110
    /**
111
     * Filling the builder with parsed data.
112
     *
113
     * @param  array  $parsed
114
     *
115
     * @return $this
116
     */
117 6
    public function raw(array $parsed): self
118
    {
119 6
        foreach ($parsed as $key => $value) {
120 6
            if (! $this->allowKey($key)) {
121 2
                throw new RuntimeException('Filling in the "' . $key . '" key is prohibited.');
122
            }
123
124 4
            $this->set($key, $value);
125
        }
126
127 4
        return $this;
128
    }
129
130
    /**
131
     * Compiles parameters to URL.
132
     *
133
     * @return string
134
     */
135 12
    public function compile(): string
136
    {
137 12
        return implode('', array_filter($this->prepare()));
138
    }
139
140
    /**
141
     * Prepares data for compilation.
142
     *
143
     * @return array
144
     */
145 12
    protected function prepare(): array
146
    {
147
        return [
148 12
            $this->getScheme() ? $this->getScheme() . '://' : '',
149 12
            $this->getUser(),
150 12
            $this->getPass() ? ':' . $this->getPass() : '',
151 12
            $this->getUser() || $this->getPass() ? '@' : '',
152 12
            $this->getHost(),
153 12
            $this->getPort() ? ':' . $this->getPort() : '',
154 12
            $this->getPath() ? '/' . ltrim($this->getPath(), '/') : '',
155 12
            $this->getQuery() ? '?' . $this->getQuery() : '',
156 12
            $this->getFragment() ? '#' . $this->getFragment() : '',
157
        ];
158
    }
159
160
    /**
161
     * Gets the index of the component.
162
     *
163
     * @param  int  $component
164
     *
165
     * @return int
166
     */
167 22
    protected function componentIndex(int $component = -1): int
168
    {
169 22
        return Arr::getKey($this->components, $component, -1);
170
    }
171
172
    /**
173
     * Gets the key for the component.
174
     *
175
     * @param  int  $component
176
     *
177
     * @return string|null
178
     */
179 22
    protected function componentKey(int $component = -1): ?string
180
    {
181 22
        return Arr::get($this->components, $component);
182
    }
183
184
    /**
185
     * Checks if calling the requested key is allowed.
186
     *
187
     * @param  string|null  $key
188
     *
189
     * @return bool
190
     */
191 54
    protected function allowKey(?string $key): bool
192
    {
193 54
        return in_array($key, $this->components);
194
    }
195
196
    /**
197
     * Checks if the method is a request for information.
198
     *
199
     * @param  string  $method
200
     *
201
     * @return bool
202
     */
203 52
    protected function isGetter(string $method): bool
204
    {
205 52
        return Str::startsWith($method, 'get');
206
    }
207
208
    /**
209
     * Checks if the method is a request to fill information.
210
     *
211
     * @param  string  $method
212
     *
213
     * @return bool
214
     */
215 26
    protected function isSetter(string $method): bool
216
    {
217 26
        return Str::startsWith($method, 'set');
218
    }
219
220
    /**
221
     * Gets the key of the component from the name of the magic method.
222
     *
223
     * @param  string  $method
224
     *
225
     * @return string|null
226
     */
227 52
    protected function parseKey(string $method): ?string
228
    {
229 52
        $search = Str::startsWith($method, 'get') ? 'get' : 'set';
230
231 52
        return Str::lower(Str::after($method, $search));
232
    }
233
234
    /**
235
     * Set the component key with a value.
236
     *
237
     * @param  string  $key
238
     * @param  mixed  $value
239
     *
240
     * @return $this
241
     */
242 24
    protected function set(string $key, $value): self
243
    {
244 24
        $this->parsed[$key] = is_array($value) ? http_build_query($value) : $value;
245
246 24
        return $this;
247
    }
248
249
    /**
250
     * Gets the value of the component.
251
     *
252
     * @param  string  $key
253
     *
254
     * @return string|null
255
     */
256 44
    protected function get(string $key): ?string
257
    {
258 44
        return $this->parsed[$key] ?? null;
259
    }
260
261
    /**
262
     * Checks the number of arguments passed.
263
     *
264
     * @param  string  $method
265
     * @param  array  $args
266
     * @param  int  $need
267
     */
268 48
    protected function validateArgumentsCount(string $method, array $args, int $need = 1): void
269
    {
270 48
        if (count($args) > 1) {
271 4
            throw new ArgumentCountError($method . ' expects at most ' . $need . ' parameter, ' . count($args) . ' given.');
272
        }
273 44
    }
274
}
275