Uri   B
last analyzed

Complexity

Total Complexity 47

Size/Duplication

Total Lines 435
Duplicated Lines 4.14 %

Coupling/Cohesion

Components 6
Dependencies 2

Importance

Changes 1
Bugs 1 Features 0
Metric Value
wmc 47
c 1
b 1
f 0
lcom 6
cbo 2
dl 18
loc 435
rs 7.3617

26 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 12 3
A getScheme() 0 8 3
A getAuthority() 0 9 3
A getUserInfo() 0 10 3
A getUsername() 9 9 2
A getPassword() 9 9 2
A getHost() 0 6 1
A getPort() 0 6 1
A getPath() 0 4 1
A setPath() 0 10 1
B getQuery() 0 17 5
A getFragment() 0 4 1
A setFragment() 0 6 3
A withScheme() 0 7 1
A setScheme() 0 4 1
A withUserInfo() 0 8 1
A setUser() 0 4 1
A setPassword() 0 4 1
A withHost() 0 7 1
A withPort() 0 11 2
A setPort() 0 4 1
A withPath() 0 11 2
A withQuery() 0 11 2
A setQuery() 0 8 2
A withFragment() 0 7 1
A __toString() 0 8 2

How to fix   Duplicated Code    Complexity   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

Complex Class

 Tip:   Before tackling complexity, make sure that you eliminate any duplication first. This often can reduce the size of classes significantly.

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 Almendra\Http\Psr\Messages;
4
5
use Psr\Http\Message\UriInterface;
6
7
use Almendra\Http\Helpers\URI as URIHelper;
8
use Almendra\Http\Server;
9
10
/**
11
 * Represents an URI.
12
 *
13
 * @package     Almendra\Psr7
14
 * @author      Richard Trujillo Torres     <[email protected]>
15
 */
