Completed
Push — master ( da88de...163d4a )
by Joschi
03:49
created

Url   D

Complexity

Total Complexity 80

Size/Duplication

Total Lines 453
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 5

Test Coverage

Coverage 100%

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 0
loc 453
ccs 170
cts 170
cp 1
rs 4.8717
wmc 80
lcom 1
cbo 5

22 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 13 2
A __toString() 0 4 1
A getUrl() 0 4 1
F getUrlInternal() 0 72 23
A getUser() 0 4 2
A getPassword() 0 4 2
A getQueryParams() 0 8 3
A setHost() 0 16 4
A setPort() 0 14 4
A setUser() 0 6 2
A setPassword() 0 6 2
A setQuery() 0 6 1
A setQueryParams() 0 6 1
A setFragment() 0 6 1
A isRemote() 0 4 2
A isAbsolute() 0 4 2
A isAbsoluteLocal() 0 14 3
A setPath() 0 8 2
A setScheme() 0 14 4
C matches() 0 53 16
A unserialize() 0 4 1
A serialize() 0 4 1

How to fix   Complexity   

Complex Class

Complex classes like Url 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 Url, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
/**
4
 * apparat-object
5
 *
6
 * @category    Apparat
7
 * @package     Apparat\Object\Domain
8
 * @author      Joschi Kuphal <[email protected]> / @jkphl
9
 * @copyright   Copyright © 2016 Joschi Kuphal <[email protected]> / @jkphl
10
 * @license     http://opensource.org/licenses/MIT The MIT License (MIT)
11
 */
12
13
/***********************************************************************************
14
 *  The MIT License (MIT)
15
 *
16
 *  Copyright © 2016 Joschi Kuphal <[email protected]> / @jkphl
17
 *
18
 *  Permission is hereby granted, free of charge, to any person obtaining a copy of
19
 *  this software and associated documentation files (the "Software"), to deal in
20
 *  the Software without restriction, including without limitation the rights to
21
 *  use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
22
 *  the Software, and to permit persons to whom the Software is furnished to do so,
23
 *  subject to the following conditions:
24
 *
25
 *  The above copyright notice and this permission notice shall be included in all
26
 *  copies or substantial portions of the Software.
27
 *
28
 *  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
29
 *  IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
30
 *  FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
31
 *  COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
32
 *  IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
33
 *  CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
34
 ***********************************************************************************/
35
36
namespace Apparat\Object\Domain\Model\Uri;
37
38
use Apparat\Kernel\Ports\Kernel;
39
use Apparat\Object\Application\Utility\ArrayUtility;
40
use Apparat\Object\Domain\Contract\SerializablePropertyInterface;
41
use Apparat\Object\Domain\Model\Uri\Traits\Psr7Trait;
42
use Psr\Http\Message\UriInterface;
43
44
/**
45
 * Object URL
46
 *
47
 * @package Apparat\Object\Domain\Model
48
 */
