Passed
Pull Request — main (#146)
by Andrey
14:27
created

Builder::resolveSame()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 6

Importance

Changes 0
Metric Value
cc 2
eloc 1
c 0
b 0
f 0
nc 2
nop 1
dl 0
loc 3
ccs 0
cts 0
cp 0
crap 6
rs 10
1
<?php
2
/******************************************************************************
3
 * This file is part of the "andrey-helldar/support" project.                 *
4
 *                                                                            *
5
 *
6
 * @see https://github.com/andrey-helldar/support                             *
7
 *                                                                            *
8
 * For the full copyright and license information, please view the LICENSE    *
9
 * file that was distributed with this source code.                           *
10
 ******************************************************************************@author Andrey Helldar <[email protected]>                                *
11
 *                                                                            *
12
 * @license MIT                                                               *
13
 *                                                                            *
14
 * @copyright 2021 Andrey Helldar                                             *
15
 *                                                                            *
16
 */
17
18
namespace Helldar\Support\Helpers\Http;
19
20
use Helldar\Support\Concerns\Castable;
21
use Helldar\Support\Concerns\Validation;
22
use Helldar\Support\Exceptions\UnknownUrlComponentIndexException;
23
use Helldar\Support\Facades\Helpers\Ables\Arrayable;
24
use Helldar\Support\Facades\Helpers\Arr;
25
use Helldar\Support\Facades\Helpers\Str;
26
use Helldar\Support\Facades\Http\Url as UrlHelper;
27
use Helldar\Support\Tools\HttpBuilderPrepare;
28
use Psr\Http\Message\UriInterface;
29
30
class Builder implements UriInterface
31
{
32
    use Castable;
33
    use Validation;
34
35
    public const PHP_URL_ALL = -1;
36
37
    protected $parsed = [];
38
39
    protected $components = [
40
        PHP_URL_SCHEME   => 'scheme',
41
        PHP_URL_HOST     => 'host',
42
        PHP_URL_PORT     => 'port',
43
        PHP_URL_USER     => 'user',
44
        PHP_URL_PASS     => 'pass',
45
        PHP_URL_QUERY    => 'query',
46
        PHP_URL_PATH     => 'path',
47
        PHP_URL_FRAGMENT => 'fragment',
48
    ];
49
50
    protected $casts = [
51
        'query' => 'array',
52
        'port'  => 'integer',
53
    ];
54
55
    protected $validate = [
56
        PHP_URL_SCHEME   => ['null', 'string'],
57
        PHP_URL_HOST     => ['null', 'string'],
58
        PHP_URL_PORT     => ['null', 'integer'],
59
        PHP_URL_USER     => ['null', 'string'],
60
        PHP_URL_PASS     => ['null', 'string'],
61
        PHP_URL_QUERY    => ['null', 'string', 'array'],
62
        PHP_URL_PATH     => ['null', 'string'],
63
        PHP_URL_FRAGMENT => ['null', 'string'],
64
    ];
65
66
    /**
67
     * Return the string representation as a URI reference.
68
     *
69
     * @return string
70 13
     */
71
    public function __toString()
72 13
    {
73
        return $this->toUrl();
74
    }
75
76
    /**
77
     * Gets the current instance of the object.
78
     *
79
     * @return \Helldar\Support\Helpers\Http\Builder
80 12
     */
81
    public function same(): self
82 12
    {
83
        return $this;
84
    }
85
86
    /**
87
     * Parse a URL.
88
     *
89
     * @param  \Psr\Http\Message\UriInterface|string|null  $url
90
     * @param  int  $component
91
     *
92
     * @return \Helldar\Support\Helpers\Http\Builder
93 138
     */
94
    public function parse($url, int $component = self::PHP_URL_ALL): self
95 138
    {
96 136
        if ($component === self::PHP_URL_ALL) {
97
            UrlHelper::validate($url);
98
        }
99 132
100
        $instance = $this->resolveSame($component);
101 132
102 130
        $key = $this->componentNameByIndex($component);
103 2
104
        return $component === self::PHP_URL_ALL || empty($key)
105 132
            ? $instance->parsed(parse_url($url))
106
            : $instance->parsed([$key => parse_url($url, $component)]);
107 132
    }
108
109
    /**
110
     * Populate an object with parsed data.
111
     *
112
     * @param  array  $parsed
113
     *
114
     * @return \Helldar\Support\Helpers\Http\Builder
115
     */
116
    public function parsed(array $parsed): self
117 10
    {
118
        $components = array_values($this->components);
119 10
120
        $filtered = Arr::only($parsed, $components);
121 10
122
        $this->parsed = Arrayable::of($this->parsed)
123 10
            ->merge($filtered)
124
            ->get();
125 10
126
        $this->cast($this->parsed);
127
128
        return $this;
129
    }
130
131
    /**
132
     * Retrieve the domain name of the URI.
133 6
     *
134
     * @return string
135 6
     */
136
    public function getDomain(): string
137
    {
138
        return $this->getHost();
139
    }
140
141
    /**
142
     * Retrieve the subdomain name of the URI.
143 6
     *
144
     * @return string
145 6
     */
146
    public function getSubDomain(): string
147 6
    {
148
        $host = explode('.', $this->getHost());
149
150
        return count($host) > 2 ? reset($host) : '';
151
    }
152
153
    /**
154
     * Retrieve the scheme and host of the URI.
155 6
     *
156
     * @return string
157 6
     */
158 6
    public function getBaseUrl(): string
159
    {
160 6
        $schema = $this->getScheme();
161
        $host   = $this->getHost();
162
163
        return (string) Str::of("$schema://$host")->trim('://');
164
    }
165
166
    /**
167
     * Retrieve the scheme component of the URI.
168 60
     *
169
     * @return string
170 60
     */
171
    public function getScheme(): string
172
    {
173
        return (string) $this->get(PHP_URL_SCHEME);
174
    }
175
176
    /**
177
     * Retrieve the authority component of the URI.
178 10
     *
179
     * @return string
180 10
     */
181 10
    public function getAuthority(): string
182 10
    {
183
        $auth = $this->getUserInfo();
184 10
        $host = $this->getHost();
185
        $port = $this->getPort();
186
187
        return (string) Str::of("$auth@$host:$port")->trim('@:');
188
    }
189
190
    /**
191
     * Retrieve the user information component of the URI.
192 23
     *
193
     * @return string
194 23
     */
195 23
    public function getUserInfo(): string
196
    {
197 23
        $user = $this->getUser();
198
        $pass = $this->getPassword();
199
200
        return (string) Str::of("$user:$pass")->trim(':');
201
    }
202
203
    /**
204
     * Retrieve the user name component of the URI.
205 73
     *
206
     * @return string
207 73
     */
208
    public function getUser(): string
209
    {
210
        return (string) $this->get(PHP_URL_USER);
211
    }
212
213
    /**
214
     * Retrieve the user password component of the URI.
215 72
     *
216
     * @return string
217 72
     */
218
    public function getPassword(): string
219
    {
220
        return (string) $this->get(PHP_URL_PASS);
221
    }
222
223
    /**
224
     * Retrieve the host component of the URI.
225 83
     *
226
     * @return string
227 83
     */
228
    public function getHost(): string
229
    {
230
        return (string) $this->get(PHP_URL_HOST);
231
    }
232
233
    /**
234
     * Retrieve the port component of the URI.
235 60
     *
236
     * @return int|null
237 60
     */
238
    public function getPort(): ?int
239
    {
240
        return $this->get(PHP_URL_PORT);
241
    }
242
243
    /**
244
     * Retrieve the path component of the URI.
245 56
     *
246
     * @return string
247 56
     */
248
    public function getPath(): string
249 56
    {
250
        $value = $this->get(PHP_URL_PATH);
251 56
252
        $path = (string) Str::of($value)->trim('/')->start('/');
253
254
        return $path !== '/' ? $path : '';
255
    }
256
257
    /**
258
     * Retrieve the query string of the URI.
259 70
     *
260
     * @return string
261 70
     */
262 33
    public function getQuery(): string
263
    {
264
        if ($value = $this->get(PHP_URL_QUERY)) {
265 46
            return is_string($value) ? $value : http_build_query($value);
266
        }
267
268
        return '';
269
    }
270
271
    /**
272
     * Retrieve the fragment component of the URI.
273 54
     *
274
     * @return string
275 54
     */
276
    public function getFragment(): string
277
    {
278
        return (string) $this->get(PHP_URL_FRAGMENT);
279
    }
280
281
    /**
282
     * Remove the fragment component from the URI.
283 4
     *
284
     * @return \Helldar\Support\Helpers\Http\Builder
285 4
     */
286
    public function removeFragment(): self
287
    {
288
        return $this->set(PHP_URL_FRAGMENT, null);
289
    }
290
291
    /**
292
     * Return an instance with the specified scheme.
293
     *
294
     * @param  string  $scheme
295 9
     *
296
     * @return \Helldar\Support\Helpers\Http\Builder
297 9
     */
298
    public function withScheme($scheme): self
299
    {
300
        return $this->set(PHP_URL_SCHEME, $scheme);
301
    }
302
303
    /**
304
     * Return an instance with the specified user information.
305
     *
306
     * @param  string  $user
307
     * @param  string|null  $password
308 9
     *
309
     * @return \Helldar\Support\Helpers\Http\Builder
310
     */
311 9
    public function withUserInfo($user, $password = null): self
312 9
    {
313
        return $this
314
            ->set(PHP_URL_USER, $user)
315
            ->set(PHP_URL_PASS, $password);
316
    }
317
318
    /**
319
     * Return an instance with the specified host.
320
     *
321
     * @param  string  $host
322 10
     *
323
     * @return \Helldar\Support\Helpers\Http\Builder
324 10
     */
325
    public function withHost($host): self
326
    {
327
        return $this->set(PHP_URL_HOST, $host);
328
    }
329
330
    /**
331
     * Return an instance with the specified port.
332
     *
333
     * @param  int|null  $port
334 8
     *
335
     * @return \Helldar\Support\Helpers\Http\Builder
336 8
     */
337
    public function withPort($port): self
338
    {
339
        return $this->set(PHP_URL_PORT, $port);
340
    }
341
342
    /**
343
     * Return an instance with the specified path.
344
     *
345
     * @param  string  $path
346 13
     *
347
     * @return \Helldar\Support\Helpers\Http\Builder
348 13
     */
349
    public function withPath($path): self
350
    {
351
        return $this->set(PHP_URL_PATH, $path);
352
    }
353
354
    /**
355
     * Return an instance with the specified query string.
356
     *
357
     * @param  array|string  $query
358 13
     *
359
     * @return \Helldar\Support\Helpers\Http\Builder
360 13
     */
361
    public function withQuery($query): self
362
    {
363
        return $this->set(PHP_URL_QUERY, $query);
364
    }
365
366
    /**
367
     * Return an instance with the specified query object.
368
     *
369
     * @param  string  $key
370
     * @param  mixed  $value
371 12
     *
372
     * @return \Helldar\Support\Helpers\Http\Builder
373 12
     */
374
    public function putQuery(string $key, $value): self
375 12
    {
376 4
        $query = $this->get(PHP_URL_QUERY);
377 4
378
        if (empty($value)) {
379
            $value = is_array($value) ? []
380 12
                : (! is_numeric($value) ? null : $value);
381
        }
382 12
383
        $query = Arr::set($query, $key, $value);
384
385
        return $this->set(PHP_URL_QUERY, $query);
386
    }
387
388
    /**
389
     * Return an instance with the specified query object.
390
     *
391
     * @param  string  $key
392 8
     *
393
     * @return \Helldar\Support\Helpers\Http\Builder
394 8
     */
395
    public function removeQuery(string $key): self
396 8
    {
397
        $query = $this->get(PHP_URL_QUERY);
398 8
399
        unset($query[$key]);
400
401
        return $this->set(PHP_URL_QUERY, $query);
402
    }
403
404
    /**
405
     * Return an instance with the specified URI fragment.
406
     *
407
     * @param  string  $fragment
408 9
     *
409
     * @return \Helldar\Support\Helpers\Http\Builder
410 9
     */
411
    public function withFragment($fragment): self
412
    {
413
        return $this->set(PHP_URL_FRAGMENT, $fragment);
414
    }
415
416
    /**
417
     * Return an instance with the specified `UriInterface`.
418
     *
419
     * @param  \Psr\Http\Message\UriInterface  $uri
420 4
     *
421
     * @return \Helldar\Support\Helpers\Http\Builder
422 4
     */
423
    public function fromPsr(UriInterface $uri): self
424 4
    {
425 4
        $this->parsed = [];
426 4
427 4
        $this->withScheme($uri->getScheme());
428 4
        $this->withHost($uri->getHost());
429 4
        $this->withPort($uri->getPort());
430
        $this->withPath($uri->getPath());
431 4
        $this->withQuery($uri->getQuery());
432
        $this->withFragment($uri->getFragment());
433 4
434 4
        $auth = explode(':', $uri->getUserInfo());
435 4
436
        $this->withUserInfo(
437
            $auth[0] ?? null,
438 4
            $auth[1] ?? null
439
        );
440 4
441
        $this->cast($this->parsed);
442
443
        return $this;
444
    }
445
446
    /**
447
     * Return the string representation as a URI reference.
448 4
     *
449
     * @return \Psr\Http\Message\UriInterface
450 4
     */
451
    public function toPsr(): UriInterface
452
    {
453
        return $this->same();
454
    }
455
456
    /**
457
     * Returns parsed data.
458 4
     *
459
     * @return null[]|string[]
460
     */
461 4
    public function toArray(): array
462 4
    {
463 4
        return [
464 4
            'scheme'   => $this->getScheme(),
465 4
            'user'     => $this->getUser(),
466 4
            'pass'     => $this->getPassword(),
467 4
            'host'     => $this->getHost(),
468 4
            'port'     => $this->getPort(),
469
            'path'     => $this->getPath(),
470
            'query'    => $this->getQuery(),
471
            'fragment' => $this->getFragment(),
472
        ];
473
    }
474
475
    /**
476
     * Return the string representation as a URI reference.
477 19
     *
478
     * @return string
479 19
     */
480 19
    public function toUrl(): string
481 19
    {
482 19
        $items = Arrayable::of($this->prepare())
483 19
            ->map(function ($value) {
484 19
                return (string) $value;
485
            })
486 19
            ->filter()
487
            ->get();
488 19
489
        $url = implode('', $items);
490
491 194
        return $url === '//' ? '' : $url;
492
    }
493 194
494
    protected function componentNameByIndex(int $component): ?string
495 194
    {
496
        $this->validateComponentIndex($component);
497
498 194
        return Arr::get($this->components, $component);
499
    }
500 194
501
    protected function validateComponentIndex(int $component): void
502 194
    {
503
        $components = array_keys($this->components);
504
505 194
        if ($component !== self::PHP_URL_ALL && ! in_array($component, $components, true)) {
506
            throw new UnknownUrlComponentIndexException($component);
507 65
        }
508
    }
509 65
510
    protected function set(int $component, $value): self
511 64
    {
512
        $this->validate($component, $value);
513 64
514
        $name = $this->componentNameByIndex($component);
515 64
516
        $this->parsed[$name] = $this->castValue($name, $value);
0 ignored issues
show
Bug introduced by
It seems like $name can also be of type null; however, parameter $key of Helldar\Support\Helpers\Http\Builder::castValue() 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

516
        $this->parsed[$name] = $this->castValue(/** @scrutinizer ignore-type */ $name, $value);
Loading history...
517
518 180
        return $this;
519
    }
520 180
521
    protected function get(int $index)
522 180
    {
523
        $name = $this->componentNameByIndex($index);
524
525 65
        return Arr::get($this->parsed, $name);
526
    }
527 65
528
    protected function getValidationType(int $component): array
529
    {
530 65
        return Arr::get($this->validate, $component, []);
531
    }
532 65
533
    protected function validate(int $component, $value): void
534 65
    {
535 64
        $type = $this->getValidationType($component);
536
537
        $this->validateType($value, $type);
538
    }
539
540
    /**
541
     * Based on code by Maksim (Ellrion) Platonov.
542
     *
543
     * @see https://gist.github.com/Ellrion/f51ba0d40ae1d62eeae44fd1adf7b704
544 19
     *
545
     * @return array
546
     */
547 19
    protected function prepare(): array
548
    {
549 19
        return [
550
            HttpBuilderPrepare::make()->of($this->getScheme())->suffix(':'),
551 19
552 19
            '//',
553
554 19
            HttpBuilderPrepare::make()->of($this->getUser()),
555
            HttpBuilderPrepare::make()->of($this->getPassword())->prefix(':'),
556 19
557 19
            $this->getUser() || $this->getPassword() ? '@' : '',
558 19
559 19
            HttpBuilderPrepare::make()->of($this->getHost()),
560 19
            HttpBuilderPrepare::make()->of($this->getPort())->prefix(':'),
561
            HttpBuilderPrepare::make()->of($this->getPath()),
562
            HttpBuilderPrepare::make()->of($this->getQuery())->prefix('?'),
563
            HttpBuilderPrepare::make()->of($this->getFragment())->prefix('#'),
564
        ];
565
    }
566
567
    protected function resolveSame(int $component = self::PHP_URL_ALL): self
568
    {
569
        return $component === self::PHP_URL_ALL ? new self() : $this;
570
    }
571
}
572