Completed
Push — issue#666 ( 5e6b12...7229dc )
by Guilherme
09:55
created

HttpUri::createFromComponents()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 16
Code Lines 11

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 11
CRAP Score 1

Importance

Changes 0
Metric Value
cc 1
eloc 11
nc 1
nop 1
dl 0
loc 16
ccs 11
cts 11
cp 1
crap 1
rs 9.4285
c 0
b 0
f 0
1
<?php
2
/**
3
 * This file is part of the login-cidadao project or it's bundles.
4
 *
5
 * (c) Guilherme Donato <guilhermednt on github>
6
 *
7
 * For the full copyright and license information, please view the LICENSE
8
 * file that was distributed with this source code.
9
 */
10
11
namespace LoginCidadao\RemoteClaimsBundle\Model;
12
13
use Psr\Http\Message\UriInterface;
14
15
class HttpUri implements UriInterface
16
{
17
    /**
18
     * Pattern extracted from Symfony\Component\Validator\Constraints\UrlValidator
19
     */
20
    const PATTERN = '~^
21
            (?<scheme>%s)://                                 # protocol
22
            (?<userInfo>([\pL\pN-]+:)?([\pL\pN-]+)@)?          # basic auth
23
            (?<host>
24
                ([\pL\pN\pS-\.])+(\.?([\pL\pN]|xn\-\-[\pL\pN-]+)+\.?) # a domain name
25
                    |                                                 # or
26
                \d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}                    # an IP address
27
                    |                                                 # or
28
                \[
29
                    (?:(?:(?:(?:(?:(?:(?:[0-9a-f]{1,4})):){6})(?:(?:(?:(?:(?:[0-9a-f]{1,4})):(?:(?:[0-9a-f]{1,4})))|(?:(?:(?:(?:(?:25[0-5]|(?:[1-9]|1[0-9]|2[0-4])?[0-9]))\.){3}(?:(?:25[0-5]|(?:[1-9]|1[0-9]|2[0-4])?[0-9])))))))|(?:(?:::(?:(?:(?:[0-9a-f]{1,4})):){5})(?:(?:(?:(?:(?:[0-9a-f]{1,4})):(?:(?:[0-9a-f]{1,4})))|(?:(?:(?:(?:(?:25[0-5]|(?:[1-9]|1[0-9]|2[0-4])?[0-9]))\.){3}(?:(?:25[0-5]|(?:[1-9]|1[0-9]|2[0-4])?[0-9])))))))|(?:(?:(?:(?:(?:[0-9a-f]{1,4})))?::(?:(?:(?:[0-9a-f]{1,4})):){4})(?:(?:(?:(?:(?:[0-9a-f]{1,4})):(?:(?:[0-9a-f]{1,4})))|(?:(?:(?:(?:(?:25[0-5]|(?:[1-9]|1[0-9]|2[0-4])?[0-9]))\.){3}(?:(?:25[0-5]|(?:[1-9]|1[0-9]|2[0-4])?[0-9])))))))|(?:(?:(?:(?:(?:(?:[0-9a-f]{1,4})):){0,1}(?:(?:[0-9a-f]{1,4})))?::(?:(?:(?:[0-9a-f]{1,4})):){3})(?:(?:(?:(?:(?:[0-9a-f]{1,4})):(?:(?:[0-9a-f]{1,4})))|(?:(?:(?:(?:(?:25[0-5]|(?:[1-9]|1[0-9]|2[0-4])?[0-9]))\.){3}(?:(?:25[0-5]|(?:[1-9]|1[0-9]|2[0-4])?[0-9])))))))|(?:(?:(?:(?:(?:(?:[0-9a-f]{1,4})):){0,2}(?:(?:[0-9a-f]{1,4})))?::(?:(?:(?:[0-9a-f]{1,4})):){2})(?:(?:(?:(?:(?:[0-9a-f]{1,4})):(?:(?:[0-9a-f]{1,4})))|(?:(?:(?:(?:(?:25[0-5]|(?:[1-9]|1[0-9]|2[0-4])?[0-9]))\.){3}(?:(?:25[0-5]|(?:[1-9]|1[0-9]|2[0-4])?[0-9])))))))|(?:(?:(?:(?:(?:(?:[0-9a-f]{1,4})):){0,3}(?:(?:[0-9a-f]{1,4})))?::(?:(?:[0-9a-f]{1,4})):)(?:(?:(?:(?:(?:[0-9a-f]{1,4})):(?:(?:[0-9a-f]{1,4})))|(?:(?:(?:(?:(?:25[0-5]|(?:[1-9]|1[0-9]|2[0-4])?[0-9]))\.){3}(?:(?:25[0-5]|(?:[1-9]|1[0-9]|2[0-4])?[0-9])))))))|(?:(?:(?:(?:(?:(?:[0-9a-f]{1,4})):){0,4}(?:(?:[0-9a-f]{1,4})))?::)(?:(?:(?:(?:(?:[0-9a-f]{1,4})):(?:(?:[0-9a-f]{1,4})))|(?:(?:(?:(?:(?:25[0-5]|(?:[1-9]|1[0-9]|2[0-4])?[0-9]))\.){3}(?:(?:25[0-5]|(?:[1-9]|1[0-9]|2[0-4])?[0-9])))))))|(?:(?:(?:(?:(?:(?:[0-9a-f]{1,4})):){0,5}(?:(?:[0-9a-f]{1,4})))?::)(?:(?:[0-9a-f]{1,4})))|(?:(?:(?:(?:(?:(?:[0-9a-f]{1,4})):){0,6}(?:(?:[0-9a-f]{1,4})))?::))))
30
                \]  # an IPv6 address
31
            )
