Completed
Push — remove-yii-autoloader ( 560d61...5f8600 )
by Alexander
27:04 queued 23:12
created

Uri::setScheme()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 4
rs 10
c 0
b 0
f 0
cc 1
eloc 2
nc 1
nop 1
1
<?php
2
/**
3
 * @link http://www.yiiframework.com/
4
 * @copyright Copyright (c) 2008 Yii Software LLC
5
 * @license http://www.yiiframework.com/license/
6
 */
7
8
namespace yii\http;
9
10
use Psr\Http\Message\UriInterface;
11
use yii\base\BaseObject;
12
use yii\base\ErrorHandler;
13
use yii\base\InvalidArgumentException;
14
15
/**
16
 * Uri represents a URI.
17
 *
18
 * Create from components example:
19
 *
20
 * ```php
21
 * $uri = new Uri([
22
 *     'scheme' => 'http',
23
 *     'user' => 'username',
24
 *     'password' => 'password',
25
 *     'host' => 'example.com',
26
 *     'port' => 9090,
27
 *     'path' => '/content/path',
28
 *     'query' => 'foo=some',
29
 *     'fragment' => 'anchor',
30
 * ]);
31
 * ```
32
 *
33
 * Create from string example:
34
 *
35
 * ```php
36
 * $uri = new Uri(['string' => 'http://example.com?foo=some']);
37
 * ```
38
 *
39
 * Create using PSR-7 syntax:
40
 *
41
 * ```php
42
 * $uri = (new Uri())
43
 *     ->withScheme('http')
44
 *     ->withUserInfo('username', 'password')
45
 *     ->withHost('example.com')
46
 *     ->withPort(9090)
47
 *     ->withPath('/content/path')
48
 *     ->withQuery('foo=some')
49
 *     ->withFragment('anchor');
50
 * ```
51
 *
52
 * @property string $scheme the scheme component of the URI.
53
 * @property string $user
54
 * @property string $password
55
 * @property string $host the hostname to be used.
56
 * @property int|null $port port number.
57
 * @property string $path the path component of the URI
58
 * @property string|array $query the query string or array of query parameters.
59
 * @property string $fragment URI fragment.
60
 * @property string $authority the authority component of the URI. This property is read-only.
61
 * @property string $userInfo the user information component of the URI. This property is read-only.
62
 *
63
 * @author Paul Klimov <[email protected]>
64
 * @since 2.1.0
65
 */
