Url::getHost()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 4
rs 10
c 0
b 0
f 0
cc 1
eloc 2
nc 1
nop 0
1
<?php
2
3
namespace Guzzle\Http;
4
5
use Guzzle\Common\Exception\InvalidArgumentException;
6
7
/**
8
 * Parses and generates URLs based on URL parts. In favor of performance, URL parts are not validated.
9
 */
10
class Url
11
{
12
    protected $scheme;
13
    protected $host;
14
    protected $port;
15
    protected $username;
16
    protected $password;
17
    protected $path = '';
18
    protected $fragment;
19
20
    /** @var QueryString Query part of the URL */
21
    protected $query;
22
23
    /**
24
     * Factory method to create a new URL from a URL string
25
     *
26
     * @param string $url Full URL used to create a Url object
27
     *
28
     * @return Url
29
     * @throws InvalidArgumentException
30
     */
31
    public static function factory($url)
32
    {
33
        static $defaults = array('scheme' => null, 'host' => null, 'path' => null, 'port' => null, 'query' => null,
34
            'user' => null, 'pass' => null, 'fragment' => null);
35
36
        if (false === ($parts = parse_url($url))) {
37
            throw new InvalidArgumentException('Was unable to parse malformed url: ' . $url);
38
        }
39
40
        $parts += $defaults;
41
42
        // Convert the query string into a QueryString object
43
        if ($parts['query'] || 0 !== strlen($parts['query'])) {
44
            $parts['query'] = QueryString::fromString($parts['query']);
45
        }
46
47
        return new static($parts['scheme'], $parts['host'], $parts['user'],
48
            $parts['pass'], $parts['port'], $parts['path'], $parts['query'],
49
            $parts['fragment']);
50
    }
51
52
    /**
53
     * Build a URL from parse_url parts. The generated URL will be a relative URL if a scheme or host are not provided.
54
     *
55
     * @param array $parts Array of parse_url parts
56
     *
57
     * @return string
58
     */
59
    public static function buildUrl(array $parts)
60
    {
61
        $url = $scheme = '';
62
63
        if (isset($parts['scheme'])) {
64
            $scheme = $parts['scheme'];
65
            $url .= $scheme . ':';
66
        }
67
68
        if (isset($parts['host'])) {
69
            $url .= '//';
70
            if (isset($parts['user'])) {
71
                $url .= $parts['user'];
72
                if (isset($parts['pass'])) {
73
                    $url .= ':' . $parts['pass'];
74
                }
75
                $url .=  '@';
76
            }
77
78
            $url .= $parts['host'];
79
80
            // Only include the port if it is not the default port of the scheme
81
            if (isset($parts['port'])
82
                && !(($scheme == 'http' && $parts['port'] == 80) || ($scheme == 'https' && $parts['port'] == 443))
83
            ) {
84
                $url .= ':' . $parts['port'];
85
            }
86
        }
87
88
        // Add the path component if present
89
        if (isset($parts['path']) && 0 !== strlen($parts['path'])) {
90
            // Always ensure that the path begins with '/' if set and something is before the path
91
            if ($url && $parts['path'][0] != '/' && substr($url, -1)  != '/') {
92
                $url .= '/';
93
            }
94
            $url .= $parts['path'];
95
        }
96
97
        // Add the query string if present
98
        if (isset($parts['query'])) {
99
            $url .= '?' . $parts['query'];
100
        }
101
102
        // Ensure that # is only added to the url if fragment contains anything.
103
        if (isset($parts['fragment'])) {
104
            $url .= '#' . $parts['fragment'];
105
        }
106
107
        return $url;
108
    }
109
110
    /**
111
     * Create a new URL from URL parts
112
     *
113
     * @param string                   $scheme   Scheme of the URL
114
     * @param string                   $host     Host of the URL
115
     * @param string                   $username Username of the URL
116
     * @param string                   $password Password of the URL
117
     * @param int                      $port     Port of the URL
118
     * @param string                   $path     Path of the URL
119
     * @param QueryString|array|string $query    Query string of the URL
120
     * @param string                   $fragment Fragment of the URL
121
     */
122
    public function __construct($scheme, $host, $username = null, $password = null, $port = null, $path = null, QueryString $query = null, $fragment = null)
123
    {
124
        $this->scheme = $scheme;
125
        $this->host = $host;
126
        $this->port = $port;
127
        $this->username = $username;
128
        $this->password = $password;
129
        $this->fragment = $fragment;
130
        if (!$query) {
131
            $this->query = new QueryString();
132
        } else {
133
            $this->setQuery($query);
134
        }
135
        $this->setPath($path);
136
    }
137
138
    /**
139
     * Clone the URL
140
     */
141
    public function __clone()
142
    {
143
        $this->query = clone $this->query;
144
    }
145
146
    /**
147
     * Returns the URL as a URL string
148
     *
149
     * @return string
150
     */
151
    public function __toString()
152
    {
153
        return self::buildUrl($this->getParts());
154
    }
155
156
    /**
157
     * Get the parts of the URL as an array
158
     *
159
     * @return array
160
     */
161
    public function getParts()
162
    {
163
        $query = (string) $this->query;
164
165
        return array(
166
            'scheme' => $this->scheme,
167
            'user' => $this->username,
168
            'pass' => $this->password,
169
            'host' => $this->host,
170
            'port' => $this->port,
171
            'path' => $this->getPath(),
172
            'query' => $query !== '' ? $query : null,
173
            'fragment' => $this->fragment,
174
        );
175
    }
176
177
    /**
178
     * Set the host of the request.
179
     *
180
     * @param string $host Host to set (e.g. www.yahoo.com, yahoo.com)
181
     *
182
     * @return Url
183
     */
184
    public function setHost($host)
185
    {
186
        if (strpos($host, ':') === false) {
187
            $this->host = $host;
188
        } else {
189
            list($host, $port) = explode(':', $host);
190
            $this->host = $host;
191
            $this->setPort($port);
192
        }
193
194
        return $this;
195
    }
196
197
    /**
198
     * Get the host part of the URL
199
     *
200
     * @return string
201
     */
202
    public function getHost()
203
    {
204
        return $this->host;
205
    }
206
207
    /**
208
     * Set the scheme part of the URL (http, https, ftp, etc)
209
     *
210
     * @param string $scheme Scheme to set
211
     *
212
     * @return Url
213
     */
214
    public function setScheme($scheme)
215
    {
216
        if ($this->scheme == 'http' && $this->port == 80) {
217
            $this->port = null;
218
        } elseif ($this->scheme == 'https' && $this->port == 443) {
219
            $this->port = null;
220
        }
221
222
        $this->scheme = $scheme;
223
224
        return $this;
225
    }
226
227
    /**
228
     * Get the scheme part of the URL
229
     *
230
     * @return string
231
     */
232
    public function getScheme()
233
    {
234
        return $this->scheme;
235
    }
236
237
    /**
238
     * Set the port part of the URL
239
     *
240
     * @param int $port Port to set
241
     *
242
     * @return Url
243
     */
244
    public function setPort($port)
245
    {
246
        $this->port = $port;
247
248
        return $this;
249
    }
250
251
    /**
252
     * Get the port part of the URl. Will return the default port for a given scheme if no port has been set.
253
     *
254
     * @return int|null
255
     */
256
    public function getPort()
257
    {
258
        if ($this->port) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->port of type integer|null is loosely compared to true; this is ambiguous if the integer can be zero. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For integer values, zero is a special case, in particular the following results might be unexpected:

0   == false // true
0   == null  // true
123 == false // false
123 == null  // false

// It is often better to use strict comparison
0 === false // false
0 === null  // false
Loading history...
259
            return $this->port;
260
        } elseif ($this->scheme == 'http') {
261
            return 80;
262
        } elseif ($this->scheme == 'https') {
263
            return 443;
264
        }
265
266
        return null;
267
    }
