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 Structure 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 Structure, and based on these observations, apply Extract Interface, too.
1 | <?php |
||
18 | class Structure implements |
||
19 | ArrayableInterface, |
||
20 | \JsonSerializable |
||
21 | { |
||
22 | /** |
||
23 | * @deprecated use self::$schema to define initial data and getters or setters to get or set field's values. |
||
24 | * @var array |
||
25 | */ |
||
26 | protected $_data = array(); |
||
27 | |||
28 | /** |
||
29 | * Document's data |
||
30 | * @var array |
||
31 | */ |
||
32 | private $data = array(); |
||
33 | |||
34 | /** |
||
35 | * Initial document's data |
||
36 | * @var array |
||
37 | */ |
||
38 | protected $schema = array(); |
||
39 | |||
40 | /** |
||
41 | * |
||
42 | * @var array original data. |
||
43 | */ |
||
44 | private $originalData = array(); |
||
45 | |||
46 | /** |
||
47 | * |
||
48 | * @var array modified fields. |
||
49 | */ |
||
50 | private $modifiedFields = array(); |
||
51 | |||
52 | /** |
||
53 | * Name of scenario, used for validating fields |
||
54 | * @var string |
||
55 | */ |
||
56 | private $scenario; |
||
57 | |||
58 | /** |
||
59 | * |
||
60 | * @var array list of namespaces |
||
61 | */ |
||
62 | private $validatorNamespaces = array( |
||
63 | '\Sokil\Mongo\Validator', |
||
64 | ); |
||
65 | |||
66 | |||
67 | /** |
||
68 | * @var array validator errors |
||
69 | */ |
||
70 | private $errors = array(); |
||
71 | |||
72 | /** |
||
73 | * @var array manually added validator errors |
||
74 | */ |
||
75 | private $triggeredErrors = array(); |
||
76 | |||
77 | /** |
||
78 | * @param array|null $data data to initialise structure |
||
79 | * @param bool $notModified define if data set as modified or not |
||
80 | */ |
||
81 | public function __construct( |
||
110 | |||
111 | /** |
||
112 | * Event handler, called before running constructor. |
||
113 | * May be overridden in child classes |
||
114 | */ |
||
115 | public function beforeConstruct() |
||
119 | |||
120 | /** |
||
121 | * Cloning not allowed because cloning of object not clone related aggregates of this object, so |
||
122 | * cloned object has links to original aggregates. This is difficult to handle. |
||
123 | */ |
||
124 | final public function __clone() |
||
128 | |||
129 | /** |
||
130 | * IMPORTANT! Do not use this method |
||
131 | * |
||
132 | * This method allow set data of document in external code. |
||
133 | * e.g. link data of document to GridFS file matadata. |
||
134 | * Modification of document's data will modify external data too. |
||
135 | * Note that also the opposite case also right - modification of external data will |
||
136 | * modify document's data directly, so document may be in unconsisted state. |
||
137 | * |
||
138 | * @param array $data reference to data in external code |
||
139 | * @return Structure |
||
140 | */ |
||
141 | protected function setDataReference(array &$data) |
||
146 | |||
147 | public function __get($name) |
||
152 | |||
153 | /** |
||
154 | * @param $selector |
||
155 | * @return mixed |
||
156 | */ |
||
157 | public function get($selector) |
||
174 | |||
175 | /** |
||
176 | * Get structure object from a document's value |
||
177 | * |
||
178 | * @param string $selector |
||
179 | * @param string|callable $className string class name or closure, which accept data and return string class name |
||
180 | * @return object representation of document with class, passed as argument |
||
181 | * @throws \Sokil\Mongo\Exception |
||
182 | */ |
||
183 | public function getObject($selector, $className = '\Sokil\Mongo\Structure') |
||
203 | |||
204 | /** |
||
205 | * Get list of structure objects from list of values in mongo document |
||
206 | * |
||
207 | * @param string $selector |
||
208 | * @param string|callable $className Structure class name or closure, which accept data and return string class name of Structure |
||
209 | * |
||
210 | * @return object|array representation of document with class, passed as argument |
||
211 | * |
||
212 | * @throws \Sokil\Mongo\Exception |
||
213 | */ |
||
214 | public function getObjectList($selector, $className = '\Sokil\Mongo\Structure') |
||
253 | |||
254 | /** |
||
255 | * Handle setting params through public property |
||
256 | * |
||
257 | * @param string $name |
||
258 | * @param mixed $value |
||
259 | */ |
||
260 | public function __set($name, $value) |
||
264 | |||
265 | /** |
||
266 | * Store value to specified selector in local cache |
||
267 | * |
||
268 | * @param string $selector point-delimited field selector |
||
269 | * @param mixed $value value |
||
270 | * |
||
271 | * @return Structure |
||
272 | * |
||
273 | * @throws Exception |
||
274 | */ |
||
275 | public function set($selector, $value) |
||
321 | |||
322 | /** |
||
323 | * Check if structure has field identified by selector |
||
324 | * |
||
325 | * @param string $selector |
||
326 | * |
||
327 | * @return bool |
||
328 | */ |
||
329 | public function has($selector) |
||
343 | |||
344 | /** |
||
345 | * @param string $name |
||
346 | * |
||
347 | * @return bool |
||
348 | */ |
||
349 | public function __isset($name) |
||
353 | |||
354 | /** |
||
355 | * Normalize variable to be able to store in database |
||
356 | * |
||
357 | * @param mixed $value |
||
358 | * |
||
359 | * @return array |
||
360 | * |
||
361 | * @throws InvalidDocumentException |
||
362 | */ |
||
363 | public static function prepareToStore($value) |
||
405 | |||
406 | public function unsetField($selector) |
||
448 | |||
449 | /** |
||
450 | * If field not exist - set value. |
||
451 | * If field exists and is not array - convert to array and append |
||
452 | * If field -s array - append |
||
453 | * |
||
454 | * @param string $selector |
||
455 | * @param mixed $value |
||
456 | * @return \Sokil\Mongo\Structure |
||
457 | */ |
||
458 | public function append($selector, $value) |
||
472 | |||
473 | /** |
||
474 | * If selector passed, return true if field is modified. |
||
475 | * If selector omitted, return true if document is modified. |
||
476 | * |
||
477 | * @param string|null $selector |
||
478 | * @return bool |
||
479 | */ |
||
480 | public function isModified($selector = null) |
||
498 | |||
499 | /** |
||
500 | * @return array |
||
501 | */ |
||
502 | public function getModifiedFields() |
||
506 | |||
507 | /** |
||
508 | * @return array |
||
509 | */ |
||
510 | public function getOriginalData() |
||
514 | |||
515 | public function toArray() |
||
519 | |||
520 | public function jsonSerialize() |
||
524 | |||
525 | /** |
||
526 | * Recursive function to merge data for Structure::mergeUnmodified() |
||
527 | * |
||
528 | * @param array $target |
||
529 | * @param array $source |
||
530 | */ |
||
531 | private function mergeUnmodifiedPartial(array &$target, array $source) |
||
541 | |||
542 | /** |
||
543 | * Merge array to current structure without setting modification mark |
||
544 | * |
||
545 | * @param array $data |
||
546 | * @return \Sokil\Mongo\Structure |
||
547 | */ |
||
548 | public function mergeUnmodified(array $data) |
||
555 | |||
556 | /** |
||
557 | * Check if array is sequential list |
||
558 | * @param array $array |
||
559 | */ |
||
560 | private function isEmbeddedDocument($array) |
||
564 | |||
565 | /** |
||
566 | * Recursive function to merge data for Structure::merge() |
||
567 | * |
||
568 | * @param array $document |
||
569 | * @param array $updatedDocument |
||
570 | * @param string $prefix |
||
571 | */ |
||
572 | private function mergePartial(array &$document, array $updatedDocument, $prefix = null) |
||
585 | |||
586 | /** |
||
587 | * Merge array to current structure with setting modification mark |
||
588 | * |
||
589 | * @param array $data |
||
590 | * @return \Sokil\Mongo\Structure |
||
591 | */ |
||
592 | public function merge(array $data) |
||
597 | |||
598 | /** |
||
599 | * Replace data of document with passed. |
||
600 | * Document became unmodified |
||
601 | * |
||
602 | * @param array $data new document data |
||
603 | */ |
||
604 | public function replace(array $data) |
||
611 | |||
612 | /** |
||
613 | * Replace modified fields with original |
||
614 | * @return $this |
||
615 | */ |
||
616 | public function reset() |
||
623 | |||
624 | /** |
||
625 | * Apply modified document fields as original |
||
626 | * |
||
627 | * @return $this |
||
628 | */ |
||
629 | protected function apply() |
||
636 | |||
637 | /** |
||
638 | * Validation rules |
||
639 | * @return array |
||
640 | */ |
||
641 | public function rules() |
||
645 | |||
646 | public function setScenario($scenario) |
||
651 | |||
652 | public function getScenario() |
||
656 | |||
657 | public function setNoScenario() |
||
662 | |||
663 | public function isScenario($scenario) |
||
667 | |||
668 | public function hasErrors() |
||
672 | |||
673 | /** |
||
674 | * get list of validation errors |
||
675 | * |
||
676 | * Format: $errors['fieldName']['rule'] = 'message'; |
||
677 | * |
||
678 | * @return array list of validation errors |
||
679 | */ |
||
680 | public function getErrors() |
||
684 | |||
685 | /** |
||
686 | * Add validator error from validator classes and methods. This error |
||
687 | * reset on every re-validation |
||
688 | * |
||
689 | * @param string $fieldName dot-notated field name |
||
690 | * @param string $ruleName name of validation rule |
||
691 | * @param string $message error message |
||
692 | * |
||
693 | * @return Structure |
||
694 | */ |
||
695 | public function addError($fieldName, $ruleName, $message) |
||
701 | |||
702 | /** |
||
703 | * Add errors |
||
704 | * |
||
705 | * @param array $errors |
||
706 | * |
||
707 | * @return Structure |
||
708 | */ |
||
709 | public function addErrors(array $errors) |
||
715 | |||
716 | /** |
||
717 | * Add custom error which not reset after validation |
||
718 | * |
||
719 | * @param string $fieldName |
||
720 | * @param string $ruleName |
||
721 | * @param string $message |
||
722 | * |
||
723 | * @return \Sokil\Mongo\Document |
||
724 | */ |
||
725 | public function triggerError($fieldName, $ruleName, $message) |
||
730 | |||
731 | /** |
||
732 | * Add custom errors |
||
733 | * |
||
734 | * @param array $errors |
||
735 | * @return \Sokil\Mongo\Document |
||
736 | */ |
||
737 | public function triggerErrors(array $errors) |
||
742 | |||
743 | /** |
||
744 | * Clear triggered and validation errors |
||
745 | * @return $this |
||
746 | */ |
||
747 | public function clearErrors() |
||
753 | |||
754 | /** |
||
755 | * Remove custom errors |
||
756 | * |
||
757 | * @return \Sokil\Mongo\Document |
||
758 | */ |
||
759 | public function clearTriggeredErrors() |
||
764 | |||
765 | /** |
||
766 | * Add own namespace of validators |
||
767 | * |
||
768 | * @param string $namespace |
||
769 | * |
||
770 | * @return Structure |
||
771 | */ |
||
772 | public function addValidatorNamespace($namespace) |
||
777 | |||
778 | private function getValidatorClassNameByRuleName($ruleName) |
||
795 | |||
796 | /** |
||
797 | * Check if filled model params is valid |
||
798 | * |
||
799 | * @return boolean |
||
800 | * |
||
801 | * @throws Exception |
||
802 | */ |
||
803 | public function isValid() |
||
848 | } |
||
849 |
Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.
The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.
This check looks for comments that seem to be mostly valid code and reports them.