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 AbstractTable 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 AbstractTable, and based on these observations, apply Extract Interface, too.
1 | <?php |
||
53 | abstract class AbstractTable extends Component implements TableInterface, LoggerAwareInterface |
||
54 | { |
||
55 | use LoggerTrait; |
||
56 | |||
57 | /** |
||
58 | * Indication that table is exists and current schema is fetched from database. |
||
59 | * |
||
60 | * @var bool |
||
61 | */ |
||
62 | private $exists = false; |
||
63 | |||
64 | /** |
||
65 | * Database specific tablePrefix. Required for table renames. |
||
66 | * |
||
67 | * @var string |
||
68 | */ |
||
69 | private $prefix = ''; |
||
70 | |||
71 | /** |
||
72 | * @invisible |
||
73 | * |
||
74 | * @var Driver |
||
75 | */ |
||
76 | protected $driver = null; |
||
77 | |||
78 | /** |
||
79 | * Initial table state. |
||
80 | * |
||
81 | * @invisible |
||
82 | * @var TableState |
||
83 | */ |
||
84 | protected $initialState = null; |
||
85 | |||
86 | /** |
||
87 | * Currently defined table state. |
||
88 | * |
||
89 | * @invisible |
||
90 | * @var TableState |
||
91 | */ |
||
92 | protected $currentState = null; |
||
93 | |||
94 | /** |
||
95 | * @param Driver $driver Parent driver. |
||
96 | * @param string $name Table name, must include table prefix. |
||
97 | * @param string $prefix Database specific table prefix. |
||
98 | */ |
||
99 | public function __construct(Driver $driver, string $name, string $prefix) |
||
117 | |||
118 | /** |
||
119 | * Get instance of associated driver. |
||
120 | * |
||
121 | * @return Driver |
||
122 | */ |
||
123 | public function getDriver(): Driver |
||
127 | |||
128 | /** |
||
129 | * @return StateComparator |
||
130 | */ |
||
131 | public function getComparator(): StateComparator |
||
135 | |||
136 | /** |
||
137 | * Check if table schema has been modified since synchronization. |
||
138 | * |
||
139 | * @return bool |
||
140 | */ |
||
141 | protected function hasChanges(): bool |
||
145 | |||
146 | /** |
||
147 | * {@inheritdoc} |
||
148 | */ |
||
149 | public function exists(): bool |
||
153 | |||
154 | /** |
||
155 | * Return database specific table prefix. |
||
156 | * |
||
157 | * @return string |
||
158 | */ |
||
159 | public function getPrefix(): string |
||
163 | |||
164 | /** |
||
165 | * Sets table name. Use this function in combination with save to rename table. |
||
166 | * |
||
167 | * @param string $name |
||
168 | * |
||
169 | * @return string Prefixed table name. |
||
170 | */ |
||
171 | public function setName(string $name): string |
||
177 | |||
178 | /** |
||
179 | * {@inheritdoc} |
||
180 | */ |
||
181 | public function getName(): string |
||
185 | |||
186 | /** |
||
187 | * Set table primary keys. Operation can only be applied for newly created tables. Now every |
||
188 | * database might support compound indexes. |
||
189 | * |
||
190 | * @param array $columns |
||
191 | * |
||
192 | * @return self |
||
193 | */ |
||
194 | public function setPrimaryKeys(array $columns): AbstractTable |
||
204 | |||
205 | /** |
||
206 | * {@inheritdoc} |
||
207 | */ |
||
208 | public function getPrimaryKeys(): array |
||
212 | |||
213 | /** |
||
214 | * {@inheritdoc} |
||
215 | */ |
||
216 | public function hasColumn(string $name): bool |
||
220 | |||
221 | /** |
||
222 | * {@inheritdoc} |
||
223 | * |
||
224 | * @return ColumnInterface[]|AbstractColumn[] |
||
225 | */ |
||
226 | public function getColumns(): array |
||
230 | |||
231 | /** |
||
232 | * {@inheritdoc} |
||
233 | */ |
||
234 | public function hasIndex(array $columns = []): bool |
||
238 | |||
239 | /** |
||
240 | * {@inheritdoc} |
||
241 | * |
||
242 | * @return IndexInterface[]|AbstractIndex[] |
||
243 | */ |
||
244 | public function getIndexes(): array |
||
248 | |||
249 | /** |
||
250 | * {@inheritdoc} |
||
251 | */ |
||
252 | public function hasForeign(string $column): bool |
||
256 | |||
257 | /** |
||
258 | * {@inheritdoc} |
||
259 | * |
||
260 | * @return ReferenceInterface[]|AbstractReference[] |
||
261 | */ |
||
262 | public function getForeigns(): array |
||
266 | |||
267 | /** |
||
268 | * {@inheritdoc} |
||
269 | */ |
||
270 | public function getDependencies(): array |
||
279 | |||
280 | /** |
||
281 | * Get/create instance of AbstractColumn associated with current table. |
||
282 | * |
||
283 | * Examples: |
||
284 | * $table->column('name')->string(); |
||
285 | * |
||
286 | * @param string $name |
||
287 | * |
||
288 | * @return AbstractColumn |
||
289 | */ |
||
290 | public function column(string $name): AbstractColumn |
||
302 | |||
303 | /** |
||
304 | * Shortcut for column() method. |
||
305 | * |
||
306 | * @param string $column |
||
307 | * |
||
308 | * @return AbstractColumn |
||
309 | */ |
||
310 | public function __get(string $column) |
||
314 | |||
315 | /** |
||
316 | * Column creation/altering shortcut, call chain is identical to: |
||
317 | * AbstractTable->column($name)->$type($arguments). |
||
318 | * |
||
319 | * Example: |
||
320 | * $table->string("name"); |
||
321 | * $table->text("some_column"); |
||
322 | * |
||
323 | * @param string $type |
||
324 | * @param array $arguments Type specific parameters. |
||
325 | * |
||
326 | * @return AbstractColumn |
||
327 | */ |
||
328 | public function __call(string $type, array $arguments) |
||
335 | |||
336 | /** |
||
337 | * Get/create instance of AbstractIndex associated with current table based on list of forming |
||
338 | * column names. |
||
339 | * |
||
340 | * Example: |
||
341 | * $table->index('key'); |
||
342 | * $table->index('key', 'key2'); |
||
343 | * $table->index(['key', 'key2']); |
||
344 | * |
||
345 | * @param mixed $columns Column name, or array of columns. |
||
346 | * |
||
347 | * @return AbstractIndex |
||
348 | * |
||
349 | * @throws SchemaException |
||
350 | */ |
||
351 | public function index($columns): AbstractIndex |
||
371 | |||
372 | /** |
||
373 | * Get/create instance of AbstractReference associated with current table based on local column |
||
374 | * name. |
||
375 | * |
||
376 | * @param string $column |
||
377 | * |
||
378 | * @return AbstractReference |
||
379 | * |
||
380 | * @throws SchemaException |
||
381 | */ |
||
382 | public function foreign(string $column): AbstractReference |
||
401 | |||
402 | /** |
||
403 | * Rename column (only if column exists). |
||
404 | * |
||
405 | * @param string $column |
||
406 | * @param string $name New column name. |
||
407 | * |
||
408 | * @return self |
||
409 | * |
||
410 | * @throws SchemaException |
||
411 | */ |
||
412 | public function renameColumn(string $column, string $name): AbstractTable |
||
423 | |||
424 | /** |
||
425 | * Rename index (only if index exists). |
||
426 | * |
||
427 | * @param array $columns Index forming columns. |
||
428 | * @param string $name New index name. |
||
429 | * |
||
430 | * @return self |
||
431 | * |
||
432 | * @throws SchemaException |
||
433 | */ |
||
434 | public function renameIndex(array $columns, string $name): AbstractTable |
||
447 | |||
448 | /** |
||
449 | * Drop column by it's name. |
||
450 | * |
||
451 | * @param string $column |
||
452 | * |
||
453 | * @return self |
||
454 | * |
||
455 | * @throws SchemaException |
||
456 | */ |
||
457 | public function dropColumn(string $column): AbstractTable |
||
483 | |||
484 | /** |
||
485 | * Drop index by it's forming columns. |
||
486 | * |
||
487 | * @param array $columns |
||
488 | * |
||
489 | * @return self |
||
490 | * |
||
491 | * @throws SchemaException |
||
492 | */ |
||
493 | public function dropIndex(array $columns): AbstractTable |
||
506 | |||
507 | /** |
||
508 | * Drop foreign key by it's name. |
||
509 | * |
||
510 | * @param string $column |
||
511 | * |
||
512 | * @return self |
||
513 | * |
||
514 | * @throws SchemaException |
||
515 | */ |
||
516 | public function dropForeign($column): AbstractTable |
||
529 | |||
530 | /** |
||
531 | * Reset table state to new form. |
||
532 | * |
||
533 | * @param TableState $state Use null to flush table schema. |
||
534 | * |
||
535 | * @return self|$this |
||
536 | */ |
||
537 | public function setState(TableState $state = null): AbstractTable |
||
548 | |||
549 | /** |
||
550 | * Reset table state to it initial form. |
||
551 | * |
||
552 | * @return self|$this |
||
553 | */ |
||
554 | public function resetState(): AbstractTable |
||
560 | |||
561 | /** |
||
562 | * @return AbstractColumn|string |
||
563 | */ |
||
564 | public function __toString(): string |
||
568 | |||
569 | /** |
||
570 | * @return array |
||
571 | */ |
||
572 | public function __debugInfo() |
||
582 | |||
583 | /** |
||
584 | * Populate table schema with values from database. |
||
585 | * |
||
586 | * @param TableState $state |
||
587 | */ |
||
588 | protected function initSchema(TableState $state) |
||
606 | |||
607 | /** |
||
608 | * Fetch index declarations from database. |
||
609 | * |
||
610 | * @return ColumnInterface[] |
||
611 | */ |
||
612 | abstract protected function fetchColumns(): array; |
||
613 | |||
614 | /** |
||
615 | * Fetch index declarations from database. |
||
616 | * |
||
617 | * @return IndexInterface[] |
||
618 | */ |
||
619 | abstract protected function fetchIndexes(): array; |
||
620 | |||
621 | /** |
||
622 | * Fetch references declaration from database. |
||
623 | * |
||
624 | * @return ReferenceInterface[] |
||
625 | */ |
||
626 | abstract protected function fetchReferences(): array; |
||
627 | |||
628 | /** |
||
629 | * Fetch names of primary keys from table. |
||
630 | * |
||
631 | * @return array |
||
632 | */ |
||
633 | abstract protected function fetchPrimaryKeys(): array; |
||
634 | |||
635 | /** |
||
636 | * Create column with a given name. |
||
637 | * |
||
638 | * @param string $name |
||
639 | * |
||
640 | * @return AbstractColumn |
||
641 | */ |
||
642 | abstract protected function createColumn(string $name): AbstractColumn; |
||
643 | |||
644 | /** |
||
645 | * Create index for a given set of columns. |
||
646 | * |
||
647 | * @param string $name |
||
648 | * |
||
649 | * @return AbstractIndex |
||
650 | */ |
||
651 | abstract protected function createIndex(string $name): AbstractIndex; |
||
652 | |||
653 | /** |
||
654 | * Create reference on a given column set. |
||
655 | * |
||
656 | * @param string $name |
||
657 | * |
||
658 | * @return AbstractReference |
||
659 | */ |
||
660 | abstract protected function createReference(string $name): AbstractReference; |
||
661 | |||
662 | /** |
||
663 | * Generate unique name for indexes and foreign keys. |
||
664 | * |
||
665 | * @param string $type |
||
666 | * @param array $columns |
||
667 | * |
||
668 | * @return string |
||
669 | */ |
||
670 | protected function createIdentifier(string $type, array $columns): string |
||
681 | |||
682 | /** |
||
683 | * @return ContainerInterface |
||
684 | */ |
||
685 | protected function iocContainer() |
||
690 | } |
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.