268
269
    /**
270
     * Set the path part of the URL
271
     *
272
     * @param array|string $path Path string or array of path segments
273
     *
274
     * @return Url
275
     */
276
    public function setPath($path)
277
    {
278
        static $pathReplace = array(' ' => '%20', '?' => '%3F');
279
        if (is_array($path)) {
280
            $path = '/' . implode('/', $path);
281
        }
282
283
        $this->path = strtr($path, $pathReplace);
284
285
        return $this;
286
    }
287
288
    /**
289
     * Normalize the URL so that double slashes and relative paths are removed
290
     *
291
     * @return Url
292
     */
293
    public function normalizePath()
294
    {
295
        if (!$this->path || $this->path == '/' || $this->path == '*') {
296
            return $this;
297
        }
298
299
        $results = array();
300
        $segments = $this->getPathSegments();
301
        foreach ($segments as $segment) {
302
            if ($segment == '..') {
303
                array_pop($results);
304
            } elseif ($segment != '.' && $segment != '') {
305
                $results[] = $segment;
306
            }
307
        }
308
309
        // Combine the normalized parts and add the leading slash if needed
310
        $this->path = ($this->path[0] == '/' ? '/' : '') . implode('/', $results);
311
312
        // Add the trailing slash if necessary
313
        if ($this->path != '/' && end($segments) == '') {
314
            $this->path .= '/';
315
        }
316
317
        return $this;
318
    }
