Completed
Push — master ( fb692e...c26f50 )
by ignace nyamagana
03:48
created

Host::newCollectionInstance()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 8
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 4
CRAP Score 2

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 0
loc 8
ccs 4
cts 4
cp 1
rs 9.4285
cc 2
eloc 4
nc 2
nop 1
crap 2
1
<?php
2
/**
3
 * League.Uri (http://uri.thephpleague.com)
4
 *
5
 * @package   League.uri
6
 * @author    Ignace Nyamagana Butera <[email protected]>
7
 * @copyright 2013-2015 Ignace Nyamagana Butera
8
 * @license   https://github.com/thephpleague/uri/blob/master/LICENSE (MIT License)
9
 * @version   4.2.0
10
 * @link      https://github.com/thephpleague/uri/
11
 */
12
namespace League\Uri\Components;
13
14
use InvalidArgumentException;
15
use League\Uri\Interfaces\Host as HostInterface;
16
17
/**
18
 * Value object representing a URI host component.
19
 *
20
 * @package League.uri
21
 * @author  Ignace Nyamagana Butera <[email protected]>
22
 * @since   1.0.0
23
 */
24
class Host extends AbstractHierarchicalComponent implements HostInterface
25
{
26
    use HostIpTrait;
27
28
    use HostnameInfoTrait;
29
30
    use HostnameTrait;
31
32
    /**
33
     * HierarchicalComponent delimiter
34
     *
35
     * @var string
36
     */
37
    protected static $separator = '.';
38
39
    /**
40
     * Host literal representation
41
     *
42
     * @var string
43
     */
44
    protected $host;
45
46
    /**
47
     * Return a new instance when needed
48
     *
49
     * @param array $data
50
     *
51
     * @return static
52
     */
53 105
    protected function newCollectionInstance(array $data)
54
    {
55 105
        if ($data == $this->data) {
56 3
            return $this;
0 ignored issues
show
Bug Best Practice introduced by
The return type of return $this; (League\Uri\Components\Host) is incompatible with the return type declared by the abstract method League\Uri\Components\Ab...::newCollectionInstance of type League\Uri\Types\ImmutableCollectionTrait.

If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.

Let’s take a look at an example:

class Author {
    private $name;

    public function __construct($name) {
        $this->name = $name;
    }

    public function getName() {
        return $this->name;
    }
}

abstract class Post {
    public function getAuthor() {
        return 'Johannes';
    }
}

class BlogPost extends Post {
    public function getAuthor() {
        return new Author('Johannes');
    }
}

class ForumPost extends Post { /* ... */ }

function my_function(Post $post) {
    echo strtoupper($post->getAuthor());
}

Our function my_function expects a Post object, and outputs the author of the post. The base class Post returns a simple string and outputting a simple string will work just fine. However, the child class BlogPost which is a sub-type of Post instead decided to return an object, and is therefore violating the SOLID principles. If a BlogPost were passed to my_function, PHP would not complain, but ultimately fail when executing the strtoupper call in its body.

Loading history...
57
        }
58
59 102
        return $this->createFromLabels($data, $this->isAbsolute);
0 ignored issues
show
Bug Best Practice introduced by
The return type of return $this->createFrom...ta, $this->isAbsolute); (League\Uri\Components\Host) is incompatible with the return type declared by the abstract method League\Uri\Components\Ab...::newCollectionInstance of type League\Uri\Types\ImmutableCollectionTrait.

If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.

Let’s take a look at an example:

class Author {
    private $name;

    public function __construct($name) {
        $this->name = $name;
    }

    public function getName() {
        return $this->name;
    }
}

abstract class Post {
    public function getAuthor() {
        return 'Johannes';
    }
}

class BlogPost extends Post {
    public function getAuthor() {
        return new Author('Johannes');
    }
}

class ForumPost extends Post { /* ... */ }

function my_function(Post $post) {
    echo strtoupper($post->getAuthor());
}

Our function my_function expects a Post object, and outputs the author of the post. The base class Post returns a simple string and outputting a simple string will work just fine. However, the child class BlogPost which is a sub-type of Post instead decided to return an object, and is therefore violating the SOLID principles. If a BlogPost were passed to my_function, PHP would not complain, but ultimately fail when executing the strtoupper call in its body.

Loading history...
60
    }
61
62
    /**
63
     * return a new instance from an array or a traversable object
64
     *
65
     * @param \Traversable|string[] $data The segments list
66
     * @param int                   $type one of the constant IS_ABSOLUTE or IS_RELATIVE
67
     *
68
     * @throws InvalidArgumentException If $type is not a recognized constant
69
     *
70
     * @return static
71
     */
72 162
    public static function createFromLabels($data, $type = self::IS_RELATIVE)