16
class Uri implements UriInterface
17
{
18
    /**
19
     * @var string
20
     */
21
    protected $_uri;
22
23
    /**
24
     * @var string The URI's scheme
25
     */
26
    protected $_scheme = 'blob'; // User-Agent
27
28
    /**
29
     * @var string The URI's query
30
     */
31
    protected $_query;
32
33
    /**
34
     * @var string The URI's path
35
     */
36
    protected $_path;
37
38
    /**
39
     * @var string The URI's fragment
40
     */
41
    protected $_fragment;
42
43
    /**
44
     * @var string The URI's host
45
     */
46
    protected $_host;
47
48
    /**
49
     * @var string The URI's port
50
     */
51
    protected $_port;
52
53
    /**
54
     * @var string The URI's specified user
55
     */
56
    protected $_username;
57
58
    /**
59
     * @var string The URI's specified password
60
     */
61
    protected $_password;
62
63
64
    /**
65
     * Constructs a new URI
66
     *
67
     * @param string $uri 		The URI
68
     * @return boolean			true if success
0 ignored issues
show
Comprehensibility Best Practice introduced by
Adding a @return annotation to constructors is generally not recommended as a constructor does not have a meaningful return value.

Adding a @return annotation to a constructor is not recommended, since a constructor does not have a meaningful return value.

Please refer to the PHP core documentation on constructors.

Loading history...
69
     */
70
    public function __construct($uri = null)
71
    {
72
        if (isset($uri) && !URIHelper::isValid($uri)) {
73
            throw new \InvalidArgumentException("Invalid URI");
74
        }
75
76
        $this -> _uri = $uri;
77
        $this -> setQuery(URIHelper::getQueryParams($uri));
78
        $this -> setPath();
79
        $this -> setFragment();
80
        $this -> setUser($this -> getUsername());
81
    }
82
83
    /**
84
     * Return the URI's scheme.
85
     *
86
     * @return string
87
     */
88
    public function getScheme()
89
    {
90
        if (!isset($this -> _scheme) || $this -> _scheme === null) {
91
            return '';
92
        }
93
94
        return $this -> _scheme;
95
    }
96
97
    /**
98
     * Returns the user authority in the "[user-info@]host[:port]" format.
99
     *
100
     * @param type name 		description
101
     * @return type 				description
102
     */
103
    public function getAuthority()
104
    {
105
        $userInfo = $this -> getUserInfo();
106
        $port = $this -> getPort();
107
108
        return ($userInfo ? $userInfo . '@' : '') .
109
            $this -> getHost() .
110
            (null !== $port ? ':' . $port : '');
111
    }
112
    
113
    /**
114
     * Returns user info in the "username[:password]" format.
115
     *
116
     * @return string
117
     */
118
    public function getUserInfo()
119
    {
120
        $userInfo = $this -> getUsername();
121
        $password = $this -> getPassword();
122
        if ('' !== $userInfo && '' !== $password) {
123
            $userInfo .= ':' . $password;
124
        }
125
126
        return $userInfo;
127
    }
128
129
    /**
130
     * Retrieves the username (user=) specified in the URI.
131
     *
132
     * @return string
133
     */
134 View Code Duplication
    public function getUsername()
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
135
    {
136
        $params = URIHelper::getQueryParams($this -> _uri, false);
137
        if (array_key_exists('username', $params)) {
138
            return $params['username'];
139
        }
140
141
        return '';
142
    }
143
144
    /**
145
     * Retrieves the password specified in the URI.
146
     *
147
     * @return string
148
     */
149 View Code Duplication
    public function getPassword()
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
150
    {
151
        $params = URIHelper::getQueryParams($this -> _uri, false);
152
        if (array_key_exists('password', $params)) {
153
            return $params['password'];
154
        }
155
156
        return '';
157
    }
158
159
    /**
160
     * Retrieves the URI's host.
161
     *
162
     * @return string
163
     */
164
    public function getHost()
165
    {
166
        $host = strtolower(Server::getValue('SERVER_NAME'));
167
        
168
        return $host;
169
    }
170
171
    /**
172
     * Retrieves the URI's port.
173
     *
174
     * @return string
175
     */
176
    public function getPort()
177
    {
178
        $port = Server::getValue('SERVER_PORT');
179
180
        return $port;
181
    }
182
183
    /**
184
     * Returns the URI's path.
185
     *
186
     * @return string
187
     */
188
    public function getPath()
189
    {
190
        return $this -> _path;
191
    }
192
193
    /**
194
     * Sets the URI's path.
195
     *
196
     * @return void
197
     */
198
    protected function setPath()
199
    {
200
        // is this the front end controller?
201
        $path = URIHelper::percentEncode(Server::getValue('PHP_SELF'));
202
203
        $path = str_replace('index.php/', '', $path); // rooted
204
        $path = str_replace('index.php', '', $path); // empty
205
206
        $this -> _path = $path;
207
    }
208
209
    /**
210
     * Retrieve the query string of the URI.
211
     *
212
     * If no query string is present, this method MUST return an empty string.
213
     *
214
     * The leading "?" character is not part of the query and MUST NOT be
215
     * added.
216
     *
217
     * The value returned MUST be percent-encoded, but MUST NOT double-encode
218
     * any characters. To determine what characters to encode, please refer to
219
     * RFC 3986, Sections 2 and 3.4.
220
     *
221
     * As an example, if a value in a key/value pair of the query string should
222
     * include an ampersand ("&") not intended as a delimiter between values,
223
     * that value MUST be passed in encoded form (e.g., "%26") to the instance.
224
     *
225
     * @see https://tools.ietf.org/html/rfc3986#section-2
226
     * @see https://tools.ietf.org/html/rfc3986#section-3.4
227
     * @return string The URI query string.
228
     */
229
    public function getQuery()
230
    {
231
        if (isset($this -> _query) && null !== $this -> _query) {
232
            return $this -> _query;
233
        }
234
235
        $query = Server::getValue('QUERY_STRING');
236
237
        // if not defined, attempt to resolve it
238
        if ('' === $query) {
239
            if (strstr($this -> _uri, '?')) {
240
                $query = substr($this -> _uri, strpos($this -> _uri, '?') + 1);
241
            }
242
        }
243
244
        return URIHelper::percentEncode($query);
245
    }
246
247
    /**
248
     * Retrieves the URI's fragment.
249
     *
250
     * @return string
251
     */
252
    public function getFragment()
253
    {
254
        return $this -> _fragment;
255
    }
256
257
    protected function setFragment($fragment = null)
258
    {
259
        $this -> _fragment = (isset($fragment) && null !== $fragment) ?
260
            $this -> _fragment = $fragment :
261
            $this -> _fragment = URIHelper::getQueryFragment($this -> _uri);
262
    }
263
264
    /**
265
     * Return an instance with the specified scheme.
266
     *
267
     * This method MUST retain the state of the current instance, and return
268
     * an instance that contains the specified scheme.
269
     *
270
     * Implementations MUST support the schemes "http" and "https" case
271
     * insensitively, and MAY accommodate other schemes if required.
272
     *
273
     * An empty scheme is equivalent to removing the scheme.
274
     *
275
     * @param string $scheme The scheme to use with the new instance.
276
     * @return static A new instance with the specified scheme.
277
     * @throws \InvalidArgumentException for invalid or unsupported schemes.
278
     */
279
    public function withScheme($scheme)
280
    {
281
        $clone = clone $this;
282
        $clone -> setScheme($scheme);
283
284
        return $clone;
285
    }
286
287
    protected function setScheme($scheme)
288
    {
289
        $this -> _scheme = $scheme;
290
    }
291
292
    /**
293
     * Return an instance with the specified user information.
294
     *
295
     * This method MUST retain the state of the current instance, and return
296
     * an instance that contains the specified user information.
297
     *
298
     * Password is optional, but the user information MUST include the
299
     * user; an empty string for the user is equivalent to removing user
300
     * information.
301
     *
302
     * @param string $user The user name to use for authority.
303
     * @param null|string $password The password associated with $user.
304
     * @return static A new instance with the specified user information.
305
     */
306
    public function withUserInfo($user, $password = null)
307
    {
308
        $clone = clone $this;
309
        $clone -> setUser($user);
310
        $clone -> setPassword($password);
311
312
        return $clone;
313
    }
314
315
    protected function setUser($user)
316
    {
317
        $this -> _username = $user;
318
    }
319
320
    protected function setPassword($password)
321
    {
322
        $this -> _password = $password;
323
    }
324
325
    /**
326
     * Return an instance with the specified host.
327
     *
328
     * This method MUST retain the state of the current instance, and return
329
     * an instance that contains the specified host.
330
     *
331
     * An empty host value is equivalent to removing the host.
332
     *
333
     * @param string $host The hostname to use with the new instance.
334
     * @return static A new instance with the specified host.
335
     * @throws \InvalidArgumentException for invalid hostnames.
336
     */
337
    public function withHost($host)
338
    {
339
        $clone = clone $this;
340
        $clone -> _host = $host;
341
342
        return $clone;
343
    }
344
345
    /**
346
     * Return an instance with the specified port.
347
     *
348
     * This method MUST retain the state of the current instance, and return
349
     * an instance that contains the specified port.
350
     *
351
     * Implementations MUST raise an exception for ports outside the
352
     * established TCP and UDP port ranges.
353
     *
354
     * A null value provided for the port is equivalent to removing the port
355
     * information.
356
     *
357
     * @param null|int $port The port to use with the new instance; a null value
358
     *     removes the port information.
359
     * @return static A new instance with the specified port.
360
     * @throws \InvalidArgumentException for invalid ports.
361
     */
362
    public function withPort($port)
363
    {
364
        if (!URIHelper::isPortValid($port)) {
365
            throw new \InvalidArgumentException("Invalid port. The port must be within the TCP and UDP port ranges.");
366
        }
367
        
368
        $clone = clone $this;
369
        $clone -> setPort($port);
370
371
        return $clone;
372
    }
373
374
    protected function setPort($port)
375
    {
376
        $this -> _port = $port;
377
    }
378
379
    /**
380
     * Return an instance with the specified path.
381
     *
382
     * This method MUST retain the state of the current instance, and return
383
     * an instance that contains the specified path.
384
     *
385
     * The path can either be empty or absolute (starting with a slash) or
386
     * rootless (not starting with a slash). Implementations MUST support all
387
     * three syntaxes.
388
     *
389
     * If the path is intended to be domain-relative rather than path relative then
390
     * it must begin with a slash ("/"). Paths not starting with a slash ("/")
391
     * are assumed to be relative to some base path known to the application or
392
     * consumer.
393
     *
394
     * Users can provide both encoded and decoded path characters.
395
     * Implementations ensure the correct encoding as outlined in getPath().
396
     *
397
     * @param string $path The path to use with the new instance.
398
     * @return static A new instance with the specified path.
399
     * @throws \InvalidArgumentException for invalid paths.
400
     */
401
    public function withPath($path)
402
    {
403
        $clone = clone $this;
404
        if (!URIHelper::isPathValid($path)) {
405
            throw new \InvalidArgumentException("Invalid path.");
406
        }
407
408
        $clone -> setPath($path);
0 ignored issues
show
Unused Code introduced by
The call to Uri::setPath() has too many arguments starting with $path.

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
409
410
        return $clone;
411
    }
412
413
    public function withQuery($query)
414
    {
415
        if (!URIHelper::isQueryValid($query)) {
416
            throw new \InvalidArgumentException("Invalid query.");
417
        }
418
419
        $clone = clone $this;
420
        $clone -> setQuery($query);
421
422
        return $clone;
423
    }
424
425
    public function setQuery($query)
426
    {
427
        if (!is_string($query)) {
428
            throw new \InvalidArgumentException("Query must be a string.");
429
        }
430
431
        $this -> _query = $query;
432
    }
433
434
    public function withFragment($fragment)
435
    {
436
        $clone = clone $this;
437
        $clone -> setFragment($fragment);
438
439
        return $clone;
440
    }
441
442
    public function __toString()
443
    {
444
        if (is_string($this -> _uri)) {
445
            return $this -> _uri;
446
        }
447
448
        return '';
449
    }
450
}
451