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 DocumentEntity 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 DocumentEntity, and based on these observations, apply Extract Interface, too.
1 | <?php |
||
45 | abstract class DocumentEntity extends SchematicEntity implements CompositableInterface |
||
46 | { |
||
47 | use SaturateTrait; |
||
48 | |||
49 | /** |
||
50 | * We are going to inherit parent validation rules, this will let spiral translator know about |
||
51 | * it and merge i18n messages. |
||
52 | * |
||
53 | * @see TranslatorTrait |
||
54 | */ |
||
55 | const I18N_INHERIT_MESSAGES = true; |
||
56 | |||
57 | /** |
||
58 | * Helper constant to identify atomic SET operations. |
||
59 | */ |
||
60 | const ATOMIC_SET = '$set'; |
||
61 | |||
62 | /** |
||
63 | * Tells ODM component that Document class must be resolved using document fields. ODM must |
||
64 | * match fields to every child of this documents and find best match. This is default definition |
||
65 | * behaviour. |
||
66 | * |
||
67 | * Example: |
||
68 | * > Class A: _id, name, address |
||
69 | * > Class B extends A: _id, name, address, email |
||
70 | * < Class B will be used to represent all documents with existed email field. |
||
71 | * |
||
72 | * @see DocumentSchema |
||
73 | */ |
||
74 | const DEFINITION_FIELDS = 1; |
||
75 | |||
76 | /** |
||
77 | * Tells ODM that logical method (defineClass) must be used to define document class. Method |
||
78 | * will receive document fields as input and must return document class name. |
||
79 | * |
||
80 | * Example: |
||
81 | * > Class A: _id, name, type (a) |
||
82 | * > Class B extends A: _id, name, type (b) |
||
83 | * > Class C extends B: _id, name, type (c) |
||
84 | * < Static method in class A (parent) should return A, B or C based on type field value (as |
||
85 | * example). |
||
86 | * |
||
87 | * Attention, ODM will always ask TOP PARENT (in collection) to define class when you loading |
||
88 | * documents from collections. |
||
89 | * |
||
90 | * @see defineClass($fields) |
||
91 | * @see DocumentSchema |
||
92 | */ |
||
93 | const DEFINITION_LOGICAL = 2; |
||
94 | |||
95 | /** |
||
96 | * Indication to ODM component of method to resolve Document class using it's fieldset. This |
||
97 | * constant is required due Document can inherit another Document. |
||
98 | */ |
||
99 | const DEFINITION = self::DEFINITION_FIELDS; |
||
100 | |||
101 | /** |
||
102 | * Automatically convert "_id" to "id" in publicFields() method. |
||
103 | */ |
||
104 | const REMOVE_ID_UNDERSCORE = true; |
||
105 | |||
106 | /** |
||
107 | * Constants used to describe aggregation relations. |
||
108 | * |
||
109 | * Example: |
||
110 | * 'items' => [self::MANY => 'Models\Database\Item', [ |
||
111 | * 'parentID' => 'key::_id' |
||
112 | * ]] |
||
113 | * |
||
114 | * @see odmSchema::$schema |
||
115 | */ |
||
116 | const MANY = 778; |
||
117 | const ONE = 899; |
||
118 | |||
119 | /** |
||
120 | * Errors in nested documents and accessors. |
||
121 | * |
||
122 | * @var array |
||
123 | */ |
||
124 | private $innerErrors = []; |
||
125 | |||
126 | /** |
||
127 | * SolidState will force document to be saved as one big data set without any atomic operations |
||
128 | * (dirty fields). |
||
129 | * |
||
130 | * @var bool |
||
131 | */ |
||
132 | private $solidState = false; |
||
133 | |||
134 | /** |
||
135 | * Document field updates (changed values). |
||
136 | * |
||
137 | * @var array |
||
138 | */ |
||
139 | private $updates = []; |
||
140 | |||
141 | /** |
||
142 | * User specified set of atomic operation to be applied to document on save() call. |
||
143 | * |
||
144 | * @var array |
||
145 | */ |
||
146 | private $atomics = []; |
||
147 | |||
148 | /** |
||
149 | * @invisible |
||
150 | * |
||
151 | * @todo change this concept in future |
||
152 | * @var EntityInterface |
||
153 | */ |
||
154 | protected $parent = null; |
||
155 | |||
156 | /** |
||
157 | * @var ODMInterface|ODM |
||
158 | */ |
||
159 | protected $odm = null; |
||
160 | |||
161 | /** |
||
162 | * Model schema provided by ODM component. |
||
163 | * |
||
164 | * @var array |
||
165 | */ |
||
166 | protected $odmSchema = []; |
||
167 | |||
168 | /** |
||
169 | * {@inheritdoc} |
||
170 | * |
||
171 | * @param array|null $schema |
||
172 | */ |
||
173 | public function __construct( |
||
200 | |||
201 | /** |
||
202 | * Change document solid state. SolidState will force document to be saved as one big data set |
||
203 | * without any atomic operations (dirty fields). |
||
204 | * |
||
205 | * @param bool $solidState |
||
206 | * @param bool $forceUpdate Mark all fields as changed to force update later. |
||
207 | * |
||
208 | * @return $this |
||
209 | */ |
||
210 | public function solidState($solidState, $forceUpdate = false) |
||
220 | |||
221 | /** |
||
222 | * Is document is solid state? |
||
223 | * |
||
224 | * @see solidState() |
||
225 | * |
||
226 | * @return bool |
||
227 | */ |
||
228 | public function isSolid() |
||
232 | |||
233 | /** |
||
234 | * Check if document has parent. |
||
235 | * |
||
236 | * @return bool |
||
237 | */ |
||
238 | public function isEmbedded() |
||
242 | |||
243 | /** |
||
244 | * {@inheritdoc} |
||
245 | */ |
||
246 | public function embed(EntityInterface $parent) |
||
271 | |||
272 | /** |
||
273 | * {@inheritdoc} |
||
274 | */ |
||
275 | public function setValue($data) |
||
279 | |||
280 | /** |
||
281 | * {@inheritdoc} |
||
282 | * |
||
283 | * Must track field updates. |
||
284 | */ |
||
285 | public function setField($name, $value, $filter = true) |
||
302 | |||
303 | /** |
||
304 | * {@inheritdoc} |
||
305 | * |
||
306 | * Will restore default value if presented. |
||
307 | */ |
||
308 | public function __unset($offset) |
||
322 | |||
323 | /** |
||
324 | * Alias for atomic operation $set. Attention, this operation is not identical to setField() |
||
325 | * method, it performs low level operation and can be used only on simple fields. No filters |
||
326 | * will be applied to field! |
||
327 | * |
||
328 | * @param string $field |
||
329 | * @param mixed $value |
||
330 | * |
||
331 | * @return $this |
||
332 | * |
||
333 | * @throws DocumentException |
||
334 | */ |
||
335 | public function set($field, $value) |
||
350 | |||
351 | /** |
||
352 | * Alias for atomic operation $inc. |
||
353 | * |
||
354 | * @param string $field |
||
355 | * @param string $value |
||
356 | * |
||
357 | * @return $this |
||
358 | * |
||
359 | * @throws DocumentException |
||
360 | */ |
||
361 | public function inc($field, $value) |
||
378 | |||
379 | /** |
||
380 | * {@inheritdoc} |
||
381 | */ |
||
382 | public function defaultValue() |
||
386 | |||
387 | /** |
||
388 | * {@inheritdoc} |
||
389 | * |
||
390 | * Include every composition public data into result. |
||
391 | */ |
||
392 | public function publicFields() |
||
433 | |||
434 | /** |
||
435 | * {@inheritdoc} |
||
436 | * |
||
437 | * @param string $field Specific field name to check for updates. |
||
438 | * @param bool $atomicsOnly Check if field has any atomic operation associated with. |
||
439 | */ |
||
440 | public function hasUpdates($field = null, $atomicsOnly = false) |
||
478 | |||
479 | /** |
||
480 | * {@inheritdoc} |
||
481 | */ |
||
482 | public function flushUpdates() |
||
492 | |||
493 | /** |
||
494 | * {@inheritdoc} |
||
495 | */ |
||
496 | public function buildAtomics($container = '') |
||
556 | |||
557 | /** |
||
558 | * @return array |
||
559 | */ |
||
560 | public function __debugInfo() |
||
568 | |||
569 | /** |
||
570 | * {@inheritdoc} |
||
571 | */ |
||
572 | public function isValid() |
||
576 | |||
577 | /** |
||
578 | * {@inheritdoc} |
||
579 | */ |
||
580 | public function getErrors($reset = false) |
||
584 | |||
585 | /** |
||
586 | * {@inheritdoc} |
||
587 | * |
||
588 | * Will validate every CompositableInterface instance. |
||
589 | * |
||
590 | * @param bool $reset |
||
591 | * |
||
592 | * @throws DocumentException |
||
593 | */ |
||
594 | View Code Duplication | protected function validate($reset = false) |
|
616 | |||
617 | /** |
||
618 | * {@inheritdoc} |
||
619 | * |
||
620 | * Accessor options include field type resolved by DocumentSchema. |
||
621 | * |
||
622 | * @throws ODMException |
||
623 | * @throws DefinitionException |
||
624 | */ |
||
625 | protected function createAccessor($accessor, $value) |
||
640 | |||
641 | /** |
||
642 | * {@inheritdoc} |
||
643 | */ |
||
644 | View Code Duplication | protected function container() |
|
652 | |||
653 | /** |
||
654 | * Create document entity using given ODM instance or load parent ODM via shared container. |
||
655 | * |
||
656 | * @see Component::staticContainer() |
||
657 | * |
||
658 | * @param array $fields Model fields to set, will be passed thought filters. |
||
659 | * @param ODMInterface $odm ODMInterface component, global container will be called if not |
||
660 | * instance provided. |
||
661 | * |
||
662 | * @return DocumentEntity |
||
663 | * |
||
664 | * @event created($document) |
||
665 | */ |
||
666 | View Code Duplication | public static function create($fields = [], ODMInterface $odm = null) |
|
678 | |||
679 | /** |
||
680 | * Called by ODM with set of loaded fields. Must return name of appropriate class. |
||
681 | * |
||
682 | * @param array $fields |
||
683 | * @param ODMInterface $odm |
||
684 | * |
||
685 | * @return string |
||
686 | * |
||
687 | * @throws DefinitionException |
||
688 | */ |
||
689 | public static function defineClass(array $fields, ODMInterface $odm) |
||
693 | } |
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.