Completed
Push — master ( 88eef6...81252c )
by Joschi
02:44
created

Url::__construct()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 12
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 7
CRAP Score 2

Importance

Changes 5
Bugs 0 Features 1
Metric Value
c 5
b 0
f 1
dl 0
loc 12
ccs 7
cts 7
cp 1
rs 9.4285
cc 2
eloc 6
nc 2
nop 1
crap 2
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)
0 ignored issues
show
Coding Style introduced by
Spaces must be used for alignment; tabs are not allowed
Loading history...
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\Path;
37
38
use Apparat\Object\Application\Utility\ArrayUtility;
39
40
/**
41
 * Object URL
42
 *
43
 * @package Apparat\Object\Domain\Model
44
 */
45
class Url
46
{
47
    /**
48
     * HTTP-Schema
49
     *
50
     * @var string
51
     */
52
    const SCHEME_HTTP = 'http';
53
    /**
54
     * HTTPS-Schema
55
     *
56
     * @var string
57
     */
58
    const SCHEME_HTTPS = 'https';
59
    /**
60
     * Valid schemes
61
     *
62
     * @var array
63
     */
64
    protected static $_schemes = [self::SCHEME_HTTP => true, self::SCHEME_HTTPS => true];
65
    /**
66
     * URL parts
67
     *
68
     * @var array
69
     */
70
    protected $_urlParts = null;
71
72
    /*******************************************************************************
73
     * PUBLIC METHODS
74
     *******************************************************************************/
75
76
    /**
77
     * URL constructor
78
     *
79
     * @param string $url URL
80
     * @throws InvalidArgumentException If the URL is invalid
81
     */
82 22
    public function __construct($url)
83
    {
84
85
        // Parse the URL
86 22
        $this->_urlParts = @parse_url($url);
87 22
        if ($this->_urlParts === false) {
88 1
            throw new InvalidArgumentException(
89 1
                sprintf('Invalid URL "%s"', $url),
90
                InvalidArgumentException::INVALID_URL
91 1
            );
92
        }
93 21
    }
94
95
    /**
96
     * Return the serialized URL
97
     *
98
     * @return string Serialized URL
99
     */
100 2
    public function __toString()
101
    {
102 2
        return $this->getUrl();
103
    }
104
105
    /**
106
     * Return the full serialized URL
107
     *
108
     * @return string Full URL
109
     */
110 2
    public function getUrl()
111
    {
112 2
        return $this->_getUrl();
113
    }
114
115
    /**
116
     * Return the a complete serialized URL
117
     *
118
     * @param array $override Override components
119
     * @return string Serialized URL
120
     */
121 4
    protected function _getUrl(array &$override = [])
122
    {
123
        // Prepare the URL scheme
124 4
        if (isset($override['scheme'])) {
125 1
            $scheme = trim($override['scheme']);
126 1
            if (strlen($scheme)) {
127 1
                $scheme .= '://';
128 1
            }
129 1
        } else {
130 3
            $scheme = !empty($this->_urlParts['scheme']) ? $this->getScheme().'://' : '';
131
        }
132 4
        $override['scheme'] = $scheme;
133
134
        // Prepare the URL user
135 4
        if (isset($override['user'])) {
136 1
            $user = $override['user'];
137 1
        } else {
138 3
            $user = !empty($this->_urlParts['user']) ? rawurlencode($this->getUser()) : '';
139
        }
140 4
        $override['user'] = $user;
141
142
        // Prepare the URL password
143 4
        if (isset($override['pass'])) {
144 1
            $pass = ':'.$override['pass'];
145 1
        } else {
146 3
            $pass = !empty($this->_urlParts['pass']) ? ':'.rawurlencode($this->getPassword()) : '';
147
        }
148 4
        if ($user || $pass) {
149 2
            $pass .= '@';
150 2
        }
151 4
        $override['pass'] = $pass;
152
153
        // Prepare the URL host
154 4
        if (isset($override['host'])) {
155 1
            $host = $override['host'];
156 1
        } else {
157 3
            $host = !empty($this->_urlParts['host']) ? $this->getHost() : '';
158
        }
159 4
        $override['host'] = $host;
160
161
        // Prepare the URL port
162 4
        if (isset($override['port'])) {
163 1
            $port = ':'.$override['port'];
164 1
        } else {
165 3
            $port = !empty($this->_urlParts['port']) ? ':'.$this->getPort() : '';
166
        }
167 4
        $override['port'] = $port;
168
169
        // Prepare the URL path
170 4
        if (isset($override['path'])) {
171 1
            $path = $override['path'];
172 1
        } else {
173 3
            $path = $this->_urlParts['path'];
174
        }
175 4
        $override['path'] = $path;
176
177
        // Prepare the URL query
178 4
        if (isset($override['query'])) {
179 2
            $query = (is_array($override['query']) ? http_build_query($override['query']) : strval($override['query']));
180 2
            if (strlen($query)) {
181 1
                $query = '?'.$query;
182 1
            }
183 2
        } else {
184 2
            $query = !empty($this->_urlParts['query']) ? '?'.$this->_urlParts['query'] : '';
185
        }
186 4
        $override['query'] = $query;
187
188
        // Prepare the URL fragment
189 4
        if (isset($override['fragment'])) {
190 2
            $fragment = $override['fragment'];
191 2
            if (strlen($fragment)) {
192 1
                $fragment = '#'.$fragment;
193 1
            }
194 2
        } else {
195 2
            $fragment = !empty($this->_urlParts['fragment']) ? '#'.$this->getFragment() : '';
196
        }
197 4
        $override['fragment'] = $fragment;
198
199 4
        return "$scheme$user$pass$host$port$path$query$fragment";
200
    }
201
202
    /**
203
     * Return the URL scheme
204
     *
205
     * @return string URL scheme
206
     */
207 6
    public function getScheme()
208
    {
209 6
        return isset($this->_urlParts['scheme']) ? $this->_urlParts['scheme'] : null;
210
    }
211
212
    /**
213
     * Return the URL user
214
     *
215
     * @return string|NULL URL user
216
     */
217 4
    public function getUser()
218
    {
219 4
        return isset($this->_urlParts['user']) ? $this->_urlParts['user'] : null;
220
    }
221
222
    /**
223
     * Return the URL password
224
     *
225
     * @return string|NULL URL password
226
     */
227 4
    public function getPassword()
228
    {
229 4
        return isset($this->_urlParts['pass']) ? $this->_urlParts['pass'] : null;
230
    }
231
232
    /**
233
     * Return the URL host
234
     *
235
     * @return string URL host
236
     */
237 6
    public function getHost()
238
    {
239 6
        return isset($this->_urlParts['host']) ? $this->_urlParts['host'] : null;
240
    }
241
242
    /**
243
     * Return the URL port
244
     *
245
     * @return int URL port
246
     */
247 4
    public function getPort()
248
    {
249 4
        return isset($this->_urlParts['port']) ? $this->_urlParts['port'] : null;
250
    }
251
252
    /**
253
     * Return the URL fragment
254
     *
255
     * @return string URL fragment
256
     */
257 4
    public function getFragment()
258
    {
259 4
        return isset($this->_urlParts['fragment']) ? $this->_urlParts['fragment'] : null;
260
    }
261
262
    /**
263
     * Set the URL host
264
     *
265
     * @param string $host URL host
266
     * @return Url New URL
267
     * @throws InvalidArgumentException If the URL host is invalid
268
     */
269 1
    public function setHost($host)
270
    {
271
        // If the hostname is invalid
272 1
        if (preg_match("%[/\?#]%", $host) || (!filter_var('http://'.$host, FILTER_VALIDATE_URL) && !filter_var(
273 1
                    $host,
274
                    FILTER_VALIDATE_IP
275 1
                ))
276 1
        ) {
277 1
            throw new InvalidArgumentException(
278 1
                sprintf('Invalid URL host "%s"', $host),
279
                InvalidArgumentException::INVALID_URL_HOST
280 1
            );
281
        }
282
283 1
        $url = clone $this;
284 1
        $url->_urlParts['host'] = $host;
285 1
        return $url;
286
    }
287
288
    /**
289
     * Set the URL port
290
     *
291
     * @param int $port URL port
292
     * @return Url New URL
293
     * @throws InvalidArgumentException If the URL port is invalid
294
     */
295 1
    public function setPort($port)
296
    {
297
        // If the URL port is invalid
298 1
        if (!is_int($port) || ($port < 0) || ($port > 65535)) {
299 1
            throw new InvalidArgumentException(
300 1
                sprintf('Invalid URL port "%s"', $port),
301
                InvalidArgumentException::INVALID_URL_PORT
302 1
            );
303
        }
304
305 1
        $url = clone $this;
306 1
        $url->_urlParts['port'] = $port;
307 1
        return $url;
308
    }
309
310
    /**
311
     * Set the URL user
312
     *
313
     * @param string|NULL $user URL user
314
     * @return Url New URL
315
     */
316 1
    public function setUser($user)
317
    {
318 1
        $url = clone $this;
319 1
        $url->_urlParts['user'] = $user ?: null;
320 1
        return $url;
321
    }
322
323
    /**
324
     * Set the URL password
325
     *
326
     * @param string $pass URL password
327
     * @return Url New URL
328
     */
329 1
    public function setPassword($pass)
330
    {
331 1
        $url = clone $this;
332 1
        $url->_urlParts['pass'] = $pass ?: null;
333 1
        return $url;
334
    }
335
336
    /**
337
     * Set the URL query
338
     *
339
     * @param array $query URL query
340
     * @return Url New URL
341
     */
342 1
    public function setQuery(array $query)
343
    {
344 1
        $url = clone $this;
345 1
        $url->_urlParts['query'] = http_build_query($query);
346 1
        return $url;
347
    }
348
349
    /**
350
     * Set the URL fragment
351
     *
352
     * @param string $fragment URL fragment
353
     * @return Url New URL
354
     */
355 1
    public function setFragment($fragment)
356
    {
357 1
        $url = clone $this;
358 1
        $url->_urlParts['fragment'] = $fragment;
359 1
        return $url;
360
    }
361
362
    /**
363
     * Test whether this URL is remote
364
     *
365
     * @return bool Remote URL
366
     */
367
    public function isRemote()
368
    {
369
        return $this->isAbsolute() && !$this->isAbsoluteLocal();
370
    }
371
372
    /**
373
     * Return whether this URL is absolute
374
     *
375
     * @return bool Absolute URL
376
     */
377 20
    public function isAbsolute()
378
    {
379 20
        return !empty($this->_urlParts['scheme']) && !empty($this->_urlParts['host']);
380
    }
381
382
    /**
383
     * Test whether this URL belongs to the local Apparat instance
384
     *
385
     * @return bool URL belongs to the local Apparat instance
386
     */
387 3
    public function isAbsoluteLocal()
388
    {
389
        // Instantiate the apparat base URL
390 3
        $apparatBaseUrl = new self(getenv('APPARAT_BASE_URL'));
391 3
        $apparatBaseUrlPath = $apparatBaseUrl->getPath();
392 3
        $apparatBaseUrl = $apparatBaseUrl->setScheme('')->setPath('');
393
394
        // If the URL matches the Apparat base URL (the scheme is disregarded)
395 3
        return $this->isAbsolute() && $this->matches($apparatBaseUrl) && !strncmp(
396
            $apparatBaseUrlPath,
397
            $this->getPath(), strlen($apparatBaseUrlPath)
398 3
        );
399
    }
400
401
    /**
402
     * Return the URL path
403
     *
404
     * @return string URL path
405
     */
406 11
    public function getPath()
407
    {
408 11
        return $this->_urlParts['path'];
409
    }
410
411
    /**
412
     * Set the URL path
413
     *
414
     * @param string $path URL path
415
     * @return Url New URL
416
     */
417 4
    public function setPath($path)
418
    {
419 4
        $path = trim($path, '/');
420
421 4
        $url = clone $this;
422 4
        $url->_urlParts['path'] = strlen($path) ? '/'.$path : null;
423 4
        return $url;
424
    }
425
426
    /**
427
     * Set the URL scheme
428
     *
429
     * @param string $scheme URL scheme
430
     * @return Url New URL
431
     * @throws InvalidArgumentException If the URL scheme is invalid
432
     */
433 4
    public function setScheme($scheme)
434
    {
435
        // If the URL scheme is not valid
436 4
        if (strlen($scheme) && !array_key_exists($scheme, static::$_schemes)) {
437 1
            throw new InvalidArgumentException(
438 1
                sprintf('Invalid URL scheme "%s"', $scheme),
439
                InvalidArgumentException::INVALID_URL_SCHEME
440 1
            );
441
        }
442
443 4
        $url = clone $this;
444 4
        $url->_urlParts['scheme'] = $scheme;
445 4
        return $url;
446
    }
447
448
    /**
449
     * Test if this URL matches all available parts of a given URL
450
     *
451
     * @param Url $url Comparison URL
452
     * @return bool This URL matches all available parts of the given URL
453
     */
454 3
    public function matches(Url $url)
455
    {
456
457
        // Test the scheme
458 3
        $urlScheme = $url->getScheme();
459 3
        if (($urlScheme !== null) && ($this->getScheme() !== $urlScheme)) {
460 3
            return false;
461
        }
462
463
        // Test the user
464 2
        $urlUser = $url->getUser();
465 2
        if (($urlUser !== null) && ($this->getUser() !== $urlUser)) {
466 1
            return false;
467
        }
468
469
        // Test the password
470 2
        $urlPassword = $url->getPassword();
471 2
        if (($urlPassword !== null) && ($this->getPassword() !== $urlPassword)) {
472 1
            return false;
473
        }
474
475
        // Test the host
476 2
        $urlHost = $url->getHost();
477 2
        if (($urlHost !== null) && ($this->getHost() !== $urlHost)) {
478 1
            return false;
479
        }
480
481
        // Test the port
482 2
        $urlPort = $url->getPort();
483 2
        if (($urlPort !== null) && ($this->getPort() !== $urlPort)) {
484 1
            return false;
485
        }
486
487
        // Test the port
488 2
        $urlPort = $url->getPort();
489 2
        if (($urlPort !== null) && ($this->getPort() !== $urlPort)) {
490
            return false;
491
        }
492
493
        // Test the path
494 2
        $urlPath = $url->getPath();
495 2
        if (($urlPath !== null) && ($this->getPath() !== $urlPath)) {
496 1
            return false;
497
        }
498
499
        // Test the query
500 2
        $urlQuery = $url->getQuery();
501 2
        if (serialize($this->getQuery()) !== serialize($urlQuery)) {
502 1
            return false;
503
        }
504
505
        // Test the fragment
506 2
        $urlFragment = $url->getFragment();
507 2
        if (($urlFragment !== null) && ($this->getFragment() !== $urlFragment)) {
508 1
            return false;
509
        }
510
511 2
        return true;
512
    }
513
514
    /*******************************************************************************
515
     * PRIVATE METHODS
516
     *******************************************************************************/
517
518
    /**
519
     * Return the URL query
520
     *
521
     * @return array URL query
522
     */
523 4
    public function getQuery()
524
    {
525 4
        $query = [];
526 4
        if (isset($this->_urlParts['query']) && !empty($this->_urlParts['query'])) {
527 4
            @parse_str($this->_urlParts['query'], $query);
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
528 4
        }
529 4
        return ArrayUtility::sortRecursiveByKey($query);
0 ignored issues
show
Bug introduced by
It seems like $query can also be of type null; however, Apparat\Object\Applicati...y::sortRecursiveByKey() does only seem to accept array, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
530
    }
531
}
532