Completed
Push — master ( f19db8...e36bb3 )
by Derek
02:14
created

Uri::getAuthority()   B

Complexity

Conditions 5
Paths 8

Size

Total Lines 19
Code Lines 10

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 13
CRAP Score 5

Importance

Changes 2
Bugs 0 Features 0
Metric Value
c 2
b 0
f 0
dl 0
loc 19
ccs 13
cts 13
cp 1
rs 8.8571
cc 5
eloc 10
nc 8
nop 0
crap 5
1
<?php
2
namespace Subreality\Dilmun\Anshar\Http;
3
4
use Psr\Http\Message\UriInterface;
5
6
class Uri implements UriInterface
7
{
8
    use SchemePortsTrait;
9
10
    protected $uri_parts = array(
11
        "scheme"    => "",
12
        "hier_part" => "",
13
        "authority" => "",
14
        "user_info" => "",
15
        "host"      => "",
16
        "port"      => null,
17
        "path"      => "",
18
        "query"     => "",
19
        "fragment"  => "",
20
    );
21
22
    /**
23
     * Uri constructor.  Accepts a string representing a URI and parses the string into the URI's component parts.
24
     *
25
     * @throws \InvalidArgumentException    Throws an \InvalidArgumentException when its parameter is not a string
26
     * @param string $uri
27
     */
28 35
    public function __construct($uri)
29
    {
30 35
        if (!is_string($uri)) {
31 6
            throw new \InvalidArgumentException("New Uri objects must be constructed with a string URI");
32
        }
33
34 29
        $this->explodeUri($uri);
35 29
    }
36
37
    /**
38
     * Retrieve the scheme component of the URI.
39
     *
40
     * If no scheme is present, this method MUST return an empty string.
41
     *
42
     * The value returned MUST be normalized to lowercase, per RFC 3986
43
     * Section 3.1.
44
     *
45
     * The trailing ":" character is not part of the scheme and MUST NOT be
46
     * added.
47
     *
48
     * @see https://tools.ietf.org/html/rfc3986#section-3.1
49
     * @return string The URI scheme.
50
     */
51 10
    public function getScheme()
52
    {
53 10
        return strtolower($this->uri_parts["scheme"]);
54
    }
55
56
    /**
57
     * Retrieve the authority component of the URI.
58
     *
59
     * If no authority information is present, this method MUST return an empty
60
     * string.
61
     *
62
     * The authority syntax of the URI is:
63
     *
64
     * <pre>
65
     * [user-info@]host[:port]
66
     * </pre>
67
     *
68
     * If the port component is not set or is the standard port for the current
69
     * scheme, it SHOULD NOT be included.
70
     *
71
     * @see https://tools.ietf.org/html/rfc3986#section-3.2
72
     * @return string The URI authority, in "[user-info@]host[:port]" format.
73
     */
74 5
    public function getAuthority()
75
    {
76 5
        $normalized_authority = $this->uri_parts["host"];
77 5
        $standard_port        = null;
78
79 5
        if (array_key_exists($this->getScheme(), $this->scheme_ports)) {
80 1
            $standard_port = $this->scheme_ports[$this->getScheme()];
81 1
        }
82
83 5
        if (!empty($this->uri_parts["user_info"])) {
84 3
            $normalized_authority = $this->uri_parts["user_info"] . "@" . $normalized_authority;
85 3
        }
86
87 5
        if (!is_null($this->uri_parts["port"]) && $this->uri_parts["port"] != $standard_port) {
88 1
            $normalized_authority = $normalized_authority . ":" . $this->uri_parts["port"];
89 1
        }
90
91 5
        return $normalized_authority;
92
    }
93
94
    /**
95
     * Retrieve the user information component of the URI.
96
     *
97
     * If no user information is present, this method MUST return an empty
98
     * string.
99
     *
100
     * If a user is present in the URI, this will return that value;
101
     * additionally, if the password is also present, it will be appended to the
102
     * user value, with a colon (":") separating the values.
103
     *
104
     * The trailing "@" character is not part of the user information and MUST
105
     * NOT be added.
106
     *
107
     * @return string The URI user information, in "username[:password]" format.
108
     */
109 3
    public function getUserInfo()
110
    {
111 3
        return $this->uri_parts["user_info"];
112
    }
113
114
    /**
115
     * Retrieve the host component of the URI.
116
     *
117
     * If no host is present, this method MUST return an empty string.
118
     *
119
     * The value returned MUST be normalized to lowercase, per RFC 3986
120
     * Section 3.2.2.
121
     *
122
     * @see http://tools.ietf.org/html/rfc3986#section-3.2.2
123
     * @return string The URI host.
124
     */
125 3
    public function getHost()
126
    {
127 3
        return $this->uri_parts["host"];
128
    }
129
130
    /**
131
     * Retrieve the port component of the URI.
132
     *
133
     * If a port is present, and it is non-standard for the current scheme,
134
     * this method MUST return it as an integer. If the port is the standard port
135
     * used with the current scheme, this method SHOULD return null.
136
     *
137
     * If no port is present, and no scheme is present, this method MUST return
138
     * a null value.
139
     *
140
     * If no port is present, but a scheme is present, this method MAY return
141
     * the standard port for that scheme, but SHOULD return null.
142
     *
143
     * @return null|int The URI port.
144
     */
145 3
    public function getPort()
146
    {
147 3
        return $this->uri_parts["port"];
148
    }
149
150
    /**
151
     * Retrieve the path component of the URI.
152
     *
153
     * The path can either be empty or absolute (starting with a slash) or
154
     * rootless (not starting with a slash). Implementations MUST support all
155
     * three syntaxes.
156
     *
157
     * Normally, the empty path "" and absolute path "/" are considered equal as
158
     * defined in RFC 7230 Section 2.7.3. But this method MUST NOT automatically
159
     * do this normalization because in contexts with a trimmed base path, e.g.
160
     * the front controller, this difference becomes significant. It's the task
161
     * of the user to handle both "" and "/".
162
     *
163
     * The value returned MUST be percent-encoded, but MUST NOT double-encode
164
     * any characters. To determine what characters to encode, please refer to
165
     * RFC 3986, Sections 2 and 3.3.
166
     *
167
     * As an example, if the value should include a slash ("/") not intended as
168
     * delimiter between path segments, that value MUST be passed in encoded
169
     * form (e.g., "%2F") to the instance.
170
     *
171
     * @see https://tools.ietf.org/html/rfc3986#section-2
172
     * @see https://tools.ietf.org/html/rfc3986#section-3.3
173
     * @return string The URI path.
174
     */
175 3
    public function getPath()
176
    {
177 3
        return $this->uri_parts["path"];
178
    }
179
180
    /**
181
     * Retrieve the query string of the URI.
182
     *
183
     * If no query string is present, this method MUST return an empty string.
184
     *
185
     * The leading "?" character is not part of the query and MUST NOT be
186
     * added.
187
     *
188
     * The value returned MUST be percent-encoded, but MUST NOT double-encode
189
     * any characters. To determine what characters to encode, please refer to
190
     * RFC 3986, Sections 2 and 3.4.
191
     *
192
     * As an example, if a value in a key/value pair of the query string should
193
     * include an ampersand ("&") not intended as a delimiter between values,
194
     * that value MUST be passed in encoded form (e.g., "%26") to the instance.
195
     *
196
     * @see https://tools.ietf.org/html/rfc3986#section-2
197
     * @see https://tools.ietf.org/html/rfc3986#section-3.4
198
     * @return string The URI query string.
199
     */
200 3
    public function getQuery()
201
    {
202 3
        return $this->uri_parts["query"];
203
    }
204
205
    /**
206
     * Retrieve the fragment component of the URI.
207
     *
208
     * If no fragment is present, this method MUST return an empty string.
209
     *
210
     * The leading "#" character is not part of the fragment and MUST NOT be
211
     * added.
212
     *
213
     * The value returned MUST be percent-encoded, but MUST NOT double-encode
214
     * any characters. To determine what characters to encode, please refer to
215
     * RFC 3986, Sections 2 and 3.5.
216
     *
217
     * @see https://tools.ietf.org/html/rfc3986#section-2
218
     * @see https://tools.ietf.org/html/rfc3986#section-3.5
219
     * @return string The URI fragment.
220
     */
221 3
    public function getFragment()
222
    {
223 3
        return $this->uri_parts["fragment"];
224
    }
225
226
    /**
227
     * Return an instance with the specified scheme.
228
     *
229
     * This method MUST retain the state of the current instance, and return
230
     * an instance that contains the specified scheme.
231
     *
232
     * Implementations MUST support the schemes "http" and "https" case
233
     * insensitively, and MAY accommodate other schemes if required.
234
     *
235
     * An empty scheme is equivalent to removing the scheme.
236
     *
237
     * @param string $scheme The scheme to use with the new instance.
238
     * @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...
239
     * @throws \InvalidArgumentException for invalid or unsupported schemes.
240
     */
241
    public function withScheme($scheme)
242
    {
243
        // 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...
244
    }
245
246
    /**
247
     * Return an instance with the specified user information.
248
     *
249
     * This method MUST retain the state of the current instance, and return
250
     * an instance that contains the specified user information.
251
     *
252
     * Password is optional, but the user information MUST include the
253
     * user; an empty string for the user is equivalent to removing user
254
     * information.
255
     *
256
     * @param string $user The user name to use for authority.
257
     * @param null|string $password The password associated with $user.
258
     * @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...
259
     */
260
    public function withUserInfo($user, $password = null)
261
    {
262
        // 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...
263
    }
264
265
    /**
266
     * Return an instance with the specified host.
267
     *
268
     * This method MUST retain the state of the current instance, and return
269
     * an instance that contains the specified host.
270
     *
271
     * An empty host value is equivalent to removing the host.
272
     *
273
     * @param string $host The hostname to use with the new instance.
274
     * @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...
275
     * @throws \InvalidArgumentException for invalid hostnames.
276
     */
277
    public function withHost($host)
278
    {
279
        // 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...
280
    }
281
282
    /**
283
     * Return an instance with the specified port.
284
     *
285
     * This method MUST retain the state of the current instance, and return
286
     * an instance that contains the specified port.
287
     *
288
     * Implementations MUST raise an exception for ports outside the
289
     * established TCP and UDP port ranges.
290
     *
291
     * A null value provided for the port is equivalent to removing the port
292
     * information.
293
     *
294
     * @param null|int $port The port to use with the new instance; a null value
295
     *     removes the port information.
296
     * @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...
297
     * @throws \InvalidArgumentException for invalid ports.
298
     */
299
    public function withPort($port)
300
    {
301
        // 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...
302
    }
303
304
    /**
305
     * Return an instance with the specified path.
306
     *
307
     * This method MUST retain the state of the current instance, and return
308
     * an instance that contains the specified path.
309
     *
310
     * The path can either be empty or absolute (starting with a slash) or
311
     * rootless (not starting with a slash). Implementations MUST support all
312
     * three syntaxes.
313
     *
314
     * If the path is intended to be domain-relative rather than path relative then
315
     * it must begin with a slash ("/"). Paths not starting with a slash ("/")
316
     * are assumed to be relative to some base path known to the application or
317
     * consumer.
318
     *
319
     * Users can provide both encoded and decoded path characters.
320
     * Implementations ensure the correct encoding as outlined in getPath().
321
     *
322
     * @param string $path The path to use with the new instance.
323
     * @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...
324
     * @throws \InvalidArgumentException for invalid paths.
325
     */
326
    public function withPath($path)
327
    {
328
        // 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...
329
    }
330
331
    /**
332
     * Return an instance with the specified query string.
333
     *
334
     * This method MUST retain the state of the current instance, and return
335
     * an instance that contains the specified query string.
336
     *
337
     * Users can provide both encoded and decoded query characters.
338
     * Implementations ensure the correct encoding as outlined in getQuery().
339
     *
340
     * An empty query string value is equivalent to removing the query string.
341
     *
342
     * @param string $query The query string to use with the new instance.
343
     * @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...
344
     * @throws \InvalidArgumentException for invalid query strings.
345
     */
346
    public function withQuery($query)
347
    {
348
        // 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...
349
    }
350
351
    /**
352
     * Return an instance with the specified URI fragment.
353
     *
354
     * This method MUST retain the state of the current instance, and return
355
     * an instance that contains the specified URI fragment.
356
     *
357
     * Users can provide both encoded and decoded fragment characters.
358
     * Implementations ensure the correct encoding as outlined in getFragment().
359
     *
360
     * An empty fragment value is equivalent to removing the fragment.
361
     *
362
     * @param string $fragment The fragment to use with the new instance.
363
     * @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...
364
     */
365
    public function withFragment($fragment)
366
    {
367
        // 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...
368
    }
369
370
    /**
371
     * Return the string representation as a URI reference.
372
     *
373
     * Depending on which components of the URI are present, the resulting
374
     * string is either a full URI or relative reference according to RFC 3986,
375
     * Section 4.1. The method concatenates the various components of the URI,
376
     * using the appropriate delimiters:
377
     *
378
     * - If a scheme is present, it MUST be suffixed by ":".
379
     * - If an authority is present, it MUST be prefixed by "//".
380
     * - The path can be concatenated without delimiters. But there are two
381
     *   cases where the path has to be adjusted to make the URI reference
382
     *   valid as PHP does not allow to throw an exception in __toString():
383
     *     - If the path is rootless and an authority is present, the path MUST
384
     *       be prefixed by "/".
385
     *     - If the path is starting with more than one "/" and no authority is
386
     *       present, the starting slashes MUST be reduced to one.
387
     * - If a query is present, it MUST be prefixed by "?".
388
     * - If a fragment is present, it MUST be prefixed by "#".
389
     *
390
     * @see http://tools.ietf.org/html/rfc3986#section-4.1
391
     * @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...
392
     */
393
    public function __toString()
394
    {
395
        // 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...
396
    }
397
398
    /**
399
     * Splits a string URI into its component parts, returning true if the URI string matches a valid URI's syntax
400
     * and false if the URI string does not
401
     *
402
     * @param string $uri   The URI string to be decomposed
403
     * @return bool         Returns true if the URI string matches a valid URI's syntax
404
     *                      Returns false otherwise
405
     */
406 29
    private function explodeUri($uri)
407
    {
408 29
        $reg_start        = '/^';
409 29
        $scheme_part      = '(?P<scheme>.[^:]+)';
410 29
        $scheme_separator = ':';
411 29
        $hier_part        = '(?<hier_part>.[^\?#]+)';
412 29
        $query_part       = '(?:\?(?P<query>.[^#]+))?';
413 29
        $fragment_part    = '(?:#(?P<fragment>.+))?';
414 29
        $reg_end          = '/';
415
416 29
        $uri_syntax = $reg_start . $scheme_part . $scheme_separator . $hier_part . $query_part . $fragment_part .
417 29
            $reg_end;
418
419 29
        $uri_valid = preg_match($uri_syntax, $uri, $parts);
420
421 29
        $this->uri_parts = array_merge($this->uri_parts, $parts); //overwriting default values with matches
422
423 29
        $hier_parts = $this->explodeHierParts($this->uri_parts["hier_part"]);
424
425 29
        $this->uri_parts = array_merge($this->uri_parts, $hier_parts);
426
427 29
        return (bool) $uri_valid;
428
    }
429
430
    /**
431
     * Splits URI hierarchy data into authority and path data, returning an array with named keys
432
     *
433
     * @param string $hier_part     The hierarchy part of a URI to be decomposed
434
     * @return string[]             An array with named keys containing the component parts of the supplied
435
     *                              hierarchy
436
     */
437 29
    private function explodeHierParts($hier_part)
438
    {
439 29
        $authority_parts = array();
440
441 29
        $reg_start      = '/^';
442 29
        $authority_part = '(?:(?:\/\/)(?P<authority>.[^\/]+))?';
443 29
        $path_part      = '(?P<path>.+)';
444 29
        $reg_end        = '/';
445
446 29
        $hier_part_syntax = $reg_start . $authority_part . $path_part . $reg_end;
447
448 29
        preg_match($hier_part_syntax, $hier_part, $hier_parts);
449
450 29
        if (isset($hier_parts["authority"])) {
451 19
            $authority_parts = $this->explodeAuthority($hier_parts["authority"]);
452 19
        }
453
454 29
        $hier_parts = array_merge($hier_parts, $authority_parts);
455
456 29
        return $hier_parts;
457
    }
458
459
    /**
460
     * Splits URI authority data into user info, host, and port data, returning an array with named keys
461
     *
462
     * @param string $authority     The authority part of a URI to be decomposed
463
     * @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...
464
     *                              authority
465
     */
466 19
    private function explodeAuthority($authority)
467
    {
468 19
        $reg_start      = '/^';
469 19
        $user_info_part = '(?:(?P<user_info>.+)@)?';
470 19
        $host_part      = '(?P<host>.[^:]+)';
471 19
        $port_part      = '(?::(?P<port>[0-9]+))?';
472 19
        $reg_end        = '/';
473
474 19
        $authority_syntax = $reg_start . $user_info_part . $host_part . $port_part . $reg_end;
475
476 19
        preg_match($authority_syntax, $authority, $authority_parts);
477
478 19
        if (isset($authority_parts["port"])) {
479 10
            $authority_parts["port"] = (int) $authority_parts["port"];
480 10
        }
481
482 19
        return $authority_parts;
483
    }
484
}
485