Completed
Push — master ( 177e73...e31061 )
by Joschi
02:23
created

Url::setQuery()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 6
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

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