319
320
    /**
321
     * Add a relative path to the currently set path.
322
     *
323
     * @param string $relativePath Relative path to add
324
     *
325
     * @return Url
326
     */
327
    public function addPath($relativePath)
328
    {
329
        if ($relativePath != '/' && is_string($relativePath) && strlen($relativePath) > 0) {
330
            // Add a leading slash if needed
331
            if ($relativePath[0] != '/') {
332
                $relativePath = '/' . $relativePath;
333
            }
334
            $this->setPath(str_replace('//', '/', $this->path . $relativePath));
335
        }
336
337
        return $this;
338
    }
339
340
    /**
341
     * Get the path part of the URL
342
     *
343
     * @return string
344
     */
345
    public function getPath()
346
    {
347
        return $this->path;
348
    }
349
350
    /**
351
     * Get the path segments of the URL as an array
352
     *
353
     * @return array
354
     */
355
    public function getPathSegments()
356
    {
357
        return array_slice(explode('/', $this->getPath()), 1);
358
    }
359
360
    /**
361
     * Set the password part of the URL
362
     *
363
     * @param string $password Password to set
364
     *
365
     * @return Url
366
     */
367
    public function setPassword($password)
368
    {
369
        $this->password = $password;
370
371
        return $this;
372
    }
373
374
    /**
375
     * Get the password part of the URL
376
     *
377
     * @return null|string
378
     */
379
    public function getPassword()
380
    {
381
        return $this->password;
382
    }
383
384
    /**
385
     * Set the username part of the URL
386
     *
387
     * @param string $username Username to set
388
     *
389
     * @return Url
390
     */
391
    public function setUsername($username)
392
    {
393
        $this->username = $username;
394
395
        return $this;
396
    }
397
398
    /**
399
     * Get the username part of the URl
400
     *
401
     * @return null|string
402
     */
403
    public function getUsername()
404
    {
405
        return $this->username;
406
    }
407
408
    /**
409
     * Get the query part of the URL as a QueryString object
410
     *
411
     * @return QueryString
412
     */
413
    public function getQuery()
414
    {
415
        return $this->query;
416
    }
417
418
    /**
419
     * Set the query part of the URL
420
     *
421
     * @param QueryString|string|array $query Query to set
422
     *
423
     * @return Url
424
     */
425
    public function setQuery($query)
426
    {
427
        if (is_string($query)) {
428
            $output = null;
429
            parse_str($query, $output);
430
            $this->query = new QueryString($output);
0 ignored issues
show
Bug introduced by
It seems like $output defined by null on line 428 can also be of type null; however, Guzzle\Common\Collection::__construct() 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...
431
        } elseif (is_array($query)) {
432
            $this->query = new QueryString($query);
433
        } elseif ($query instanceof QueryString) {
434
            $this->query = $query;
435
        }
436
437
        return $this;
438
    }
