Passed
Push — master ( 338249...ac9ce0 )
by Charis
02:12
created

Uri::withString()   A

Complexity

Conditions 3
Paths 2

Size

Total Lines 11
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 11
rs 9.4285
c 0
b 0
f 0
cc 3
eloc 7
nc 2
nop 2
1
<?php
2
3
namespace Resilient\Http;
4
5
use InvalidArgumentException;
6
use \Psr\Http\Message\UriInterface;
7
8
class Uri implements UriInterface
9
{
10
11
12
    /**
13
     * Uri scheme (without "://" suffix)
14
     *
15
     * @var string
16
     */
17
    protected $scheme = '';
18
19
    /**
20
     * Uri user
21
     *
22
     * @var string
23
     */
24
    protected $user = '';
25
26
    /**
27
     * Uri password
28
     *
29
     * @var string
30
     */
31
    protected $password = '';
32
33
    /**
34
     * Uri host
35
     *
36
     * @var string
37
     */
38
    protected $host = '';
39
40
    /**
41
     * Uri port number
42
     *
43
     * @var null|int
44
     */
45
    protected $port;
46
47
    /**
48
     * Uri base path
49
     *
50
     * @var string
51
     */
52
    protected $basePath = '';
53
54
    /**
55
     * Uri path
56
     *
57
     * @var string
58
     */
59
    protected $path = '';
60
61
    /**
62
     * Uri query string (without "?" prefix)
63
     *
64
     * @var string
65
     */
66
    protected $query = '';
67
68
    /**
69
     * Uri fragment string (without "#" prefix)
70
     *
71
     * @var string
72
     */
73
    protected $fragment = '';
74
75
    /**
76
     * Instance new Uri.
77
     *
78
     * @param string $scheme   Uri scheme.
79
     * @param string $host     Uri host.
80
     * @param int    $port     Uri port number.
81
     * @param string $path     Uri path.
82
     * @param string $query    Uri query string.
83
     * @param string $fragment Uri fragment.
84
     * @param string $user     Uri user.
85
     * @param string $password Uri password.
86
     */
87
    public function __construct(
88
        $scheme,
89
        $host,
90
        $port = null,
91
        $path = '/',
92
        $query = '',
93
        $fragment = '',
94
        $user = '',
95
        $password = ''
96
    ) {
97
        $this->scheme = $this->filterScheme($scheme);
98
        $this->host = $host;
99
        $this->port = $this->filterPort($port);
100
        $this->path = empty($path) ? '/' : $this->filterPath($path);
101
        $this->query = $this->filterQuery($query);
102
        $this->fragment = $this->filterQuery($fragment);
103
        $this->user = $user;
104
        $this->password = $password;
105
    }
106
107
    /**
108
     * {@inheritdoc}
109
     */
110
    public function getScheme()
111
    {
112
        return $this->scheme;
113
    }
114
115
    /**
116
     * {@inheritdoc}
117
     */
118
    public function getAuthority()
119
    {
120
        $userInfo = $this->getUserInfo();
121
        $host = $this->getHost();
122
        $port = $this->getPort();
123
124
        return ($userInfo ? $userInfo . '@' : '') . $host . ($port !== null ? ':' . $port : '');
125
    }
126
127
    /**
128
     * {@inheritdoc}
129
     */
130
    public function getUserInfo()
131
    {
132
        return $this->user . ($this->password ? ':' . $this->password : '');
133
    }
134
135
    /**
136
     * {@inheritdoc}
137
     */
138
    public function getHost()
139
    {
140
        return $this->host;
141
    }
142
143
    /**
144
     * {@inheritdoc}
145
     */
146
    public function getPort()
147
    {
148
        return $this->port !== null && !$this->hasStandardPort() ? $this->port : null;
149
    }
150
151
    /**
152
     * {@inheritdoc}
153
     */
154
    public function getPath()
155
    {
156
        return $this->path;
157
    }
158
159
    /**
160
     * {@inheritdoc}
161
     */
162
    public function getQuery()
163
    {
164
        return $this->query;
165
    }
166
167
    /**
168
     * {@inheritdoc}
169
     */
170
    public function getFragment()
171
    {
172
        return $this->fragment;
173
    }
174
175
    /**
176
     * {@inheritdoc}
177
     */
178
    public function withScheme($scheme)
179
    {
180
        $scheme = $this->filterScheme($scheme);
181
        $clone = clone $this;
182
        $clone->scheme = $scheme;
183
184
        return $clone;
185
    }
186
187
    /**
188
     * {@inheritdoc}
189
     */
190
    public function withUserInfo($user, $password = null)
191
    {
192
        $clone = clone $this;
193
        $clone->user = $user;
194
        $clone->password = $password ? $password : '';
195
196
        return $clone;
197
    }
198
199
    /**
200
     * {@inheritdoc}
201
     */
202
    public function withHost($host)
203
    {
204
        $clone = clone $this;
205
        $clone->host = $host;
206
207
        return $clone;
208
    }
209
210
    /**
211
     * {@inheritdoc}
212
     */
213
    public function withPort($port)
214
    {
215
        $port = $this->filterPort($port);
216
        $clone = clone $this;
217
        $clone->port = $port;
218
219
        return $clone;
220
    }
221
222
    /**
223
     * {@inheritdoc}
224
     */
225
    public function withPath($path)
226
    {
227
        $clone = clone $this;
228
        $clone->path = $this->filterPath($path);
229
230
        // if the path is absolute, then clear basePath
231
        if (substr($path, 0, 1) == '/') {
232
            $clone->basePath = '';
233
        }
234
235
        return $clone;
236
    }
237
238
    /**
239
     * {@inheritdoc}
240
     */
241
    public function withQuery($query)
242
    {
243
        return $this->withString($query);
244
    }
245
246
    /**
247
     * {@inheritdoc}
248
     */
249
    public function withFragment($fragment)
250
    {
251
        return $this->withString($fragment, 'fragment');
252
    }
253
    
254
    /**
255
     * withString function.
256
     * 
257
     * @access protected
258
     * @param mixed $str
0 ignored issues
show
Bug introduced by
There is no parameter named $str. Was it maybe removed?

This check looks for PHPDoc comments describing methods or function parameters that do not exist on the corresponding method or function.

Consider the following example. The parameter $italy is not defined by the method finale(...).

/**
 * @param array $germany
 * @param array $island
 * @param array $italy
 */
function finale($germany, $island) {
    return "2:1";
}

The most likely cause is that the parameter was removed, but the annotation was not.

Loading history...
259
     * @return Uri
260
     */
261
    protected function withString($string, $name = 'query')
262
    {
263
        if (!is_string($string) && !method_exists($string, '__toString')) {
264
            throw new InvalidArgumentException('Uri fragment must be a string');
265
        }
266
        $string = ltrim((string)$string, '#');
267
        $clone = clone $this;
268
        $clone->$name = $this->filterQuery($string);
269
270
        return $clone;
271
    }
272
273
    /**
274
     * {@inheritdoc}
275
     */
276
    public function __toString()
277
    {
278
        $scheme = $this->getScheme();
279
        $authority = $this->getAuthority();
280
        $basePath = $this->getBasePath();
281
        $path = $this->getPath();
282
        $query = $this->getQuery();
283
        $fragment = $this->getFragment();
284
285
        $path = $basePath . '/' . ltrim($path, '/');
286
287
        return ($scheme ? $scheme . ':' : '')
288
            . ($authority ? '//' . $authority : '')
289
            . $path
290
            . ($query ? '?' . $query : '')
291
            . ($fragment ? '#' . $fragment : '');
292
    }
293
294
    /*
295
        END OF UriInterface Implementation
296
    */
297
298
    /**
299
     * filter scheme given to only allow certain scheme, no file:// or ftp:// or other scheme because its http message uri interface
300
     *
301
     * @access protected
302
     * @param string $scheme
303
     * @return string $scheme
304
     * @throws InvalidArgumentException if not corret scheme is present
305
     */
306
    protected function filterScheme(string $scheme)
307
    {
308
        static $valid = [
309
            '' => true,
310
            'https' => true,
311
            'http' => true,
312
        ];
313
314
        $scheme = str_replace('://', '', strtolower($scheme));
315
        if (!isset($valid[$scheme])) {
316
            throw new InvalidArgumentException('Uri scheme must be one of: "", "https", "http"');
317
        }
318
319
        return $scheme;
320
    }
321
322
323
    /**
324
     * Filter allowable port to minimize risk
325
     *
326
     * @access protected
327
     * @param integer|null $port
328
     * @return null|integer $port
329
     * @throws InvalidArgumentException for incorrect port assigned
330
     */
331
    protected function filterPort($port)
332
    {
333
        if (is_null($port) || (is_integer($port) && ($port >= 1 && $port <= 65535))) {
334
            return $port;
335
        }
336
337
        throw new InvalidArgumentException('Uri port must be null or an integer between 1 and 65535 (inclusive)');
338
    }
339
340
    /**
341
     * Path allowed chars filter, no weird path on uri yes?.
342
     *
343
     * @access protected
344
     * @param string $path
345
     * @return string of cleared path
346
     */
347
    protected function filterPath($path)
348
    {
349
        return preg_replace_callback(
350
            '/(?:[^a-zA-Z0-9_\-\.~:@&=\+\$,\/;%]+|%(?![A-Fa-f0-9]{2}))/',
351
            function($match) {
352
                return rawurlencode($match[0]);
353
            },
354
            $path
355
        );
356
    }
357
358
    /**
359
     * replace query to clear not allowed chars
360
     *
361
     * @access protected
362
     * @param string $query
363
     * @return string of replaced query
364
     */
365
    protected function filterQuery($query)
366
    {
367
        return preg_replace_callback(
368
            '/(?:[^a-zA-Z0-9_\-\.~!\$&\'\(\)\*\+,;=%:@\/\?]+|%(?![A-Fa-f0-9]{2}))/',
369
            function($match) {
370
                return rawurlencode($match[0]);
371
            },
372
            $query
373
        );
374
    }
375
376
    /**
377
     * cek if current uri scheme use standard port
378
     *
379
     * @access protected
380
     * @return boolean
381
     */
382
    protected function hasStandardPort()
383
    {
384
        return ($this->scheme === 'http' && $this->port === 80) || ($this->scheme === 'https' && $this->port === 443);
385
    }
386
387
388
    /**
389
     * get BasePath property.
390
     *
391
     * @access public
392
     * @return string basePath
393
     */
394
    public function getBasePath()
395
    {
396
        return $this->basePath;
397
    }
398
399
    /**
400
     * Set BasePath Function to rewrite request.
401
     *
402
     * @access public
403
     * @param string $basePath
404
     * @return void
405
     */
406
    public function withBasePath(string $basePath)
407
    {
408
        $this->basePath = $basePath;
409
    }
410
411
412
    /**
413
     * get Base Url
414
     *
415
     * @access public
416
     * @return string
417
     */
418
    public function getBaseUrl()
419
    {
420
        $scheme = $this->getScheme();
421
        $authority = $this->getAuthority();
422
        $basePath = $this->getBasePath();
423
424
        if ($authority && substr($basePath, 0, 1) !== '/') {
425
            $basePath = $basePath . '/' . $basePath;
426
        }
427
428
        return ($scheme ? $scheme . ':' : '')
429
            . ($authority ? '//' . $authority : '')
430
            . rtrim($basePath, '/');
431
    }
432
433
    /**
434
     * Create uri Instance from header $_SERVER.
435
     *
436
     * @access public
437
     * @static
438
     * @return Uri
439
     */
440
    public static function createFromServer($serv)
0 ignored issues
show
Coding Style introduced by
createFromServer uses the super-global variable $_SERVER which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

    public function __construct($host)
    {
        $this->host = $host;
    }

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
441
    {
442
        $scheme = isset($serv['HTTPS']) ? 'https://' : 'http://';
443
        $host = empty($serv['HTTP_HOST']) ? $serv['HTTP_HOST'] : $serv['SERVER_NAME'];
444
        $port = empty($serv['SERVER_PORT']) ? $serv['SERVER_PORT'] : null;
445
446
        //Path
447
        $scriptName = parse_url($serv['SCRIPT_NAME'], PHP_URL_PATH);
448
        $scriptPath = dirname($scriptName);
449
450
        $requestUri = (string) parse_url('http://www.example.com/' . $_SERVER['REQUEST_URI'], PHP_URL_PATH);
451
452
        if (stripos($requestUri, $scriptName) === 0) {
453
            $basePath = $scriptName;
454
        } elseif ($scriptPath !== '/' && stripos($requestUri, $scriptPath) === 0) {
455
            $basePath = $scriptPath;
456
        }
457
458
        if (empty($basePath)) {
459
            $path = $requestUri;
460
            $basePath = '';
461
        } else {
462
            $path = ltrim(substr($requestUri, strlen($basePath)), '/');
463
        }
464
465
        $query = empty($serv['QUERY_STRING']) ? parse_url('http://example.com' . $serv['REQUEST_URI'], PHP_URL_QUERY) : $serv['QUERY_STRING'];
466
467
        $fragment = '';
468
469
        $user = !empty($serv['PHP_AUTH_USER']) ? $serv['PHP_AUTH_USER'] : '';
470
        $password = !empty($serv['PHP_AUTH_PW']) ? $serv['PHP_AUTH_PW'] : '';
471
472
        if (empty($user) && empty($password) && !empty($serv['HTTP_AUTHORIZATION'])) {
473
            list($user, $password) = explode(':', base64_decode(substr($serv['HTTP_AUTHORIZATION'], 6)));
474
        }
475
476
        $uri = new static($scheme, $host, $port, $path, $query, $fragment, $user, $password);
477
478
        if ($basePath) {
479
            $uri->withBasePath($basePath);
480
        }
481
482
        return $uri;
483
    }
484
485
486
    /**
487
     * Create Uri Instance from string http://www.example.com/url/path.html
488
     *
489
     * @access public
490
     * @static
491
     * @param string $uri
492
     * @return Uri
493
     */
494
    public static function createFromString(string $uri)
495
    {
496
        $parts = parse_url($uri);
497
        $scheme = isset($parts['scheme']) ? $parts['scheme'] : '';
498
        $user = isset($parts['user']) ? $parts['user'] : '';
499
        $pass = isset($parts['pass']) ? $parts['pass'] : '';
500
        $host = isset($parts['host']) ? $parts['host'] : '';
501
        $port = isset($parts['port']) ? $parts['port'] : null;
502
        $path = isset($parts['path']) ? $parts['path'] : '';
503
        $query = isset($parts['query']) ? $parts['query'] : '';
504
        $fragment = isset($parts['fragment']) ? $parts['fragment'] : '';
505
506
        return new static($scheme, $host, $port, $path, $query, $fragment, $user, $pass);
507
    }
508
}
509