Completed
Push — master ( 90c4f7...76cd54 )
by Derek
02:11
created

Uri::getAuthority()   A

Complexity

Conditions 3
Paths 4

Size

Total Lines 16
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 10
CRAP Score 3

Importance

Changes 2
Bugs 0 Features 0
Metric Value
c 2
b 0
f 0
dl 0
loc 16
ccs 10
cts 10
cp 1
rs 9.4285
cc 3
eloc 8
nc 4
nop 0
crap 3
1
<?php
2
namespace Subreality\Dilmun\Anshar\Http;
3
4
use Psr\Http\Message\UriInterface;
5
use Subreality\Dilmun\Anshar\Utils\ArrayHelper;
6
7
class Uri implements UriInterface
8
{
9
    use SchemePortsTrait;
10
11
    protected $uri_parts = array(
12
        "scheme"    => "",
13
        "hier_part" => "",
14
        "authority" => "",
15
        "user_info" => "",
16
        "host"      => "",
17
        "port"      => null,
18
        "path"      => "",
19
        "query"     => "",
20
        "fragment"  => "",
21
    );
22
23
    /**
24
     * Uri constructor.  Accepts a string representing a URI and parses the string into the URI's component parts.
25
     *
26
     * @throws \InvalidArgumentException    Throws an \InvalidArgumentException when its parameter is not a string
27
     * @param string $uri
28
     */
29 25
    public function __construct($uri)
30
    {
31 25
        if (!is_string($uri)) {
32 6
            throw new \InvalidArgumentException("New Uri objects must be constructed with a string URI");
33
        }
34
35 19
        $this->explodeUri($uri);
36 19
    }
37
38
    /**
39
     * Retrieve the parsed components of the URI string.
40
     *
41
     * If the class was provided an invalid URI string, URI components will be empty strings, except port, which will
42
     * be null
43
     *
44
     * @return mixed[]
45
     */
46 7
    public function getParsedUri()
47
    {
48 7
        return $this->uri_parts;
49
    }
50
51
    /**
52
     * Retrieve the scheme component of the URI.
53
     *
54
     * If no scheme is present, this method MUST return an empty string.
55
     *
56
     * The value returned MUST be normalized to lowercase, per RFC 3986
57
     * Section 3.1.
58
     *
59
     * The trailing ":" character is not part of the scheme and MUST NOT be
60
     * added.
61
     *
62
     * @see https://tools.ietf.org/html/rfc3986#section-3.1
63
     * @return string The URI scheme.
64
     */
65 3
    public function getScheme()
66
    {
67 3
        return strtolower($this->uri_parts["scheme"]);
68
    }
69
70
    /**
71
     * Retrieve the authority component of the URI.
72
     *
73
     * If no authority information is present, this method MUST return an empty
74
     * string.
75
     *
76
     * The authority syntax of the URI is:
77
     *
78
     * <pre>
79
     * [user-info@]host[:port]
80
     * </pre>
81
     *
82
     * If the port component is not set or is the standard port for the current
83
     * scheme, it SHOULD NOT be included.
84
     *
85
     * @see https://tools.ietf.org/html/rfc3986#section-3.2
86
     * @return string The URI authority, in "[user-info@]host[:port]" format.
87
     */
88 4
    public function getAuthority()
89
    {
90 4
        $normalized_authority = $this->uri_parts["host"];
91
92 4
        if (!empty($this->uri_parts["user_info"])) {
93 4
            $normalized_authority = $this->uri_parts["user_info"] . "@" . $normalized_authority;
94 4
        }
95
96 4
        $normalized_port = $this->normalizePort();
97
98 4
        if (!is_null($normalized_port)) {
99 2
            $normalized_authority = $normalized_authority . ":" . $normalized_port;
100 2
        }
101
102 4
        return $normalized_authority;
103
    }
104
105
    /**
106
     * Retrieve the user information component of the URI.
107
     *
108
     * If no user information is present, this method MUST return an empty
109
     * string.
110
     *
111
     * If a user is present in the URI, this will return that value;
112
     * additionally, if the password is also present, it will be appended to the
113
     * user value, with a colon (":") separating the values.
114
     *
115
     * The trailing "@" character is not part of the user information and MUST
116
     * NOT be added.
117
     *
118
     * @return string The URI user information, in "username[:password]" format.
119
     */
120 2
    public function getUserInfo()
121
    {
122 2
        return $this->uri_parts["user_info"];
123
    }
124
125
    /**
126
     * Retrieve the host component of the URI.
127
     *
128
     * If no host is present, this method MUST return an empty string.
129
     *
130
     * The value returned MUST be normalized to lowercase, per RFC 3986
131
     * Section 3.2.2.
132
     *
133
     * @see http://tools.ietf.org/html/rfc3986#section-3.2.2
134
     * @return string The URI host.
135
     */
136 3
    public function getHost()
137
    {
138 3
        return strtolower($this->uri_parts["host"]);
139
    }
140
141
    /**
142
     * Retrieve the port component of the URI.
143
     *
144
     * If a port is present, and it is non-standard for the current scheme,
145
     * this method MUST return it as an integer. If the port is the standard port
146
     * used with the current scheme, this method SHOULD return null.
147
     *
148
     * If no port is present, and no scheme is present, this method MUST return
149
     * a null value.
150
     *
151
     * If no port is present, but a scheme is present, this method MAY return
152
     * the standard port for that scheme, but SHOULD return null.
153
     *
154
     * @return null|int The URI port.
155
     */
156 4
    public function getPort()
157
    {
158 4
        $normalized_port = $this->normalizePort();
159
160 4
        return $normalized_port;
161
    }
162
163
    /**
164
     * Retrieve the path component of the URI.
165
     *
166
     * The path can either be empty or absolute (starting with a slash) or
167
     * rootless (not starting with a slash). Implementations MUST support all
168
     * three syntaxes.
169
     *
170
     * Normally, the empty path "" and absolute path "/" are considered equal as
171
     * defined in RFC 7230 Section 2.7.3. But this method MUST NOT automatically
172
     * do this normalization because in contexts with a trimmed base path, e.g.
173
     * the front controller, this difference becomes significant. It's the task
174
     * of the user to handle both "" and "/".
175
     *
176
     * The value returned MUST be percent-encoded, but MUST NOT double-encode
177
     * any characters. To determine what characters to encode, please refer to
178
     * RFC 3986, Sections 2 and 3.3.
179
     *
180
     * As an example, if the value should include a slash ("/") not intended as
181
     * delimiter between path segments, that value MUST be passed in encoded
182
     * form (e.g., "%2F") to the instance.
183
     *
184
     * @see https://tools.ietf.org/html/rfc3986#section-2
185
     * @see https://tools.ietf.org/html/rfc3986#section-3.3
186
     * @return string The URI path.
187
     */
188 2
    public function getPath()
189
    {
190 2
        return $this->uri_parts["path"];
191
    }
192
193
    /**
194
     * Retrieve the query string of the URI.
195
     *
196
     * If no query string is present, this method MUST return an empty string.
197
     *
198
     * The leading "?" character is not part of the query and MUST NOT be
199
     * added.
200
     *
201
     * The value returned MUST be percent-encoded, but MUST NOT double-encode
202
     * any characters. To determine what characters to encode, please refer to
203
     * RFC 3986, Sections 2 and 3.4.
204
     *
205
     * As an example, if a value in a key/value pair of the query string should
206
     * include an ampersand ("&") not intended as a delimiter between values,
207
     * that value MUST be passed in encoded form (e.g., "%26") to the instance.
208
     *
209
     * @see https://tools.ietf.org/html/rfc3986#section-2
210
     * @see https://tools.ietf.org/html/rfc3986#section-3.4
211
     * @return string The URI query string.
212
     */
213
    public function getQuery()
214
    {
215
        return $this->uri_parts["query"];
216
    }
217
218
    /**
219
     * Retrieve the fragment component of the URI.
220
     *
221
     * If no fragment is present, this method MUST return an empty string.
222
     *
223
     * The leading "#" character is not part of the fragment and MUST NOT be
224
     * added.
225
     *
226
     * The value returned MUST be percent-encoded, but MUST NOT double-encode
227
     * any characters. To determine what characters to encode, please refer to
228
     * RFC 3986, Sections 2 and 3.5.
229
     *
230
     * @see https://tools.ietf.org/html/rfc3986#section-2
231
     * @see https://tools.ietf.org/html/rfc3986#section-3.5
232
     * @return string The URI fragment.
233
     */
234
    public function getFragment()
235
    {
236
        return $this->uri_parts["fragment"];
237
    }
238
239
    /**
240
     * Return an instance with the specified scheme.
241
     *
242
     * This method MUST retain the state of the current instance, and return
243
     * an instance that contains the specified scheme.
244
     *
245
     * Implementations MUST support the schemes "http" and "https" case
246
     * insensitively, and MAY accommodate other schemes if required.
247
     *
248
     * An empty scheme is equivalent to removing the scheme.
249
     *
250
     * @param string $scheme The scheme to use with the new instance.
251
     * @return static A new instance with the specified scheme.
0 ignored issues
show
Documentation introduced by
Should the return type not be Uri|null?

This check compares the return type specified in the @return annotation of a function or method doc comment with the types returned by the function and raises an issue if they mismatch.

Loading history...
252
     * @throws \InvalidArgumentException for invalid or unsupported schemes.
253
     */
254
    public function withScheme($scheme)
255
    {
256
        // TODO: Implement withScheme() method.
0 ignored issues
show
Coding Style Best Practice introduced by
Comments for TODO tasks are often forgotten in the code; it might be better to use a dedicated issue tracker.
Loading history...
257
    }
258
259
    /**
260
     * Return an instance with the specified user information.
261
     *
262
     * This method MUST retain the state of the current instance, and return
263
     * an instance that contains the specified user information.
264
     *
265
     * Password is optional, but the user information MUST include the
266
     * user; an empty string for the user is equivalent to removing user
267
     * information.
268
     *
269
     * @param string $user The user name to use for authority.
270
     * @param null|string $password The password associated with $user.
271
     * @return static A new instance with the specified user information.
0 ignored issues
show
Documentation introduced by
Should the return type not be Uri|null?

This check compares the return type specified in the @return annotation of a function or method doc comment with the types returned by the function and raises an issue if they mismatch.

Loading history...
272
     */
273
    public function withUserInfo($user, $password = null)
274
    {
275
        // TODO: Implement withUserInfo() method.
0 ignored issues
show
Coding Style Best Practice introduced by
Comments for TODO tasks are often forgotten in the code; it might be better to use a dedicated issue tracker.
Loading history...
276
    }
277
278
    /**
279
     * Return an instance with the specified host.
280
     *
281
     * This method MUST retain the state of the current instance, and return
282
     * an instance that contains the specified host.
283
     *
284
     * An empty host value is equivalent to removing the host.
285
     *
286
     * @param string $host The hostname to use with the new instance.
287
     * @return static A new instance with the specified host.
0 ignored issues
show
Documentation introduced by
Should the return type not be Uri|null?

This check compares the return type specified in the @return annotation of a function or method doc comment with the types returned by the function and raises an issue if they mismatch.

Loading history...
288
     * @throws \InvalidArgumentException for invalid hostnames.
289
     */
290
    public function withHost($host)
291
    {
292
        // TODO: Implement withHost() method.
0 ignored issues
show
Coding Style Best Practice introduced by
Comments for TODO tasks are often forgotten in the code; it might be better to use a dedicated issue tracker.
Loading history...
293
    }
294
295
    /**
296
     * Return an instance with the specified port.
297
     *
298
     * This method MUST retain the state of the current instance, and return
299
     * an instance that contains the specified port.
300
     *
301
     * Implementations MUST raise an exception for ports outside the
302
     * established TCP and UDP port ranges.
303
     *
304
     * A null value provided for the port is equivalent to removing the port
305
     * information.
306
     *
307
     * @param null|int $port The port to use with the new instance; a null value
308
     *     removes the port information.
309
     * @return static A new instance with the specified port.
0 ignored issues
show
Documentation introduced by
Should the return type not be Uri|null?

This check compares the return type specified in the @return annotation of a function or method doc comment with the types returned by the function and raises an issue if they mismatch.

Loading history...
310
     * @throws \InvalidArgumentException for invalid ports.
311
     */
312
    public function withPort($port)
313
    {
314
        // TODO: Implement withPort() method.
0 ignored issues
show
Coding Style Best Practice introduced by
Comments for TODO tasks are often forgotten in the code; it might be better to use a dedicated issue tracker.
Loading history...
315
    }
316
317
    /**
318
     * Return an instance with the specified path.
319
     *
320
     * This method MUST retain the state of the current instance, and return
321
     * an instance that contains the specified path.
322
     *
323
     * The path can either be empty or absolute (starting with a slash) or
324
     * rootless (not starting with a slash). Implementations MUST support all
325
     * three syntaxes.
326
     *
327
     * If the path is intended to be domain-relative rather than path relative then
328
     * it must begin with a slash ("/"). Paths not starting with a slash ("/")
329
     * are assumed to be relative to some base path known to the application or
330
     * consumer.
331
     *
332
     * Users can provide both encoded and decoded path characters.
333
     * Implementations ensure the correct encoding as outlined in getPath().
334
     *
335
     * @param string $path The path to use with the new instance.
336
     * @return static A new instance with the specified path.
0 ignored issues
show
Documentation introduced by
Should the return type not be Uri|null?

This check compares the return type specified in the @return annotation of a function or method doc comment with the types returned by the function and raises an issue if they mismatch.

Loading history...
337
     * @throws \InvalidArgumentException for invalid paths.
338
     */
339
    public function withPath($path)
340
    {
341
        // TODO: Implement withPath() method.
0 ignored issues
show
Coding Style Best Practice introduced by
Comments for TODO tasks are often forgotten in the code; it might be better to use a dedicated issue tracker.
Loading history...
342
    }
343
344
    /**
345
     * Return an instance with the specified query string.
346
     *
347
     * This method MUST retain the state of the current instance, and return
348
     * an instance that contains the specified query string.
349
     *
350
     * Users can provide both encoded and decoded query characters.
351
     * Implementations ensure the correct encoding as outlined in getQuery().
352
     *
353
     * An empty query string value is equivalent to removing the query string.
354
     *
355
     * @param string $query The query string to use with the new instance.
356
     * @return static A new instance with the specified query string.
0 ignored issues
show
Documentation introduced by
Should the return type not be Uri|null?

This check compares the return type specified in the @return annotation of a function or method doc comment with the types returned by the function and raises an issue if they mismatch.

Loading history...
357
     * @throws \InvalidArgumentException for invalid query strings.
358
     */
359
    public function withQuery($query)
360
    {
361
        // TODO: Implement withQuery() method.
0 ignored issues
show
Coding Style Best Practice introduced by
Comments for TODO tasks are often forgotten in the code; it might be better to use a dedicated issue tracker.
Loading history...
362
    }
363
364
    /**
365
     * Return an instance with the specified URI fragment.
366
     *
367
     * This method MUST retain the state of the current instance, and return
368
     * an instance that contains the specified URI fragment.
369
     *
370
     * Users can provide both encoded and decoded fragment characters.
371
     * Implementations ensure the correct encoding as outlined in getFragment().
372
     *
373
     * An empty fragment value is equivalent to removing the fragment.
374
     *
375
     * @param string $fragment The fragment to use with the new instance.
376
     * @return static A new instance with the specified fragment.
0 ignored issues
show
Documentation introduced by
Should the return type not be Uri|null?

This check compares the return type specified in the @return annotation of a function or method doc comment with the types returned by the function and raises an issue if they mismatch.

Loading history...
377
     */
378
    public function withFragment($fragment)
379
    {
380
        // TODO: Implement withFragment() method.
0 ignored issues
show
Coding Style Best Practice introduced by
Comments for TODO tasks are often forgotten in the code; it might be better to use a dedicated issue tracker.
Loading history...
381
    }
382
383
    /**
384
     * Return the string representation as a URI reference.
385
     *
386
     * Depending on which components of the URI are present, the resulting
387
     * string is either a full URI or relative reference according to RFC 3986,
388
     * Section 4.1. The method concatenates the various components of the URI,
389
     * using the appropriate delimiters:
390
     *
391
     * - If a scheme is present, it MUST be suffixed by ":".
392
     * - If an authority is present, it MUST be prefixed by "//".
393
     * - The path can be concatenated without delimiters. But there are two
394
     *   cases where the path has to be adjusted to make the URI reference
395
     *   valid as PHP does not allow to throw an exception in __toString():
396
     *     - If the path is rootless and an authority is present, the path MUST
397
     *       be prefixed by "/".
398
     *     - If the path is starting with more than one "/" and no authority is
399
     *       present, the starting slashes MUST be reduced to one.
400
     * - If a query is present, it MUST be prefixed by "?".
401
     * - If a fragment is present, it MUST be prefixed by "#".
402
     *
403
     * @see http://tools.ietf.org/html/rfc3986#section-4.1
404
     * @return string
0 ignored issues
show
Documentation introduced by
Should the return type not be string|null?

This check compares the return type specified in the @return annotation of a function or method doc comment with the types returned by the function and raises an issue if they mismatch.

Loading history...
405
     */
406
    public function __toString()
407
    {
408
        // TODO: Implement __toString() method.
0 ignored issues
show
Coding Style Best Practice introduced by
Comments for TODO tasks are often forgotten in the code; it might be better to use a dedicated issue tracker.
Loading history...
409
    }
410
411
    /**
412
     * Splits a string URI into its component parts, returning true if the URI string matches a valid URI's syntax
413
     * and false if the URI string does not
414
     *
415
     * @param string $uri   The URI string to be decomposed
416
     * @return bool         Returns true if the URI string matches a valid URI's syntax
417
     *                      Returns false otherwise
418
     */
419 19
    private function explodeUri($uri)
420
    {
421 19
        $reg_start        = '/^';
422 19
        $scheme_part      = '(?P<scheme>.[^:]+)';
423 19
        $scheme_separator = ':';
424 19
        $hier_part        = '(?<hier_part>.[^\?#]+)';
425 19
        $query_part       = '(?:\?(?P<query>.[^#]+))?';
426 19
        $fragment_part    = '(?:#(?P<fragment>.+))?';
427 19
        $reg_end          = '/';
428
429 19
        $uri_syntax = $reg_start . $scheme_part . $scheme_separator . $hier_part . $query_part . $fragment_part .
430 19
            $reg_end;
431
432 19
        $uri_valid = preg_match($uri_syntax, $uri, $parts);
433
434 19
        $this->uri_parts = array_merge($this->uri_parts, $parts); //overwriting default values with matches
435
436 19
        $hier_parts = $this->explodeHierParts($this->uri_parts["hier_part"]);
437
438 19
        $this->uri_parts = array_merge($this->uri_parts, $hier_parts);
439
440 19
        foreach ($this->uri_parts as $key => $value) {
441 19
            if (is_int($key)) {
442 19
                unset($this->uri_parts[$key]);
443 19
            }
444 19
        }
445
446 19
        return (bool) $uri_valid;
447
    }
448
449
    /**
450
     * Splits URI hierarchy data into authority and path data, returning an array with named keys
451
     *
452
     * @param string $hier_part     The hierarchy part of a URI to be decomposed
453
     * @return string[]             An array with named keys containing the component parts of the supplied
454
     *                              hierarchy
455
     */
456 19
    private function explodeHierParts($hier_part)
457
    {
458 19
        $authority_parts = array();
459
460 19
        $reg_start      = '/^';
461 19
        $authority_part = '(?:(?:\/\/)(?P<authority>.[^\/]+))?';
462 19
        $path_part      = '(?P<path>.+)?';
463 19
        $reg_end        = '/';
464
465 19
        $hier_part_syntax = $reg_start . $authority_part . $path_part . $reg_end;
466
467 19
        preg_match($hier_part_syntax, $hier_part, $hier_parts);
468
469 19
        if (isset($hier_parts["authority"])) {
470 14
            $authority_parts = $this->explodeAuthority($hier_parts["authority"]);
471 14
        }
472
473 19
        $hier_parts = array_merge($hier_parts, $authority_parts);
474
475 19
        return $hier_parts;
476
    }
477
478
    /**
479
     * Splits URI authority data into user info, host, and port data, returning an array with named keys
480
     *
481
     * @param string $authority     The authority part of a URI to be decomposed
482
     * @return (string|int|null)[]  An array with named keys containing the component parts of the supplied
0 ignored issues
show
Documentation introduced by
Should the return type not be array<*,string|integer|null>?

This check compares the return type specified in the @return annotation of a function or method doc comment with the types returned by the function and raises an issue if they mismatch.

Loading history...
483
     *                              authority
484
     */
485 14
    private function explodeAuthority($authority)
486
    {
487 14
        $reg_start      = '/^';
488 14
        $user_info_part = '(?:(?P<user_info>.+)@)?';
489 14
        $host_part      = '(?P<host>.[^:]+)';
490 14
        $port_part      = '(?::(?P<port>[0-9]+))?';
491 14
        $reg_end        = '/';
492
493 14
        $authority_syntax = $reg_start . $user_info_part . $host_part . $port_part . $reg_end;
494
495 14
        preg_match($authority_syntax, $authority, $authority_parts);
496
497 14
        if (isset($authority_parts["port"])) {
498 7
            $authority_parts["port"] = (int) $authority_parts["port"];
499 7
        }
500
501 14
        return $authority_parts;
502
    }
503
504
    /**
505
     * Normalizes a port string based on whether the URI's port is standard for its scheme
506
     *
507
     * @return int|null     Returns null if the port is standard for the scheme
508
     *                      Returns the port prepended with a colon if the port is not standard for the scheme
509
     */
510 8
    private function normalizePort()
511
    {
512 8
        $scheme_port_array = new ArrayHelper($this->scheme_ports);
513
514 8
        $standard_port = $scheme_port_array->valueLookup($this->uri_parts["scheme"]);
515
516 8
        if ($this->uri_parts["port"] == $standard_port) {
517 5
            $normalized_port = null;
518 5
        } else {
519 3
            $normalized_port = $this->uri_parts["port"];
520
        }
521
522 8
        return $normalized_port;
523
    }
524
}
525