Completed
Pull Request — master (#58)
by ignace nyamagana
03:11
created

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