32
            (?<port>:[0-9]+)?                              # a port (optional)
33
            (?<path>(?:/ (?:[\pL\pN\-._\~!$&\'()*+,;=:@]|%%[0-9A-Fa-f]{2})* )*)      # a path
34
            (?<query>\? (?:[\pL\pN\-._\~!$&\'()*+,;=:@/?]|%%[0-9A-Fa-f]{2})* )?   # a query (optional)
35
            (?<fragment>\# (?:[\pL\pN\-._\~!$&\'()*+,;=:@/?]|%%[0-9A-Fa-f]{2})* )?   # a fragment (optional)
36
        $~ixu';
37
38
    /** @var string */
39
    private $scheme = '';
40
41
    /** @var string */
42
    private $userInfo = '';
43
44
    /** @var string */
45
    private $host = '';
46
47
    /** @var null|int */
48
    private $port = null;
49
50
    /** @var string */
51
    private $path = '';
52
53
    /** @var string */
54
    private $query = '';
55
56
    /** @var string */
57
    private $fragment = '';
58
59
    /**
60
     * Retrieve the scheme component of the URI.
61
     *
62
     * If no scheme is present, this method MUST return an empty string.
63
     *
64
     * The value returned MUST be normalized to lowercase, per RFC 3986
65
     * Section 3.1.
66
     *
67
     * The trailing ":" character is not part of the scheme and MUST NOT be
68
     * added.
69
     *
70
     * @see https://tools.ietf.org/html/rfc3986#section-3.1
71
     * @return string The URI scheme.
72
     */
73 10
    public function getScheme()
74
    {
75 10
        return $this->scheme;
76
    }
77
78
    /**
79
     * Retrieve the authority component of the URI.
80
     *
81
     * If no authority information is present, this method MUST return an empty
82
     * string.
83
     *
84
     * The authority syntax of the URI is:
85
     *
86
     * <pre>
87
     * [user-info@]host[:port]
88
     * </pre>
89
     *
90
     * If the port component is not set or is the standard port for the current
91
     * scheme, it SHOULD NOT be included.
92
     *
93
     * @see https://tools.ietf.org/html/rfc3986#section-3.2
94
     * @return string The URI authority, in "[user-info@]host[:port]" format.
95
     */
96 9
    public function getAuthority()
97
    {
98 9
        $userInfoHost = implode('@', array_filter([$this->getUserInfo(), $this->getHost()]));
99
100 9
        return implode(':', array_filter([$userInfoHost, $this->getPort()]));
101
    }
102
103
    /**
104
     * Retrieve the user information component of the URI.
105
     *
106
     * If no user information is present, this method MUST return an empty
107
     * string.
108
     *
109
     * If a user is present in the URI, this will return that value;
110
     * additionally, if the password is also present, it will be appended to the
111
     * user value, with a colon (":") separating the values.
112
     *
113
     * The trailing "@" character is not part of the user information and MUST
114
     * NOT be added.
115
     *
116
     * @return string The URI user information, in "username[:password]" format.
117
     */
118 9
    public function getUserInfo()
119
    {
120 9
        return $this->userInfo;
121
    }
122
123
    /**
124
     * Retrieve the host component of the URI.
125
     *
126
     * If no host is present, this method MUST return an empty string.
127
     *
128
     * The value returned MUST be normalized to lowercase, per RFC 3986
129
     * Section 3.2.2.
130
     *
131
     * @see http://tools.ietf.org/html/rfc3986#section-3.2.2
132
     * @return string The URI host.
133
     */
134 10
    public function getHost()
135
    {
136 10
        return $this->host;
137
    }
138
139
    /**
140
     * Retrieve the port component of the URI.
141
     *
142
     * If a port is present, and it is non-standard for the current scheme,
143
     * this method MUST return it as an integer. If the port is the standard port
144
     * used with the current scheme, this method SHOULD return null.
145
     *
146
     * If no port is present, and no scheme is present, this method MUST return
147
     * a null value.
148
     *
149
     * If no port is present, but a scheme is present, this method MAY return
150
     * the standard port for that scheme, but SHOULD return null.
151
     *
152
     * @return null|int The URI port.
153
     */
154 9
    public function getPort()
155
    {
156 9
        return $this->port;
157
    }
158
159
    /**
160
     * Retrieve the path component of the URI.
161
     *
162
     * The path can either be empty or absolute (starting with a slash) or
163
     * rootless (not starting with a slash). Implementations MUST support all
164
     * three syntaxes.
165
     *
166
     * Normally, the empty path "" and absolute path "/" are considered equal as
167
     * defined in RFC 7230 Section 2.7.3. But this method MUST NOT automatically
168
     * do this normalization because in contexts with a trimmed base path, e.g.
169
     * the front controller, this difference becomes significant. It's the task
170
     * of the user to handle both "" and "/".
171
     *
172
     * The value returned MUST be percent-encoded, but MUST NOT double-encode
173
     * any characters. To determine what characters to encode, please refer to
174
     * RFC 3986, Sections 2 and 3.3.
175
     *
176
     * As an example, if the value should include a slash ("/") not intended as
177
     * delimiter between path segments, that value MUST be passed in encoded
178
     * form (e.g., "%2F") to the instance.
179
     *
180
     * @see https://tools.ietf.org/html/rfc3986#section-2
181
     * @see https://tools.ietf.org/html/rfc3986#section-3.3
182
     * @return string The URI path.
183
     */
184 9
    public function getPath()
185
    {
186 9
        return $this->path;
187
    }
188
189
    /**
190
     * Retrieve the query string of the URI.
191
     *
192
     * If no query string is present, this method MUST return an empty string.
193
     *
194
     * The leading "?" character is not part of the query and MUST NOT be
195
     * added.
196
     *
197
     * The value returned MUST be percent-encoded, but MUST NOT double-encode
198
     * any characters. To determine what characters to encode, please refer to
199
     * RFC 3986, Sections 2 and 3.4.
200
     *
201
     * As an example, if a value in a key/value pair of the query string should
202
     * include an ampersand ("&") not intended as a delimiter between values,
203
     * that value MUST be passed in encoded form (e.g., "%26") to the instance.
204
     *
205
     * @see https://tools.ietf.org/html/rfc3986#section-2
206
     * @see https://tools.ietf.org/html/rfc3986#section-3.4
207
     * @return string The URI query string.
208
     */
209 9
    public function getQuery()
210
    {
211 9
        return $this->query;
212
    }
213
214
    /**
215
     * Retrieve the fragment component of the URI.
216
     *
217
     * If no fragment is present, this method MUST return an empty string.
218
     *
219
     * The leading "#" character is not part of the fragment and MUST NOT be
220
     * added.
221
     *
222
     * The value returned MUST be percent-encoded, but MUST NOT double-encode
223
     * any characters. To determine what characters to encode, please refer to
224
     * RFC 3986, Sections 2 and 3.5.
225
     *
226
     * @see https://tools.ietf.org/html/rfc3986#section-2
227
     * @see https://tools.ietf.org/html/rfc3986#section-3.5
228
     * @return string The URI fragment.
229
     */
230 9
    public function getFragment()
231
    {
232 9
        return $this->fragment;
233
    }
234
235
    /**
236
     * Return an instance with the specified scheme.
237
     *
238
     * This method MUST retain the state of the current instance, and return
239
     * an instance that contains the specified scheme.
240
     *
241
     * Implementations MUST support the schemes "http" and "https" case
242
     * insensitively, and MAY accommodate other schemes if required.
243
     *
244
     * An empty scheme is equivalent to removing the scheme.
245
     *
246
     * @param string $scheme The scheme to use with the new instance.
247
     * @return static A new instance with the specified scheme.
0 ignored issues
show
Documentation introduced by
Should the return type not be HttpUri|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...
248
     * @throws \InvalidArgumentException for invalid or unsupported schemes.
249
     */
250
    public function withScheme($scheme)
251
    {
252
        // TODO: Implement withScheme() method.
253
    }
254
255
    /**
256
     * Return an instance with the specified user information.
257
     *
258
     * This method MUST retain the state of the current instance, and return
259
     * an instance that contains the specified user information.
260
     *
261
     * Password is optional, but the user information MUST include the
262
     * user; an empty string for the user is equivalent to removing user
263
     * information.
264
     *
265
     * @param string $user The user name to use for authority.
266
     * @param null|string $password The password associated with $user.
267
     * @return static A new instance with the specified user information.
0 ignored issues
show
Documentation introduced by
Should the return type not be HttpUri|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
     */
269
    public function withUserInfo($user, $password = null)
270
    {
271
        // TODO: Implement withUserInfo() method.
272
    }
273
274
    /**
275
     * Return an instance with the specified host.
276
     *
277
     * This method MUST retain the state of the current instance, and return
278
     * an instance that contains the specified host.
279
     *
280
     * An empty host value is equivalent to removing the host.
281
     *
282
     * @param string $host The hostname to use with the new instance.
283
     * @return static A new instance with the specified host.
0 ignored issues
show
Documentation introduced by
Should the return type not be HttpUri|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...
284
     * @throws \InvalidArgumentException for invalid hostnames.
285
     */
286
    public function withHost($host)
287
    {
288
        // TODO: Implement withHost() method.
289
    }
290
291
    /**
292
     * Return an instance with the specified port.
293
     *
294
     * This method MUST retain the state of the current instance, and return
295
     * an instance that contains the specified port.
296
     *
297
     * Implementations MUST raise an exception for ports outside the
298
     * established TCP and UDP port ranges.
299
     *
300
     * A null value provided for the port is equivalent to removing the port
301
     * information.
302
     *
303
     * @param null|int $port The port to use with the new instance; a null value
304
     *     removes the port information.
305
     * @return static A new instance with the specified port.
0 ignored issues
show
Documentation introduced by
Should the return type not be HttpUri|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 ports.
307
     */
308
    public function withPort($port)
309
    {
310
        // TODO: Implement withPort() method.
311
    }
312
313
    /**
314
     * Return an instance with the specified path.
315
     *
316
     * This method MUST retain the state of the current instance, and return
317
     * an instance that contains the specified path.
318
     *
319
     * The path can either be empty or absolute (starting with a slash) or
320
     * rootless (not starting with a slash). Implementations MUST support all
321
     * three syntaxes.
322
     *
323
     * If the path is intended to be domain-relative rather than path relative then
324
     * it must begin with a slash ("/"). Paths not starting with a slash ("/")
325
     * are assumed to be relative to some base path known to the application or
326
     * consumer.
327
     *
328
     * Users can provide both encoded and decoded path characters.
329
     * Implementations ensure the correct encoding as outlined in getPath().
330
     *
331
     * @param string $path The path to use with the new instance.
332
     * @return static A new instance with the specified path.
0 ignored issues
show
Documentation introduced by
Should the return type not be HttpUri|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...
333
     * @throws \InvalidArgumentException for invalid paths.
334
     */
335
    public function withPath($path)
336
    {
337
        // TODO: Implement withPath() method.
338
    }
339
340
    /**
341
     * Return an instance with the specified query string.
342
     *
343
     * This method MUST retain the state of the current instance, and return
344
     * an instance that contains the specified query string.
345
     *
346
     * Users can provide both encoded and decoded query characters.
347
     * Implementations ensure the correct encoding as outlined in getQuery().
348
     *
349
     * An empty query string value is equivalent to removing the query string.
350
     *
351
     * @param string $query The query string to use with the new instance.
352
     * @return static A new instance with the specified query string.
0 ignored issues
show
Documentation introduced by
Should the return type not be HttpUri|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...
353
     * @throws \InvalidArgumentException for invalid query strings.
354
     */
355
    public function withQuery($query)
356
    {
357
        // TODO: Implement withQuery() method.
358
    }
359
360
    /**
361
     * Return an instance with the specified URI fragment.
362
     *
363
     * This method MUST retain the state of the current instance, and return
364
     * an instance that contains the specified URI fragment.
365
     *
366
     * Users can provide both encoded and decoded fragment characters.
367
     * Implementations ensure the correct encoding as outlined in getFragment().
368
     *
369
     * An empty fragment value is equivalent to removing the fragment.
370
     *
371
     * @param string $fragment The fragment to use with the new instance.
372
     * @return static A new instance with the specified fragment.
0 ignored issues
show
Documentation introduced by
Should the return type not be HttpUri|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...
373
     */
374
    public function withFragment($fragment)
375
    {
376
        // TODO: Implement withFragment() method.
377
    }
378
379
    /**
380
     * Return the string representation as a URI reference.
381
     *
382
     * Depending on which components of the URI are present, the resulting
383
     * string is either a full URI or relative reference according to RFC 3986,
384
     * Section 4.1. The method concatenates the various components of the URI,
385
     * using the appropriate delimiters:
386
     *
387
     * - If a scheme is present, it MUST be suffixed by ":".
388
     * - If an authority is present, it MUST be prefixed by "//".
389
     * - The path can be concatenated without delimiters. But there are two
390
     *   cases where the path has to be adjusted to make the URI reference
391
     *   valid as PHP does not allow to throw an exception in __toString():
392
     *     - If the path is rootless and an authority is present, the path MUST
393
     *       be prefixed by "/".
394
     *     - If the path is starting with more than one "/" and no authority is
395
     *       present, the starting slashes MUST be reduced to one.
396
     * - If a query is present, it MUST be prefixed by "?".
397
     * - If a fragment is present, it MUST be prefixed by "#".
398
     *
399
     * @see http://tools.ietf.org/html/rfc3986#section-4.1
400
     * @return string
401
     */
402 9
    public function __toString()
403
    {
404 9
        $scheme = $this->getScheme();
405 9
        $authority = $this->getAuthority();
406 9
        $path = $this->getPath();
407 9
        $fragment = $this->getFragment() ? '#'.$this->getFragment() : '';
408 9
        $query = $this->getQuery() ? '?'.$this->getQuery() : '';
409
410 9
        return "{$scheme}://{$authority}{$path}{$query}{$fragment}";
411
    }
412
413
    /**
414
     * @param string $scheme
415
     * @return HttpUri
416
     */
417 10
    public function setScheme($scheme)
418
    {
419 10
        $this->scheme = $scheme;
420
421 10
        return $this;
422
    }
423
424
    /**
425
     * @param string $userInfo
426
     * @return HttpUri
427
     */
428 10
    public function setUserInfo($userInfo)
429
    {
430 10
        $this->userInfo = $userInfo;
431
432 10
        return $this;
433
    }
434
435
    /**
436
     * @param string $host
437
     * @return HttpUri
438
     */
439 10
    public function setHost($host)
440
    {
441 10
        $this->host = $host;
442
443 10
        return $this;
444
    }
445
446
    /**
447
     * @param int|null $port
448
     * @return HttpUri
449
     */
450 10
    public function setPort($port)
451
    {
452 10
        $this->port = $port;
453
454 10
        return $this;
455
    }
456
457
    /**
458
     * @param string $path
459
     * @return HttpUri
460
     */
461 10
    public function setPath($path)
462
    {
463 10
        $this->path = $path;
464
465 10
        return $this;
466
    }
467
468
    /**
469
     * @param string $query
470
     * @return HttpUri
471
     */
472 10
    public function setQuery($query)
473
    {
474 10
        $this->query = $query;
475
476 10
        return $this;
477
    }
478
479
    /**
480
     * @param string $fragment
481
     * @return HttpUri
482
     */
483 10
    public function setFragment($fragment)
484
    {
485 10
        $this->fragment = $fragment;
486
487 10
        return $this;
488
    }
489
490 12
    public static function parseUri($uri)
491
    {
492 12
        $pattern = sprintf(self::PATTERN, implode('|', ['http', 'https']));
493
494 12
        if (!preg_match($pattern, $uri, $m)) {
495 4
            throw new \InvalidArgumentException("Invalid HTTP URI");
496
        }
497
498 9
        $parts = self::getDefaultComponents();
499
500 9
        foreach ($m as $part => $value) {
501 9
            if (is_numeric($part)) {
502 9
                continue;
503
            }
504
            switch ($part) {
505 9
                case 'scheme':
506 9
                case 'userInfo':
507 9
                case 'host':
508 9
                case 'port':
509 9
                case 'path':
510 1
                case 'query':
511 1
                case 'fragment':
512 9
                    $parts[$part] = $value;
513 9
                    continue;
514
                default:
515 9
                    continue;
516
            }
517
        }
518
519 9
        array_walk($parts, function (&$value, $part) {
520
            switch ($part) {
521 9
                case 'userInfo':
522 9
                    $value = preg_replace('/[@]$/', '', $value);
523 9
                    break;
524 9
                case 'port':
525 9
                    $value = str_replace(':', '', $value);
526 9
                    if (!is_numeric($value)) {
527 8
                        $value = null;
528
                    }
529 9
                    break;
530 9
                case 'query':
531 9
                    $value = preg_replace('/^[?]/', '', $value);
532 9
                    break;
533 9
                case 'fragment':
534 9
                    $value = preg_replace('/^[#]/', '', $value);
535 9
                    break;
536
                default:
537 9
                    return;
538
            }
539 9
        });
540
541 9
        return $parts;
542
    }
543
544 10
    public static function createFromString($uri)
545
    {
546 10
        $parts = self::parseUri($uri);
547
548 7
        return self::createFromComponents($parts);
549
    }
550
551 10
    public static function createFromComponents($parts)
552
    {
553
        // Set default values
554 10
        $parts = array_merge(self::getDefaultComponents(), $parts);
555
556 10
        $uri = (new HttpUri())
557 10
            ->setScheme($parts['scheme'])
558 10
            ->setUserInfo($parts['userInfo'])
559 10
            ->setHost($parts['host'])
560 10
            ->setPort($parts['port'])
561 10
            ->setPath($parts['path'])
562 10
            ->setQuery($parts['query'])
563 10
            ->setFragment($parts['fragment']);
564
565 10
        return $uri;
566
    }
567
568 12
    private static function getDefaultComponents()
569
    {
570
        return [
571 12
            'scheme' => '',
572
            'userInfo' => '',
573
            'host' => '',
574
            'port' => null,
575
            'path' => '',
576
            'query' => '',
577
            'fragment' => '',
578
        ];
579
    }
580
}
581