439
440
    /**
441
     * Get the fragment part of the URL
442
     *
443
     * @return null|string
444
     */
445
    public function getFragment()
446
    {
447
        return $this->fragment;
448
    }
449
450
    /**
451
     * Set the fragment part of the URL
452
     *
453
     * @param string $fragment Fragment to set
454
     *
455
     * @return Url
456
     */
457
    public function setFragment($fragment)
458
    {
459
        $this->fragment = $fragment;
460
461
        return $this;
462
    }
463
464
    /**
465
     * Check if this is an absolute URL
466
     *
467
     * @return bool
468
     */
469
    public function isAbsolute()
470
    {
471
        return $this->scheme && $this->host;
472
    }
473
474
    /**
475
     * Combine the URL with another URL. Follows the rules specific in RFC 3986 section 5.4.
476
     *
477
     * @param string $url           Relative URL to combine with
478
     * @param bool   $strictRfc3986 Set to true to use strict RFC 3986 compliance when merging paths. When first
479
     *                              released, Guzzle used an incorrect algorithm for combining relative URL paths. In
480
     *                              order to not break users, we introduced this flag to allow the merging of URLs based
481
     *                              on strict RFC 3986 section 5.4.1. This means that "http://a.com/foo/baz" merged with
482
     *                              "bar" would become "http://a.com/foo/bar". When this value is set to false, it would
483
     *                              become "http://a.com/foo/baz/bar".
484
     * @return Url
485
     * @throws InvalidArgumentException
486
     * @link http://tools.ietf.org/html/rfc3986#section-5.4
487
     */
488
    public function combine($url, $strictRfc3986 = false)
489
    {
490
        $url = self::factory($url);
491
492
        // Use the more absolute URL as the base URL
493
        if (!$this->isAbsolute() && $url->isAbsolute()) {
494
            $url = $url->combine($this);
495
        }
496
497
        // Passing a URL with a scheme overrides everything
498 View Code Duplication
        if ($buffer = $url->getScheme()) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across 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...
499
            $this->scheme = $buffer;
500
            $this->host = $url->getHost();
501
            $this->port = $url->getPort();
502
            $this->username = $url->getUsername();
503
            $this->password = $url->getPassword();
504
            $this->path = $url->getPath();
505
            $this->query = $url->getQuery();
506
            $this->fragment = $url->getFragment();
507
            return $this;
508
        }
509
510
        // Setting a host overrides the entire rest of the URL
511 View Code Duplication
        if ($buffer = $url->getHost()) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across 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...
512
            $this->host = $buffer;
513
            $this->port = $url->getPort();
514
            $this->username = $url->getUsername();
515
            $this->password = $url->getPassword();
516
            $this->path = $url->getPath();
517
            $this->query = $url->getQuery();
518
            $this->fragment = $url->getFragment();
519
            return $this;
520
        }
521
522
        $path = $url->getPath();
523
        $query = $url->getQuery();
524
525
        if (!$path) {
526
            if (count($query)) {
527
                $this->addQuery($query, $strictRfc3986);
528
            }
529
        } else {
530
            if ($path[0] == '/') {
531
                $this->path = $path;
532
            } elseif ($strictRfc3986) {
533
                $this->path .= '/../' . $path;
534
            } else {
535
                $this->path .= '/' . $path;
536
            }
537
            $this->normalizePath();
538
            $this->addQuery($query, $strictRfc3986);
539
        }
540
541
        $this->fragment = $url->getFragment();
542
543
        return $this;
544
    }
545
546
    private function addQuery(QueryString $new, $strictRfc386)
547
    {
548
        if (!$strictRfc386) {
549
            $new->merge($this->query);
550
        }
551
552
        $this->query = $new;
553
    }
554
}
555