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 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 |
||
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) |
|
63 | |||
64 | /** |
||
65 | * {@inheritdoc} |
||
66 | * |
||
67 | * @see \IPLib\Address\AddressInterface::__toString() |
||
68 | */ |
||
69 | 67 | public function __toString() |
|
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) |
|
|||
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) |
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() |
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() |
|
266 | |||
267 | /** |
||
268 | * {@inheritdoc} |
||
269 | * |
||
270 | * @see \IPLib\Address\AddressInterface::getDefaultReservedRangeType() |
||
271 | */ |
||
272 | 173 | public static function getDefaultReservedRangeType() |
|
276 | |||
277 | /** |
||
278 | * {@inheritdoc} |
||
279 | * |
||
280 | * @see \IPLib\Address\AddressInterface::getReservedRanges() |
||
281 | */ |
||
282 | 341 | public static function getReservedRanges() |
|
331 | |||
332 | /** |
||
333 | * {@inheritdoc} |
||
334 | * |
||
335 | * @see \IPLib\Address\AddressInterface::getRangeType() |
||
336 | */ |
||
337 | 171 | public function getRangeType() |
|
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() |
|
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() |
|
374 | |||
375 | /** |
||
376 | * {@inheritdoc} |
||
377 | * |
||
378 | * @see \IPLib\Address\AddressInterface::getComparableString() |
||
379 | */ |
||
380 | 421 | public function getComparableString() |
|
392 | |||
393 | /** |
||
394 | * {@inheritdoc} |
||
395 | * |
||
396 | * @see \IPLib\Address\AddressInterface::matches() |
||
397 | */ |
||
398 | 11 | public function matches(RangeInterface $range) |
|
402 | |||
403 | /** |
||
404 | * {@inheritdoc} |
||
405 | * |
||
406 | * @see \IPLib\Address\AddressInterface::getAddressAtOffset() |
||
407 | */ |
||
408 | 29 | View Code Duplication | public function getAddressAtOffset($n) |
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() |
|
443 | |||
444 | /** |
||
445 | * {@inheritdoc} |
||
446 | * |
||
447 | * @see \IPLib\Address\AddressInterface::getPreviousAddress() |
||
448 | */ |
||
449 | 9 | public function getPreviousAddress() |
|
453 | |||
454 | /** |
||
455 | * {@inheritdoc} |
||
456 | * |
||
457 | * @see \IPLib\Address\AddressInterface::getReverseDNSLookupName() |
||
458 | */ |
||
459 | 14 | public function getReverseDNSLookupName() |
|
466 | } |
||
467 |
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.