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 AbstractTDBMObject 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 AbstractTDBMObject, and based on these observations, apply Extract Interface, too.
1 | <?php |
||
32 | abstract class AbstractTDBMObject implements JsonSerializable |
||
33 | { |
||
34 | /** |
||
35 | * The service this object is bound to. |
||
36 | * |
||
37 | * @var TDBMService |
||
38 | */ |
||
39 | protected $tdbmService; |
||
40 | |||
41 | /** |
||
42 | * An array of DbRow, indexed by table name. |
||
43 | * |
||
44 | * @var DbRow[] |
||
45 | */ |
||
46 | protected $dbRows = array(); |
||
47 | |||
48 | /** |
||
49 | * One of TDBMObjectStateEnum::STATE_NEW, TDBMObjectStateEnum::STATE_NOT_LOADED, TDBMObjectStateEnum::STATE_LOADED, TDBMObjectStateEnum::STATE_DELETED. |
||
50 | * $status = TDBMObjectStateEnum::STATE_NEW when a new object is created with DBMObject:getNewObject. |
||
51 | * $status = TDBMObjectStateEnum::STATE_NOT_LOADED when the object has been retrieved with getObject but when no data has been accessed in it yet. |
||
52 | * $status = TDBMObjectStateEnum::STATE_LOADED when the object is cached in memory. |
||
53 | * |
||
54 | * @var string |
||
55 | */ |
||
56 | private $status; |
||
57 | |||
58 | /** |
||
59 | * Array storing beans related via many to many relationships (pivot tables). |
||
60 | * |
||
61 | * @var \SplObjectStorage[] Key: pivot table name, value: SplObjectStorage |
||
62 | */ |
||
63 | private $relationships = []; |
||
64 | |||
65 | /** |
||
66 | * @var bool[] Key: pivot table name, value: whether a query was performed to load the data. |
||
67 | */ |
||
68 | private $loadedRelationships = []; |
||
69 | |||
70 | /** |
||
71 | * Used with $primaryKeys when we want to retrieve an existing object |
||
72 | * and $primaryKeys=[] if we want a new object. |
||
73 | * |
||
74 | * @param string $tableName |
||
75 | * @param array $primaryKeys |
||
76 | * @param TDBMService $tdbmService |
||
77 | * |
||
78 | * @throws TDBMException |
||
79 | * @throws TDBMInvalidOperationException |
||
80 | */ |
||
81 | public function __construct($tableName = null, array $primaryKeys = array(), TDBMService $tdbmService = null) |
||
99 | |||
100 | /** |
||
101 | * Alternative constructor called when data is fetched from database via a SELECT. |
||
102 | * |
||
103 | * @param array $beanData array<table, array<column, value>> |
||
104 | * @param TDBMService $tdbmService |
||
105 | */ |
||
106 | public function _constructFromData(array $beanData, TDBMService $tdbmService) |
||
116 | |||
117 | /** |
||
118 | * Alternative constructor called when bean is lazily loaded. |
||
119 | * |
||
120 | * @param string $tableName |
||
121 | * @param array $primaryKeys |
||
122 | * @param TDBMService $tdbmService |
||
123 | */ |
||
124 | public function _constructLazy($tableName, array $primaryKeys, TDBMService $tdbmService) |
||
132 | |||
133 | public function _attach(TDBMService $tdbmService) |
||
160 | |||
161 | /** |
||
162 | * Sets the state of the TDBM Object |
||
163 | * One of TDBMObjectStateEnum::STATE_NEW, TDBMObjectStateEnum::STATE_NOT_LOADED, TDBMObjectStateEnum::STATE_LOADED, TDBMObjectStateEnum::STATE_DELETED. |
||
164 | * $status = TDBMObjectStateEnum::STATE_NEW when a new object is created with DBMObject:getNewObject. |
||
165 | * $status = TDBMObjectStateEnum::STATE_NOT_LOADED when the object has been retrieved with getObject but when no data has been accessed in it yet. |
||
166 | * $status = TDBMObjectStateEnum::STATE_LOADED when the object is cached in memory. |
||
167 | * |
||
168 | * @param string $state |
||
169 | */ |
||
170 | public function _setStatus($state) |
||
179 | |||
180 | /** |
||
181 | * Checks that $tableName is ok, or returns the only possible table name if "$tableName = null" |
||
182 | * or throws an error. |
||
183 | * |
||
184 | * @param string $tableName |
||
185 | * |
||
186 | * @return string |
||
187 | */ |
||
188 | private function checkTableName($tableName = null) |
||
208 | |||
209 | protected function get($var, $tableName = null) |
||
215 | |||
216 | View Code Duplication | protected function set($var, $value, $tableName = null) |
|
237 | |||
238 | /** |
||
239 | * @param string $foreignKeyName |
||
240 | * @param AbstractTDBMObject $bean |
||
241 | */ |
||
242 | View Code Duplication | protected function setRef($foreignKeyName, AbstractTDBMObject $bean, $tableName = null) |
|
263 | |||
264 | /** |
||
265 | * @param string $foreignKeyName A unique name for this reference |
||
266 | * |
||
267 | * @return AbstractTDBMObject|null |
||
268 | */ |
||
269 | protected function getRef($foreignKeyName, $tableName = null) |
||
275 | |||
276 | /** |
||
277 | * Adds a many to many relationship to this bean. |
||
278 | * |
||
279 | * @param string $pivotTableName |
||
280 | * @param AbstractTDBMObject $remoteBean |
||
281 | */ |
||
282 | protected function addRelationship($pivotTableName, AbstractTDBMObject $remoteBean) |
||
286 | |||
287 | /** |
||
288 | * Returns true if there is a relationship to this bean. |
||
289 | * |
||
290 | * @param string $pivotTableName |
||
291 | * @param AbstractTDBMObject $remoteBean |
||
292 | * |
||
293 | * @return bool |
||
294 | */ |
||
295 | protected function hasRelationship($pivotTableName, AbstractTDBMObject $remoteBean) |
||
307 | |||
308 | /** |
||
309 | * Internal TDBM method. Removes a many to many relationship from this bean. |
||
310 | * |
||
311 | * @param string $pivotTableName |
||
312 | * @param AbstractTDBMObject $remoteBean |
||
313 | */ |
||
314 | public function _removeRelationship($pivotTableName, AbstractTDBMObject $remoteBean) |
||
323 | |||
324 | /** |
||
325 | * Returns the list of objects linked to this bean via $pivotTableName. |
||
326 | * |
||
327 | * @param $pivotTableName |
||
328 | * |
||
329 | * @return \SplObjectStorage |
||
330 | */ |
||
331 | private function retrieveRelationshipsStorage($pivotTableName) |
||
354 | |||
355 | /** |
||
356 | * Internal TDBM method. Returns the list of objects linked to this bean via $pivotTableName. |
||
357 | * |
||
358 | * @param $pivotTableName |
||
359 | * |
||
360 | * @return AbstractTDBMObject[] |
||
361 | */ |
||
362 | public function _getRelationships($pivotTableName) |
||
366 | |||
367 | private function relationshipStorageToArray(\SplObjectStorage $storage) |
||
379 | |||
380 | /** |
||
381 | * Declares a relationship between. |
||
382 | * |
||
383 | * @param string $pivotTableName |
||
384 | * @param AbstractTDBMObject $remoteBean |
||
385 | * @param string $status |
||
386 | */ |
||
387 | private function setRelationship($pivotTableName, AbstractTDBMObject $remoteBean, $status) |
||
398 | |||
399 | /** |
||
400 | * Returns the SplObjectStorage associated to this relationship (creates it if it does not exists). |
||
401 | * |
||
402 | * @param $pivotTableName |
||
403 | * |
||
404 | * @return \SplObjectStorage |
||
405 | */ |
||
406 | private function getRelationshipStorage($pivotTableName) |
||
417 | |||
418 | /** |
||
419 | * Reverts any changes made to the object and resumes it to its DB state. |
||
420 | * This can only be called on objects that come from database and that have not been deleted. |
||
421 | * Otherwise, this will throw an exception. |
||
422 | * |
||
423 | * @throws TDBMException |
||
424 | */ |
||
425 | public function discardChanges() |
||
437 | |||
438 | /** |
||
439 | * Method used internally by TDBM. You should not use it directly. |
||
440 | * This method returns the status of the TDBMObject. |
||
441 | * This is one of TDBMObjectStateEnum::STATE_NEW, TDBMObjectStateEnum::STATE_NOT_LOADED, TDBMObjectStateEnum::STATE_LOADED, TDBMObjectStateEnum::STATE_DELETED. |
||
442 | * $status = TDBMObjectStateEnum::STATE_NEW when a new object is created with DBMObject:getNewObject. |
||
443 | * $status = TDBMObjectStateEnum::STATE_NOT_LOADED when the object has been retrieved with getObject but when no data has been accessed in it yet. |
||
444 | * $status = TDBMObjectStateEnum::STATE_LOADED when the object is cached in memory. |
||
445 | * |
||
446 | * @return string |
||
447 | */ |
||
448 | public function _getStatus() |
||
452 | |||
453 | /** |
||
454 | * Override the native php clone function for TDBMObjects. |
||
455 | */ |
||
456 | public function __clone() |
||
486 | |||
487 | /** |
||
488 | * Returns raw database rows. |
||
489 | * |
||
490 | * @return DbRow[] Key: table name, Value: DbRow object |
||
491 | */ |
||
492 | public function _getDbRows() |
||
496 | |||
497 | private function registerTable($tableName) |
||
515 | |||
516 | /** |
||
517 | * Internal function: return the list of relationships. |
||
518 | * |
||
519 | * @return \SplObjectStorage[] |
||
520 | */ |
||
521 | public function _getCachedRelationships() |
||
525 | } |
||
526 |
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.