Uri   F
last analyzed

Complexity

Total Complexity 60

Size/Duplication

Total Lines 395
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 0

Importance

Changes 0
Metric Value
dl 0
loc 395
c 0
b 0
f 0
wmc 60
lcom 1
cbo 0
rs 3.6

26 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 6 2
B parseUri() 0 45 10
A protectUserInfo() 0 15 3
A getScheme() 0 4 1
A getUserInfo() 0 4 1
A getRawUserInfo() 0 4 1
A getHost() 0 4 1
A getPort() 0 4 1
A getPath() 0 4 1
A getQuery() 0 4 1
A getFragment() 0 4 1
A getAuthority() 0 11 3
A getRawAuthority() 0 11 3
A getAbsoluteUri() 0 20 5
A getRelativeUri() 0 12 3
A __toString() 0 20 5
A setPath() 0 12 3
A setQuery() 0 4 1
A addToQuery() 0 7 2
A setFragment() 0 4 1
A setScheme() 0 4 1
A setUserInfo() 0 5 2
A setPort() 0 10 5
A setHost() 0 4 1
A hasExplicitTrailingHostSlash() 0 4 1
A hasExplicitPortSpecified() 0 4 1

How to fix   Complexity   

Complex Class

Complex classes like Uri often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use Uri, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
namespace OAuth\Common\Http\Uri;
4
5
use InvalidArgumentException;
6
7
/**
8
 * Standards-compliant URI class.
9
 */
