Completed
Push — master ( eca9d2...97a5f5 )
by Derek
02:04
created

Uri::explodeAuthority()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 20
Code Lines 12

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 13
CRAP Score 2

Importance

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