Completed
Push — master ( 4c14df...2efaf2 )
by ignace nyamagana
02:28
created

Uri::__construct()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 19
Code Lines 9

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 10
CRAP Score 1

Importance

Changes 0
Metric Value
cc 1
eloc 9
nc 1
nop 8
dl 0
loc 19
ccs 10
cts 10
cp 1
crap 1
rs 9.9666
c 0
b 0
f 0

How to fix   Many Parameters   

Many Parameters

Methods with many parameters are not only hard to understand, but their parameters also often become inconsistent when you need more, or different data.

There are several approaches to avoid long parameter lists:

1
<?php
2
3
/**
4
 * League.Uri (https://uri.thephpleague.com)
5
 *
6
 * (c) Ignace Nyamagana Butera <[email protected]>
7
 *
8
 * For the full copyright and license information, please view the LICENSE
9
 * file that was distributed with this source code.
10
 */
11
12
declare(strict_types=1);
13
14
namespace League\Uri;
15
16
use finfo;
17
use League\Uri\Contracts\UriInterface;
18
use League\Uri\Exceptions\IdnSupportMissing;
19
use League\Uri\Exceptions\SyntaxError;
20
use Psr\Http\Message\UriInterface as Psr7UriInterface;
21
use TypeError;
22
use function array_filter;
23
use function array_map;
24
use function base64_decode;
25
use function base64_encode;
26
use function count;
27
use function defined;
28
use function explode;
29
use function file_get_contents;
30
use function filter_var;
31
use function function_exists;
32
use function idn_to_ascii;
33
use function implode;
34
use function in_array;
35
use function inet_pton;
36
use function is_scalar;
37
use function mb_detect_encoding;
38
use function method_exists;
39
use function preg_match;
40
use function preg_replace;
41
use function preg_replace_callback;
42
use function rawurlencode;
43
use function sprintf;
44
use function str_replace;
45
use function strlen;
46
use function strpos;
47
use function strtolower;
48
use function substr;
49
use const FILEINFO_MIME;
50
use const FILTER_FLAG_IPV4;
51
use const FILTER_FLAG_IPV6;
52
use const FILTER_NULL_ON_FAILURE;
53
use const FILTER_VALIDATE_BOOLEAN;
54
use const FILTER_VALIDATE_IP;
55
use const IDNA_CHECK_BIDI;
56
use const IDNA_CHECK_CONTEXTJ;
57
use const IDNA_ERROR_BIDI;
58
use const IDNA_ERROR_CONTEXTJ;
59
use const IDNA_ERROR_DISALLOWED;
60
use const IDNA_ERROR_DOMAIN_NAME_TOO_LONG;
61
use const IDNA_ERROR_EMPTY_LABEL;
62
use const IDNA_ERROR_HYPHEN_3_4;
63
use const IDNA_ERROR_INVALID_ACE_LABEL;
64
use const IDNA_ERROR_LABEL_HAS_DOT;
65
use const IDNA_ERROR_LABEL_TOO_LONG;
66
use const IDNA_ERROR_LEADING_COMBINING_MARK;
67
use const IDNA_ERROR_LEADING_HYPHEN;
68
use const IDNA_ERROR_PUNYCODE;
69
use const IDNA_ERROR_TRAILING_HYPHEN;
70
use const IDNA_NONTRANSITIONAL_TO_ASCII;
71
use const IDNA_NONTRANSITIONAL_TO_UNICODE;
72
use const INTL_IDNA_VARIANT_UTS46;
73
74
final class Uri implements UriInterface
75
{
76
    /**
77
     * RFC3986 invalid characters.
78
     *
79
     * @see http://tools.ietf.org/html/rfc3986#section-2.2
80
     *
81
     * @var string
82
     */
83
    private const REGEXP_INVALID_CHARS = '/[\x00-\x1f\x7f]/';
84
85
    /**
86
     * RFC3986 Sub delimiter characters regular expression pattern.
87
     *
88
     * @see http://tools.ietf.org/html/rfc3986#section-2.2
89
     *
90
     * @var string
91
     */
92
    private const REGEXP_CHARS_SUBDELIM = "\!\$&'\(\)\*\+,;\=%";
93
94
    /**
95
     * RFC3986 unreserved characters regular expression pattern.
96
     *
97
     * @see http://tools.ietf.org/html/rfc3986#section-2.3
98
     *
99
     * @var string
100
     */
101
    private const REGEXP_CHARS_UNRESERVED = 'A-Za-z0-9_\-\.~';
102
103
104
    private const REGEXP_SCHEME = ',^[a-z]([-a-z0-9+.]+)?$,i';
105
106
    private const REGEXP_HOST_REGNAME = '/^(
107
        (?<unreserved>[a-z0-9_~\-\.])|
108
        (?<sub_delims>[!$&\'()*+,;=])|
109
        (?<encoded>%[A-F0-9]{2})
110
    )+$/x';
111
112
    private const REGEXP_HOST_GEN_DELIMS = '/[:\/?#\[\]@ ]/'; // Also includes space.
113
114
    private const REGEXP_HOST_IPFUTURE = '/^
115
        v(?<version>[A-F0-9])+\.
116
        (?:
117
            (?<unreserved>[a-z0-9_~\-\.])|
118
            (?<sub_delims>[!$&\'()*+,;=:])  # also include the : character
119
        )+
120
    $/ix';
121
122
    private const HOST_ADDRESS_BLOCK = "\xfe\x80";
123
124
    private const REGEXP_FILE_PATH = ',^(?<delim>/)?(?<root>[a-zA-Z][:|\|])(?<rest>.*)?,';
125
126
    private const REGEXP_MIMETYPE = ',^\w+/[-.\w]+(?:\+[-.\w]+)?$,';
127
128
    private const REGEXP_BINARY = ',(;|^)base64$,';
129
130
    /**
131
     * IDNA errors.
132
     */
133
    private const IDNA_ERRORS = [
134
        IDNA_ERROR_EMPTY_LABEL => 'a non-final domain name label (or the whole domain name) is empty',
135
        IDNA_ERROR_LABEL_TOO_LONG => 'a domain name label is longer than 63 bytes',
136
        IDNA_ERROR_DOMAIN_NAME_TOO_LONG => 'a domain name is longer than 255 bytes in its storage form',
137
        IDNA_ERROR_LEADING_HYPHEN => 'a label starts with a hyphen-minus ("-")',
138
        IDNA_ERROR_TRAILING_HYPHEN => 'a label ends with a hyphen-minus ("-")',
139
        IDNA_ERROR_HYPHEN_3_4 => 'a label contains hyphen-minus ("-") in the third and fourth positions',
140
        IDNA_ERROR_LEADING_COMBINING_MARK => 'a label starts with a combining mark',
141
        IDNA_ERROR_DISALLOWED => 'a label or domain name contains disallowed characters',
142
        IDNA_ERROR_PUNYCODE => 'a label starts with "xn--" but does not contain valid Punycode',
143
        IDNA_ERROR_LABEL_HAS_DOT => 'a label contains a dot=full stop',
144
        IDNA_ERROR_INVALID_ACE_LABEL => 'An ACE label does not contain a valid label string',
145
        IDNA_ERROR_BIDI => 'a label does not meet the IDNA BiDi requirements (for right-to-left characters)',
146
        IDNA_ERROR_CONTEXTJ => 'a label does not meet the IDNA CONTEXTJ requirements',
147
    ];
148
149
    private const REGEXP_WINDOW_PATH = ',^(?<root>[a-zA-Z][:|\|]),';
150
151
    /**
152
     * Supported schemes and corresponding default port.
153
     *
154
     * @var array
155
     */
156
    private const SCHEME_DEFAULT_PORT = [
157
        'data' => null,
158
        'file' => null,
159
        'ftp' => 21,
160
        'gopher' => 70,
161
        'http' => 80,
162
        'https' => 443,
163
        'ws' => 80,
164
        'wss' => 443,
165
    ];
166
167
    /**
168
     * URI validation methods per scheme.
169
     *
170
     * @var array
171
     */
172
    private const SCHEME_VALIDATION_METHOD = [
173
        'data' => 'isUriWithSchemeAndPathOnly',
174
        'file' => 'isUriWithSchemeHostAndPathOnly',
175
        'ftp' => 'isNonEmptyHostUriWithoutFragmentAndQuery',
176
        'gopher' => 'isNonEmptyHostUriWithoutFragmentAndQuery',
177
        'http' => 'isNonEmptyHostUri',
178
        'https' => 'isNonEmptyHostUri',
179
        'ws' => 'isNonEmptyHostUriWithoutFragment',
180
        'wss' => 'isNonEmptyHostUriWithoutFragment',
181
    ];
182
183
    /**
184
     * URI scheme component.
185
     *
186
     * @var string|null
187
     */
188
    private $scheme;
189
190
    /**
191
     * URI user info part.
192
     *
193
     * @var string|null
194
     */
195
    private $user_info;
196
197
    /**
198
     * URI host component.
199
     *
200
     * @var string|null
201
     */
202
    private $host;
203
204
    /**
205
     * URI port component.
206
     *
207
     * @var int|null
208
     */
209
    private $port;
210
211
    /**
212
     * URI authority string representation.
213
     *
214
     * @var string|null
215
     */
216
    private $authority;
217
218
    /**
219
     * URI path component.
220
     *
221
     * @var string
222
     */
223
    private $path = '';
224
225
    /**
226
     * URI query component.
227
     *
228
     * @var string|null
229
     */
230
    private $query;
231
232
    /**
233
     * URI fragment component.
234
     *
235
     * @var string|null
236
     */
237
    private $fragment;
238
239
    /**
240
     * URI string representation.
241
     *
242
     * @var string|null
243
     */
244
    private $uri;
245
246
    /**
247
     * Create a new instance.
248
     *
249
     * @param ?string $scheme
250
     * @param ?string $user
251
     * @param ?string $pass
252
     * @param ?string $host
253
     * @param ?int    $port
254
     * @param ?string $query
255
     * @param ?string $fragment
256
     */
257 300
    private function __construct(
258
        ?string $scheme,
259
        ?string $user,
260
        ?string $pass,
261
        ?string $host,
262
        ?int $port,
263
        string $path,
264
        ?string $query,
265
        ?string $fragment
266
    ) {
267 300
        $this->scheme = $this->formatScheme($scheme);
268 300
        $this->user_info = $this->formatUserInfo($user, $pass);
269 300
        $this->host = $this->formatHost($host);
270 300
        $this->port = $this->formatPort($port);
271 300
        $this->authority = $this->setAuthority();
272 300
        $this->path = $this->formatPath($path);
273 300
        $this->query = $this->formatQueryAndFragment($query);
274 300
        $this->fragment = $this->formatQueryAndFragment($fragment);
275 300
        $this->assertValidState();
276 286
    }
277
278
    /**
279
     * Format the Scheme and Host component.
280
     *
281
     * @param ?string $scheme
282
     *
283
     * @throws SyntaxError if the scheme is invalid
284
     */
285 306
    private function formatScheme(?string $scheme): ?string
286
    {
287 306
        if ('' === $scheme || null === $scheme) {
288 234
            return $scheme;
289
        }
290
291 238
        $formatted_scheme = strtolower($scheme);
292 238
        if (1 === preg_match(self::REGEXP_SCHEME, $formatted_scheme)) {
293 238
            return $formatted_scheme;
294
        }
295
296 2
        throw new SyntaxError(sprintf('The scheme `%s` is invalid', $scheme));
297
    }
298
299
    /**
300
     * Set the UserInfo component.
301
     *
302
     * @param ?string $user
303
     * @param ?string $password
304
     */
305 308
    private function formatUserInfo(?string $user, ?string $password): ?string
306
    {
307 308
        if (null === $user) {
308 282
            return $user;
309
        }
310
311 56
        static $user_pattern = '/(?:[^%'.self::REGEXP_CHARS_UNRESERVED.self::REGEXP_CHARS_SUBDELIM.']++|%(?![A-Fa-f0-9]{2}))/';
312 56
        $user = preg_replace_callback($user_pattern, [Uri::class, 'urlEncodeMatch'], $user);
313 56
        if (null === $password) {
314 6
            return $user;
315
        }
316
317 56
        static $password_pattern = '/(?:[^%:'.self::REGEXP_CHARS_UNRESERVED.self::REGEXP_CHARS_SUBDELIM.']++|%(?![A-Fa-f0-9]{2}))/';
318
319 56
        return $user.':'.preg_replace_callback($password_pattern, [Uri::class, 'urlEncodeMatch'], $password);
320
    }
321
322
    /**
323
     * Returns the RFC3986 encoded string matched.
324
     */
325 10
    private static function urlEncodeMatch(array $matches): string
326
    {
327 10
        return rawurlencode($matches[0]);
328
    }
329
330
    /**
331
     * Validate and Format the Host component.
332
     *
333
     * @param ?string $host
334
     */
335 332
    private function formatHost(?string $host): ?string
336
    {
337 332
        if (null === $host || '' === $host) {
338 234
            return $host;
339
        }
340
341 266
        if ('[' !== $host[0]) {
342 266
            return $this->formatRegisteredName($host);
343
        }
344
345 2
        return $this->formatIp($host);
346
    }
347
348
    /**
349
     * Validate and format a registered name.
350
     *
351
     * The host is converted to its ascii representation if needed
352
     *
353
     * @throws IdnSupportMissing if the submitted host required missing or misconfigured IDN support
354
     * @throws SyntaxError       if the submitted host is not a valid registered name
355
     */
356 264
    private function formatRegisteredName(string $host): string
357
    {
358
        // @codeCoverageIgnoreStart
359
        // added because it is not possible in travis to disabled the ext/intl extension
360
        // see travis issue https://github.com/travis-ci/travis-ci/issues/4701
361
        static $idn_support = null;
362
        $idn_support = $idn_support ?? function_exists('idn_to_ascii') && defined('INTL_IDNA_VARIANT_UTS46');
363
        // @codeCoverageIgnoreEnd
364
365 264
        $formatted_host = rawurldecode(strtolower($host));
366 264
        if (1 === preg_match(self::REGEXP_HOST_REGNAME, $formatted_host)) {
367 264
            if (false === strpos($formatted_host, 'xn--')) {
368 260
                return $formatted_host;
369
            }
370
371
            // @codeCoverageIgnoreStart
372
            if (!$idn_support) {
373
                throw new IdnSupportMissing(sprintf('the host `%s` could not be processed for IDN. Verify that ext/intl is installed for IDN support and that ICU is at least version 4.6.', $host));
374
            }
375
            // @codeCoverageIgnoreEnd
376
377 8
            $unicode = idn_to_utf8(
378 8
                $host,
379 8
                IDNA_CHECK_BIDI | IDNA_CHECK_CONTEXTJ | IDNA_NONTRANSITIONAL_TO_UNICODE,
380 8
                INTL_IDNA_VARIANT_UTS46,
381 4
                $arr
382
            );
383
384 8
            if (0 !== $arr['errors']) {
385 2
                throw new SyntaxError(sprintf('The host `%s` is invalid : %s', $host, $this->getIDNAErrors($arr['errors'])));
386
            }
387
388
            // @codeCoverageIgnoreStart
389
            if (false === $unicode) {
390
                throw new IdnSupportMissing(sprintf('The Intl extension is misconfigured for %s, please correct this issue before proceeding.', PHP_OS));
391
            }
392
            // @codeCoverageIgnoreEnd
393
394 6
            return $formatted_host;
395
        }
396
397 6
        if (1 === preg_match(self::REGEXP_HOST_GEN_DELIMS, $formatted_host)) {
398 2
            throw new SyntaxError(sprintf('The host `%s` is invalid : a registered name can not contain URI delimiters or spaces', $host));
399
        }
400
401
        // @codeCoverageIgnoreStart
402
        if (!$idn_support) {
403
            throw new IdnSupportMissing(sprintf('the host `%s` could not be processed for IDN. Verify that ext/intl is installed for IDN support and that ICU is at least version 4.6.', $host));
404
        }
405
        // @codeCoverageIgnoreEnd
406
407 4
        $formatted_host = idn_to_ascii(
408 4
            $formatted_host,
409 4
            IDNA_CHECK_BIDI | IDNA_CHECK_CONTEXTJ | IDNA_NONTRANSITIONAL_TO_ASCII,
410 4
            INTL_IDNA_VARIANT_UTS46,
411 4
            $arr
412
        );
413 4
        if (0 !== $arr['errors']) {
414 2
            throw new SyntaxError(sprintf('The host `%s` is invalid : %s', $host, $this->getIDNAErrors($arr['errors'])));
415
        }
416
417
        // @codeCoverageIgnoreStart
418
        if (false === $formatted_host) {
419
            throw new IdnSupportMissing(sprintf('The Intl extension is misconfigured for %s, please correct this issue before proceeding.', PHP_OS));
420
        }
421
        // @codeCoverageIgnoreEnd
422
423 2
        return $arr['result'];
424
    }
425
426
    /**
427
     * Retrieves and format IDNA conversion error message.
428
     *
429
     * @see http://icu-project.org/apiref/icu4j/com/ibm/icu/text/IDNA.Error.html
430
     */
431 4
    private function getIDNAErrors(int $error_byte): string
432
    {
433 4
        $res = [];
434 4
        foreach (self::IDNA_ERRORS as $error => $reason) {
435 4
            if ($error === ($error_byte & $error)) {
436 4
                $res[] = $reason;
437
            }
438
        }
439
440 4
        return [] === $res ? 'Unknown IDNA conversion error.' : implode(', ', $res).'.';
441
    }
442
443
    /**
444
     * Validate and Format the IPv6/IPvfuture host.
445
     *
446
     * @throws SyntaxError if the submitted host is not a valid IP host
447
     */
448 16
    private function formatIp(string $host): string
449
    {
450 16
        $ip = substr($host, 1, -1);
451 16
        if (false !== filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6)) {
452 2
            return $host;
453
        }
454
455 14
        if (1 === preg_match(self::REGEXP_HOST_IPFUTURE, $ip, $matches) && !in_array($matches['version'], ['4', '6'], true)) {
456 2
            return $host;
457
        }
458
459 12
        $pos = strpos($ip, '%');
460 12
        if (false === $pos) {
461 4
            throw new SyntaxError(sprintf('The host `%s` is invalid : the IP host is malformed', $host));
462
        }
463
464 8
        if (1 === preg_match(self::REGEXP_HOST_GEN_DELIMS, rawurldecode(substr($ip, $pos)))) {
465 2
            throw new SyntaxError(sprintf('The host `%s` is invalid : the IP host is malformed', $host));
466
        }
467
468 6
        $ip = substr($ip, 0, $pos);
469 6
        if (false === filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6)) {
470 2
            throw new SyntaxError(sprintf('The host `%s` is invalid : the IP host is malformed', $host));
471
        }
472
473
        //Only the address block fe80::/10 can have a Zone ID attach to
474
        //let's detect the link local significant 10 bits
475 4
        if (0 === strpos((string) inet_pton($ip), self::HOST_ADDRESS_BLOCK)) {
476 2
            return $host;
477
        }
478
479 2
        throw new SyntaxError(sprintf('The host `%s` is invalid : the IP host is malformed', $host));
480
    }
481
482
    /**
483
     * Format the Port component.
484
     *
485
     * @param null|mixed $port
486
     */
487 328
    private function formatPort($port = null): ?int
488
    {
489 328
        if (null === $port || '' === $port) {
490 278
            return null;
491
        }
492
493 92
        if (!is_int($port) && !(is_string($port) && 1 === preg_match('/^\d*$/', $port))) {
494 2
            throw new SyntaxError(sprintf('The port `%s` is invalid', $port));
495
        }
496
497 92
        $port = (int) $port;
498 92
        if (0 > $port) {
499 2
            throw new SyntaxError(sprintf('The port `%s` is invalid', $port));
500
        }
501
502 92
        $defaultPort = self::SCHEME_DEFAULT_PORT[$this->scheme] ?? null;
503 92
        if ($defaultPort === $port) {
504 14
            return null;
505
        }
506
507 84
        return $port;
508
    }
509
510
    /**
511
     * {@inheritdoc}
512
     */
513 18
    public static function __set_state(array $components): self
514
    {
515 18
        $components['user'] = null;
516 18
        $components['pass'] = null;
517 18
        if (null !== $components['user_info']) {
518 14
            [$components['user'], $components['pass']] = explode(':', $components['user_info'], 2) + [1 => null];
519
        }
520
521 18
        return new self(
522 18
            $components['scheme'],
523 18
            $components['user'],
524 18
            $components['pass'],
525 18
            $components['host'],
526 18
            $components['port'],
527 18
            $components['path'],
528 18
            $components['query'],
529 18
            $components['fragment']
530
        );
531
    }
532
533
    /**
534
     * Create a new instance from a URI and a Base URI.
535
     *
536
     * The returned URI must be absolute.
537
     *
538
     * @param mixed $uri      the input URI to create
539
     * @param mixed $base_uri the base URI used for reference
540
     */
541 86
    public static function createFromBaseUri($uri, $base_uri = null): UriInterface
542
    {
543 86
        if (!$uri instanceof UriInterface) {
544 86
            $uri = self::createFromString($uri);
545
        }
546
547 86
        if (null === $base_uri) {
548 6
            if (null === $uri->getScheme()) {
549 2
                throw new SyntaxError(sprintf('the URI `%s` must be absolute', (string) $uri));
550
            }
551
552 4
            if (null === $uri->getAuthority()) {
553 2
                return $uri;
554
            }
555
556
            /** @var UriInterface $uri */
557 2
            $uri = UriResolver::resolve($uri, $uri->withFragment(null)->withQuery(null)->withPath(''));
558
559 2
            return $uri;
560
        }
561
562 80
        if (!$base_uri instanceof UriInterface) {
563 80
            $base_uri = self::createFromString($base_uri);
564
        }
565
566 80
        if (null === $base_uri->getScheme()) {
567 2
            throw new SyntaxError(sprintf('the base URI `%s` must be absolute', (string) $base_uri));
568
        }
569
570
        /** @var UriInterface $uri */
571 78
        $uri = UriResolver::resolve($uri, $base_uri);
572
573 78
        return $uri;
574
    }
575
576
    /**
577
     * Create a new instance from a string.
578
     *
579
     * @param string|mixed $uri
580
     */
581 278
    public static function createFromString($uri = ''): self
582
    {
583 278
        $components = UriString::parse($uri);
584
585 278
        return new self(
586 278
            $components['scheme'],
587 278
            $components['user'],
588 278
            $components['pass'],
589 278
            $components['host'],
590 278
            $components['port'],
591 278
            $components['path'],
592 278
            $components['query'],
593 278
            $components['fragment']
594
        );
595
    }
596
597
    /**
598
     * Create a new instance from a hash of parse_url parts.
599
     *
600
     * Create an new instance from a hash representation of the URI similar
601
     * to PHP parse_url function result
602
     */
603 90
    public static function createFromComponents(array $components = []): self
604
    {
605
        $components += [
606 90
            'scheme' => null, 'user' => null, 'pass' => null, 'host' => null,
607
            'port' => null, 'path' => '', 'query' => null, 'fragment' => null,
608
        ];
609
610 90
        return new self(
611 90
            $components['scheme'],
612 90
            $components['user'],
613 90
            $components['pass'],
614 90
            $components['host'],
615 90
            $components['port'],
616 90
            $components['path'],
617 90
            $components['query'],
618 90
            $components['fragment']
619
        );
620
    }
621
622
    /**
623
     * Create a new instance from a data file path.
624
     *
625
     * @param resource|null $context
626
     *
627
     * @throws SyntaxError If the file does not exist or is not readable
628
     */
629 6
    public static function createFromDataPath(string $path, $context = null): self
630
    {
631 6
        $file_args = [$path, false];
632 6
        $mime_args = [$path, FILEINFO_MIME];
633 6
        if (null !== $context) {
634 4
            $file_args[] = $context;
635 4
            $mime_args[] = $context;
636
        }
637
638 6
        $raw = @file_get_contents(...$file_args);
0 ignored issues
show
Bug introduced by
$file_args is expanded, but the parameter $filename of file_get_contents() 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

638
        $raw = @file_get_contents(/** @scrutinizer ignore-type */ ...$file_args);
Loading history...
639 6
        if (false === $raw) {
640 2
            throw new SyntaxError(sprintf('The file `%s` does not exist or is not readable', $path));
641
        }
642
643 4
        $mimetype = (string) (new finfo(FILEINFO_MIME))->file(...$mime_args);
0 ignored issues
show
Bug introduced by
$mime_args is expanded, but the parameter $file_name of finfo::file() 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

643
        $mimetype = (string) (new finfo(FILEINFO_MIME))->file(/** @scrutinizer ignore-type */ ...$mime_args);
Loading history...
644
645 4
        return Uri::createFromComponents([
646 4
            'scheme' => 'data',
647 4
            'path' => str_replace(' ', '', $mimetype.';base64,'.base64_encode($raw)),
648
        ]);
649
    }
650
651
    /**
652
     * Create a new instance from a Unix path string.
653
     */
654 10
    public static function createFromUnixPath(string $uri = ''): self
655
    {
656 10
        $uri = implode('/', array_map('rawurlencode', explode('/', $uri)));
657 10
        if ('/' !== ($uri[0] ?? '')) {
658 4
            return Uri::createFromComponents(['path' => $uri]);
659
        }
660
661 6
        return Uri::createFromComponents(['path' => $uri, 'scheme' => 'file', 'host' => '']);
662
    }
663
664
    /**
665
     * Create a new instance from a local Windows path string.
666
     */
667 16
    public static function createFromWindowsPath(string $uri = ''): self
668
    {
669 16
        $root = '';
670 16
        if (1 === preg_match(self::REGEXP_WINDOW_PATH, $uri, $matches)) {
671 8
            $root = substr($matches['root'], 0, -1).':';
672 8
            $uri = substr($uri, strlen($root));
673
        }
674 16
        $uri = str_replace('\\', '/', $uri);
675 16
        $uri = implode('/', array_map('rawurlencode', explode('/', $uri)));
676
677
        //Local Windows absolute path
678 16
        if ('' !== $root) {
679 8
            return Uri::createFromComponents(['path' => '/'.$root.$uri, 'scheme' => 'file', 'host' => '']);
680
        }
681
682
        //UNC Windows Path
683 8
        if ('//' !== substr($uri, 0, 2)) {
684 6
            return Uri::createFromComponents(['path' => $uri]);
685
        }
686
687 2
        $parts = explode('/', substr($uri, 2), 2) + [1 => null];
688
689 2
        return Uri::createFromComponents(['host' => $parts[0], 'path' => '/'.$parts[1], 'scheme' => 'file']);
690
    }
691
692
    /**
693
     * Create a new instance from a URI object.
694
     *
695
     * @param Psr7UriInterface|UriInterface $uri the input URI to create
696
     */
697 4
    public static function createFromUri($uri): self
698
    {
699 4
        if ($uri instanceof UriInterface) {
700 2
            $user_info = $uri->getUserInfo();
701 2
            $user = null;
702 2
            $pass = null;
703 2
            if (null !== $user_info) {
704 2
                [$user, $pass] = explode(':', $user_info, 2) + [1 => null];
705
            }
706
707 2
            return new self(
708 2
                $uri->getScheme(),
709 1
                $user,
710 1
                $pass,
711 2
                $uri->getHost(),
712 2
                $uri->getPort(),
713 2
                $uri->getPath(),
714 2
                $uri->getQuery(),
715 2
                $uri->getFragment()
716
            );
717
        }
718
 
719 4
        if (!$uri instanceof Psr7UriInterface) {
0 ignored issues
show
introduced by
$uri is always a sub-type of Psr\Http\Message\UriInterface.
Loading history...
720 2
            throw new TypeError(sprintf('The object must implement the `%s` or the `%s`', Psr7UriInterface::class, UriInterface::class));
721
        }
722
723 2
        $scheme = $uri->getScheme();
724 2
        if ('' === $scheme) {
725 2
            $scheme = null;
726
        }
727
728 2
        $fragment = $uri->getFragment();
729 2
        if ('' === $fragment) {
730 2
            $fragment = null;
731
        }
732
733 2
        $query = $uri->getQuery();
734 2
        if ('' === $query) {
735 2
            $query = null;
736
        }
737
738 2
        $host = $uri->getHost();
739 2
        if ('' === $host) {
740 2
            $host = null;
741
        }
742
743 2
        $user_info = $uri->getUserInfo();
744 2
        $user = null;
745 2
        $pass = null;
746 2
        if ('' !== $user_info) {
747 2
            [$user, $pass] = explode(':', $user_info, 2) + [1 => null];
748
        }
749
750 2
        return new self(
751 2
            $scheme,
752 1
            $user,
753 1
            $pass,
754 1
            $host,
755 2
            $uri->getPort(),
756 2
            $uri->getPath(),
757 1
            $query,
758 1
            $fragment
759
        );
760
    }
761
762
    /**
763
     * Create a new instance from the environment.
764
     */
765 26
    public static function createFromServer(array $server): self
766
    {
767 26
        [$user, $pass] = self::fetchUserInfo($server);
768 26
        [$host, $port] = self::fetchHostname($server);
769 26
        [$path, $query] = self::fetchRequestUri($server);
770
771 26
        return Uri::createFromComponents([
772 26
            'scheme' => self::fetchScheme($server),
773 26
            'user' => $user,
774 26
            'pass' => $pass,
775 26
            'host' => $host,
776 26
            'port' => $port,
777 26
            'path' => $path,
778 26
            'query' => $query,
779
        ]);
780
    }
781
782
    /**
783
     * Returns the environment scheme.
784
     */
785 26
    private static function fetchScheme(array $server): string
786
    {
787 26
        $server += ['HTTPS' => ''];
788 26
        $res = filter_var($server['HTTPS'], FILTER_VALIDATE_BOOLEAN, FILTER_NULL_ON_FAILURE);
789
790 26
        return $res !== false ? 'https' : 'http';
791
    }
792
793
    /**
794
     * Returns the environment user info.
795
     */
796 28
    private static function fetchUserInfo(array $server): array
797
    {
798 28
        $server += ['PHP_AUTH_USER' => null, 'PHP_AUTH_PW' => null, 'HTTP_AUTHORIZATION' => ''];
799 28
        $user = $server['PHP_AUTH_USER'];
800 28
        $pass = $server['PHP_AUTH_PW'];
801 28
        if (0 === strpos(strtolower($server['HTTP_AUTHORIZATION']), 'basic')) {
802 4
            $userinfo = base64_decode(substr($server['HTTP_AUTHORIZATION'], 6), true);
803 4
            if (false === $userinfo) {
804 2
                throw new SyntaxError('The user info could not be detected');
805
            }
806 2
            [$user, $pass] = explode(':', $userinfo, 2) + [1 => null];
807
        }
808
809 26
        if (null !== $user) {
810 4
            $user = rawurlencode($user);
811
        }
812
813 26
        if (null !== $pass) {
814 4
            $pass = rawurlencode($pass);
815
        }
816
817 26
        return [$user, $pass];
818
    }
819
820
    /**
821
     * Returns the environment host.
822
     *
823
     * @throws SyntaxError If the host can not be detected
824
     */
825 28
    private static function fetchHostname(array $server): array
826
    {
827 28
        $server += ['SERVER_PORT' => null];
828 28
        if (null !== $server['SERVER_PORT']) {
829 26
            $server['SERVER_PORT'] = (int) $server['SERVER_PORT'];
830
        }
831
832 28
        if (isset($server['HTTP_HOST'])) {
833 18
            preg_match(',^(?<host>(\[.*\]|[^:])*)(\:(?<port>[^/?\#]*))?$,x', $server['HTTP_HOST'], $matches);
834
835
            return [
836 18
                $matches['host'],
837 18
                isset($matches['port']) ? (int) $matches['port'] : $server['SERVER_PORT'],
838
            ];
839
        }
840
841 10
        if (!isset($server['SERVER_ADDR'])) {
842 2
            throw new SyntaxError('The host could not be detected');
843
        }
844
845 8
        if (false === filter_var($server['SERVER_ADDR'], FILTER_VALIDATE_IP, FILTER_FLAG_IPV4)) {
846 2
            $server['SERVER_ADDR'] = '['.$server['SERVER_ADDR'].']';
847
        }
848
849 8
        return [$server['SERVER_ADDR'], $server['SERVER_PORT']];
850
    }
851
852
    /**
853
     * Returns the environment path.
854
     */
855 26
    private static function fetchRequestUri(array $server): array
856
    {
857 26
        $server += ['IIS_WasUrlRewritten' => null, 'UNENCODED_URL' => '', 'PHP_SELF' => '', 'QUERY_STRING' => null];
858 26
        if ('1' === $server['IIS_WasUrlRewritten'] && '' !== $server['UNENCODED_URL']) {
859 2
            return explode('?', $server['UNENCODED_URL'], 2) + [1 => null];
860
        }
861
862 24
        if (isset($server['REQUEST_URI'])) {
863 20
            [$path, ] = explode('?', $server['REQUEST_URI'], 2);
864 20
            $query = ('' !== $server['QUERY_STRING']) ? $server['QUERY_STRING'] : null;
865
866 20
            return [$path, $query];
867
        }
868
869 4
        return [$server['PHP_SELF'], $server['QUERY_STRING']];
870
    }
871
872
    /**
873
     * Generate the URI authority part.
874
     *
875
     */
876 300
    private function setAuthority(): ?string
877
    {
878 300
        $authority = null;
879 300
        if (null !== $this->user_info) {
880 48
            $authority = $this->user_info.'@';
881
        }
882
883 300
        if (null !== $this->host) {
884 252
            $authority .= $this->host;
885
        }
886
887 300
        if (null !== $this->port) {
888 56
            $authority .= ':'.$this->port;
889
        }
890
891 300
        return $authority;
892
    }
893
894
    /**
895
     * Format the Path component.
896
     */
897 312
    private function formatPath(string $path): string
898
    {
899 312
        $path = $this->formatDataPath($path);
900
901 312
        static $pattern = '/(?:[^'.self::REGEXP_CHARS_UNRESERVED.self::REGEXP_CHARS_SUBDELIM.'%:@\/}{]++\|%(?![A-Fa-f0-9]{2}))/';
902
903 312
        $path = (string) preg_replace_callback($pattern, [Uri::class, 'urlEncodeMatch'], $path);
904
905 312
        return $this->formatFilePath($path);
906
    }
907
908
    /**
909
     * Filter the Path component.
910
     *
911
     * @see https://tools.ietf.org/html/rfc2397
912
     *
913
     * @throws SyntaxError If the path is not compliant with RFC2397
914
     */
915 326
    private function formatDataPath(string $path): string
916
    {
917 326
        if ('data' !== $this->scheme) {
918 298
            return $path;
919
        }
920
921 28
        if ('' == $path) {
922 2
            return 'text/plain;charset=us-ascii,';
923
        }
924
925 26
        if (false === mb_detect_encoding($path, 'US-ASCII', true) || false === strpos($path, ',')) {
926 4
            throw new SyntaxError(sprintf('The path `%s` is invalid according to RFC2937', $path));
927
        }
928
929 22
        $parts = explode(',', $path, 2) + [1 => null];
930 22
        $mediatype = explode(';', (string) $parts[0], 2) + [1 => null];
931 22
        $data = (string) $parts[1];
932 22
        $mimetype = $mediatype[0];
933 22
        if (null === $mimetype || '' === $mimetype) {
934 4
            $mimetype = 'text/plain';
935
        }
936
937 22
        $parameters = $mediatype[1];
938 22
        if (null === $parameters || '' === $parameters) {
939 6
            $parameters = 'charset=us-ascii';
940
        }
941
942 22
        $this->assertValidPath($mimetype, $parameters, $data);
943
944 14
        return $mimetype.';'.$parameters.','.$data;
945
    }
946
947
    /**
948
     * Assert the path is a compliant with RFC2397.
949
     *
950
     * @see https://tools.ietf.org/html/rfc2397
951
     *
952
     * @throws SyntaxError If the mediatype or the data are not compliant with the RFC2397
953
     */
954 22
    private function assertValidPath(string $mimetype, string $parameters, string $data): void
955
    {
956 22
        if (1 !== preg_match(self::REGEXP_MIMETYPE, $mimetype)) {
957 2
            throw new SyntaxError(sprintf('The path mimetype `%s` is invalid', $mimetype));
958
        }
959
960 20
        $is_binary = 1 === preg_match(self::REGEXP_BINARY, $parameters, $matches);
961 20
        if ($is_binary) {
962 8
            $parameters = substr($parameters, 0, - strlen($matches[0]));
963
        }
964
965 20
        $res = array_filter(array_filter(explode(';', $parameters), [$this, 'validateParameter']));
966 20
        if ([] !== $res) {
967 4
            throw new SyntaxError(sprintf('The path paremeters `%s` is invalid', $parameters));
968
        }
969
970 16
        if (!$is_binary) {
971 12
            return;
972
        }
973
974 4
        $res = base64_decode($data, true);
975 4
        if (false === $res || $data !== base64_encode($res)) {
976 2
            throw new SyntaxError(sprintf('The path data `%s` is invalid', $data));
977
        }
978 2
    }
979
980
    /**
981
     * Validate mediatype parameter.
982
     */
983 4
    private function validateParameter(string $parameter): bool
984
    {
985 4
        $properties = explode('=', $parameter);
986
987 4
        return 2 != count($properties) || strtolower($properties[0]) === 'base64';
988
    }
989
990 320
    private function formatFilePath(string $path): string
991
    {
992 320
        if ('file' !== $this->scheme) {
993 312
            return $path;
994
        }
995
996
        $replace = static function (array $matches): string {
997 2
            return $matches['delim'].str_replace('|', ':', $matches['root']).$matches['rest'];
998 8
        };
999
1000 8
        return (string) preg_replace_callback(self::REGEXP_FILE_PATH, $replace, $path);
1001
    }
1002
1003
    /**
1004
     * Format the Query or the Fragment component.
1005
     *
1006
     * Returns a array containing:
1007
     * <ul>
1008
     * <li> the formatted component (a string or null)</li>
1009
     * <li> a boolean flag telling wether the delimiter is to be added to the component
1010
     * when building the URI string representation</li>
1011
     * </ul>
1012
     *
1013
     * @param ?string $component
1014
     */
1015 306
    private function formatQueryAndFragment(?string $component): ?string
1016
    {
1017 306
        if (null === $component || '' === $component) {
1018 282
            return $component;
1019
        }
1020
1021 206
        static $pattern = '/(?:[^'.self::REGEXP_CHARS_UNRESERVED.self::REGEXP_CHARS_SUBDELIM.'%:@\/\?]++|%(?![A-Fa-f0-9]{2}))/';
1022 206
        return preg_replace_callback($pattern, [Uri::class, 'urlEncodeMatch'], $component);
1023
    }
1024
1025
    /**
1026
     * assert the URI internal state is valid.
1027
     *
1028
     * @see https://tools.ietf.org/html/rfc3986#section-3
1029
     * @see https://tools.ietf.org/html/rfc3986#section-3.3
1030
     *
1031
     * @throws SyntaxError if the URI is in an invalid state according to RFC3986
1032
     * @throws SyntaxError if the URI is in an invalid state according to scheme specific rules
1033
     */
1034 354
    private function assertValidState(): void
1035
    {
1036 354
        if (null !== $this->authority && ('' !== $this->path && '/' !== $this->path[0])) {
1037 4
            throw new SyntaxError('If an authority is present the path must be empty or start with a `/`');
1038
        }
1039
1040 354
        if (null === $this->authority && 0 === strpos($this->path, '//')) {
1041 10
            throw new SyntaxError(sprintf('If there is no authority the path `%s` can not start with a `//`', $this->path));
1042
        }
1043
1044 354
        $pos = strpos($this->path, ':');
1045 354
        if (null === $this->authority
1046 354
            && null === $this->scheme
1047 354
            && false !== $pos
1048 354
            && false === strpos(substr($this->path, 0, $pos), '/')
1049
        ) {
1050 6
            throw new SyntaxError('In absence of a scheme and an authority the first path segment cannot contain a colon (":") character.');
1051
        }
1052
1053 354
        $validationMethod = self::SCHEME_VALIDATION_METHOD[$this->scheme] ?? null;
1054 354
        if (null === $validationMethod || true === $this->$validationMethod()) {
1055 326
            $this->uri = null;
1056
1057 326
            return;
1058
        }
1059
1060 38
        throw new SyntaxError(sprintf('The uri `%s` is invalid for the data scheme', (string) $this));
1061
    }
1062
1063
    /**
1064
     * URI validation for URI schemes which allows only scheme and path components.
1065
     */
1066 2
    private function isUriWithSchemeAndPathOnly()
1067
    {
1068 2
        return null === $this->authority
1069 2
            && null === $this->query
1070 2
            && null === $this->fragment;
1071
    }
1072
1073
    /**
1074
     * URI validation for URI schemes which allows only scheme, host and path components.
1075
     */
1076 20
    private function isUriWithSchemeHostAndPathOnly()
1077
    {
1078 20
        return null === $this->user_info
1079 20
            && null === $this->port
1080 20
            && null === $this->query
1081 20
            && null === $this->fragment
1082 20
            && !('' != $this->scheme && null === $this->host);
1083
    }
1084
1085
    /**
1086
     * URI validation for URI schemes which disallow the empty '' host.
1087
     */
1088 246
    private function isNonEmptyHostUri()
1089
    {
1090 246
        return '' !== $this->host
1091 246
            && !(null !== $this->scheme && null === $this->host);
1092
    }
1093
1094
    /**
1095
     * URI validation for URIs schemes which disallow the empty '' host
1096
     * and forbids the fragment component.
1097
     */
1098 18
    private function isNonEmptyHostUriWithoutFragment()
1099
    {
1100 18
        return $this->isNonEmptyHostUri() && null === $this->fragment;
1101
    }
1102
1103
    /**
1104
     * URI validation for URIs schemes which disallow the empty '' host
1105
     * and forbids fragment and query components.
1106
     */
1107 22
    private function isNonEmptyHostUriWithoutFragmentAndQuery()
1108
    {
1109 22
        return $this->isNonEmptyHostUri() && null === $this->fragment && null === $this->query;
1110
    }
1111
1112
    /**
1113
     * Generate the URI string representation from its components.
1114
     *
1115
     * @see https://tools.ietf.org/html/rfc3986#section-5.3
1116
     * @param ?string $scheme
1117
     * @param ?string $authority
1118
     * @param ?string $query
1119
     * @param ?string $fragment
1120
     */
1121 250
    private function getUriString(
1122
        ?string $scheme,
1123
        ?string $authority,
1124
        string $path,
1125
        ?string $query,
1126
        ?string $fragment
1127
    ): string {
1128 250
        if (null !== $scheme) {
1129 132
            $scheme = $scheme.':';
1130
        }
1131
1132 250
        if (null !== $authority) {
1133 122
            $authority = '//'.$authority;
1134
        }
1135
1136 250
        if (null !== $query) {
1137 40
            $query = '?'.$query;
1138
        }
1139
1140 250
        if (null !== $fragment) {
1141 34
            $fragment = '#'.$fragment;
1142
        }
1143
1144 250
        return $scheme.$authority.$path.$query.$fragment;
1145
    }
1146
1147
    /**
1148
     * {@inheritDoc}.
1149
     */
1150 260
    public function __toString(): string
1151
    {
1152 260
        $this->uri = $this->uri ?? $this->getUriString(
1153 260
            $this->scheme,
1154 260
            $this->authority,
1155 260
            $this->path,
1156 260
            $this->query,
1157 260
            $this->fragment
1158
        );
1159
1160 260
        return $this->uri;
1161
    }
1162
1163
    /**
1164
     * {@inheritdoc}
1165
     */
1166 2
    public function jsonSerialize(): string
1167
    {
1168 2
        return $this->__toString();
1169
    }
1170
1171
    /**
1172
     * {@inheritdoc}
1173
     */
1174 2
    public function __debugInfo(): array
1175
    {
1176
        return [
1177 2
            'scheme' => $this->scheme,
1178 2
            'user_info' => isset($this->user_info) ? preg_replace(',\:(.*).?$,', ':***', $this->user_info) : null,
1179 2
            'host' => $this->host,
1180 2
            'port' => $this->port,
1181 2
            'path' => $this->path,
1182 2
            'query' => $this->query,
1183 2
            'fragment' => $this->fragment,
1184
        ];
1185
    }
1186
1187
    /**
1188
     * {@inheritDoc}.
1189
     */
1190 230
    public function getScheme(): ?string
1191
    {
1192 230
        return $this->scheme;
1193
    }
1194
1195
    /**
1196
     * {@inheritDoc}.
1197
     */
1198 198
    public function getAuthority(): ?string
1199
    {
1200 198
        return $this->authority;
1201
    }
1202
1203
    /**
1204
     * {@inheritDoc}.
1205
     */
1206 96
    public function getUserInfo(): ?string
1207
    {
1208 96
        return $this->user_info;
1209
    }
1210
1211
    /**
1212
     * {@inheritDoc}.
1213
     */
1214 2
    public function getUser(): ?string
1215
    {
1216 2
        if (null === $this->user_info) {
1217 2
            return null;
1218
        }
1219
1220 2
        [$user, ] = explode(':', $this->user_info, 2);
1221
1222 2
        return $user;
1223
    }
1224
1225
    /**
1226
     * {@inheritDoc}.
1227
     */
1228 2
    public function getPass(): ?string
1229
    {
1230 2
        if (null === $this->user_info) {
1231 2
            return null;
1232
        }
1233
1234 2
        [$user, $pass] = explode(':', $this->user_info, 2) + [1 => null];
1235
1236 2
        return $pass;
1237
    }
1238
1239
    /**
1240
     * {@inheritDoc}.
1241
     */
1242 208
    public function getHost(): ?string
1243
    {
1244 208
        return $this->host;
1245
    }
1246
1247
    /**
1248
     * {@inheritDoc}.
1249
     */
1250 238
    public function getPort(): ?int
1251
    {
1252 238
        return $this->port;
1253
    }
1254
1255
    /**
1256
     * {@inheritDoc}.
1257
     */
1258 204
    public function getPath(): string
1259
    {
1260 204
        return $this->path;
1261
    }
1262
1263
    /**
1264
     * {@inheritDoc}.
1265
     */
1266 114
    public function getQuery(): ?string
1267
    {
1268 114
        return $this->query;
1269
    }
1270
1271
    /**
1272
     * {@inheritDoc}.
1273
     */
1274 26
    public function getFragment(): ?string
1275
    {
1276 26
        return $this->fragment;
1277
    }
1278
1279
    /**
1280
     * {@inheritDoc}.
1281
     */
1282 146
    public function withScheme($scheme): UriInterface
1283
    {
1284 146
        $scheme = $this->formatScheme($this->filterString($scheme));
1285 144
        if ($scheme === $this->scheme) {
1286 10
            return $this;
1287
        }
1288
1289 136
        $clone = clone $this;
1290 136
        $clone->scheme = $scheme;
1291 136
        $clone->port = $clone->formatPort($clone->port);
1292 136
        $clone->authority = $clone->setAuthority();
1293 136
        $clone->assertValidState();
1294
1295 136
        return $clone;
1296
    }
1297
1298
    /**
1299
     * Filter a string.
1300
     *
1301
     * @param mixed $str the value to evaluate as a string
1302
     *
1303
     * @throws SyntaxError if the submitted data can not be converted to string
1304
     */
1305 206
    private function filterString($str): ?string
1306
    {
1307 206
        if (null === $str) {
1308 146
            return $str;
1309
        }
1310
1311 204
        if (!is_scalar($str) && !method_exists($str, '__toString')) {
1312 2
            throw new TypeError(sprintf('The component must be a string, a scalar or a stringable object %s given', gettype($str)));
1313
        }
1314
1315 202
        $str = (string) $str;
1316 202
        if (1 !== preg_match(self::REGEXP_INVALID_CHARS, $str)) {
1317 200
            return $str;
1318
        }
1319
1320 2
        throw new SyntaxError(sprintf('The component `%s` contains invalid characters', $str));
1321
    }
1322
1323
    /**
1324
     * {@inheritDoc}.
1325
     * @param null|mixed $password
1326
     */
1327 146
    public function withUserInfo($user, $password = null): UriInterface
1328
    {
1329 146
        $user_info = null;
1330 146
        $user = $this->filterString($user);
1331 146
        if (null !== $password) {
1332 16
            $password = $this->filterString($password);
1333
        }
1334
1335 146
        if ('' !== $user) {
1336 76
            $user_info = $this->formatUserInfo($user, $password);
1337
        }
1338
1339 146
        if ($user_info === $this->user_info) {
1340 128
            return $this;
1341
        }
1342
1343 20
        $clone = clone $this;
1344 20
        $clone->user_info = $user_info;
1345 20
        $clone->authority = $clone->setAuthority();
1346 20
        $clone->assertValidState();
1347
1348 20
        return $clone;
1349
    }
1350
1351
    /**
1352
     * {@inheritDoc}.
1353
     */
1354 178
    public function withHost($host): UriInterface
1355
    {
1356 178
        $host = $this->formatHost($this->filterString($host));
1357 176
        if ($host === $this->host) {
1358 96
            return $this;
1359
        }
1360
1361 132
        $clone = clone $this;
1362 132
        $clone->host = $host;
1363 132
        $clone->authority = $clone->setAuthority();
1364 132
        $clone->assertValidState();
1365
1366 132
        return $clone;
1367
    }
1368
1369
    /**
1370
     * {@inheritDoc}.
1371
     */
1372 136
    public function withPort($port): UriInterface
1373
    {
1374 136
        $port = $this->formatPort($port);
1375 132
        if ($port === $this->port) {
1376 130
            return $this;
1377
        }
1378
1379 4
        $clone = clone $this;
1380 4
        $clone->port = $port;
1381 4
        $clone->authority = $clone->setAuthority();
1382 4
        $clone->assertValidState();
1383
1384 4
        return $clone;
1385
    }
1386
1387
    /**
1388
     * {@inheritDoc}.
1389
     */
1390 172
    public function withPath($path): UriInterface
1391
    {
1392 172
        $path = $this->filterString($path);
1393 172
        if (null === $path) {
0 ignored issues
show
introduced by
The condition null === $path is always false.
Loading history...
1394 2
            throw new TypeError('A path must be a string NULL given');
1395
        }
1396
1397 170
        $path = $this->formatPath($path);
1398 170
        if ($path === $this->path) {
1399 34
            return $this;
1400
        }
1401
1402 158
        $clone = clone $this;
1403 158
        $clone->path = $path;
1404 158
        $clone->assertValidState();
1405
1406 146
        return $clone;
1407
    }
1408
1409
    /**
1410
     * {@inheritDoc}.
1411
     */
1412 104
    public function withQuery($query): UriInterface
1413
    {
1414 104
        $query = $this->formatQueryAndFragment($this->filterString($query));
1415 104
        if ($query === $this->query) {
1416 96
            return $this;
1417
        }
1418
1419 14
        $clone = clone $this;
1420 14
        $clone->query = $query;
1421 14
        $clone->assertValidState();
1422
1423 14
        return $clone;
1424
    }
1425
1426
    /**
1427
     * {@inheritDoc}.
1428
     */
1429 24
    public function withFragment($fragment): UriInterface
1430
    {
1431 24
        $fragment = $this->formatQueryAndFragment($this->filterString($fragment));
1432 24
        if ($fragment === $this->fragment) {
1433 24
            return $this;
1434
        }
1435
1436 4
        $clone = clone $this;
1437 4
        $clone->fragment = $fragment;
1438 4
        $clone->assertValidState();
1439
1440 4
        return $clone;
1441
    }
1442
}
1443