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 Struct 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 Struct, and based on these observations, apply Extract Interface, too.
1 | <?php |
||
18 | class Struct extends AbstractConfig implements StructInterface, MetadataModificationInterface |
||
19 | { |
||
20 | /** |
||
21 | * Initial contents for structure properties |
||
22 | * |
||
23 | * @var array |
||
24 | */ |
||
25 | protected $initialContents; |
||
26 | /** |
||
27 | * TRUE to skip property change notification, FALSE otherwise |
||
28 | * |
||
29 | * @var boolean |
||
30 | */ |
||
31 | protected $skipNotify = false; |
||
32 | /** |
||
33 | * Parent structure or NULL if this is top-level structure |
||
34 | * |
||
35 | * @var Struct |
||
36 | */ |
||
37 | protected $parent; |
||
38 | /** |
||
39 | * Structure contents |
||
40 | * |
||
41 | * @var array |
||
42 | */ |
||
43 | private $struct; |
||
44 | /** |
||
45 | * Structure size (for Countable interface) |
||
46 | * |
||
47 | * @var int |
||
48 | */ |
||
49 | private $count = 0; |
||
50 | /** |
||
51 | * Current index in structure (for Iterator interface) |
||
52 | * |
||
53 | * @var int |
||
54 | */ |
||
55 | private $index = 0; |
||
56 | /** |
||
57 | * Structure metadata |
||
58 | * |
||
59 | * @var StructMetadata |
||
60 | */ |
||
61 | private $metadata; |
||
62 | |||
63 | /** |
||
64 | * Class constructor |
||
65 | * |
||
66 | * @param array|object $contents OPTIONAL Contents to initialize structure with |
||
67 | * @param array|object $config OPTIONAL Configuration for this structure |
||
68 | * @throws \Flying\Struct\Exception |
||
69 | * @throws \RuntimeException |
||
70 | */ |
||
71 | 163 | View Code Duplication | public function __construct($contents = null, $config = null) |
83 | |||
84 | /** |
||
85 | * Create structure from given metadata information |
||
86 | * |
||
87 | * @param StructMetadata $metadata |
||
88 | * @throws Exception |
||
89 | * @return void |
||
90 | * @throws \RuntimeException |
||
91 | */ |
||
92 | 163 | protected function createStruct(StructMetadata $metadata = null) |
|
125 | |||
126 | /** |
||
127 | * Get structure metadata |
||
128 | * |
||
129 | * @return StructMetadata |
||
130 | * @throws \Flying\Struct\Exception |
||
131 | */ |
||
132 | 163 | protected function getMetadata() |
|
154 | |||
155 | /** |
||
156 | * Get initial structure contents |
||
157 | * |
||
158 | * @param string $name OPTIONAL Structure property name to get contents of, |
||
159 | * NULL to get all available contents |
||
160 | * @return mixed |
||
161 | */ |
||
162 | 163 | protected function getInitialContents($name = null) |
|
173 | |||
174 | /** |
||
175 | * Set initial structure contents |
||
176 | * |
||
177 | * @param array|object $contents |
||
178 | * @return void |
||
179 | */ |
||
180 | 9 | protected function setInitialContents($contents) |
|
191 | |||
192 | /** |
||
193 | * Defined by Iterator interface |
||
194 | * |
||
195 | * @return void |
||
196 | */ |
||
197 | 163 | public function rewind() |
|
202 | |||
203 | /** |
||
204 | * Modify metadata for this structure after it was parsed by MetadataManager |
||
205 | * |
||
206 | * @param StructMetadata $metadata |
||
207 | */ |
||
208 | 161 | public static function modifyMetadata(StructMetadata $metadata) |
|
212 | |||
213 | /** |
||
214 | * Handling of object cloning |
||
215 | * |
||
216 | * @return void |
||
217 | */ |
||
218 | 4 | public function __clone() |
|
230 | |||
231 | /** |
||
232 | * Get structure property with given name |
||
233 | * |
||
234 | * @param string $name |
||
235 | * @return PropertyInterface|ComplexPropertyInterface|null |
||
236 | */ |
||
237 | 7 | public function getProperty($name) |
|
244 | |||
245 | /** |
||
246 | * Support isset() overloading |
||
247 | * |
||
248 | * @param string $name |
||
249 | * @return boolean |
||
250 | */ |
||
251 | 9 | public function __isset($name) |
|
255 | |||
256 | /** |
||
257 | * Magic function so that $obj->value will work. |
||
258 | * |
||
259 | * @param string $name |
||
260 | * @return mixed |
||
261 | */ |
||
262 | 23 | public function __get($name) |
|
266 | |||
267 | /** |
||
268 | * Magic function for setting structure property value |
||
269 | * |
||
270 | * @param string $name Structure property name to set value of |
||
271 | * @param mixed $value New value for this property |
||
272 | * @return void |
||
273 | * @throws \RuntimeException |
||
274 | */ |
||
275 | 15 | public function __set($name, $value) |
|
279 | |||
280 | /** |
||
281 | * Retrieve value of structure property with given name and return $default if there is no such property |
||
282 | * |
||
283 | * @param string $name Structure property name to get value of |
||
284 | * @param mixed $default OPTIONAL Default value to return in a case if property is not available |
||
285 | * @return mixed |
||
286 | */ |
||
287 | 77 | public function get($name, $default = null) |
|
288 | { |
||
289 | 77 | $result = $default; |
|
290 | 77 | if (array_key_exists($name, $this->struct)) { |
|
291 | 75 | $property = $this->struct[$name]; |
|
292 | 75 | if ($property instanceof ComplexPropertyInterface) { |
|
293 | 40 | $result = $property; |
|
294 | 75 | } elseif ($property instanceof PropertyInterface) { |
|
295 | 64 | $result = $property->getValue(); |
|
296 | 64 | } |
|
297 | 75 | } else { |
|
298 | 4 | $result = $this->getMissed($name, $default); |
|
299 | } |
||
300 | 77 | return $result; |
|
301 | } |
||
302 | |||
303 | /** |
||
304 | * Handle get requests for missed structure properties |
||
305 | * |
||
306 | * @param string $name Requested structure property name |
||
307 | * @param mixed $default Given default value |
||
308 | * @return mixed |
||
309 | */ |
||
310 | 4 | protected function getMissed($name, $default) |
|
316 | |||
317 | /** |
||
318 | * Set value of structure property with given name |
||
319 | * |
||
320 | * @param string|array $name Either name of structure property to set value of |
||
321 | * or array of structure properties to set |
||
322 | * @param mixed $value OPTIONAL New value for this property (only if $name is a string) |
||
323 | * @return void |
||
324 | * @throws \RuntimeException |
||
325 | */ |
||
326 | 54 | public function set($name, $value = null) |
|
344 | |||
345 | /** |
||
346 | * Handle set requests for missed structure properties |
||
347 | * |
||
348 | * @param string $name Structure property name to set |
||
349 | * @param mixed $value Given value for the property |
||
350 | * @return void |
||
351 | */ |
||
352 | 10 | protected function setMissed($name, $value) |
|
357 | |||
358 | /** |
||
359 | * Handle notification about update of given property |
||
360 | * |
||
361 | * @param SimplePropertyInterface $property |
||
362 | * @return void |
||
363 | * @throws \RuntimeException |
||
364 | */ |
||
365 | 53 | View Code Duplication | public function updateNotify(SimplePropertyInterface $property) |
374 | |||
375 | /** |
||
376 | * Structure properties change event handler |
||
377 | * |
||
378 | * @param string $name Name of changed property |
||
379 | * @return void |
||
380 | */ |
||
381 | 49 | protected function onChange($name) |
|
386 | |||
387 | /** |
||
388 | * Reset structure to its initial state |
||
389 | * |
||
390 | * @return void |
||
391 | */ |
||
392 | 2 | public function reset() |
|
403 | |||
404 | /** |
||
405 | * Defined by Countable interface |
||
406 | * |
||
407 | * @return int |
||
408 | */ |
||
409 | 14 | public function count() |
|
413 | |||
414 | /** |
||
415 | * Defined by Iterator interface |
||
416 | * |
||
417 | * @return mixed |
||
418 | */ |
||
419 | 32 | public function current() |
|
423 | |||
424 | /** |
||
425 | * Defined by Iterator interface |
||
426 | * |
||
427 | * @return mixed |
||
428 | */ |
||
429 | 32 | public function key() |
|
433 | |||
434 | /** |
||
435 | * Defined by Iterator interface |
||
436 | * |
||
437 | * @return void |
||
438 | */ |
||
439 | 32 | public function next() |
|
444 | |||
445 | /** |
||
446 | * Defined by Iterator interface |
||
447 | * |
||
448 | * @return boolean |
||
449 | */ |
||
450 | 32 | public function valid() |
|
454 | |||
455 | /** |
||
456 | * Defined by RecursiveIterator interface |
||
457 | * |
||
458 | * @return boolean |
||
459 | */ |
||
460 | 14 | public function hasChildren() |
|
464 | |||
465 | /** |
||
466 | * Defined by RecursiveIterator interface |
||
467 | * |
||
468 | * @return \RecursiveIterator |
||
469 | */ |
||
470 | 7 | public function getChildren() |
|
474 | |||
475 | /** |
||
476 | * Defined by ArrayAccess interface |
||
477 | * |
||
478 | * @param mixed $offset |
||
479 | * @return boolean |
||
480 | */ |
||
481 | 2 | public function offsetExists($offset) |
|
485 | |||
486 | /** |
||
487 | * Defined by ArrayAccess interface |
||
488 | * |
||
489 | * @param mixed $offset |
||
490 | * @return mixed |
||
491 | */ |
||
492 | 18 | public function offsetGet($offset) |
|
496 | |||
497 | /** |
||
498 | * Defined by ArrayAccess interface |
||
499 | * |
||
500 | * @param mixed $offset |
||
501 | * @param mixed $value |
||
502 | * @return void |
||
503 | * @throws \RuntimeException |
||
504 | */ |
||
505 | 4 | public function offsetSet($offset, $value) |
|
509 | |||
510 | /** |
||
511 | * Defined by ArrayAccess interface |
||
512 | * |
||
513 | * @param mixed $offset |
||
514 | * @return void |
||
515 | */ |
||
516 | 2 | public function offsetUnset($offset) |
|
520 | |||
521 | /** |
||
522 | * Support unset() overloading |
||
523 | * Unset of structure property in a term of removing it from structure is not allowed, |
||
524 | * so unset() just reset field's value. |
||
525 | * |
||
526 | * @param string $name |
||
527 | * @return void |
||
528 | */ |
||
529 | 4 | public function __unset($name) |
|
537 | |||
538 | /** |
||
539 | * Implementation of Serializable interface |
||
540 | * |
||
541 | * @return string |
||
542 | * @throws \Flying\Struct\Exception |
||
543 | */ |
||
544 | 14 | public function serialize() |
|
551 | |||
552 | /** |
||
553 | * Get structure contents as associative array |
||
554 | * |
||
555 | * @return array |
||
556 | */ |
||
557 | 74 | public function toArray() |
|
569 | |||
570 | /** |
||
571 | * Implementation of Serializable interface |
||
572 | * |
||
573 | * @param string $serialized Serialized object data |
||
574 | * @return void |
||
575 | * @throws \InvalidArgumentException |
||
576 | * @throws \RuntimeException |
||
577 | * @throws \Flying\Struct\Exception |
||
578 | */ |
||
579 | 14 | public function unserialize($serialized) |
|
597 | |||
598 | /** |
||
599 | * Attempt to convert given structure contents to array |
||
600 | * |
||
601 | * @param mixed $contents Value to convert to array |
||
602 | * @return array |
||
603 | */ |
||
604 | 10 | protected function convertToArray($contents) |
|
624 | |||
625 | /** |
||
626 | * Initialize list of configuration options |
||
627 | * |
||
628 | * @return void |
||
629 | * @throws \RuntimeException |
||
630 | * @throws \InvalidArgumentException |
||
631 | */ |
||
632 | 1 | protected function initConfig() |
|
643 | |||
644 | /** |
||
645 | * Perform "lazy initialization" of configuration option with given name |
||
646 | * |
||
647 | * @param string $name Configuration option name |
||
648 | * @return mixed |
||
649 | * @throws \RuntimeException |
||
650 | */ |
||
651 | 161 | protected function lazyConfigInit($name) |
|
666 | |||
667 | /** |
||
668 | * Check that given value of configuration option is valid |
||
669 | * |
||
670 | * @param string $name Configuration option name |
||
671 | * @param mixed $value Option value (passed by reference) |
||
672 | * @throws \InvalidArgumentException |
||
673 | * @return boolean |
||
674 | */ |
||
675 | 163 | protected function validateConfig($name, &$value) |
|
717 | |||
718 | /** |
||
719 | * {@inheritdoc} |
||
720 | */ |
||
721 | 73 | protected function onConfigChange($name, $value) |
|
733 | } |
||
734 |
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.