49
class Url extends Uri implements UriInterface, SerializablePropertyInterface
50
{
51
    /**
52
     * Use PSR-7 method
53
     */
54
    use Psr7Trait;
55
    /**
56
     * HTTP scheme
57
     *
58
     * @var string
59
     */
60
    const SCHEME_HTTP = 'http';
61
    /**
62
     * HTTPS schema
63
     *
64
     * @var string
65
     */
66
    const SCHEME_HTTPS = 'https';
67
    /**
68
     * Valid schemes
69
     *
70
     * @var array
71
     */
72
    protected static $schemes = [self::SCHEME_HTTP => true, self::SCHEME_HTTPS => true];
73
    /**
74
     * URL parts
75
     *
76
     * @var array
77
     */
78
    protected $urlParts = null;
79
80
    /**
81
     * URL constructor
82
     *
83
     * @param string $url URL
84
     * @throws InvalidArgumentException If the URL is invalid
85
     */
86 64
    public function __construct($url)
87
    {
88 64
        parent::__construct($url);
89
90
        // Parse the URL
91 64
        $this->urlParts = @parse_url($url);
92 64
        if ($this->urlParts === false) {
93 2
            throw new InvalidArgumentException(
94 2
                sprintf('Invalid URL "%s"', $url),
95
                InvalidArgumentException::INVALID_URL
96 2
            );
97
        }
98 63
    }
99
100
    /**
101
     * Return the serialized URL
102
     *
103
     * @return string Serialized URL
104
     */
105 31
    public function __toString()
106
    {
107 31
        return $this->getUrl();
108
    }
109
110
    /**
111
     * Return the full serialized URL
112
     *
113
     * @return string Full URL
114
     */
115 31
    public function getUrl()
116
    {
117 31
        return $this->getUrlInternal();
118
    }
119
120
    /**
121
     * Return the a complete serialized URL
122
     *
123
     * @param array $override Override components
124
     * @return string Serialized URL
125
     */
126 34
    protected function getUrlInternal(array &$override = [])
127
    {
128
        // Prepare the URL scheme
129 34
        $scheme = !empty($this->urlParts['scheme']) ? $this->getScheme().'://' : '';
130 34
        if (isset($override['scheme'])) {
131 1
            $scheme = trim($override['scheme']);
132 1
            if (strlen($scheme)) {
133 1
                $scheme .= '://';
134 1
            }
135 1
        }
136 34
        $override['scheme'] = $scheme;
137
138
        // Prepare the URL user
139 34
        $user = !empty($this->urlParts['user']) ? rawurlencode($this->getUser()) : '';
140 34
        if (isset($override['user'])) {
141 1
            $user = $override['user'];
142 1
        }
143 34
        $override['user'] = $user;
144
145
        // Prepare the URL password
146 34
        $pass = !empty($this->urlParts['pass']) ? ':'.rawurlencode($this->getPassword()) : '';
147 34
        if (isset($override['pass'])) {
148 1
            $pass = ':'.$override['pass'];
149 1
        }
150 34
        if ($user || $pass) {
151 7
            $pass .= '@';
152 7
        }
153 34
        $override['pass'] = $pass;
154
155
        // Prepare the URL host
156 34
        $host = !empty($this->urlParts['host']) ? $this->getHost() : '';
157 34
        if (isset($override['host'])) {
158 1
            $host = $override['host'];
159 1
        }
160 34
        $override['host'] = $host;
161
162
        // Prepare the URL port
163 34
        $port = !empty($this->urlParts['port']) ? ':'.$this->getPort() : '';
164 34
        if (isset($override['port'])) {
165 1
            $port = ':'.$override['port'];
166 1
        }
167 34
        $override['port'] = $port;
168
169
        // Prepare the URL path
170 34
        $path = empty($this->urlParts['path']) ? '' : $this->urlParts['path'];
171 34
        if (isset($override['path'])) {
172 1
            $path = $override['path'];
173 1
        }
174 34
        $override['path'] = $path;
175
176
        // Prepare the URL query
177 34
        $query = !empty($this->urlParts['query']) ? '?'.$this->urlParts['query'] : '';
178 34
        if (isset($override['query'])) {
179 4
            $query = (is_array($override['query']) ? http_build_query($override['query']) : strval($override['query']));
180 4
            if (strlen($query)) {
181 1
                $query = '?'.$query;
182 1
            }
183 4
        }
184 34
        $override['query'] = $query;
185
186
        // Prepare the URL fragment
187 34
        $fragment = !empty($this->urlParts['fragment']) ? '#'.$this->getFragment() : '';
188 34
        if (isset($override['fragment'])) {
189 4
            $fragment = $override['fragment'];
190 4
            if (strlen($fragment)) {
191 1
                $fragment = '#'.$fragment;
192 1
            }
193 4
        }
194 34
        $override['fragment'] = $fragment;
195
196 34
        return "$scheme$user$pass$host$port$path$query$fragment";
197
    }
198
199
    /**
200
     * Return the URL user
201
     *
202
     * @return string|NULL URL user
203
     */
204 12
    public function getUser()
205
    {
206 12
        return isset($this->urlParts['user']) ? $this->urlParts['user'] : null;
207
    }
208
209
    /**
210
     * Return the URL password
211
     *
212
     * @return string|NULL URL password
213
     */
214 12
    public function getPassword()
215
    {
216 12
        return isset($this->urlParts['pass']) ? $this->urlParts['pass'] : null;
217
    }
218
219
    /**
220
     * Return the URL query parameters as list
221
     *
222
     * @return array URL query parameters
223
     */
224 2
    public function getQueryParams()
225
    {
226 2
        $query = [];
227 2
        if (isset($this->urlParts['query']) && !empty($this->urlParts['query'])) {
228 2
            parse_str($this->urlParts['query'], $query);
229 2
        }
230 2
        return ArrayUtility::sortRecursiveByKey((array)$query);
231
    }
232
233
    /**
234
     * Set the URL host
235
     *
236
     * @param string $host URL host
237
     * @return Url New URL
238
     * @throws InvalidArgumentException If the URL host is invalid
239
     */
240 2
    public function setHost($host)
241
    {
242
        // If the hostname is invalid
243 2
        if (preg_match("%[/\?#]%", $host) ||
244 2
            (!filter_var('http://'.$host, FILTER_VALIDATE_URL) && !filter_var($host, FILTER_VALIDATE_IP))
245 2
        ) {
246 1
            throw new InvalidArgumentException(
247 1
                sprintf('Invalid URL host "%s"', $host),
248
                InvalidArgumentException::INVALID_URL_HOST
249 1
            );
250
        }
251
252 2
        $url = clone $this;
253 2
        $url->urlParts['host'] = $host;
254 2
        return $url;
255
    }
256
257
    /**
258
     * Set the URL port
259
     *
260
     * @param int|null $port URL port
261
     * @return Url New URL
262
     * @throws InvalidArgumentException If the URL port is invalid
263
     */
264 2
    public function setPort($port)
265
    {
266
        // If the URL port is invalid
267 2
        if (is_int($port) ? (($port < 0) || ($port > 65535)) : ($port !== null)) {
268 1
            throw new InvalidArgumentException(
269 1
                sprintf('Invalid URL port "%s"', $port),
270
                InvalidArgumentException::INVALID_URL_PORT
271 1
            );
272
        }
273
274 2
        $url = clone $this;
275 2
        $url->urlParts['port'] = $port;
276 2
        return $url;
277
    }
278
279
    /**
280
     * Set the URL user
281
     *
282
     * @param string|NULL $user URL user
283
     * @return Url New URL
284
     */
285 2
    public function setUser($user)
286
    {
287 2
        $url = clone $this;
288 2
        $url->urlParts['user'] = $user ?: null;
289 2
        return $url;
290
    }
291
292
    /**
293
     * Set the URL password
294
     *
295
     * @param string $pass URL password
296
     * @return Url New URL
297
     */
298 2
    public function setPassword($pass)
299
    {
300 2
        $url = clone $this;
301 2
        $url->urlParts['pass'] = $pass ?: null;
302 2
        return $url;
303
    }
304
305
    /**
306
     * Set the URL query
307
     *
308
     * @param string $query URL query
309
     * @return Url New URL
310
     */
311 2
    public function setQuery($query)
312
    {
313 2
        $url = clone $this;
314 2
        $url->urlParts['query'] = trim($query);
315 2
        return $url;
316
    }
317
318
    /**
319
     * Set the URL query parameters
320
     *
321
     * @param array $query URL query parameters
322
     * @return Url New URL
323
     */
324 1
    public function setQueryParams(array $query)
325
    {
326 1
        $url = clone $this;
327 1
        $url->urlParts['query'] = http_build_query($query);
328 1
        return $url;
329
    }
330
331
    /**
332
     * Set the URL fragment
333
     *
334
     * @param string $fragment URL fragment
335
     * @return Url New URL
336
     */
337 2
    public function setFragment($fragment)
338
    {
339 2
        $url = clone $this;
340 2
        $url->urlParts['fragment'] = $fragment;
341 2
        return $url;
342
    }
343
344
    /**
345
     * Test whether this URL is remote
346
     *
347
     * @return bool Remote URL
348
     */
349 1
    public function isRemote()
350
    {
351 1
        return $this->isAbsolute() && !$this->isAbsoluteLocal();
352
    }
353
354
    /**
355
     * Return whether this URL is absolute
356
     *
357
     * @return bool Absolute URL
358
     */
359 56
    public function isAbsolute()
360
    {
361 56
        return !empty($this->urlParts['scheme']) && !empty($this->urlParts['host']);
362
    }
363
364
    /**
365
     * Test whether this URL belongs to the local Apparat instance
366
     *
367
     * @return bool URL belongs to the local Apparat instance
368
     */
369 25
    public function isAbsoluteLocal()
370
    {
371
        // Instantiate the apparat base URL
372 25
        $apparatBaseUrl = new self(getenv('APPARAT_BASE_URL'));
373 25
        $apparatBaseUrlPath = $apparatBaseUrl->getPath();
374 25
        $apparatBaseUrl = $apparatBaseUrl->setScheme(null)->setPath(null);
375
376
        // If the URL matches the Apparat base URL (the scheme is disregarded)
377 25
        return $this->isAbsolute() && $this->matches($apparatBaseUrl) && !strncmp(
378 3
            $apparatBaseUrlPath,
379 3
            $this->getPath(),
380 3
            strlen($apparatBaseUrlPath)
381 25
        );
382
    }
383
384
    /**
385
     * Set the URL path
386
     *
387
     * @param string $path URL path
388
     * @return Url New URL
389
     */
390 27
    public function setPath($path)
391
    {
392 27
        $path = trim($path, '/');
393
394 27
        $url = clone $this;
395 27
        $url->urlParts['path'] = strlen($path) ? '/'.$path : null;
396 27
        return $url;
397
    }
398
399
    /**
400
     * Set the URL scheme
401
     *
402
     * @param string $scheme URL scheme
403
     * @return Url New URL
404
     * @throws InvalidArgumentException If the URL scheme is invalid
405
     */
406 27
    public function setScheme($scheme)
407
    {
408
        // If the URL scheme is not valid
409 27
        if (strlen($scheme) && !array_key_exists(strtolower($scheme), static::$schemes)) {
410 1
            throw new InvalidArgumentException(
411 1
                sprintf('Invalid URL scheme "%s"', $scheme),
412
                InvalidArgumentException::INVALID_URL_SCHEME
413 1
            );
414
        }
415
416 27
        $url = clone $this;
417 27
        $url->urlParts['scheme'] = $scheme ? strtolower($scheme) : null;
418 27
        return $url;
419
    }
420
421
    /**
422
     * Test if this URL matches all available parts of a given URL
423
     *
424
     * @param Url $url Comparison URL
425
     * @return bool This URL matches all available parts of the given URL
426
     */
427 8
    public function matches(Url $url)
428
    {
429
430
        // Test the scheme
431 8
        $urlScheme = $url->getScheme();
432 8
        if (($urlScheme !== null) && ($this->getScheme() !== $urlScheme)) {
433 2
            return false;
434
        }
435
436
        // Test the user
437 8
        $urlUser = $url->getUser();
438 8
        if (($urlUser !== null) && ($this->getUser() !== $urlUser)) {
439 1
            return false;
440
        }
441
442
        // Test the password
443 8
        $urlPassword = $url->getPassword();
444 8
        if (($urlPassword !== null) && ($this->getPassword() !== $urlPassword)) {
445 1
            return false;
446
        }
447
448
        // Test the host
449 8
        $urlHost = $url->getHost();
450 8
        if (($urlHost !== null) && ($this->getHost() !== $urlHost)) {
451 5
            return false;
452
        }
453
454
        // Test the port
455 5
        $urlPort = $url->getPort();
456 5
        if (($urlPort !== null) && ($this->getPort() !== $urlPort)) {
457 1
            return false;
458
        }
459
460
        // Test the path
461 5
        $urlPath = $url->getPath();
462 5
        if (($urlPath !== null) && ($this->getPath() !== $urlPath)) {
463 1
            return false;
464
        }
465
466
        // Test the query
467 5
        $urlQuery = $url->getQuery();
468 5
        if (serialize($this->getQuery()) !== serialize($urlQuery)) {
469 1
            return false;
470
        }
471
472
        // Test the fragment
473 5
        $urlFragment = $url->getFragment();
474 5
        if (($urlFragment !== null) && ($this->getFragment() !== $urlFragment)) {
475 1
            return false;
476
        }
477
478 5
        return true;
479
    }
480
481
    /**
482
     * Unserialize the string representation of this property
483
     *
484
     * @param string $str Serialized property
485
     * @return SerializablePropertyInterface Property
486
     */
487 1
    public static function unserialize($str)
488
    {
489 1
        return Kernel::create(static::class, [$str]);
490
    }
491
492
    /**
493
     * Serialize the property
494
     *
495
     * @return mixed Property serialization
496
     */
497 1
    public function serialize()
498
    {
499 1
        return strval($this);
500
    }
501
}
502