Completed
Pull Request — master (#40)
by ignace nyamagana
11:26
created

AbstractUri::withScheme()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

Changes 3
Bugs 0 Features 0
Metric Value
c 3
b 0
f 0
dl 0
loc 4
ccs 2
cts 2
cp 1
rs 10
cc 1
eloc 2
nc 1
nop 1
crap 1
1
<?php
2
/**
3
 * League.Uri (http://uri.thephpleague.com)
4
 *
5
 * @package   League.uri
6
 * @author    Ignace Nyamagana Butera <[email protected]>
7
 * @copyright 2013-2015 Ignace Nyamagana Butera
8
 * @license   https://github.com/thephpleague/uri/blob/master/LICENSE (MIT License)
9
 * @version   4.1.0
10
 * @link      https://github.com/thephpleague/uri/
11
 */
12
namespace League\Uri\Schemes\Generic;
13
14
use InvalidArgumentException;
15
use League\Uri\Interfaces\Fragment;
16
use League\Uri\Interfaces\Host;
17
use League\Uri\Interfaces\Path;
18
use League\Uri\Interfaces\Port;
19
use League\Uri\Interfaces\Query;
20
use League\Uri\Interfaces\Scheme;
21
use League\Uri\Interfaces\UserInfo;
22
use League\Uri\Types\ImmutablePropertyTrait;
23
use League\Uri\UriParser;
24
use RuntimeException;
25
26
/**
27
 * common URI Object properties and methods
28
 *
29
 * @package League.uri
30
 * @author  Ignace Nyamagana Butera <[email protected]>
31
 * @since   4.0.0
32
 */
33
abstract class AbstractUri
34
{
35
    use AuthorityValidatorTrait;
36
37
    use ImmutablePropertyTrait;
38
39
    use UriBuilderTrait;
40
41
    /**
42
     * Host Component
43
     *
44
     * @var Host
45
     */
46
    protected $host;
47
48
    /**
49
     * Scheme Component
50
     *
51
     * @var Scheme
52
     */
53
    protected $scheme;
54
55
    /**
56
     * User Information Part
57
     *
58
     * @var UserInfo
59
     */
60
    protected $userInfo;
61
62
    /**
63
     * Port Component
64
     *
65
     * @var Port
66
     */
67
    protected $port;
68
69
    /**
70
     * Path Component
71
     *
72
     * @var Path
73
     */
74
    protected $path;
75
76
    /**
77
     * Query Component
78
     *
79
     * @var Query
80
     */
81
    protected $query;
82
83
    /**
84
     * Fragment Component
85
     *
86
     * @var Fragment
87
     */
88
    protected $fragment;
89
90
    /**
91
     * Supported Schemes
92
     *
93
     * @var array
94
     */
95
    protected static $supportedSchemes = [];
96
97
    /**
98
     * Retrieve the scheme component of the URI.
99 374
     *
100
     * If no scheme is present, this method MUST return an empty string.
101 374
     *
102
     * The value returned MUST be normalized to lowercase, per RFC 3986
103
     * Section 3.1.
104
     *
105
     * The trailing ":" character is not part of the scheme and MUST NOT be
106
     * added.
107 102
     *
108
     * @see https://tools.ietf.org/html/rfc3986#section-3.1
109 102
     *
110
     * @return string The URI scheme.
111
     */
112
    public function getScheme()
113
    {
114
        return $this->scheme->__toString();
115 74
    }
116
117 74
    /**
118
     * Return an instance with the specified scheme.
119
     *
120
     * This method MUST retain the state of the current instance, and return
121
     * an instance that contains the specified scheme.
122
     *
123 76
     * An empty scheme is equivalent to removing the scheme.
124
     *
125 76
     * @param string $scheme The scheme to use with the new instance.
126 72
     *
127 72
     * @throws InvalidArgumentException for invalid schemes.
128
     * @throws InvalidArgumentException for unsupported schemes.
129 76
     * @throws RuntimeException         if the returned URI object is invalid.
130 76
     *
131 76
     * @return static A new instance with the specified scheme.
132 76
     */
133 70
    public function withScheme($scheme)
134
    {
135
        return $this->withProperty('scheme', $this->filterPropertyValue($scheme));
136 6
    }
137 6
138
    /**
139 6
     * Retrieve the user information component of the URI.
140
     *
141
     * If no user information is present, this method MUST return an empty
142
     * string.
143
     *
144
     * If a user is present in the URI, this will return that value;
145 340
     * additionally, if the password is also present, it will be appended to the
146
     * user value, with a colon (":") separating the values.
147 340
     *
148
     * The trailing "@" character is not part of the user information and MUST
149
     * NOT be added.
150
     *
151
     * @return string The URI user information, in "username[:password]" format.
152
     */
153 120
    public function getUserInfo()
154
    {
155 120
        return $this->userInfo->__toString();
156
    }
157
158
    /**
159
     * Return an instance with the specified user information.
160
     *
161 80
     * This method MUST retain the state of the current instance, and return
162
     * an instance that contains the specified user information.
163 80
     *
164
     * Password is optional, but the user information MUST include the
165
     * user; an empty string for the user is equivalent to removing user
166
     * information.
167
     *
168
     * @param string      $user     The user name to use for authority.
169 76
     * @param null|string $password The password associated with $user.
170
     *
171 76
     * @throws RuntimeException if the returned URI object is invalid.
172
     *
173
     * @return static A new instance with the specified user information.
174
     */
175
    public function withUserInfo($user, $password = null)
176
    {
177 158
        if (null === $password) {
178
            $password = '';
179 158
        }
180
181
        $userInfo = $this->userInfo->withUser($this->filterPropertyValue($user))->withPass($password);
182
        if ($this->userInfo->getUser() == $userInfo->getUser()
183
            && $this->userInfo->getPass() == $userInfo->getPass()
184
        ) {
185 154
            return $this;
186
        }
187 154
188
        $clone = clone $this;
189
        $clone->userInfo = $userInfo;
190
191
        return $clone;
192
    }
193 114
194
    /**
195 114
     * Retrieve the host component of the URI.
196
     *
197
     * If no host is present, this method MUST return an empty string.
198
     *
199
     * The value returned MUST be normalized to lowercase, per RFC 3986
200
     * Section 3.2.2.
201 108
     *
202
     * @see http://tools.ietf.org/html/rfc3986#section-3.2.2
203 108
     *
204
     * @return string The URI host.
205
     */
206
    public function getHost()
207
    {
208
        return $this->host->__toString();
209 82
    }
210
211 82
    /**
212
     * Return an instance with the specified host.
213
     *
214
     * This method MUST retain the state of the current instance, and return
215
     * an instance that contains the specified host.
216
     *
217 74
     * An empty host value is equivalent to removing the host.
218
     *
219 74
     * @param string $host The hostname to use with the new instance.
220
     *
221
     * @throws InvalidArgumentException for invalid hostnames.
222
     * @throws RuntimeException         if the returned URI object is invalid.
223
     *
224
     * @return static A new instance with the specified host.
225 186
     */
226
    public function withHost($host)
227 186
    {
228
        return $this->withProperty('host', $this->filterPropertyValue($host));
229
    }
230
231
    /**
232
     * Retrieve the port component of the URI.
233
     *
234
     * If a port is present, and it is non-standard for the current scheme,
235
     * this method MUST return it as an integer. If the port is the standard port
236
     * used with the current scheme, this method SHOULD return null.
237
     *
238 364
     * If no port is present, and no scheme is present, this method MUST return
239
     * a null value.
240 364
     *
241 364
     * If no port is present, but a scheme is present, this method MAY return
242 328
     * the standard port for that scheme, but SHOULD return null.
243 328
     *
244
     * @return null|int The URI port.
245
     */
246 364
    public function getPort()
247 364
    {
248 364
        return $this->hasStandardPort() ? null : $this->port->toInt();
249
    }
250
251
    /**
252
     * Return an instance with the specified port.
253
     *
254 364
     * This method MUST retain the state of the current instance, and return
255
     * an instance that contains the specified port.
256 364
     *
257 364
     * Implementations MUST raise an exception for ports outside the
258 86
     * established TCP and UDP port ranges.
259 86
     *
260
     * A null value provided for the port is equivalent to removing the port
261 364
     * information.
262
     *
263
     * @param null|int $port The port to use with the new instance; a null value
264
     *                       removes the port information.
265
     *
266
     * @throws InvalidArgumentException for invalid ports.
267
     * @throws RuntimeException         if the returned URI object is invalid.
268
     *
269
     * @return static A new instance with the specified port.
270 364
     */
271
    public function withPort($port)
272 364
    {
273 364
        return $this->withProperty('port', $port);
274 290
    }
275
276
    /**
277 94
     * Retrieve the path component of the URI.
278 94
     *
279 8
     * The path can either be empty or absolute (starting with a slash) or
280
     * rootless (not starting with a slash). Implementations MUST support all
281
     * three syntaxes.
282 92
     *
283 92
     * Normally, the empty path "" and absolute path "/" are considered equal as
284
     * defined in RFC 7230 Section 2.7.3. But this method MUST NOT automatically
285
     * do this normalization because in contexts with a trimmed base path, e.g.
286
     * the front controller, this difference becomes significant. It's the task
287
     * of the user to handle both "" and "/".
288
     *
289
     * The value returned MUST be percent-encoded, but MUST NOT double-encode
290
     * any characters. To determine what characters to encode, please refer to
291 376
     * RFC 3986, Sections 2 and 3.3.
292
     *
293 376
     * As an example, if the value should include a slash ("/") not intended as
294 18
     * delimiter between path segments, that value MUST be passed in encoded
295
     * form (e.g., "%2F") to the instance.
296 342
     *
297
     * @see https://tools.ietf.org/html/rfc3986#section-2
298
     * @see https://tools.ietf.org/html/rfc3986#section-3.3
299
     *
300
     * @return string The URI path.
301
     */
302
    public function getPath()
303
    {
304
        return $this->path->__toString();
305
    }
306
307
    /**
308
     * Return an instance with the specified path.
309
     *
310
     * This method MUST retain the state of the current instance, and return
311
     * an instance that contains the specified path.
312
     *
313 370
     * The path can either be empty or absolute (starting with a slash) or
314
     * rootless (not starting with a slash). Implementations MUST support all
315 370
     * three syntaxes.
316 370
     *
317 370
     * If the path is intended to be domain-relative rather than path relative then
318
     * it must begin with a slash ("/"). Paths not starting with a slash ("/")
319
     * are assumed to be relative to some base path known to the application or
320 2
     * consumer.
321 2
     *
322 2
     * Users can provide both encoded and decoded path characters.
323
     * Implementations ensure the correct encoding as outlined in getPath().
324 2
     *
325
     * @param string $path The path to use with the new instance.
326
     *
327
     * @throws InvalidArgumentException for invalid paths.
328
     * @throws RuntimeException         if the returned URI object is invalid.
329
     *
330
     * @return static A new instance with the specified path.
331
     */
332 350
    public function withPath($path)
333
    {
334 350
        return $this->withProperty('path', $path);
335
    }
336 330
337
    /**
338
     * Retrieve the query string of the URI.
339
     *
340
     * If no query string is present, this method MUST return an empty string.
341
     *
342
     * The leading "?" character is not part of the query and MUST NOT be
343
     * added.
344 334
     *
345
     * The value returned MUST be percent-encoded, but MUST NOT double-encode
346 334
     * any characters. To determine what characters to encode, please refer to
347 334
     * RFC 3986, Sections 2 and 3.4.
348 22
     *
349
     * As an example, if a value in a key/value pair of the query string should
350 314
     * include an ampersand ("&") not intended as a delimiter between values,
351
     * that value MUST be passed in encoded form (e.g., "%26") to the instance.
352
     *
353
     * @see https://tools.ietf.org/html/rfc3986#section-2
354
     * @see https://tools.ietf.org/html/rfc3986#section-3.4
355
     *
356
     * @return string The URI query string.
357
     */
358
    public function getQuery()
359
    {
360
        return $this->query->__toString();
361
    }
362
363
    /**
364
     * Return an instance with the specified query string.
365
     *
366
     * This method MUST retain the state of the current instance, and return
367
     * an instance that contains the specified query string.
368
     *
369
     * Users can provide both encoded and decoded query characters.
370
     * Implementations ensure the correct encoding as outlined in getQuery().
371
     *
372
     * An empty query string value is equivalent to removing the query string.
373
     *
374
     * @param string $query The query string to use with the new instance.
375
     *
376
     * @throws InvalidArgumentException for invalid query strings.
377
     * @throws RuntimeException         if the returned URI object is invalid.
378
     *
379
     * @return static A new instance with the specified query string.
380
     */
381
    public function withQuery($query)
382
    {
383
        return $this->withProperty('query', $this->filterPropertyValue($query));
384
    }
385
386
    /**
387
     * Retrieve the fragment component of the URI.
388
     *
389
     * If no fragment is present, this method MUST return an empty string.
390
     *
391
     * The leading "#" character is not part of the fragment and MUST NOT be
392
     * added.
393
     *
394
     * The value returned MUST be percent-encoded, but MUST NOT double-encode
395
     * any characters. To determine what characters to encode, please refer to
396
     * RFC 3986, Sections 2 and 3.5.
397
     *
398
     * @see https://tools.ietf.org/html/rfc3986#section-2
399
     * @see https://tools.ietf.org/html/rfc3986#section-3.5
400
     *
401
     * @return string The URI fragment.
402
     */
403
    public function getFragment()
404
    {
405
        return $this->fragment->__toString();
406
    }
407
408
    /**
409
     * Return an instance with the specified URI fragment.
410
     *
411
     * This method MUST retain the state of the current instance, and return
412
     * an instance that contains the specified URI fragment.
413
     *
414
     * Users can provide both encoded and decoded fragment characters.
415
     * Implementations ensure the correct encoding as outlined in getFragment().
416
     *
417
     * An empty fragment value is equivalent to removing the fragment.
418
     *
419
     * @param string $fragment The fragment to use with the new instance.
420
     *
421
     * @throws RuntimeException if the returned URI object is invalid.
422
     *
423
     * @return static A new instance with the specified fragment.
424
     */
425
    public function withFragment($fragment)
426
    {
427
        return $this->withProperty('fragment', $this->filterPropertyValue($fragment));
428
    }
429
430
    /**
431
     * Return the string representation as a URI reference.
432
     *
433
     * Depending on which components of the URI are present, the resulting
434
     * string is either a full URI or relative reference according to RFC 3986,
435
     * Section 4.1. The method concatenates the various components of the URI,
436
     * using the appropriate delimiters:
437
     *
438
     * - If a scheme is present, it MUST be suffixed by ":".
439
     * - If an authority is present, it MUST be prefixed by "//".
440
     * - The path can be concatenated without delimiters. But there are two
441
     *   cases where the path has to be adjusted to make the URI reference
442
     *   valid as PHP does not allow to throw an exception in __toString():
443
     *     - If the path is rootless and an authority is present, the path MUST
444
     *       be prefixed by "/".
445
     *     - If the path is starting with more than one "/" and no authority is
446
     *       present, the starting slashes MUST be reduced to one.
447
     * - If a query is present, it MUST be prefixed by "?".
448
     * - If a fragment is present, it MUST be prefixed by "#".
449
     *
450
     * @see http://tools.ietf.org/html/rfc3986#section-4.1
451
     *
452
     * @return string
453
     */
454
    public function __toString()
455
    {
456
        return $this->scheme->getUriComponent().$this->getSchemeSpecificPart();
457
    }
458
459
    /**
460
     * Retrieve the scheme specific part of the URI.
461
     *
462
     * If no specific part information is present, this method MUST return an empty
463
     * string.
464
     *
465
     * @return string The URI authority, in "[user-info@]host[:port]" format.
466
     */
467
    protected function getSchemeSpecificPart()
468
    {
469
        $auth = $this->getAuthority();
470
        if ('' !== $auth) {
471
            $auth = '//'.$auth;
472
        }
473
474
        return $auth
475
            .$this->formatPath($this->path->getUriComponent(), $auth)
476
            .$this->query->getUriComponent()
477
            .$this->fragment->getUriComponent();
478
    }
479
480
    /**
481
     * Retrieve the authority component of the URI.
482
     *
483
     * If no authority information is present, this method MUST return an empty
484
     * string.
485
     *
486
     * The authority syntax of the URI is:
487
     *
488
     * <pre>
489
     * [user-info@]host[:port]
490
     * </pre>
491
     *
492
     * If the port component is not set or is the standard port for the current
493
     * scheme, it SHOULD NOT be included.
494
     *
495
     * @see https://tools.ietf.org/html/rfc3986#section-3.2
496
     *
497
     * @return string The URI authority, in "[user-info@]host[:port]" format.
498
     */
499
    public function getAuthority()
500
    {
501
        $port = '';
502
        if (!$this->hasStandardPort()) {
503
            $port = $this->port->getUriComponent();
504
        }
505
506
        return $this->userInfo->getUriComponent().$this->host->getUriComponent().$port;
507
    }
508
509
    /**
510
     * @inheritdoc
511
     */
512
    public function __debugInfo()
513
    {
514
        return (new UriParser())->__invoke($this);
515
    }
516
517
    /**
518
     * Returns whether the standard port for the given scheme is used, when
519
     * the scheme is unknown or unsupported will the method return false
520
     *
521
     * @return bool
522
     */
523
    protected function hasStandardPort()
524
    {
525
        $port = $this->port->toInt();
526
        if (null === $port) {
527
            return true;
528
        }
529
530
        $scheme = $this->scheme->__toString();
531
        if ('' === $scheme) {
532
            return false;
533
        }
534
535
        return isset(static::$supportedSchemes[$scheme])
536
            && static::$supportedSchemes[$scheme] === $port;
537
    }
538
539
    /**
540
     * Assert if the current URI object is valid
541
     *
542
     * @throws RuntimeException if the resulting URI is not valid
543
     */
544
    protected function assertValidObject()
545
    {
546
        if (!$this->isValid()) {
547
            throw new RuntimeException('The URI properties will produce an invalid `'.get_class($this).'`');
548
        }
549
    }
550
551
    /**
552
     * Tell whether the current URI is valid.
553
     *
554
     * The URI object validity depends on the scheme. This method
555
     * MUST be implemented on every URI object
556
     *
557
     * @return bool
558
     */
559
    abstract protected function isValid();
560
561
    /**
562
     *  Tell whether any generic URI is valid
563
     *
564
     * @return bool
565
     */
566
    protected function isValidGenericUri()
567
    {
568
        $path = $this->path->getUriComponent();
569
        if (false === strpos($path, ':')) {
570
            return true;
571
        }
572
573
        $path = explode(':', $path);
574
        $path = array_shift($path);
575
        $str = $this->scheme->getUriComponent().$this->getAuthority();
576
577
        return !('' === $str && strpos($path, '/') === false);
578
    }
579
580
    /**
581
     * Tell whether Http URI like scheme URI are valid
582
     *
583
     * @return bool
584
     */
585
    protected function isValidHierarchicalUri()
586
    {
587
        $this->assertSupportedScheme();
588
589
        return $this->isAuthorityValid();
590
    }
591
592
    /**
593
     * Assert whether the current scheme is supported by the URI object
594
     *
595
     * @throws InvalidArgumentException If the Scheme is not supported
596
     */
597
    protected function assertSupportedScheme()
598
    {
599
        $scheme = $this->getScheme();
600
        if (!isset(static::$supportedSchemes[$scheme])) {
601
            throw new InvalidArgumentException('The submitted scheme is unsupported by `'.get_class($this).'`');
602
        }
603
    }
604
}
605