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 AbstractActiveRecord 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 AbstractActiveRecord, and based on these observations, apply Extract Interface, too.
1 | <?php |
||
19 | abstract class AbstractActiveRecord implements ActiveRecordInterface |
||
20 | { |
||
21 | const COLUMN_NAME_ID = 'id'; |
||
22 | const COLUMN_TYPE_ID = 'INT UNSIGNED'; |
||
23 | |||
24 | /** @var \PDO The PDO object. */ |
||
25 | protected $pdo; |
||
26 | |||
27 | /** @var null|int The ID. */ |
||
28 | private $id; |
||
29 | |||
30 | /** @var array A map of column name to functions that hook the insert function */ |
||
31 | protected $registeredCreateHooks; |
||
32 | |||
33 | /** @var array A map of column name to functions that hook the read function */ |
||
34 | protected $registeredReadHooks; |
||
35 | |||
36 | /** @var array A map of column name to functions that hook the update function */ |
||
37 | protected $registeredUpdateHooks; |
||
38 | |||
39 | /** @var array A map of column name to functions that hook the update function */ |
||
40 | protected $registeredDeleteHooks; |
||
41 | |||
42 | /** @var array A map of column name to functions that hook the search function */ |
||
43 | protected $registeredSearchHooks; |
||
44 | |||
45 | /** @var array A list of table column definitions */ |
||
46 | protected $tableDefinition; |
||
47 | |||
48 | /** |
||
49 | * Construct an abstract active record with the given PDO. |
||
50 | * |
||
51 | * @param \PDO $pdo |
||
52 | */ |
||
53 | 71 | public function __construct(\PDO $pdo) |
|
81 | |||
82 | 28 | private function checkHookConstraints($columnName, $hookMap) |
|
97 | |||
98 | /** |
||
99 | * Register a new hook for a specific column that gets called before execution of the create() method |
||
100 | * Only one hook per column can be registered at a time |
||
101 | * @param string $columnName The name of the column that is registered. |
||
102 | * @param string|callable $fn Either a callable, or the name of a method on the inheriting object. |
||
103 | */ |
||
104 | 6 | View Code Duplication | public function registerCreateHook($columnName, $fn) |
116 | |||
117 | /** |
||
118 | * Register a new hook for a specific column that gets called before execution of the read() method |
||
119 | * Only one hook per column can be registered at a time |
||
120 | * @param string $columnName The name of the column that is registered. |
||
121 | * @param string|callable $fn Either a callable, or the name of a method on the inheriting object. |
||
122 | */ |
||
123 | 3 | View Code Duplication | public function registerReadHook($columnName, $fn) |
135 | |||
136 | |||
137 | |||
138 | |||
139 | /** |
||
140 | * Register a new hook for a specific column that gets called before execution of the update() method |
||
141 | * Only one hook per column can be registered at a time |
||
142 | * @param string $columnName The name of the column that is registered. |
||
143 | * @param string|callable $fn Either a callable, or the name of a method on the inheriting object. |
||
144 | */ |
||
145 | 6 | View Code Duplication | public function registerUpdateHook($columnName, $fn) |
157 | |||
158 | /** |
||
159 | * Register a new hook for a specific column that gets called before execution of the delete() method |
||
160 | * Only one hook per column can be registered at a time |
||
161 | * @param string $columnName The name of the column that is registered. |
||
162 | * @param string|callable $fn Either a callable, or the name of a method on the inheriting object. |
||
163 | */ |
||
164 | 3 | View Code Duplication | public function registerDeleteHook($columnName, $fn) |
176 | |||
177 | /** |
||
178 | * Register a new hook for a specific column that gets called before execution of the search() method |
||
179 | * Only one hook per column can be registered at a time |
||
180 | * @param string $columnName The name of the column that is registered. |
||
181 | * @param string|callable $fn Either a callable, or the name of a method on the inheriting object. The callable is required to take one argument: an instance of miBadger\Query\Query; |
||
182 | */ |
||
183 | 13 | View Code Duplication | public function registerSearchHook($columnName, $fn) |
195 | |||
196 | /** |
||
197 | * Adds a new column definition to the table. |
||
198 | * @param string $columnName The name of the column that is registered. |
||
199 | * @param Array $definition The definition of that column. |
||
200 | */ |
||
201 | 38 | public function extendTableDefinition($columnName, $definition) |
|
216 | |||
217 | /** |
||
218 | * Returns the type string as it should appear in the mysql create table statement for the given column |
||
219 | * @return string The type string |
||
220 | */ |
||
221 | 22 | private function getDatabaseTypeString($colName, $type, $length) |
|
255 | |||
256 | /** |
||
257 | * Builds the part of a MySQL create table statement that corresponds to the supplied column |
||
258 | * @param string $colName Name of the database column |
||
259 | * @param string $type The type of the string |
||
260 | * @param int $properties The set of Column properties that apply to this column (See ColumnProperty for options) |
||
261 | * @return string |
||
262 | */ |
||
263 | 22 | private function buildCreateTableColumnEntry($colName, $type, $length, $properties, $default) |
|
291 | |||
292 | /** |
||
293 | * Sorts the column statement components in the order such that the id appears first, |
||
294 | * followed by all other columns in alphabetical ascending order |
||
295 | * @param Array $colStatements Array of column statements |
||
296 | * @return Array |
||
297 | */ |
||
298 | 22 | private function sortColumnStatements($colStatements) |
|
315 | |||
316 | /** |
||
317 | * Builds the MySQL Create Table statement for the internal table definition |
||
318 | * @return string |
||
319 | */ |
||
320 | 22 | public function buildCreateTableSQL() |
|
351 | |||
352 | /** |
||
353 | * Creates the entity as a table in the database |
||
354 | */ |
||
355 | 22 | public function createTable() |
|
359 | |||
360 | /** |
||
361 | * builds a MySQL constraint statement for the given parameters |
||
362 | * @param string $parentTable |
||
363 | * @param string $parentColumn |
||
364 | * @param string $childTable |
||
365 | * @param string $childColumn |
||
366 | * @return string The MySQL table constraint string |
||
367 | */ |
||
368 | 4 | protected function buildConstraint($parentTable, $parentColumn, $childTable, $childColumn) |
|
379 | |||
380 | /** |
||
381 | * Iterates over the specified constraints in the table definition, |
||
382 | * and applies these to the database. |
||
383 | */ |
||
384 | 1 | public function createTableConstraints() |
|
397 | |||
398 | /** |
||
399 | * Returns the name -> variable mapping for the table definition. |
||
400 | * @return Array The mapping |
||
401 | */ |
||
402 | 44 | protected function getActiveRecordColumns() |
|
416 | |||
417 | /** |
||
418 | * {@inheritdoc} |
||
419 | */ |
||
420 | 19 | public function create() |
|
439 | |||
440 | /** |
||
441 | * {@inheritdoc} |
||
442 | */ |
||
443 | 17 | public function read($id) |
|
468 | |||
469 | /** |
||
470 | * {@inheritdoc} |
||
471 | */ |
||
472 | 8 | View Code Duplication | public function update() |
490 | |||
491 | /** |
||
492 | * {@inheritdoc} |
||
493 | */ |
||
494 | 6 | View Code Duplication | public function delete() |
514 | |||
515 | /** |
||
516 | * {@inheritdoc} |
||
517 | */ |
||
518 | 2 | public function sync() |
|
526 | |||
527 | /** |
||
528 | * {@inheritdoc} |
||
529 | */ |
||
530 | 3 | public function exists() |
|
534 | |||
535 | /** |
||
536 | * {@inheritdoc} |
||
537 | */ |
||
538 | 26 | public function fill(array $attributes) |
|
551 | |||
552 | /** |
||
553 | * {@inheritdoc} |
||
554 | */ |
||
555 | 3 | public function searchOne(array $where = [], array $orderBy = []) |
|
569 | |||
570 | /** |
||
571 | * {@inheritdoc} |
||
572 | */ |
||
573 | 13 | public function search(array $where = [], array $orderBy = [], $limit = -1, $offset = 0) |
|
590 | |||
591 | /** |
||
592 | * Returns the search query result with the given where, order by, limit and offset clauses. |
||
593 | * |
||
594 | * @param array $where = [] |
||
595 | * @param array $orderBy = [] |
||
596 | * @param int $limit = -1 |
||
597 | * @param int $offset = 0 |
||
598 | * @return \miBadger\Query\QueryResult the search query result with the given where, order by, limit and offset clauses. |
||
599 | */ |
||
600 | 16 | private function getSearchQueryResult(array $where = [], array $orderBy = [], $limit = -1, $offset = 0) |
|
623 | |||
624 | /** |
||
625 | * Returns the given query after adding the given where conditions. |
||
626 | * |
||
627 | * @param \miBadger\Query\Query $query |
||
628 | * @param array $where |
||
629 | * @return \miBadger\Query\Query the given query after adding the given where conditions. |
||
630 | */ |
||
631 | 16 | private function getSearchQueryWhere($query, $where) |
|
639 | |||
640 | /** |
||
641 | * Returns the given query after adding the given order by conditions. |
||
642 | * |
||
643 | * @param \miBadger\Query\Query $query |
||
644 | * @param array $orderBy |
||
645 | * @return \miBadger\Query\Query the given query after adding the given order by conditions. |
||
646 | */ |
||
647 | 16 | private function getSearchQueryOrderBy($query, $orderBy) |
|
655 | |||
656 | /** |
||
657 | * Returns the given query after adding the given limit and offset conditions. |
||
658 | * |
||
659 | * @param \miBadger\Query\Query $query |
||
660 | * @param int $limit |
||
661 | * @param int $offset |
||
662 | * @return \miBadger\Query\Query the given query after adding the given limit and offset conditions. |
||
663 | */ |
||
664 | 16 | private function getSearchQueryLimit($query, $limit, $offset) |
|
673 | |||
674 | /** |
||
675 | * Returns the PDO. |
||
676 | * |
||
677 | * @return \PDO the PDO. |
||
678 | */ |
||
679 | 50 | public function getPdo() |
|
683 | |||
684 | /** |
||
685 | * Set the PDO. |
||
686 | * |
||
687 | * @param \PDO $pdo |
||
688 | * @return $this |
||
689 | */ |
||
690 | 71 | protected function setPdo($pdo) |
|
696 | |||
697 | /** |
||
698 | * Returns the ID. |
||
699 | * |
||
700 | * @return null|int The ID. |
||
701 | */ |
||
702 | 21 | public function getId() |
|
706 | |||
707 | /** |
||
708 | * Set the ID. |
||
709 | * |
||
710 | * @param int $id |
||
711 | * @return $this |
||
712 | */ |
||
713 | 39 | protected function setId($id) |
|
719 | |||
720 | /** |
||
721 | * Returns the active record table. |
||
722 | * |
||
723 | * @return string the active record table. |
||
724 | */ |
||
725 | abstract protected function getActiveRecordTable(); |
||
726 | |||
727 | /** |
||
728 | * Returns the active record columns. |
||
729 | * |
||
730 | * @return array the active record columns. |
||
731 | */ |
||
732 | abstract protected function getActiveRecordTableDefinition(); |
||
733 | } |
||
734 |
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.