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 |
||
22 | abstract class AbstractDbEntity implements \Serializable |
||
23 | { |
||
24 | /** |
||
25 | * The database table name (meant to be overridden). |
||
26 | * |
||
27 | * @var string |
||
28 | */ |
||
29 | protected static $dbTableName; |
||
30 | |||
31 | /** |
||
32 | * Entity's database properties and their attributes (meant to be overridden). |
||
33 | * Example format: |
||
34 | * |
||
35 | * $dbProperties = [ |
||
36 | * 'productId' => ['type' => 'int'], |
||
37 | * 'otherId' => ['type' => 'int', 'required' => true, 'validate' => false], |
||
38 | * 'name' => ['type' => 'string', 'maxLength' => 10, 'required' => true, 'default' => 'Some name'], |
||
39 | * ]; |
||
40 | * |
||
41 | * 'type' => 'int' Corresponding PHP type (required). |
||
42 | * 'validate' => false Turn off data validation, for example on required key fields that are set internally. |
||
43 | * 'required' => true The value have to be set (not '', null, false) |
||
44 | * 'nonEmpty' => true The value should not be empty ('', 0, null) |
||
45 | * |
||
46 | * Properties correspond to database table's columns but words are |
||
47 | * camel cased instead of separated with underscore (_) as in the database. |
||
48 | * |
||
49 | * @var array |
||
50 | */ |
||
51 | protected static $dbProperties = []; |
||
52 | |||
53 | /** |
||
54 | * Object database field name that is used for primary key (meant to be overridden). |
||
55 | * Should be camel cased as it maps to the dbFields array. |
||
56 | * |
||
57 | * @var string|array |
||
58 | */ |
||
59 | protected static $primaryDbPropertyKey; |
||
60 | |||
61 | /** |
||
62 | * @var array |
||
63 | */ |
||
64 | private static $cachedDefaultDbData = []; |
||
65 | |||
66 | /** |
||
67 | * @var array |
||
68 | */ |
||
69 | private static $cachedDbPropertyNames; |
||
70 | |||
71 | /** |
||
72 | * @var array |
||
73 | */ |
||
74 | private static $cachedDbFieldNames; |
||
75 | |||
76 | /** |
||
77 | * @var array |
||
78 | */ |
||
79 | private static $typeDefaults = [ |
||
80 | 'string' => '', |
||
81 | 'int' => 0, |
||
82 | 'float' => 0.0, |
||
83 | 'bool' => false, |
||
84 | ]; |
||
85 | |||
86 | /** |
||
87 | * Database row data with field names and their values. |
||
88 | * |
||
89 | * @var array |
||
90 | */ |
||
91 | private $dbData = []; |
||
92 | |||
93 | /** |
||
94 | * Database fields that has had their value modified since init/load. |
||
95 | * |
||
96 | * @var array |
||
97 | */ |
||
98 | private $modifiedDbProperties = []; |
||
99 | |||
100 | /** |
||
101 | * @var bool |
||
102 | */ |
||
103 | private $deleteFromDbOnSave = false; |
||
104 | |||
105 | /** |
||
106 | * @var bool |
||
107 | */ |
||
108 | private $deleted = false; |
||
109 | |||
110 | /** |
||
111 | * @var bool |
||
112 | */ |
||
113 | private $forceDbInsertOnSave = false; |
||
114 | |||
115 | /** |
||
116 | * Constructor. |
||
117 | * |
||
118 | * @param mixed $primaryDbValueOrRowData |
||
119 | */ |
||
120 | 64 | public function __construct($primaryDbValueOrRowData = null) |
|
132 | |||
133 | /** |
||
134 | * Make sure that class has all necessary static properties set. |
||
135 | */ |
||
136 | 64 | private static function checkStaticProperties() |
|
153 | |||
154 | /** |
||
155 | * @param mixed $primaryDbValueOrRowData |
||
156 | */ |
||
157 | 13 | public function setPrimaryDbValueOrRowData($primaryDbValueOrRowData = null) |
|
166 | |||
167 | /** |
||
168 | * Get all default database values. |
||
169 | * |
||
170 | * @return array |
||
171 | */ |
||
172 | 63 | public function getDefaultDbData() |
|
184 | |||
185 | /** |
||
186 | * Get default db value (can be overridden if non static default values need to be used). |
||
187 | * |
||
188 | * @param string $propertyName |
||
189 | * @return mixed |
||
190 | */ |
||
191 | 6 | public function getDefaultDbPropertyValue($propertyName) |
|
203 | |||
204 | /** |
||
205 | * @return mixed |
||
206 | */ |
||
207 | 21 | public function getPrimaryDbValue() |
|
220 | |||
221 | /** |
||
222 | * @param mixed $primaryDbValue |
||
223 | */ |
||
224 | 17 | public function setPrimaryDbValue($primaryDbValue) |
|
240 | |||
241 | /** |
||
242 | * @return bool |
||
243 | */ |
||
244 | 9 | public function isNewDbEntity() |
|
255 | |||
256 | /** |
||
257 | * @return bool |
||
258 | */ |
||
259 | 8 | public function shouldInsertOnDbSave() |
|
264 | |||
265 | /** |
||
266 | * Set a row field value. |
||
267 | * |
||
268 | * @param string $property |
||
269 | * @param mixed $value |
||
270 | * @param bool $setAsModified |
||
271 | */ |
||
272 | 23 | protected function setDbValue($property, $value, $setAsModified = true) |
|
297 | |||
298 | /** |
||
299 | * Get a database field value. |
||
300 | * |
||
301 | * @param string $property |
||
302 | * @return mixed |
||
303 | */ |
||
304 | 8 | protected function getDbValue($property) |
|
308 | |||
309 | /** |
||
310 | * Get raw (with underscore as word separator as it is formatted in database) |
||
311 | * field name from a object field property name (camelcased). |
||
312 | * |
||
313 | * @param string $propertyName |
||
314 | * @return string |
||
315 | */ |
||
316 | 18 | public static function getDbFieldName($propertyName) |
|
324 | |||
325 | /** |
||
326 | * Get object field property name (camelCased) from database field name (underscore separated). |
||
327 | * |
||
328 | * @param string $dbFieldName |
||
329 | * @return string |
||
330 | */ |
||
331 | 7 | public static function getDbPropertyName($dbFieldName) |
|
339 | |||
340 | /** |
||
341 | * @return bool |
||
342 | */ |
||
343 | 5 | public function hasModifiedDbProperties() |
|
344 | { |
||
345 | 5 | return !empty($this->modifiedDbProperties); |
|
346 | } |
||
347 | |||
348 | /** |
||
349 | * @param string $property |
||
350 | * @return bool |
||
351 | */ |
||
352 | 15 | public function isDbPropertyModified($property) |
|
353 | { |
||
354 | 15 | return in_array($property, $this->modifiedDbProperties); |
|
355 | } |
||
356 | |||
357 | /** |
||
358 | * @return array |
||
359 | */ |
||
360 | 5 | public function getModifiedDbData() |
|
364 | |||
365 | /** |
||
366 | * @param string $property |
||
367 | */ |
||
368 | public function clearModifiedDbProperty($property) |
||
374 | |||
375 | /** |
||
376 | */ |
||
377 | 4 | public function clearModifiedDbProperties() |
|
381 | |||
382 | /** |
||
383 | * Magic method used to automate getters & setters for row data. |
||
384 | * |
||
385 | * @param string $name |
||
386 | * @param array $arguments |
||
387 | * @return mixed |
||
388 | */ |
||
389 | 17 | public function __call($name, array $arguments = []) |
|
406 | |||
407 | /** |
||
408 | * Set database fields' data. |
||
409 | * |
||
410 | * @param array $data |
||
411 | */ |
||
412 | 3 | public function setDbData(array $data) |
|
420 | |||
421 | /** |
||
422 | * Set db data from raw database row data with field names in database format. |
||
423 | * |
||
424 | * @param array $rowData |
||
425 | */ |
||
426 | 7 | public function setDbDataFromRow(array $rowData) |
|
446 | |||
447 | /** |
||
448 | * @return array |
||
449 | */ |
||
450 | 8 | public function getDbData() |
|
454 | |||
455 | /** |
||
456 | * @return array |
||
457 | */ |
||
458 | 2 | public function getDbDataWithoutPrimary() |
|
472 | |||
473 | /** |
||
474 | * @param bool $deleteFromDbOnSave |
||
475 | */ |
||
476 | 3 | public function setDeleteFromDbOnSave($deleteFromDbOnSave = true) |
|
480 | |||
481 | /** |
||
482 | * @return bool |
||
483 | */ |
||
484 | 10 | public function shouldBeDeletedFromDbOnSave() |
|
488 | |||
489 | /** |
||
490 | * @return bool; |
||
491 | */ |
||
492 | 1 | public function isDeleted() |
|
496 | |||
497 | /** |
||
498 | * @param bool $deleted |
||
499 | */ |
||
500 | 3 | public function setDeleted($deleted = true) |
|
504 | |||
505 | /** |
||
506 | * @param bool $forceDbInsertOnSave |
||
507 | */ |
||
508 | 5 | public function setForceDbInsertOnSave($forceDbInsertOnSave) |
|
512 | |||
513 | /** |
||
514 | * @return bool |
||
515 | */ |
||
516 | 9 | public function shouldForceDbInsertOnSave() |
|
520 | |||
521 | /** |
||
522 | * @param string $propertyName |
||
523 | * @return int|null |
||
524 | */ |
||
525 | 6 | public static function getDbPropertyMaxLength($propertyName) |
|
531 | |||
532 | /** |
||
533 | * @param string $propertyName |
||
534 | * @return bool |
||
535 | */ |
||
536 | 2 | public static function getDbPropertyRequired($propertyName) |
|
542 | |||
543 | /** |
||
544 | * @param string $propertyName |
||
545 | * @return bool |
||
546 | */ |
||
547 | 3 | public static function getDbPropertyNonEmpty($propertyName) |
|
553 | |||
554 | /** |
||
555 | * Get validator for object's database data. |
||
556 | * |
||
557 | * @param ValidatorTranslatorInterface|SymfonyTranslatorInterface|null $translator |
||
558 | * @return Validator |
||
559 | */ |
||
560 | 3 | public function getDbDataValidator($translator = null) |
|
580 | |||
581 | /** |
||
582 | * Validate and (if no error messages) set database data. |
||
583 | * |
||
584 | * @param array $data The data (e.g. from a form post) to be validated and set |
||
585 | * @param ValidatorTranslatorInterface|SymfonyTranslatorInterface|null $translator |
||
586 | * @return array An array with all (if any) of error messages |
||
587 | */ |
||
588 | 2 | public function validateAndSetDbData(array $data, $translator = null) |
|
603 | |||
604 | /** |
||
605 | * @return string|array |
||
606 | */ |
||
607 | 17 | public static function getPrimaryDbPropertyKey() |
|
611 | |||
612 | /** |
||
613 | * @return string|array |
||
614 | */ |
||
615 | 9 | public static function getPrimaryDbFieldKey() |
|
630 | |||
631 | /** |
||
632 | * Return array with db property names. |
||
633 | * |
||
634 | * @param array $exclude |
||
635 | * @return array |
||
636 | */ |
||
637 | 1 | public static function getDbPropertyNames(array $exclude = []) |
|
643 | |||
644 | /** |
||
645 | * Return array with raw db field names. |
||
646 | * |
||
647 | * @param array $exclude |
||
648 | * @return array |
||
649 | */ |
||
650 | 3 | public static function getDbFieldNames(array $exclude = []) |
|
659 | |||
660 | |||
661 | /** |
||
662 | * Get raw database field names prefixed (id, name becomes t.id, t.name etc.). |
||
663 | * |
||
664 | * @param string $dbTableAlias |
||
665 | * @param array $exclude |
||
666 | * @return array |
||
667 | */ |
||
668 | 1 | public static function getPrefixedDbFieldNames($dbTableAlias, array $exclude = []) |
|
672 | |||
673 | /** |
||
674 | * Get database columns transformed from e.g. "productId, date" to "p.product_id AS p_product_id, p.date AS p_date". |
||
675 | * |
||
676 | * @param string $dbTableAlias |
||
677 | * @param array $exclude |
||
678 | * @return array |
||
679 | */ |
||
680 | 1 | public static function getAliasedDbFieldNames($dbTableAlias, array $exclude = []) |
|
691 | |||
692 | /** |
||
693 | * Filters a full db item array by it's table alias and the strips the table alias. |
||
694 | * |
||
695 | * @param array $rowData |
||
696 | * @param string $dbTableAlias |
||
697 | * @param bool $skipStrip For cases when you want to filter only (no stripping) |
||
698 | * @return array |
||
699 | */ |
||
700 | 1 | public static function filterStripDbRowData(array $rowData, $dbTableAlias, $skipStrip = false) |
|
714 | |||
715 | /** |
||
716 | * @return string |
||
717 | */ |
||
718 | 8 | public static function getDbTableName() |
|
722 | |||
723 | /** |
||
724 | * Method to handle the serialization of this object. |
||
725 | * |
||
726 | * Implementation of Serializable interface. If descendant private properties |
||
727 | * should be serialized, they need to be visible to this parent (i.e. not private). |
||
728 | * |
||
729 | * @return string |
||
730 | */ |
||
731 | 2 | public function serialize() |
|
735 | |||
736 | /** |
||
737 | * Method to handle the unserialization of this object. |
||
738 | * |
||
739 | * Implementation of Serializable interface. If descendant private properties |
||
740 | * should be unserialized, they need to be visible to this parent (i.e. not private). |
||
741 | * |
||
742 | * @param string $serializedObject |
||
743 | */ |
||
744 | 1 | public function unserialize($serializedObject) |
|
752 | |||
753 | /** |
||
754 | * Merges other object's modified database data into this object. |
||
755 | * |
||
756 | * @param AbstractDbEntity $otherEntity |
||
757 | */ |
||
758 | 1 | public function mergeWith(AbstractDbEntity $otherEntity) |
|
763 | } |
||
764 |
This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.
Consider making the comparison explicit by using
empty(..)
or! empty(...)
instead.