73
    {
74 162
        static $type_list = [self::IS_ABSOLUTE => 1, self::IS_RELATIVE => 1];
75
76 162
        if (!isset($type_list[$type])) {
77 3
            throw new InvalidArgumentException('Please verify the submitted constant');
78
        }
79
80 159
        if (empty($data)) {
81 12
            return new static();
82
        }
83
84 147
        if ([''] === $data) {
85 3
            return new static('');
86
        }
87
88 144
        return new static(static::formatComponentString($data, $type));
89
    }
90
91
    /**
92
     * DEPRECATION WARNING! This method will be removed in the next major point release
93
     *
94
     * @deprecated deprecated since version 4.2
95
     *
96
     * return a new instance from an array or a traversable object
97
     *
98
     * @param \Traversable|string[] $data The segments list
99
     * @param int                   $type one of the constant IS_ABSOLUTE or IS_RELATIVE
100
     *
101
     * @throws InvalidArgumentException If $type is not a recognized constant
102
     *
103
     * @return static
104
     */
105
    public static function createFromArray($data, $type = self::IS_RELATIVE)
106
    {
107
        return self::createFromLabels($data, $type);
108
    }
109
110
    /**
111
     * New instance
112
     *
113
     * @param null|string $host
114
     */
115 1141
    public function __construct($host = null)
116
    {
117 1141
        $this->data = $this->validate($host);
118 1075
        $this->setLiteral();
119 1075
    }
120
121
    /**
122
     * Returns whether or not the host is an IDN
123
     *
124
     * @return bool
125
     */
126 9
    public function isIdn()
127
    {
128 9
        return $this->isIdn;
129
    }
130
131
    /**
132
     * Returns whether or not the host is an IP address
133
     *
134
     * @return bool
135
     */
136 1075
    public function isIp()
137
    {
138 1075
        return $this->hostAsIpv4 || $this->hostAsIpv6;
139
    }
140
141
    /**
142
     * Returns whether or not the host is an IPv4 address
143
     *
144
     * @return bool
145
     */
146 39
    public function isIpv4()
147
    {
148 39
        return $this->hostAsIpv4;
149
    }
150
151
    /**
152
     * Returns whether or not the host is an IPv6 address
153
     *
154
     * @return bool
155
     */
156 39
    public function isIpv6()
157
    {
158 39
        return $this->hostAsIpv6;
159
    }
160
161
    /**
162
     * Returns whether or not the host has a ZoneIdentifier
163
     *
164
     * @return bool
165
     *
166
     * @see http://tools.ietf.org/html/rfc6874#section-4
167
     */
168 12
    public function hasZoneIdentifier()
169
    {
170 12
        return $this->hasZoneIdentifier;
171
    }
172
173
    /**
174
     * Host literal setter
175
     */
176 1075
    protected function setLiteral()
177
    {
178 1075
        $this->host = !$this->isIp() ? $this->__toString() : $this->data[0];
179 1075
    }
180
181
    /**
182
     * DEPRECATION WARNING! This method will be removed in the next major point release
183
     *
184
     * @deprecated deprecated since version 4.2
185
     *
186
     * Returns the instance literal representation
187
     * without encoding
188
     *
189
     * @return string
190
     */
191
    public function getLiteral()
192
    {
193
        return $this->host;
194
    }
195
196
    /**
197
     * validate the submitted data
198
     *
199
     * @param string $str
200
     *
201
     * @return array
202
     */
203 1141
    protected function validate($str)
204
    {
205 1141
        if (null === $str) {
206 354
            return [];
207
        }
208
209 1021
        $str = $this->validateString($str);
210 1012
        if ('' === $str) {
211 24
            return [''];
212
        }
213
214 1003
        $res = $this->validateIpHost($str);
215 1003
        if (!empty($res)) {
216 120
            return $res;
217
        }
218
219 904
        return $this->validateStringHost($str);
220
    }
221
222
    /**
223
     * Retrieves a single host label.
224
     *
225
     * Retrieves a single host label. If the label offset has not been set,
226
     * returns the default value provided.
227
     *
228
     * @param string $offset  the label offset
229
     * @param mixed  $default Default value to return if the offset does not exist.
230
     *
231
     * @return mixed
232
     */
233 3
    public function getLabel($offset, $default = null)
234
    {
235 3
        if (isset($this->data[$offset])) {
236 3
            return $this->isIdn ? $this->data[$offset] : idn_to_ascii($this->data[$offset]);
237
        }
238
239 3
        return $default;
240
    }
241
242
    /**
243
     * Returns an array representation of the host
244
     *
245
     * @return array
246
     */
247 868
    public function toArray()
248
    {
249 868
        return $this->convertToAscii($this->data, !$this->isIdn);
250
    }
251
252
    /**
253
     * @inheritdoc
254
     */
255 1051
    public function getContent()
256
    {
257 1051
        if ([] === $this->data) {
258 369
            return null;
259
        }
260
261 925
        if ($this->isIp()) {
262 75
            return $this->formatIp($this->data[0]);
263
        }
264
265 862
        return $this->formatComponentString($this->toArray(), $this->isAbsolute);
266
    }
