Completed
Push — master ( ab2517...f19db8 )
by Derek
02:26
created

Uri::withFragment()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

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