10
class Uri implements UriInterface
11
{
12
    /**
13
     * @var string
14
     */
15
    private $scheme = 'http';
16
17
    /**
18
     * @var string
19
     */
20
    private $userInfo = '';
21
22
    /**
23
     * @var string
24
     */
25
    private $rawUserInfo = '';
26
27
    /**
28
     * @var string
29
     */
30
    private $host;
31
32
    /**
33
     * @var int
34
     */
35
    private $port = 80;
36
37
    /**
38
     * @var string
39
     */
40
    private $path = '/';
41
42
    /**
43
     * @var string
44
     */
45
    private $query = '';
46
47
    /**
48
     * @var string
49
     */
50
    private $fragment = '';
51
52
    /**
53
     * @var bool
54
     */
55
    private $explicitPortSpecified = false;
56
57
    /**
58
     * @var bool
59
     */
60
    private $explicitTrailingHostSlash = false;
61
62
    /**
63
     * @param string $uri
64
     */
65
    public function __construct($uri = null)
66
    {
67
        if (null !== $uri) {
68
            $this->parseUri($uri);
69
        }
70
    }
71
72
    /**
73
     * @param string $uri
74
     */
75
    protected function parseUri($uri): void
76
    {
77
        if (false === ($uriParts = parse_url($uri))) {
78
            // congratulations if you've managed to get parse_url to fail,
79
            // it seems to always return some semblance of a parsed url no matter what
80
            throw new InvalidArgumentException("Invalid URI: $uri");
81
        }
82
83
        if (!isset($uriParts['scheme'])) {
84
            throw new InvalidArgumentException('Invalid URI: http|https scheme required');
85
        }
86
87
        $this->scheme = $uriParts['scheme'];
88
        $this->host = $uriParts['host'];
89
90
        if (isset($uriParts['port'])) {
91
            $this->port = $uriParts['port'];
0 ignored issues
show
Documentation Bug introduced by
The property $port was declared of type integer, but $uriParts['port'] is of type string. Maybe add a type cast?

This check looks for assignments to scalar types that may be of the wrong type.

To ensure the code behaves as expected, it may be a good idea to add an explicit type cast.

$answer = 42;

$correct = false;

$correct = (bool) $answer;
Loading history...
92
            $this->explicitPortSpecified = true;
93
        } else {
94
            $this->port = strcmp('https', $uriParts['scheme']) ? 80 : 443;
95
            $this->explicitPortSpecified = false;
96
        }
97
98
        if (isset($uriParts['path'])) {
99
            $this->path = $uriParts['path'];
100
            if ('/' === $uriParts['path']) {
101
                $this->explicitTrailingHostSlash = true;
102
            }
103
        } else {
104
            $this->path = '/';
105
        }
106
107
        $this->query = $uriParts['query'] ?? '';
108
        $this->fragment = $uriParts['fragment'] ?? '';
109
110
        $userInfo = '';
111
        if (!empty($uriParts['user'])) {
112
            $userInfo .= $uriParts['user'];
113
        }
114
        if ($userInfo && !empty($uriParts['pass'])) {
115
            $userInfo .= ':' . $uriParts['pass'];
116
        }
117
118
        $this->setUserInfo($userInfo);
119
    }
120
121
    /**
122
     * @param string $rawUserInfo
123
     *
124
     * @return string
125
     */
126
    protected function protectUserInfo($rawUserInfo)
127
    {
128
        $colonPos = strpos($rawUserInfo, ':');
129
130
        // rfc3986-3.2.1 | http://tools.ietf.org/html/rfc3986#section-3.2
131
        // "Applications should not render as clear text any data
132
        // after the first colon (":") character found within a userinfo
133
        // subcomponent unless the data after the colon is the empty string
134
        // (indicating no password)"
135
        if ($colonPos !== false && strlen($rawUserInfo) - 1 > $colonPos) {
136
            return substr($rawUserInfo, 0, $colonPos) . ':********';
137
        }
138
139
        return $rawUserInfo;
140
    }
141
142
    /**
143
     * @return string
144
     */
145
    public function getScheme()
146
    {
147
        return $this->scheme;
148
    }
149
150
    /**
151
     * @return string
152
     */
153
    public function getUserInfo()
154
    {
155
        return $this->userInfo;
156
    }
157
158
    /**
159
     * @return string
160
     */
161
    public function getRawUserInfo()
162
    {
163
        return $this->rawUserInfo;
164
    }
165
166
    /**
167
     * @return string
168
     */
169
    public function getHost()
170
    {
171
        return $this->host;
172
    }
173
174
    /**
175
     * @return int
176
     */
177
    public function getPort()
178
    {
179
        return $this->port;
180
    }
181
182
    /**
183
     * @return string
184
     */
185
    public function getPath()
186
    {
187
        return $this->path;
188
    }
189
190
    /**
191
     * @return string
192
     */
193
    public function getQuery()
194
    {
195
        return $this->query;
196
    }
197
198
    /**
199
     * @return string
200
     */
201
    public function getFragment()
202
    {
203
        return $this->fragment;
204
    }
205
206
    /**
207
     * Uses protected user info by default as per rfc3986-3.2.1
208
     * Uri::getRawAuthority() is available if plain-text password information is desirable.
209
     *
210
     * @return string
211
     */
212
    public function getAuthority()
213
    {
214
        $authority = $this->userInfo ? $this->userInfo . '@' : '';
215
        $authority .= $this->host;
216
217
        if ($this->explicitPortSpecified) {
218
            $authority .= ":{$this->port}";
219
        }
220
221
        return $authority;
222
    }
223
224
    /**
225
     * @return string
226
     */
227
    public function getRawAuthority()
228
    {
229
        $authority = $this->rawUserInfo ? $this->rawUserInfo . '@' : '';
230
        $authority .= $this->host;
231
232
        if ($this->explicitPortSpecified) {
233
            $authority .= ":{$this->port}";
234
        }
235
236
        return $authority;
237
    }
238
239
    /**
240
     * @return string
241
     */
242
    public function getAbsoluteUri()
243
    {
244
        $uri = $this->scheme . '://' . $this->getRawAuthority();
245
246
        if ('/' === $this->path) {
247
            $uri .= $this->explicitTrailingHostSlash ? '/' : '';
248
        } else {
249
            $uri .= $this->path;
250
        }
251
252
        if (!empty($this->query)) {
253
            $uri .= "?{$this->query}";
254
        }
255
256
        if (!empty($this->fragment)) {
257
            $uri .= "#{$this->fragment}";
258
        }
259
260
        return $uri;
261
    }
262
263
    /**
264
     * @return string
265
     */
266
    public function getRelativeUri()
267
    {
268
        $uri = '';
269
270
        if ('/' === $this->path) {
271
            $uri .= $this->explicitTrailingHostSlash ? '/' : '';
272
        } else {
273
            $uri .= $this->path;
274
        }
275
276
        return $uri;
277
    }
278
279
    /**
280
     * Uses protected user info by default as per rfc3986-3.2.1
281
     * Uri::getAbsoluteUri() is available if plain-text password information is desirable.
282
     *
283
     * @return string
284
     */
285
    public function __toString()
286
    {
287
        $uri = $this->scheme . '://' . $this->getAuthority();
288
289
        if ('/' === $this->path) {
290
            $uri .= $this->explicitTrailingHostSlash ? '/' : '';
291
        } else {
292
            $uri .= $this->path;
293
        }
294
295
        if (!empty($this->query)) {
296
            $uri .= "?{$this->query}";
297
        }
298
299
        if (!empty($this->fragment)) {
300
            $uri .= "#{$this->fragment}";
301
        }
302
303
        return $uri;
304
    }
305
306
    /**
307
     * @param $path
308
     */
309
    public function setPath($path): void
310
    {
311
        if (empty($path)) {
312
            $this->path = '/';
313
            $this->explicitTrailingHostSlash = false;
314
        } else {
315
            $this->path = $path;
316
            if ('/' === $this->path) {
317
                $this->explicitTrailingHostSlash = true;
318
            }
319
        }
320
    }
321
322
    /**
323
     * @param string $query
324
     */
325
    public function setQuery($query): void
326
    {
327
        $this->query = $query;
328
    }
329
330
    /**
331
     * @param string $var
332
     * @param string $val
333
     */
334
    public function addToQuery($var, $val): void
335
    {
336
        if (strlen($this->query) > 0) {
337
            $this->query .= '&';
338
        }
339
        $this->query .= http_build_query([$var => $val], '', '&');
340
    }
341
342
    /**
343
     * @param string $fragment
344
     */
345
    public function setFragment($fragment): void
346
    {
347
        $this->fragment = $fragment;
348
    }
349
350
    /**
351
     * @param string $scheme
352
     */
353
    public function setScheme($scheme): void
354
    {
355
        $this->scheme = $scheme;
356
    }
357
358
    /**
359
     * @param string $userInfo
360
     */
361
    public function setUserInfo($userInfo): void
362
    {
363
        $this->userInfo = $userInfo ? $this->protectUserInfo($userInfo) : '';
364
        $this->rawUserInfo = $userInfo;
365
    }
366
367
    /**
368
     * @param int $port
369
     */
370
    public function setPort($port): void
371
    {
372
        $this->port = (int) $port;
373
374
        if (('https' === $this->scheme && $this->port === 443) || ('http' === $this->scheme && $this->port === 80)) {
375
            $this->explicitPortSpecified = false;
376
        } else {
377
            $this->explicitPortSpecified = true;
378
        }
379
    }
380
381
    /**
382
     * @param string $host
383
     */
384
    public function setHost($host): void
385
    {
386
        $this->host = $host;
387
    }
388
389
    /**
390
     * @return bool
391
     */
392
    public function hasExplicitTrailingHostSlash()
393
    {
394
        return $this->explicitTrailingHostSlash;
395
    }
396
397
    /**
398
     * @return bool
399
     */
400
    public function hasExplicitPortSpecified()
401
    {
402
        return $this->explicitPortSpecified;
403
    }
404
}
405