Completed
Push — master ( 74ac7c...809da3 )
by Damien
02:39
created

Uri::createFromString()   D

Complexity

Conditions 12
Paths 131

Size

Total Lines 36
Code Lines 21

Duplication

Lines 0
Ratio 0 %
Metric Value
dl 0
loc 36
rs 4.8484
cc 12
eloc 21
nc 131
nop 1

How to fix   Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
/**
3
 * Veto.
4
 * PHP Microframework.
5
 *
6
 * @author Damien Walsh <[email protected]>
7
 * @copyright Damien Walsh 2013-2014
8
 * @version 0.1
9
 * @package veto
10
 */
11
namespace Veto\Http;
12
13
use Psr\Http\Message\UriInterface;
14
use Veto\Collection\Bag;
15
16
/**
17
 * HTTP URI Value Type
18
 *
19
 * Substantially based on Slim Framework's URI implementation.
20
 *
21
 * @link https://github.com/slimphp/Slim/
22
 */
23
class Uri implements UriInterface
24
{
25
    /**
26
     * The URI scheme (without "://" suffix)
27
     *
28
     * @var string
29
     */
30
    protected $scheme = '';
31
32
    /**
33
     * The user info encoded in the url (for URLS like http://user@password:example.com
34
     *
35
     * @var string Either empty or encoded in form username[:password]
36
     */
37
    protected $userInfo = '';
38
39
    /**
40
     * The URI host
41
     *
42
     * @var string
43
     */
44
    protected $host = '';
45
46
    /**
47
     * The URI port number
48
     *
49
     * @var int
50
     */
51
    protected $port;
52
53
    /**
54
     * The URI path
55
     *
56
     * @var string
57
     */
58
    protected $path = '';
59
60
    /**
61
     * The URI query string (without "?" prefix)
62
     *
63
     * @var string
64
     */
65
    protected $query = '';
66
67
    /**
68
     * The URI fragment string (without "#" prefix)
69
     *
70
     * @var string
71
     */
72
    protected $fragment = '';
73
74
    /**
75
     * Create a new URI
76
     *
77
     * @param string $scheme URI scheme
78
     * @param string $host URI host
79
     * @param int|null $port URI port number
80
     * @param string $path URI path
81
     * @param string $query URI query string
82
     * @param string $fragment
83
     * @param string $userInfo (optional) username & password encoded in URI
84
     */
85
    public function __construct($scheme, $host, $port = null, $path = '/', $query = '', $fragment = '', $userInfo = '')
86
    {
87
        $this->scheme = $scheme;
88
        $this->host = $host;
89
        $this->path = empty($path) ? '/' : $path;
90
        $this->query = $query;
91
        $this->fragment = $fragment;
92
        $this->userInfo = $userInfo;
93
94
        // Default ports SHOULD be null
95
        $this->port = $port;
96
        if ($this->isStandardPort($port, $scheme)) {
97
            $this->port = null;
98
        }
99
    }
100
101
    /**
102
     * Create new Uri from string
103
     *
104
     * Substantially based on Slim Framework's URI implementation.
105
     *
106
     * @param  string $uri Complete Uri string (i.e., https://user:pass@host:443/path?query)
107
     * @link https://github.com/slimphp/Slim/
108
     * @throws \InvalidArgumentException
109
     * @return self
110
     */
111
    public static function createFromString($uri)
112
    {
113
        // Check for valid argument type
114
        if (!is_string($uri) && !method_exists($uri, '__toString')) {
115
            throw new \InvalidArgumentException(
116
                '\Veto\Http\Uri::createFromString() argument must be a string'
117
            );
118
        }
119
120
        // Normalize URL before validation as filter_var requires this for a valid url
121
        $mungedUri = $uri;
122
        if (!array_key_exists('scheme', parse_url($uri))) {
123
            $mungedUri = 'http://' . $uri;
124
        }
125
126
        $parts = parse_url($mungedUri);
127
128
        // Ensure that the URL is valid
129
        if (!filter_var($mungedUri, FILTER_VALIDATE_URL) || !strlen($parts['host'])) {
130
            throw new \InvalidArgumentException(
131
                'Call to \\' . __METHOD__ . '() with invalid URI "' . $uri . '"'
132
            );
133
        }
134
135
        $scheme = $parts['scheme'];
136
        $user = isset($parts['user']) ? $parts['user'] : '';
137
        $pass = isset($parts['pass']) ? $parts['pass'] : '';
138
        $host = $parts['host'];
139
        $port = isset($parts['port']) ? $parts['port'] : null;
140
        $path = isset($parts['path']) ? $parts['path'] : '';
141
        $query = isset($parts['query']) ? $parts['query'] : '';
142
        $fragment = isset($parts['fragment']) ? $parts['fragment'] : '';
143
        $userInfo = static::buildUserInfo($user, $pass);
144
145
        return new static($scheme, $host, $port, $path, $query, $fragment, $userInfo);
146
    }
147
148
    /**
149
     * Create new URI from the provided environment
150
     *
151
     * Substantially based on Slim Framework's URI implementation.
152
     *
153
     * @param Bag $environment
154
     * @link https://github.com/slimphp/Slim/
155
     * @return self
156
     */
157
    public static function createFromEnvironment(Bag $environment)
158
    {
159
        // Scheme
160
        if ($environment->has('HTTP_X_FORWARDED_PROTO') === true) {
161
            $scheme = $environment->get('HTTP_X_FORWARDED_PROTO');
162
        } else {
163
            $https = $environment->get('HTTPS', '');
164
            $scheme = empty($https) || $https === 'off' ? 'http' : 'https';
165
        }
166
167
        // Authority
168
        $user = $environment->get('PHP_AUTH_USER', '');
169
        $password = $environment->get('PHP_AUTH_PW', '');
170
        $host = $environment->get('HTTP_HOST', $environment->get('SERVER_NAME'));
171
        $port = (int)$environment->get('SERVER_PORT', null);
172
173
        // Path
174
        $requestUri = parse_url($environment->get('REQUEST_URI'), PHP_URL_PATH);
175
        $path = '/' . ltrim($requestUri, '/');
176
177
        // Query string
178
        $queryString = $environment->get('QUERY_STRING', '');
179
180
        // Fragment
181
        $fragment = '';
182
183
        $userInfo = static::buildUserInfo($user, $password);
184
185
        // Build Uri
186
        return new static($scheme, $host, $port, $path, $queryString, $fragment, $userInfo);
187
    }
188
189
    /**
190
     * Build user info string from username/password components.
191
     *
192
     * @param string $username
193
     * @param string $password
194
     * @return string
195
     */
196
    protected static function buildUserInfo($username, $password)
197
    {
198
        $userInfo = '';
199
        if (strlen($username)) {
200
            $userInfo = $username;
201
202
            if (strlen($password)) {
203
                $userInfo .= ':' . $password;
204
            }
205
        }
206
207
        return $userInfo;
208
    }
209
210
    /**
211
     * Retrieve the URI scheme.
212
     *
213
     * Implementations SHOULD restrict values to "http", "https", or an empty
214
     * string but MAY accommodate other schemes if required.
215
     *
216
     * If no scheme is present, this method MUST return an empty string.
217
     *
218
     * The string returned MUST omit the trailing "://" delimiter if present.
219
     *
220
     * @return string The scheme of the URI.
221
     */
222
    public function getScheme()
223
    {
224
        return $this->scheme;
225
    }
226
227
    /**
228
     * Retrieve the authority portion of the URI.
229
     *
230
     * The authority portion of the URI is:
231
     *
232
     * <pre>
233
     * [user-info@]host[:port]
234
     * </pre>
235
     *
236
     * If the port component is not set or is the standard port for the current
237
     * scheme, it SHOULD NOT be included.
238
     *
239
     * This method MUST return an empty string if no authority information is
240
     * present.
241
     *
242
     * @return string Authority portion of the URI, in "[user-info@]host[:port]"
243
     *     format.
244
     */
245
    public function getAuthority()
246
    {
247
        $userInfo = $this->getUserInfo();
248
        $host = $this->getHost();
249
        $port = $this->getPort();
250
        $showPort = !$this->isStandardPort($port, $this->scheme);
251
252
        return ($userInfo ? $userInfo . '@' : '') . $host . ($port && $showPort ? ':' . $port : '');
253
    }
254
255
    /**
256
     * Retrieve the user information portion of the URI, if present.
257
     *
258
     * If a user is present in the URI, this will return that value;
259
     * additionally, if the password is also present, it will be appended to the
260
     * user value, with a colon (":") separating the values.
261
     *
262
     * Implementations MUST NOT return the "@" suffix when returning this value.
263
     *
264
     * @return string User information portion of the URI, if present, in
265
     *     "username[:password]" format.
266
     */
267
    public function getUserInfo()
268
    {
269
        return $this->userInfo;
270
    }
271
272
    /**
273
     * Retrieve the host segment of the URI.
274
     *
275
     * This method MUST return a string; if no host segment is present, an
276
     * empty string MUST be returned.
277
     *
278
     * @return string Host segment of the URI.
279
     */
280
    public function getHost()
281
    {
282
        return $this->host;
283
    }
284
285
    /**
286
     * Retrieve the port segment of the URI.
287
     *
288
     * If a port is present, and it is non-standard for the current scheme,
289
     * this method MUST return it as an integer. If the port is the standard port
290
     * used with the current scheme, this method SHOULD return null.
291
     *
292
     * If no port is present, and no scheme is present, this method MUST return
293
     * a null value.
294
     *
295
     * If no port is present, but a scheme is present, this method MAY return
296
     * the standard port for that scheme, but SHOULD return null.
297
     *
298
     * @return null|int The port for the URI.
299
     */
300
    public function getPort()
301
    {
302
        return $this->port;
303
    }
304
305
    /**
306
     * Retrieve the path segment of the URI.
307
     *
308
     * This method MUST return a string; if no path is present it MUST return
309
     * the string "/".
310
     *
311
     * @return string The path segment of the URI.
312
     */
313
    public function getPath()
314
    {
315
        return $this->path;
316
    }
317
318
    /**
319
     * Retrieve the query string of the URI.
320
     *
321
     * This method MUST return a string; if no query string is present, it MUST
322
     * return an empty string.
323
     *
324
     * The string returned MUST omit the leading "?" character.
325
     *
326
     * @return string The URI query string.
327
     */
328
    public function getQuery()
329
    {
330
        return $this->query;
331
    }
332
333
    /**
334
     * Retrieve the fragment segment of the URI.
335
     *
336
     * This method MUST return a string; if no fragment is present, it MUST
337
     * return an empty string.
338
     *
339
     * The string returned MUST omit the leading "#" character.
340
     *
341
     * @return string The URI fragment.
342
     */
343
    public function getFragment()
344
    {
345
        return $this->fragment;
346
    }
347
348
    /**
349
     * Create a new instance with the specified scheme.
350
     *
351
     * This method MUST retain the state of the current instance, and return
352
     * a new instance that contains the specified scheme. If the scheme
353
     * provided includes the "://" delimiter, it MUST be removed.
354
     *
355
     * Implementations SHOULD restrict values to "http", "https", or an empty
356
     * string but MAY accommodate other schemes if required.
357
     *
358
     * An empty scheme is equivalent to removing the scheme.
359
     *
360
     * @param string $scheme The scheme to use with the new instance.
361
     * @return self A new instance with the specified scheme.
362
     * @throws \InvalidArgumentException for invalid or unsupported schemes.
363
     */
364
    public function withScheme($scheme)
365
    {
366
        $clone = clone $this;
367
        $clone->scheme = $scheme;
368
369
        return $clone;
370
    }
371
372
    /**
373
     * Create a new instance with the specified user information.
374
     *
375
     * This method MUST retain the state of the current instance, and return
376
     * a new instance that contains the specified user information.
377
     *
378
     * Password is optional, but the user information MUST include the
379
     * user; an empty string for the user is equivalent to removing user
380
     * information.
381
     *
382
     * @param string $user User name to use for authority.
383
     * @param null|string $password Password associated with $user.
384
     * @return self A new instance with the specified user information.
385
     */
386
    public function withUserInfo($user, $password = null)
387
    {
388
        $clone = clone $this;
389
390
        $clone->userInfo = static::buildUserInfo($user, $password);
391
392
        return $clone;
393
    }
394
395
    /**
396
     * Create a new instance with the specified host.
397
     *
398
     * This method MUST retain the state of the current instance, and return
399
     * a new instance that contains the specified host.
400
     *
401
     * An empty host value is equivalent to removing the host.
402
     *
403
     * @param string $host Hostname to use with the new instance.
404
     * @return self A new instance with the specified host.
405
     * @throws \InvalidArgumentException for invalid hostnames.
406
     */
407
    public function withHost($host)
408
    {
409
        $clone = clone $this;
410
        $clone->host = $host;
411
412
        return $clone;
413
    }
414
415
    /**
416
     * Create a new instance with the specified port.
417
     *
418
     * This method MUST retain the state of the current instance, and return
419
     * a new instance that contains the specified port.
420
     *
421
     * Implementations MUST raise an exception for ports outside the
422
     * established TCP and UDP port ranges.
423
     *
424
     * A null value provided for the port is equivalent to removing the port
425
     * information.
426
     *
427
     * @param null|int $port Port to use with the new instance; a null value
428
     *     removes the port information.
429
     * @return self A new instance with the specified port.
430
     * @throws \InvalidArgumentException for invalid ports.
431
     */
432
    public function withPort($port)
433
    {
434
        $clone = clone $this;
435
        $clone->port = $port;
436
437
        return $clone;
438
    }
439
440
    /**
441
     * Create a new instance with the specified path.
442
     *
443
     * This method MUST retain the state of the current instance, and return
444
     * a new instance that contains the specified path.
445
     *
446
     * The path MUST be prefixed with "/"; if not, the implementation MAY
447
     * provide the prefix itself.
448
     *
449
     * The implementation MUST percent-encode reserved characters as
450
     * specified in RFC 3986, Section 2, but MUST NOT double-encode any
451
     * characters.
452
     *
453
     * An empty path value is equivalent to removing the path.
454
     *
455
     * @param string $path The path to use with the new instance.
456
     * @return self A new instance with the specified path.
457
     * @throws \InvalidArgumentException for invalid paths.
458
     */
459
    public function withPath($path)
460
    {
461
        $clone = clone $this;
462
        $clone->path = $path;
463
464
        return $clone;
465
    }
466
467
    /**
468
     * Create a new instance with the specified query string.
469
     *
470
     * This method MUST retain the state of the current instance, and return
471
     * a new instance that contains the specified query string.
472
     *
473
     * If the query string is prefixed by "?", that character MUST be removed.
474
     * Additionally, the query string SHOULD be parseable by parse_str() in
475
     * order to be valid.
476
     *
477
     * The implementation MUST percent-encode reserved characters as
478
     * specified in RFC 3986, Section 2, but MUST NOT double-encode any
479
     * characters.
480
     *
481
     * An empty query string value is equivalent to removing the query string.
482
     *
483
     * @param string $query The query string to use with the new instance.
484
     * @return self A new instance with the specified query string.
485
     * @throws \InvalidArgumentException for invalid query strings.
486
     */
487
    public function withQuery($query)
488
    {
489
        $clone = clone $this;
490
        $clone->query = $query;
491
492
        return $clone;
493
    }
494
495
    /**
496
     * Create a new instance with the specified URI fragment.
497
     *
498
     * This method MUST retain the state of the current instance, and return
499
     * a new instance that contains the specified URI fragment.
500
     *
501
     * If the fragment is prefixed by "#", that character MUST be removed.
502
     *
503
     * An empty fragment value is equivalent to removing the fragment.
504
     *
505
     * @param string $fragment The URI fragment to use with the new instance.
506
     * @return self A new instance with the specified URI fragment.
507
     */
508
    public function withFragment($fragment)
509
    {
510
        $clone = clone $this;
511
        $clone->fragment = $fragment;
512
513
        return $clone;
514
    }
515
516
    /**
517
     * Convert this URI to the string representation.
518
     *
519
     * @return string
520
     */
521
    public function __toString()
522
    {
523
        $scheme = $this->getScheme();
524
        $authority = $this->getAuthority();
525
        $path = $this->getPath();
526
        $query = $this->getQuery();
527
        $fragment = $this->getFragment();
528
529
        return ($scheme ? $scheme . '://' : '') . $authority . $path . ($query ? '?' . $query : '') . ($fragment ? '#' . $fragment : '');
530
    }
531
532
    /**
533
     * Does this URI use a standard port?
534
     *
535
     * @param int|null $port
536
     * @param string $scheme
537
     *
538
     * @return bool
539
     */
540
    protected function isStandardPort($port, $scheme)
541
    {
542
        return ($scheme === 'http' && $port === 80) ||
543
            ($scheme === 'https' && $port === 443);
544
    }
545
}
546