Completed
Push — master ( f9ae58...a2a3bd )
by Michele
01:44 queued 12s
created

IPv4   F

Complexity

Total Complexity 60

Size/Duplication

Total Lines 455
Duplicated Lines 16.48 %

Coupling/Cohesion

Components 2
Dependencies 4

Test Coverage

Coverage 100%

Importance

Changes 0
Metric Value
wmc 60
lcom 2
cbo 4
dl 75
loc 455
ccs 140
cts 140
cp 1
rs 3.6
c 0
b 0
f 0

22 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 7 1
A __toString() 0 4 1
A getAddressType() 0 4 1
A getNumberOfBits() 0 4 1
C fromString() 0 42 13
A toString() 0 8 2
A toOctal() 13 13 3
A toHexadecimal() 13 13 3
A getBits() 9 9 2
A getDefaultReservedRangeType() 0 4 1
B getReservedRanges() 5 49 5
A getRangeType() 10 15 5
A toIPv6() 0 6 1
A toIPv6IPv4Mapped() 0 4 1
A getComparableString() 0 12 3
A matches() 0 4 1
A getNextAddress() 0 4 1
A getPreviousAddress() 0 4 1
A getReverseDNSLookupName() 0 7 1
A fromBytes() 0 17 6
A getBytes() 0 13 2
A getAddressAtOffset() 25 25 5

How to fix   Duplicated Code    Complexity   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

Complex Class

 Tip:   Before tackling complexity, make sure that you eliminate any duplication first. This often can reduce the size of classes significantly.

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

1
<?php
2
3
namespace IPLib\Address;
4
5
use IPLib\Range\RangeInterface;
6
use IPLib\Range\Subnet;
7
use IPLib\Range\Type as RangeType;
8
9
/**
10
 * An IPv4 address.
11
 */