267
268
    /**
269
     * @inheritdoc
270
     */
271 2
    public function __debugInfo()
272
    {
273 2
        return ['host' => $this->getContent()];
274
    }
275
276
    /**
277
     * @inheritdoc
278
     */
279 15
    public static function __set_state(array $properties)
280
    {
281 15
        $host = static::createFromArray($properties['data'], $properties['isAbsolute']);
0 ignored issues
show
Deprecated Code introduced by
The method League\Uri\Components\Host::createFromArray() has been deprecated with message: deprecated since version 4.2 return a new instance from an array or a traversable object

This method has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the method will be removed from the class and what other method or class to use instead.

Loading history...
282 15
        $host->hostnameInfoLoaded = $properties['hostnameInfoLoaded'];
283 15
        $host->hostnameInfo = $properties['hostnameInfo'];
284
285 15
        return $host;
286
    }
287
288
    /**
289
     * Returns a host in his punycode encoded form
290
     *
291
     * This method MUST retain the state of the current instance, and return
292
     * an instance with the host transcoded using to ascii the RFC 3492 rules
293
     *
294
     * @see http://tools.ietf.org/html/rfc3492
295
     *
296
     * @return static
297
     */
298 108
    public function toAscii()
299
    {
300 108
        if ($this->isIp() || !$this->isIdn) {
301 36
            return $this;
302
        }
303
304 75
        return $this->modify($this->formatComponentString(
305 75
            $this->convertToAscii($this->data, $this->isIdn),
306 75
            $this->isAbsolute
307 50
        ));
308
    }
309
310
    /**
311
     * Returns a host in his IDN form
312
     *
313
     * This method MUST retain the state of the current instance, and return
314
     * an instance with the host in its IDN form using RFC 3492 rules
315
     *
316
     * @see http://tools.ietf.org/html/rfc3492
317
     *
318
     * @return static
319
     */
320 81
    public function toUnicode()
321
    {
322 81
        if ($this->isIp() || $this->isIdn) {
323 69
            return $this;
324
        }
325
326 72
        return $this->modify($this->formatComponentString($this->data, $this->isAbsolute));
327
    }
328
329
    /**
330
     * @inheritdoc
331
     */
332 889
    protected static function formatComponentString($data, $type)
333
    {
334 889
        $hostname = implode(static::$separator, array_reverse(static::validateIterator($data)));
335 877
        if (self::IS_ABSOLUTE == $type) {
336 36
            return $hostname.static::$separator;
337
        }
338
339 853
        return $hostname;
340
    }
341
342
    /**
343
     * Return an host without its zone identifier according to RFC6874
344
     *
345
     * This method MUST retain the state of the current instance, and return
346
     * an instance without the host zone identifier according to RFC6874
347
     *
348
     * @see http://tools.ietf.org/html/rfc6874#section-4
349
     *
350
     * @return static
351
     */
352 18
    public function withoutZoneIdentifier()
353
    {
354 18
        if ($this->hasZoneIdentifier) {
355 9
            return $this->modify(substr($this->data[0], 0, strpos($this->data[0], '%')));
356
        }
357
358 9
        return $this;
359
    }
360
361
    /**
362
     * Validated the Host Label Count
363
     *
364
     * @param array $labels Host labels
365
     *
366
     * @throws InvalidArgumentException If the validation fails
367
     */
368 889
    protected function assertLabelsCount(array $labels)
369
    {
370 889
        if (127 <= count(array_merge($this->data, $labels))) {
371 3
            throw new InvalidArgumentException('Invalid Hostname, verify labels count');
372
        }
373 886
    }
374
375
    /**
376
     * set the FQDN property
377
     *
378
     * @param string $str
379
     *
380
     * @return string
381
     */
382 904
    protected function setIsAbsolute($str)
383
    {
384 904
        $this->isAbsolute = self::IS_RELATIVE;
385 904
        if ('.' == mb_substr($str, -1, 1, 'UTF-8')) {
386 42
            $this->isAbsolute = self::IS_ABSOLUTE;
387 42
            $str = mb_substr($str, 0, -1, 'UTF-8');
388 28
        }
389
390 904
        return $str;
391
    }
392
393
    /**
394
     * @inheritdoc
395
     */
396 30
    public function prepend($component)
397
    {
398 30
        return $this->createFromLabels(
399 30
                $this->validateComponent($component),
400 30
                $this->isAbsolute
401 30
            )->append($this);
402
    }
403
404
    /**
405
     * @inheritdoc
406
     */
407 60
    public function append($component)
408
    {
409 60
        return $this->newCollectionInstance(array_merge(
410 60
            $this->validateComponent($component)->toArray(),
411 60
            $this->toArray()
412 40
        ));
413
    }
414
}
415