66
class Uri extends BaseObject implements UriInterface
67
{
68
    /**
69
     * @var string URI complete string.
70
     */
71
    private $_string;
72
    /**
73
     * @var array URI components.
74
     */
75
    private $_components;
76
    /**
77
     * @var array scheme default ports in format: `[scheme => port]`
78
     */
79
    private static $defaultPorts = [
80
        'http'  => 80,
81
        'https' => 443,
82
        'ftp' => 21,
83
        'gopher' => 70,
84
        'nntp' => 119,
85
        'news' => 119,
86
        'telnet' => 23,
87
        'tn3270' => 23,
88
        'imap' => 143,
89
        'pop' => 110,
90
        'ldap' => 389,
91
    ];
92
93
94
    /**
95
     * @return string URI string representation.
96
     */
97
    public function getString()
98
    {
99
        if ($this->_string !== null) {
100
            return $this->_string;
101
        }
102
        if ($this->_components === null) {
103
            return '';
104
        }
105
        return $this->composeUri($this->_components);
106
    }
107
108
    /**
109
     * @param string $string URI full string.
110
     */
111
    public function setString($string)
112
    {
113
        $this->_string = $string;
114
        $this->_components = null;
0 ignored issues
show
Documentation Bug introduced by
It seems like null of type null is incompatible with the declared type array of property $_components.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
115
    }
116
117
    /**
118
     * {@inheritdoc}
119
     */
120
    public function getScheme()
121
    {
122
        return $this->getComponent('scheme');
123
    }
124
125
    /**
126
     * Sets up the scheme component of the URI.
127
     * @param string $scheme the scheme.
128
     */
129
    public function setScheme($scheme)
130
    {
131
        $this->setComponent('scheme', $scheme);
132
    }
133
134
    /**
135
     * {@inheritdoc}
136
     */
137
    public function withScheme($scheme)
138
    {
139
        if ($this->getScheme() === $scheme) {
140
            return $this;
141
        }
142
143
        $newInstance = clone $this;
144
        $newInstance->setScheme($scheme);
145
        return $newInstance;
146
    }
147
148
    /**
149
     * {@inheritdoc}
150
     */
151
    public function getAuthority()
152
    {
153
        return $this->composeAuthority($this->getComponents());
154
    }
155
156
    /**
157
     * {@inheritdoc}
158
     */
159
    public function getUserInfo()
160
    {
161
        return $this->composeUserInfo($this->getComponents());
162
    }
163
164
    /**
165
     * {@inheritdoc}
166
     */
167
    public function getHost()
168
    {
169
        return $this->getComponent('host', '');
170
    }
171
172
    /**
173
     * Specifies hostname.
174
     * @param string $host the hostname to be used.
175
     */
176
    public function setHost($host)
177
    {
178
        $this->setComponent('host', $host);
179
    }
180
181
    /**
182
     * {@inheritdoc}
183
     */
184
    public function withHost($host)
185
    {
186
        if ($this->getHost() === $host) {
187
            return $this;
188
        }
189
190
        $newInstance = clone $this;
191
        $newInstance->setHost($host);
192
        return $newInstance;
193
    }
194
195
    /**
196
     * {@inheritdoc}
197
     */
198
    public function getPort()
199
    {
200
        return $this->getComponent('port');
201
    }
202
203
    /**
204
     * Specifies port.
205
     * @param int|null $port The port to be used; a `null` value removes the port information.
206
     */
207
    public function setPort($port)
208
    {
209
        if ($port !== null) {
210
            if (!is_int($port)) {
211
                throw new InvalidArgumentException('URI port must be an integer.');
212
            }
213
        }
214
        $this->setComponent('port', $port);
215
    }
216
217
    /**
218
     * {@inheritdoc}
219
     */
220
    public function withPort($port)
221
    {
222
        if ($this->getPort() === $port) {
223
            return $this;
224
        }
225
226
        $newInstance = clone $this;
227
        $newInstance->setPort($port);
228
        return $newInstance;
229
    }
230
231
    /**
232
     * {@inheritdoc}
233
     */
234
    public function getPath()
235
    {
236
        return $this->getComponent('path', '');
237
    }
238
239
    /**
240
     * Specifies path component of the URI
241
     * @param string $path the path to be used.
242
     */
243
    public function setPath($path)
244
    {
245
        $this->setComponent('path', $path);
246
    }
247
248
    /**
249
     * {@inheritdoc}
250
     */
251
    public function withPath($path)
252
    {
253
        if ($this->getPath() === $path) {
254
            return $this;
255
        }
256
257
        $newInstance = clone $this;
258
        $newInstance->setPath($path);
259
        return $newInstance;
260
    }
261
262
    /**
263
     * {@inheritdoc}
264
     */
265
    public function getQuery()
266
    {
267
        return $this->getComponent('query', '');
268
    }
269
270
    /**
271
     * Specifies query string.
272
     * @param string|array|object $query the query string or array of query parameters.
273
     */
274
    public function setQuery($query)
275
    {
276
        if (is_array($query) || is_object($query)) {
277
            $query = http_build_query($query);
278
        }
279
        $this->setComponent('query', $query);
280
    }
281
282
    /**
283
     * {@inheritdoc}
284
     */
285
    public function withQuery($query)
286
    {
287
        if ($this->getQuery() === $query) {
288
            return $this;
289
        }
290
291
        $newInstance = clone $this;
292
        $newInstance->setQuery($query);
293
        return $newInstance;
294
    }
295
296
    /**
297
     * {@inheritdoc}
298
     */
299
    public function getFragment()
300
    {
301
        return $this->getComponent('fragment', '');
302
    }
303
304
    /**
305
     * Specifies URI fragment.
306
     * @param string $fragment the fragment to be used.
307
     */
308
    public function setFragment($fragment)
309
    {
310
        $this->setComponent('fragment', $fragment);
311
    }
312
313
    /**
314
     * {@inheritdoc}
315
     */
316
    public function withFragment($fragment)
317
    {
318
        if ($this->getFragment() === $fragment) {
319
            return $this;
320
        }
321
322
        $newInstance = clone $this;
323
        $newInstance->setFragment($fragment);
324
        return $newInstance;
325
    }
326
327
    /**
328
     * @return string the user name to use for authority.
329
     */
330
    public function getUser()
331
    {
332
        return $this->getComponent('user', '');
333
    }
334
335
    /**
336
     * @param string $user the user name to use for authority.
337
     */
338
    public function setUser($user)
339
    {
340
        $this->setComponent('user', $user);
341
    }
342
343
    /**
344
     * @return string password associated with [[user]].
345
     */
346
    public function getPassword()
347
    {
348
        return $this->getComponent('pass', '');
349
    }
350
351
    /**
352
     * @param string $password password associated with [[user]].
353
     */
354
    public function setPassword($password)
355
    {
356
        $this->setComponent('pass', $password);
357
    }
358
359
    /**
360
     * {@inheritdoc}
361
     */
362
    public function withUserInfo($user, $password = null)
363
    {
364
        $userInfo = $user;
365
        if ($password != '') {
366
            $userInfo .= ':' . $password;
367
        }
368
369
        if ($userInfo === $this->composeUserInfo($this->getComponents())) {
370
            return $this;
371
        }
372
373
        $newInstance = clone $this;
374
        $newInstance->setUser($user);
375
        $newInstance->setPassword($password);
376
        return $newInstance;
377
    }
378
379
    /**
380
     * {@inheritdoc}
381
     */
382
    public function __toString()
383
    {
384
        // __toString cannot throw exception
385
        // use trigger_error to bypass this limitation
386
        try {
387
            return $this->getString();
388
        } catch (\Exception $e) {
389
            ErrorHandler::convertExceptionToError($e);
390
            return '';
391
        }
392
    }
393
394
    /**
395
     * Sets up particular URI component.
396
     * @param string $name URI component name.
397
     * @param mixed $value URI component value.
398
     */
399
    protected function setComponent($name, $value)
400
    {
401
        if ($this->_string !== null) {
402
            $this->_components = $this->parseUri($this->_string);
403
        }
404
        $this->_components[$name] = $value;
405
        $this->_string = null;
406
    }
407
408
    /**
409
     * @param string $name URI component name.
410
     * @param mixed $default default value, which should be returned in case component is not exist.
411
     * @return mixed URI component value.
412
     */
413
    protected function getComponent($name, $default = null)
414
    {
415
        $components = $this->getComponents();
416
        if (isset($components[$name])) {
417
            return $components[$name];
418
        }
419
        return $default;
420
    }
421
422
    /**
423
     * Returns URI components for this instance as an associative array.
424
     * @return array URI components in format: `[name => value]`
425
     */
426
    protected function getComponents()
427
    {
428
        if ($this->_components === null) {
429
            if ($this->_string === null) {
430
                return [];
431
            }
432
            $this->_components = $this->parseUri($this->_string);
433
        }
434
        return $this->_components;
435
    }
436
437
    /**
438
     * Parses a URI and returns an associative array containing any of the various components of the URI
439
     * that are present.
440
     * @param string $uri the URI string to parse.
441
     * @return array URI components.
442
     */
443
    protected function parseUri($uri)
444
    {
445
        $components = parse_url($uri);
446
        if ($components === false) {
447
            throw new InvalidArgumentException("URI string '{$uri}' is not a valid URI.");
448
        }
449
        return $components;
450
    }
451
452
    /**
453
     * Composes URI string from given components.
454
     * @param array $components URI components.
455
     * @return string URI full string.
456
     */
457
    protected function composeUri(array $components)
458
    {
459
        $uri = '';
460
461
        $scheme = empty($components['scheme']) ? '' : $components['scheme'];
462
        if ($scheme !== '') {
463
            $uri .= $components['scheme'] . ':';
464
        }
465
466
        $authority = $this->composeAuthority($components);
467
468
        if ($authority !== '' || $scheme === 'file') {
469
            // authority separator is added even when the authority is missing/empty for the "file" scheme
470
            // while `file:///myfile` and `file:/myfile` are equivalent according to RFC 3986, `file:///` is more common
471
            // PHP functions and Chrome, for example, use this format
472
            $uri .= '//' . $authority;
473
        }
474
475
        if (!empty($components['path'])) {
476
            $uri .= $components['path'];
477
        }
478
479
        if (!empty($components['query'])) {
480
            $uri .= '?' . $components['query'];
481
        }
482
483
        if (!empty($components['fragment'])) {
484
            $uri .= '#' . $components['fragment'];
485
        }
486
487
        return $uri;
488
    }
489
490
    /**
491
     * @param array $components URI components.
492
     * @return string user info string.
493
     */
494
    protected function composeUserInfo(array $components)
495
    {
496
        $userInfo = '';
497
        if (!empty($components['user'])) {
498
            $userInfo .= $components['user'];
499
        }
500
        if (!empty($components['pass'])) {
501
            $userInfo .= ':' . $components['pass'];
502
        }
503
        return $userInfo;
504
    }
505
506
    /**
507
     * @param array $components URI components.
508
     * @return string authority string.
509
     */
510
    protected function composeAuthority(array $components)
511
    {
512
        $authority = '';
513
514
        $scheme = empty($components['scheme']) ? '' : $components['scheme'];
515
516
        if (empty($components['host'])) {
517
            if (in_array($scheme, ['http', 'https'], true)) {
518
                $authority = 'localhost';
519
            }
520
        } else {
521
            $authority = $components['host'];
522
        }
523
        if (!empty($components['port']) && !$this->isDefaultPort($scheme, $components['port'])) {
524
            $authority .= ':' . $components['port'];
525
        }
526
527
        $userInfo = $this->composeUserInfo($components);
528
        if ($userInfo !== '') {
529
            $authority = $userInfo . '@' . $authority;
530
        }
531
532
        return $authority;
533
    }
534
535
    /**
536
     * Checks whether specified port is default one for the specified scheme.
537
     * @param string $scheme scheme.
538
     * @param int $port port number.
539
     * @return bool whether specified port is default for specified scheme
540
     */
541
    protected function isDefaultPort($scheme, $port)
542
    {
543
        if (!isset(self::$defaultPorts[$scheme])) {
544
            return false;
545
        }
546
        return self::$defaultPorts[$scheme] == $port;
547
    }
548
}