12
class IPv4 implements AddressInterface
13
{
14
    /**
15
     * The string representation of the address.
16
     *
17
     * @var string
18
     *
19
     * @example '127.0.0.1'
20
     */
21
    protected $address;
22
23
    /**
24
     * The byte list of the IP address.
25
     *
26
     * @var int[]|null
27
     */
28
    protected $bytes;
29
30
    /**
31
     * The type of the range of this IP address.
32
     *
33
     * @var int|null
34
     */
35
    protected $rangeType;
36
37
    /**
38
     * A string representation of this address than can be used when comparing addresses and ranges.
39
     *
40
     * @var string
41
     */
42
    protected $comparableString;
43
44
    /**
45
     * An array containing RFC designated address ranges.
46
     *
47
     * @var array|null
48
     */
49
    private static $reservedRanges = null;
50
51
    /**
52
     * Initializes the instance.
53
     *
54
     * @param string $address
55
     */
56 764
    protected function __construct($address)
57
    {
58 764
        $this->address = $address;
59 764
        $this->bytes = null;
60 764
        $this->rangeType = null;
61 764
        $this->comparableString = null;
62 764
    }
63
64
    /**
65
     * {@inheritdoc}
66
     *
67
     * @see \IPLib\Address\AddressInterface::__toString()
68
     */
69 67
    public function __toString()
70
    {
71 67
        return $this->address;
72
    }
73
74
    /**
75
     * {@inheritdoc}
76
     *
77
     * @see \IPLib\Address\AddressInterface::getNumberOfBits()
78
     */
79 30
    public static function getNumberOfBits()
80
    {
81 30
        return 32;
82
    }
83
84
    /**
85
     * Parse a string and returns an IPv4 instance if the string is valid, or null otherwise.
86
     *
87
     * @param string|mixed $address the address to parse
88
     * @param bool $mayIncludePort set to false to avoid parsing addresses with ports
89
     * @param bool $supportNonDecimalIPv4 set to true to support parsing non decimal (that is, octal and hexadecimal) IPv4 addresses
90
     *
91
     * @return static|null
92
     */
93 1277
    public static function fromString($address, $mayIncludePort = true, $supportNonDecimalIPv4 = false)
94
    {
95 1277
        if (!is_string($address) || !strpos($address, '.')) {
96 514
            return null;
97
        }
98 776
        $rxChunk = '0?[0-9]{1,3}';
99 776
        if ($supportNonDecimalIPv4) {
100 32
            $rxChunk = "(?:0[Xx]0*[0-9A-Fa-f]{1,2})|(?:{$rxChunk})";
101
        }
102 776
        $rx = "0*?({$rxChunk})\.0*?({$rxChunk})\.0*?({$rxChunk})\.0*?({$rxChunk})";
103 776
        if ($mayIncludePort) {
104 776
            $rx .= '(?::\d+)?';
105
        }
106 776
        $matches = null;
107 776
        if (!preg_match('/^' . $rx . '$/', $address, $matches)) {
108 27
            return null;
109
        }
110 758
        $nums = array();
111 758
        for ($i = 1; $i <= 4; $i++) {
112 758
            $s = $matches[$i];
113 758
            if ($supportNonDecimalIPv4) {
114 32
                if (stripos($s, '0x') === 0) {
115 21
                    $n = hexdec(substr($s, 2));
116 32
                } elseif ($s[0] === '0') {
117 32
                    if (!preg_match('/^[0-7]+$/', $s)) {
118 3
                        return null;
119
                    }
120 29
                    $n = octdec(substr($s, 1));
121
                } else {
122 29
                    $n = (int) $s;
123
                }
124
            } else {
125 738
                $n = (int) $s;
126
            }
127 755
            if ($n < 0 || $n > 255) {
128 6
                return null;
129
            }
130 755
            $nums[] = (string) $n;
131
        }
132
133 749
        return new static(implode('.', $nums));
134
    }
135
136
    /**
137
     * Parse an array of bytes and returns an IPv4 instance if the array is valid, or null otherwise.
138
     *
139
     * @param int[]|array $bytes
140
     *
141
     * @return static|null
142
     */
143 686
    public static function fromBytes(array $bytes)
144
    {
145 686
        $result = null;
146 686
        if (count($bytes) === 4) {
147 390
            $chunks = array_map(
148
                function ($byte) {
149 390
                    return (is_int($byte) && $byte >= 0 && $byte <= 255) ? (string) $byte : false;
150 390
                },
151 390
                $bytes
152
            );
153 390
            if (in_array(false, $chunks, true) === false) {
154 390
                $result = new static(implode('.', $chunks));
155
            }
156
        }
157
158 686
        return $result;
159
    }
160
161
    /**
162
     * {@inheritdoc}
163
     *
164
     * @see \IPLib\Address\AddressInterface::toString()
165
     */
166 621
    public function toString($long = false)
167
    {
168 621
        if ($long) {
169 37
            return $this->getComparableString();
170
        }
171
172 621
        return $this->address;
173
    }
174
175
    /**
176
     * Get the octal representation of this IP address.
177
     *
178
     * @param bool $long
179
     *
180
     * @return string
181
     *
182
     * @example if $long == false: if the decimal representation is '0.7.8.255': '0.7.010.0377'
183
     * @example if $long == true: if the decimal representation is '0.7.8.255': '0000.0007.0010.0377'
184
     */
185 12 View Code Duplication
    public function toOctal($long = false)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in 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...
186
    {
187 12
        $chunks = array();
188 12
        foreach ($this->getBytes() as $byte) {
189 12
            if ($long) {
190 12
                $chunks[] = sprintf('%04o', $byte);
191
            } else {
192 12
                $chunks[] = '0' . decoct($byte);
193
            }
194
        }
195
196 12
        return implode('.', $chunks);
197
    }
198
199
    /**
200
     * Get the hexadecimal representation of this IP address.
201
     *
202
     * @param bool $long
203
     *
204
     * @return string
205
     *
206
     * @example if $long == false: if the decimal representation is '0.9.10.255': '0.9.0xa.0xff'
207
     * @example if $long == true: if the decimal representation is '0.9.10.255': '0x00.0x09.0x0a.0xff'
208
     */
209 12 View Code Duplication
    public function toHexadecimal($long = false)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in 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...
210
    {
211 12
        $chunks = array();
212 12
        foreach ($this->getBytes() as $byte) {
213 12
            if ($long) {
214 12
                $chunks[] = sprintf('0x%02x', $byte);
215
            } else {
216 12
                $chunks[] = '0x' . dechex($byte);
217
            }
218
        }
219
220 12
        return implode('.', $chunks);
221
    }
222
223
    /**
224
     * {@inheritdoc}
225
     *
226
     * @see \IPLib\Address\AddressInterface::getBytes()
227
     */
228 581
    public function getBytes()
229
    {
230 581
        if ($this->bytes === null) {
231 581
            $this->bytes = array_map(
232
                function ($chunk) {
233 581
                    return (int) $chunk;
234 581
                },
235 581
                explode('.', $this->address)
236
            );
237
        }
238
239 581
        return $this->bytes;
240
    }
241
242
    /**
243
     * {@inheritdoc}
244
     *
245
     * @see \IPLib\Address\AddressInterface::getBits()
246
     */
247 29 View Code Duplication
    public function getBits()
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in 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...
248
    {
249 29
        $parts = array();
250 29
        foreach ($this->getBytes() as $byte) {
251 29
            $parts[] = sprintf('%08b', $byte);
252
        }
253
254 29
        return implode('', $parts);
255
    }
256
257
    /**
258
     * {@inheritdoc}
259
     *
260
     * @see \IPLib\Address\AddressInterface::getAddressType()
261
     */
262 583
    public function getAddressType()
263
    {
264 583
        return Type::T_IPv4;
265
    }
266
267
    /**
268
     * {@inheritdoc}
269
     *
270
     * @see \IPLib\Address\AddressInterface::getDefaultReservedRangeType()
271
     */
272 173
    public static function getDefaultReservedRangeType()
273
    {
274 173
        return RangeType::T_PUBLIC;
275
    }
276
277
    /**
278
     * {@inheritdoc}
279
     *
280
     * @see \IPLib\Address\AddressInterface::getReservedRanges()
281
     */
282 341
    public static function getReservedRanges()
283
    {
284 341
        if (self::$reservedRanges === null) {
285 1
            $reservedRanges = array();
286
            foreach (array(
287
                // RFC 5735
288 1
                '0.0.0.0/8' => array(RangeType::T_THISNETWORK, array('0.0.0.0/32' => RangeType::T_UNSPECIFIED)),
289
                // RFC 5735
290
                '10.0.0.0/8' => array(RangeType::T_PRIVATENETWORK),
291
                // RFC 6598
292
                '100.64.0.0/10' => array(RangeType::T_CGNAT),
293
                // RFC 5735
294
                '127.0.0.0/8' => array(RangeType::T_LOOPBACK),
295
                // RFC 5735
296
                '169.254.0.0/16' => array(RangeType::T_LINKLOCAL),
297
                // RFC 5735
298
                '172.16.0.0/12' => array(RangeType::T_PRIVATENETWORK),
299
                // RFC 5735
300
                '192.0.0.0/24' => array(RangeType::T_RESERVED),
301
                // RFC 5735
302
                '192.0.2.0/24' => array(RangeType::T_RESERVED),
303
                // RFC 5735
304
                '192.88.99.0/24' => array(RangeType::T_ANYCASTRELAY),
305
                // RFC 5735
306
                '192.168.0.0/16' => array(RangeType::T_PRIVATENETWORK),
307
                // RFC 5735
308
                '198.18.0.0/15' => array(RangeType::T_RESERVED),
309
                // RFC 5735
310
                '198.51.100.0/24' => array(RangeType::T_RESERVED),
311
                // RFC 5735
312
                '203.0.113.0/24' => array(RangeType::T_RESERVED),
313
                // RFC 5735
314
                '224.0.0.0/4' => array(RangeType::T_MULTICAST),
315
                // RFC 5735
316
                '240.0.0.0/4' => array(RangeType::T_RESERVED, array('255.255.255.255/32' => RangeType::T_LIMITEDBROADCAST)),
317
            ) as $range => $data) {
318 1
                $exceptions = array();
319 1 View Code Duplication
                if (isset($data[1])) {
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...
320 1
                    foreach ($data[1] as $exceptionRange => $exceptionType) {
321 1
                        $exceptions[] = new AssignedRange(Subnet::fromString($exceptionRange), $exceptionType);
322
                    }
323
                }
324 1
                $reservedRanges[] = new AssignedRange(Subnet::fromString($range), $data[0], $exceptions);
325
            }
326 1
            self::$reservedRanges = $reservedRanges;
327
        }
328
329 341
        return self::$reservedRanges;
330
    }
331
332
    /**
333
     * {@inheritdoc}
334
     *
335
     * @see \IPLib\Address\AddressInterface::getRangeType()
336
     */
337 171
    public function getRangeType()
338
    {
339 171 View Code Duplication
        if ($this->rangeType === null) {
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...
340 171
            $rangeType = null;
341 171
            foreach (static::getReservedRanges() as $reservedRange) {
342 171
                $rangeType = $reservedRange->getAddressType($this);
343 171
                if ($rangeType !== null) {
344 168
                    break;
345
                }
346
            }
347 171
            $this->rangeType = $rangeType === null ? static::getDefaultReservedRangeType() : $rangeType;
348
        }
349
350 171
        return $this->rangeType;
351
    }
352
353
    /**
354
     * Create an IPv6 representation of this address (in 6to4 notation).
355
     *
356
     * @return \IPLib\Address\IPv6
357
     */
358 4
    public function toIPv6()
359
    {
360 4
        $myBytes = $this->getBytes();
361
362 4
        return IPv6::fromString('2002:' . sprintf('%02x', $myBytes[0]) . sprintf('%02x', $myBytes[1]) . ':' . sprintf('%02x', $myBytes[2]) . sprintf('%02x', $myBytes[3]) . '::');
363
    }
364
365
    /**
366
     * Create an IPv6 representation of this address (in IPv6 IPv4-mapped notation).
367
     *
368
     * @return \IPLib\Address\IPv6
369
     */
370 4
    public function toIPv6IPv4Mapped()
371
    {
372 4
        return IPv6::fromBytes(array_merge(array(0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff), $this->getBytes()));
373
    }
374
375
    /**
376
     * {@inheritdoc}
377
     *
378
     * @see \IPLib\Address\AddressInterface::getComparableString()
379
     */
380 421
    public function getComparableString()
381
    {
382 421
        if ($this->comparableString === null) {
383 421
            $chunks = array();
384 421
            foreach ($this->getBytes() as $byte) {
385 421
                $chunks[] = sprintf('%03d', $byte);
386
            }
387 421
            $this->comparableString = implode('.', $chunks);
388
        }
389
390 421
        return $this->comparableString;
391
    }
392
393
    /**
394
     * {@inheritdoc}
395
     *
396
     * @see \IPLib\Address\AddressInterface::matches()
397
     */
398 11
    public function matches(RangeInterface $range)
399
    {
400 11
        return $range->contains($this);
401
    }
402
403
    /**
404
     * {@inheritdoc}
405
     *
406
     * @see \IPLib\Address\AddressInterface::getAddressAtOffset()
407
     */
408 29 View Code Duplication
    public function getAddressAtOffset($n)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in 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...
409
    {
410 29
        if (!is_int($n)) {
411 1
            return null;
412
        }
413
414 28
        $boundary = 256;
415 28
        $mod = $n;
416 28
        $bytes = $this->getBytes();
417 28
        for ($i = count($bytes) - 1; $i >= 0; $i--) {
418 28
            $tmp = ($bytes[$i] + $mod) % $boundary;
419 28
            $mod = (int) floor(($bytes[$i] + $mod) / $boundary);
420 28
            if ($tmp < 0) {
421 13
                $tmp += $boundary;
422
            }
423
424 28
            $bytes[$i] = $tmp;
425
        }
426
427 28
        if ($mod !== 0) {
428 8
            return null;
429
        }
430
431 22
        return static::fromBytes($bytes);
432
    }
433
434
    /**
435
     * {@inheritdoc}
436
     *
437
     * @see \IPLib\Address\AddressInterface::getNextAddress()
438
     */
439 9
    public function getNextAddress()
440
    {
441 9
        return $this->getAddressAtOffset(1);
442
    }
443
444
    /**
445
     * {@inheritdoc}
446
     *
447
     * @see \IPLib\Address\AddressInterface::getPreviousAddress()
448
     */
449 9
    public function getPreviousAddress()
450
    {
451 9
        return $this->getAddressAtOffset(-1);
452
    }
453
454
    /**
455
     * {@inheritdoc}
456
     *
457
     * @see \IPLib\Address\AddressInterface::getReverseDNSLookupName()
458
     */
459 14
    public function getReverseDNSLookupName()
460
    {
461 14
        return implode(
462 14
            '.',
463 14
            array_reverse($this->getBytes())
464 14
        ) . '.in-addr.arpa';
465
    }
466
}
467