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

This check looks TODO comments that have been left in the code.

``TODO``s show that something is left unfinished and should be attended to.

Loading history...
503
     * Converts a given array of URI parts to a string according to the specification of the __toString magic method
504
     *
505
     * @see Uri::__toString
506
     *
507
     * @param array $uri_parts  The URI parts to be combined into a string
508
     * @return string           The string combined from the array of URI parts
509
     */
510 17
    private function toString(array $uri_parts)
511
    {
512 17
        $uri_string = "";
513
514 17
        $uri_string .= $this->scheme->toUriString();
515
516 17
        $uri_string .= $this->authority->toUriString($this->scheme);
517
518 17
        $uri_string .= $this->pathToString($uri_parts["path"], $uri_parts["authority"]);
519
520 17
        $uri_string .= $this->query->toUriString();
521
522 17
        $uri_string .= $this->fragment->toUriString();
523
524 17
        return $uri_string;
525
    }
526
527
    /**
528
     * Splits a string URI into its component parts, returning true if the URI string matches a valid URI's syntax
529
     * and false if the URI string does not
530
     *
531
     * @param string $uri   The URI string to be decomposed
532
     * @return bool         Returns true if the URI string matches a valid URI's syntax
533
     *                      Returns false otherwise
534
     */
535 81
    private function explodeUri($uri)
536
    {
537 81
        $reg_start     = '/^';
538 81
        $scheme_part   = '(?:(?\'scheme\'[A-Za-z0-9][^\/\?#:]+):)?';
539 81
        $hier_part     = '(?\'hier_part\'[^\?#]+)?';
540 81
        $query_part    = '(?:\?(?\'query\'[^#]*))?';
541 81
        $fragment_part = '(?:#(?\'fragment\'.*))?';
542 81
        $reg_end       = '/';
543
544 81
        $uri_syntax = $reg_start . $scheme_part . $hier_part . $query_part . $fragment_part . $reg_end;
545
546 81
        $uri_valid = preg_match($uri_syntax, $uri, $parts);
547
548 81
        $this->uri_parts = array_merge($this->uri_parts, $parts); //overwriting default values with matches
549
550 81
        $this->explodeHierParts($this->uri_parts["hier_part"]);
551
552 81
        $this->scheme   = new Scheme($this->uri_parts["scheme"]);
553 81
        $this->query    = new Query($this->uri_parts["query"]);
554 81
        $this->fragment = new Fragment($this->uri_parts["fragment"]);
555
556 81
        $this->sanitizeUriPartsArray();
557
558 81
        return (bool) $uri_valid;
559
    }
560
561
    /**
562
     * Splits URI hierarchy data into authority and path data.
563
     *
564
     * @param string $hier_part     The hierarchy part of a URI to be decomposed
565
     * @return void
566
     */
567 81
    private function explodeHierParts($hier_part)
568
    {
569
        $hier_parts = array(
570 81
            "authority" => "",
571 81
            "path"      => "",
572 81
        );
573
574 81
        $reg_start      = '/^';
575 81
        $authority_part = '(?:(?:\/\/)(?\'authority\'.[^\/]+))?';
576 81
        $path_part      = '(?\'path\'.+)?';
577 81
        $reg_end        = '/';
578
579 81
        $hier_part_syntax = $reg_start . $authority_part . $path_part . $reg_end;
580
581 81
        preg_match($hier_part_syntax, $hier_part, $matches);
582
583 81
        $hier_parts = array_merge($hier_parts, $matches);
584
585 81
        $this->authority = new Authority($hier_parts["authority"]);
586 81
        $this->user_info = $this->authority->getUserInfo();
587 81
        $this->host      = $this->authority->getHost();
588 81
        $this->port      = $this->authority->getPort();
589
590 81
        $this->uri_parts["user_info"] = (string) $this->user_info;
591 81
        $this->uri_parts["host"]      = (string) $this->host;
592 81
        $this->uri_parts["port"]      = $this->port->getPort();
593
594 81
        $this->uri_parts = array_merge($this->uri_parts, $hier_parts);
595 81
    }
596
597
    /**
598
     * Sanitizes the URI component array by removing redundant key/value pairs
599
     *
600
     * @return void
601
     */
602 81
    private function sanitizeUriPartsArray()
603
    {
604 81
        $uri_part_array = new ArrayHelper($this->uri_parts);
605
606 81
        $this->uri_parts = $uri_part_array->removeNumericKeys();
607 81
    }
608
609
    /**
610
     * Percent encodes a component string except for sub-delims and unencoded pchar characters as defined by RFC 3986
611
     * in addition to any component-specific unencoded characters
612
     *
613
     * @param string $component_string          The string representing a URI component
614
     * @param string[] $component_unencoded     [OPTIONAL] Any additional unencoded characters specific to the component
615
     *
616
     * @return string                           The string with appropriate characters percent-encoded
617
     */
618 4
    private function encodeComponent($component_string, array $component_unencoded = array())
619
    {
620 4
        $uri_unencoded = array_merge($component_unencoded, $this->sub_delims, $this->pchar_unencoded);
621
622 4
        $string_helper = new StringHelper($component_string);
623
624 4
        $encoded_string = $string_helper->affectChunks("rawurlencode", ...$uri_unencoded);
625
626 4
        return $encoded_string;
627
    }
628
629
    /**
630
     * Determines whether a string contains unallowed URI characters, provided a string of allowed characters for a
631
     * given component.
632
     *
633
     * Note that a percent-encoded character (e.g. %20 for space) automatically counts as an allowed character, whereas
634
     * a percent sign not followed by two hex digits (e.g. %2X) does not count as an allowed character.
635
     *
636
     * @param string $string    The string to be checked for unallowed characters
637
     * @param string $allowed   A string containing all allowed characters for a given component
638
     *
639
     * @return bool             Returns true if the string contains unallowed characters
640
     *                          Returns false if the string contains only allowed characters (including percent-encoded
641
     *                          characters)
642
     */
643 8
    private function containsUnallowedUriCharacters($string, $allowed)
644
    {
645 8
        $allowed = preg_quote($allowed, "/");
646
647 8
        $pattern = "/^([0-9a-zA-Z\\.\\-_~{$allowed}]|%[0-9a-fA-F]{2})*\$/";
648
649 8
        $matches_allowed = preg_match($pattern, $string);
650
651 8
        return (bool) !$matches_allowed;
652
    }
653
654
    /**
655
     * Returns the appropriate path string based upon __toString specification rules.
656
     *
657
     * @see Uri::__toString()
658
     *
659
     * @param string $path          The path to compile into a URI-friendly string
660
     * @param string $authority     [optional] The authority of the URI
661
     *
662
     * @return string               The URI-friendly path string
663
     */
664 17
    private function pathToString($path, $authority = "")
665
    {
666 17
        $path_string        = "";
667 17
        $path_string_helper = new StringHelper($path);
668
669 17
        if (empty($authority)) {
670 5
            $collapsed_slashes = $path_string_helper->collapseStartingRepetition("/");
671
672 5
            $path_string .= $collapsed_slashes;
673 17
        } elseif (!empty($path)) {
674 11
            if (!$path_string_helper->startsWith("/")) {
675 1
                $path_string .= "/" . $path;
676 1
            } else {
677 10
                $path_string .= $path;
678
            }
679 11
        }
680
681 17
        return $path_string;
682
    }
683
}
684