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 AbstractDbEntity 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 AbstractDbEntity, and based on these observations, apply Extract Interface, too.
1 | <?php |
||
19 | abstract class AbstractDbEntity implements \Serializable |
||
20 | { |
||
21 | /** |
||
22 | * The database table name (meant to be overridden). |
||
23 | * |
||
24 | * @var string |
||
25 | */ |
||
26 | protected static $dbTableName; |
||
27 | |||
28 | /** |
||
29 | * Entity's database properties and their attributes (meant to be overridden). |
||
30 | * Example format: |
||
31 | * |
||
32 | * $dbProperties = [ |
||
33 | * 'productId' => ['type' => 'int'], |
||
34 | * 'otherId' => ['type' => 'int', 'required' => true,], |
||
35 | * 'name' => ['type' => 'string', 'maxLength' => 10, 'required' => true, 'default' => 'Some name'], |
||
36 | * ]; |
||
37 | * |
||
38 | * 'type' => 'int' Corresponding PHP type (required). |
||
39 | * 'required' => true The value have to be set (not '', null, false) |
||
40 | * 'nonEmpty' => true The value should not be empty ('', 0, null) |
||
41 | * |
||
42 | * Properties correspond to database table's columns but words are |
||
43 | * camel cased instead of separated with underscore (_) as in the database. |
||
44 | * |
||
45 | * @var array |
||
46 | */ |
||
47 | protected static $dbProperties = []; |
||
48 | |||
49 | /** |
||
50 | * Object database field name that is used for primary key (meant to be overridden). |
||
51 | * Should be camel cased as it maps to the dbFields array. |
||
52 | * |
||
53 | * @var string|array |
||
54 | */ |
||
55 | protected static $primaryDbPropertyKey; |
||
56 | |||
57 | /** |
||
58 | * @var array |
||
59 | */ |
||
60 | private static $cachedDefaultDbData = []; |
||
61 | |||
62 | /** |
||
63 | * @var array |
||
64 | */ |
||
65 | private static $cachedDbPropertyNames; |
||
66 | |||
67 | /** |
||
68 | * @var array |
||
69 | */ |
||
70 | private static $cachedDbFieldNames; |
||
71 | |||
72 | /** |
||
73 | * @var array |
||
74 | */ |
||
75 | private static $typeDefaults = [ |
||
76 | 'string' => '', |
||
77 | 'int' => 0, |
||
78 | 'float' => 0.0, |
||
79 | 'bool' => false, |
||
80 | 'dateTime' => null, |
||
81 | ]; |
||
82 | |||
83 | /** |
||
84 | * Database row data with field names and their values. |
||
85 | * |
||
86 | * @var array |
||
87 | */ |
||
88 | private $dbData = []; |
||
89 | |||
90 | /** |
||
91 | * The primary key value currently set in database. This |
||
92 | * can be different from the value in $dbData if the primary value |
||
93 | * is changed. |
||
94 | * |
||
95 | * @var mixed |
||
96 | */ |
||
97 | private $primaryDbValue; |
||
98 | |||
99 | /** |
||
100 | * Database fields that has had their value modified since init/load. |
||
101 | * |
||
102 | * @var array |
||
103 | */ |
||
104 | private $modifiedDbProperties = []; |
||
105 | |||
106 | /** |
||
107 | * @var bool |
||
108 | */ |
||
109 | private $deleteFromDbOnSave = false; |
||
110 | |||
111 | /** |
||
112 | * @var bool |
||
113 | */ |
||
114 | private $deleted = false; |
||
115 | |||
116 | /** |
||
117 | * @var bool |
||
118 | */ |
||
119 | private $forceDbInsertOnSave = false; |
||
120 | |||
121 | /** |
||
122 | * Constructor. |
||
123 | * |
||
124 | * @param mixed $primaryDbValueOrRowData |
||
125 | */ |
||
126 | 73 | public function __construct($primaryDbValueOrRowData = null) |
|
138 | |||
139 | /** |
||
140 | * Make sure that class has all necessary static properties set. |
||
141 | */ |
||
142 | 73 | private static function checkStaticProperties() |
|
159 | |||
160 | /** |
||
161 | * @param mixed $primaryDbValueOrRowData |
||
162 | */ |
||
163 | 13 | public function setPrimaryDbValueOrRowData($primaryDbValueOrRowData = null) |
|
173 | |||
174 | /** |
||
175 | * Get all default database values. |
||
176 | * |
||
177 | * @return array |
||
178 | */ |
||
179 | 72 | public function getDefaultDbData() |
|
191 | |||
192 | /** |
||
193 | * Get default db value (can be overridden if non static default values need to be used). |
||
194 | * |
||
195 | * @param string $propertyName |
||
196 | * @return mixed |
||
197 | */ |
||
198 | 17 | public function getDefaultDbPropertyValue($propertyName) |
|
210 | |||
211 | /** |
||
212 | * The primary key value currently set in database. |
||
213 | * |
||
214 | * This can be different from the value/values in $dbData |
||
215 | * if a primary value is changed. |
||
216 | * |
||
217 | * @return mixed |
||
218 | */ |
||
219 | 26 | public function getPrimaryDbValue() |
|
223 | |||
224 | /** |
||
225 | * @param mixed $primaryDbValue |
||
226 | */ |
||
227 | 24 | public function setPrimaryDbValue($primaryDbValue) |
|
234 | |||
235 | /** |
||
236 | * @param mixed $primaryDbValue |
||
237 | */ |
||
238 | 23 | protected function setDbDataPrimaryValue($primaryDbValue) |
|
250 | |||
251 | /** |
||
252 | * Update primary database value with data from set database data. |
||
253 | */ |
||
254 | 12 | public function updatePrimaryDbValueFromDbData() |
|
262 | |||
263 | /** |
||
264 | * @return mixed|array |
||
265 | */ |
||
266 | 12 | View Code Duplication | protected function getDbDataPrimaryValue() |
279 | |||
280 | /** |
||
281 | * @return mixed|array |
||
282 | */ |
||
283 | 12 | View Code Duplication | private function getDbDataPrimaryDefaultValue() |
296 | |||
297 | /** |
||
298 | * @return bool |
||
299 | */ |
||
300 | 10 | public function isNewDbEntity() |
|
311 | |||
312 | /** |
||
313 | * @return bool |
||
314 | */ |
||
315 | 9 | public function shouldInsertOnDbSave() |
|
320 | |||
321 | /** |
||
322 | * Set a row field value. |
||
323 | * |
||
324 | * @param string $property |
||
325 | * @param mixed $value |
||
326 | * @param bool $setAsModified |
||
327 | * @param bool $force |
||
328 | */ |
||
329 | 32 | protected function setDbValue($property, $value, $setAsModified = true, $force = false) |
|
341 | |||
342 | /** |
||
343 | * @param string $property |
||
344 | * @param mixed $value |
||
345 | * @return mixed |
||
346 | */ |
||
347 | 44 | private function getValueWithPropertyType(string $property, $value) |
|
372 | |||
373 | /** |
||
374 | * @param string $property |
||
375 | * @param mixed $value |
||
376 | * @return mixed |
||
377 | */ |
||
378 | 24 | private function getPrimaryDbValueWithPropertyType($primaryDbValue) |
|
406 | |||
407 | /** |
||
408 | * @param string $value |
||
409 | * @return \DateTime|\Carbon\Carbon|null |
||
410 | */ |
||
411 | 1 | protected function createDateTimeDbValue($value) |
|
422 | |||
423 | /** |
||
424 | * Get a database field value. |
||
425 | * |
||
426 | * @param string $property |
||
427 | * @return mixed |
||
428 | */ |
||
429 | 19 | protected function getDbValue($property) |
|
433 | |||
434 | /** |
||
435 | * Get raw (with underscore as word separator as it is formatted in database) |
||
436 | * field name from a object field property name (camelcased). |
||
437 | * |
||
438 | * @param string $propertyName |
||
439 | * @return string |
||
440 | */ |
||
441 | 24 | public static function getDbFieldName($propertyName) |
|
449 | |||
450 | /** |
||
451 | * Get object field property name (camelCased) from database field name (underscore separated). |
||
452 | * |
||
453 | * @param string $dbFieldName |
||
454 | * @return string |
||
455 | */ |
||
456 | 9 | public static function getDbPropertyName($dbFieldName) |
|
464 | |||
465 | /** |
||
466 | * @return bool |
||
467 | */ |
||
468 | 6 | public function hasModifiedDbProperties() |
|
472 | |||
473 | /** |
||
474 | * @param string $property |
||
475 | * @return bool |
||
476 | */ |
||
477 | 19 | public function isDbPropertyModified($property) |
|
481 | |||
482 | /** |
||
483 | * @return array |
||
484 | */ |
||
485 | 6 | public function getModifiedDbData() |
|
489 | |||
490 | /** |
||
491 | * @return array |
||
492 | */ |
||
493 | 1 | public function getModifiedDbProperties() |
|
497 | |||
498 | /** |
||
499 | * @param string $property |
||
500 | */ |
||
501 | 1 | public function clearModifiedDbProperty($property) |
|
507 | |||
508 | 4 | public function clearModifiedDbProperties() |
|
512 | |||
513 | 1 | public function setAllDbPropertiesAsModified() |
|
517 | |||
518 | /** |
||
519 | * Magic method used to automate getters & setters for row data. |
||
520 | * |
||
521 | * @param string $name |
||
522 | * @param array $arguments |
||
523 | * @return mixed |
||
524 | */ |
||
525 | 24 | public function __call($name, array $arguments = []) |
|
542 | |||
543 | /** |
||
544 | * Set database fields' data. |
||
545 | * |
||
546 | * @param array $data |
||
547 | */ |
||
548 | 3 | public function setDbData(array $data) |
|
556 | |||
557 | /** |
||
558 | * Set db data from raw database row data with field names in database format. |
||
559 | * |
||
560 | * @param array $rowData |
||
561 | */ |
||
562 | 9 | public function setDbDataFromRow(array $rowData) |
|
584 | |||
585 | /** |
||
586 | * @return mixed |
||
587 | */ |
||
588 | protected function getPrimaryDbValueFromRow(array $rowData) |
||
604 | |||
605 | /** |
||
606 | * @return array |
||
607 | */ |
||
608 | 11 | public function getDbData() |
|
612 | |||
613 | /** |
||
614 | * @return array |
||
615 | */ |
||
616 | 1 | public function getDbRowData() |
|
626 | |||
627 | /** |
||
628 | * @return array |
||
629 | */ |
||
630 | 2 | public function getDbDataWithoutPrimary() |
|
644 | |||
645 | /** |
||
646 | * @param bool $deleteFromDbOnSave |
||
647 | */ |
||
648 | 3 | public function setDeleteFromDbOnSave($deleteFromDbOnSave = true) |
|
652 | |||
653 | /** |
||
654 | * @return bool |
||
655 | */ |
||
656 | 11 | public function shouldBeDeletedFromDbOnSave() |
|
660 | |||
661 | /** |
||
662 | * @return bool |
||
663 | */ |
||
664 | 1 | public function isDeleted() |
|
668 | |||
669 | /** |
||
670 | * @param bool $deleted |
||
671 | */ |
||
672 | 3 | public function setDeleted($deleted = true) |
|
676 | |||
677 | /** |
||
678 | * @param bool $forceDbInsertOnSave |
||
679 | */ |
||
680 | 5 | public function setForceDbInsertOnSave($forceDbInsertOnSave) |
|
684 | |||
685 | /** |
||
686 | * @return bool |
||
687 | */ |
||
688 | 5 | public function shouldForceDbInsertOnSave() |
|
692 | |||
693 | /** |
||
694 | * @return array |
||
695 | */ |
||
696 | 1 | public static function getDbProperties() |
|
700 | |||
701 | /** |
||
702 | * @param string $propertyName |
||
703 | * @return int|null |
||
704 | */ |
||
705 | 6 | public static function getDbPropertyMaxLength($propertyName) |
|
711 | |||
712 | /** |
||
713 | * @param string $propertyName |
||
714 | * @return bool |
||
715 | */ |
||
716 | 3 | public static function getDbPropertyRequired($propertyName) |
|
722 | |||
723 | /** |
||
724 | * @param string $propertyName |
||
725 | * @return bool |
||
726 | */ |
||
727 | 6 | public static function getDbPropertyNonEmpty($propertyName) |
|
733 | |||
734 | /** |
||
735 | * @return string|array |
||
736 | */ |
||
737 | 16 | public static function getPrimaryDbPropertyKey() |
|
741 | |||
742 | /** |
||
743 | * @return string|array |
||
744 | */ |
||
745 | 12 | public static function getPrimaryDbFieldKey() |
|
760 | |||
761 | /** |
||
762 | * Return array with db property names. |
||
763 | * |
||
764 | * @param array $exclude |
||
765 | * @return array |
||
766 | */ |
||
767 | 1 | public static function getDbPropertyNames(array $exclude = []) |
|
773 | |||
774 | /** |
||
775 | * Return array with raw db field names. |
||
776 | * |
||
777 | * @param array $exclude |
||
778 | * @return array |
||
779 | */ |
||
780 | 3 | public static function getDbFieldNames(array $exclude = []) |
|
789 | |||
790 | |||
791 | /** |
||
792 | * Get raw database field names prefixed (id, name becomes t.id, t.name etc.). |
||
793 | * |
||
794 | * @param string $dbTableAlias |
||
795 | * @param array $exclude |
||
796 | * @return array |
||
797 | */ |
||
798 | 1 | public static function getPrefixedDbFieldNames($dbTableAlias, array $exclude = []) |
|
802 | |||
803 | /** |
||
804 | * Get database columns transformed from e.g. "productId, date" to "p.product_id AS p_product_id, p.date AS p_date". |
||
805 | * |
||
806 | * @param string $dbTableAlias |
||
807 | * @param array $exclude |
||
808 | * @return array |
||
809 | */ |
||
810 | 1 | public static function getAliasedDbFieldNames($dbTableAlias, array $exclude = []) |
|
821 | |||
822 | /** |
||
823 | * Filters a full db item array by it's table alias and the strips the table alias. |
||
824 | * |
||
825 | * @param array $rowData |
||
826 | * @param string $dbTableAlias |
||
827 | * @param bool $skipStrip For cases when you want to filter only (no stripping) |
||
828 | * @return array |
||
829 | */ |
||
830 | 1 | public static function filterStripDbRowData(array $rowData, $dbTableAlias, $skipStrip = false) |
|
844 | |||
845 | /** |
||
846 | * @return string |
||
847 | */ |
||
848 | 8 | public static function getDbTableName() |
|
852 | |||
853 | /** |
||
854 | * Method to handle the serialization of this object. |
||
855 | * |
||
856 | * Implementation of Serializable interface. If descendant private properties |
||
857 | * should be serialized, they need to be visible to this parent (i.e. not private). |
||
858 | * |
||
859 | * @return string |
||
860 | */ |
||
861 | 2 | public function serialize() |
|
865 | |||
866 | /** |
||
867 | * Method to handle the unserialization of this object. |
||
868 | * |
||
869 | * Implementation of Serializable interface. If descendant private properties |
||
870 | * should be unserialized, they need to be visible to this parent (i.e. not private). |
||
871 | * |
||
872 | * @param string $serializedObject |
||
873 | */ |
||
874 | 1 | public function unserialize($serializedObject) |
|
882 | |||
883 | /** |
||
884 | * Merges other object's modified database data into this object. |
||
885 | * |
||
886 | * @param AbstractDbEntity $otherEntity |
||
887 | */ |
||
888 | 1 | public function mergeWith(AbstractDbEntity $otherEntity) |
|
893 | } |
||
894 |
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.