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 |
||
57 | abstract class AbstractTable extends Component implements TableInterface, LoggerAwareInterface |
||
58 | { |
||
59 | use LoggerTrait; |
||
60 | |||
61 | /** |
||
62 | * Table states. |
||
63 | */ |
||
64 | const STATUS_NEW = 0; |
||
65 | const STATUS_EXISTS = 1; |
||
66 | const STATUS_DROPPED = 2; |
||
67 | |||
68 | /** |
||
69 | * Indication that table is exists and current schema is fetched from database. |
||
70 | * |
||
71 | * @var int |
||
72 | */ |
||
73 | private $status = self::STATUS_NEW; |
||
74 | |||
75 | /** |
||
76 | * Database specific tablePrefix. Required for table renames. |
||
77 | * |
||
78 | * @var string |
||
79 | */ |
||
80 | private $prefix = ''; |
||
81 | |||
82 | /** |
||
83 | * @invisible |
||
84 | * |
||
85 | * @var Driver |
||
86 | */ |
||
87 | protected $driver = null; |
||
88 | |||
89 | /** |
||
90 | * Initial table state. |
||
91 | * |
||
92 | * @invisible |
||
93 | * @var TableState |
||
94 | */ |
||
95 | protected $initial = null; |
||
96 | |||
97 | /** |
||
98 | * Currently defined table state. |
||
99 | * |
||
100 | * @invisible |
||
101 | * @var TableState |
||
102 | */ |
||
103 | protected $current = null; |
||
104 | |||
105 | /** |
||
106 | * @param Driver $driver Parent driver. |
||
107 | * @param string $name Table name, must include table prefix. |
||
108 | * @param string $prefix Database specific table prefix. |
||
109 | */ |
||
110 | public function __construct(Driver $driver, string $name, string $prefix) |
||
130 | |||
131 | /** |
||
132 | * Get instance of associated driver. |
||
133 | * |
||
134 | * @return Driver |
||
135 | */ |
||
136 | public function getDriver(): Driver |
||
140 | |||
141 | /** |
||
142 | * @return StateComparator |
||
143 | */ |
||
144 | public function getComparator(): StateComparator |
||
148 | |||
149 | /** |
||
150 | * Check if table schema has been modified since synchronization. |
||
151 | * |
||
152 | * @return bool |
||
153 | */ |
||
154 | protected function hasChanges(): bool |
||
158 | |||
159 | /** |
||
160 | * {@inheritdoc} |
||
161 | */ |
||
162 | public function exists(): bool |
||
166 | |||
167 | /** |
||
168 | * Table status (see codes above). |
||
169 | * |
||
170 | * @return int |
||
171 | */ |
||
172 | public function getStatus(): int |
||
176 | |||
177 | /** |
||
178 | * Return database specific table prefix. |
||
179 | * |
||
180 | * @return string |
||
181 | */ |
||
182 | public function getPrefix(): string |
||
186 | |||
187 | /** |
||
188 | * Sets table name. Use this function in combination with save to rename table. |
||
189 | * |
||
190 | * @param string $name |
||
191 | * |
||
192 | * @return string Prefixed table name. |
||
193 | */ |
||
194 | public function setName(string $name): string |
||
200 | |||
201 | /** |
||
202 | * {@inheritdoc} |
||
203 | */ |
||
204 | public function getName(): string |
||
208 | |||
209 | /** |
||
210 | * Table name before rename. |
||
211 | * |
||
212 | * @return string |
||
213 | */ |
||
214 | public function getInitialName(): string |
||
218 | |||
219 | /** |
||
220 | * Declare table as dropped, you have to sync table using "save" method in order to apply this |
||
221 | * change. |
||
222 | */ |
||
223 | public function declareDropped() |
||
232 | |||
233 | /** |
||
234 | * Set table primary keys. Operation can only be applied for newly created tables. Now every |
||
235 | * database might support compound indexes. |
||
236 | * |
||
237 | * @param array $columns |
||
238 | * |
||
239 | * @return self |
||
240 | */ |
||
241 | public function setPrimaryKeys(array $columns): AbstractTable |
||
251 | |||
252 | /** |
||
253 | * {@inheritdoc} |
||
254 | */ |
||
255 | public function getPrimaryKeys(): array |
||
259 | |||
260 | /** |
||
261 | * {@inheritdoc} |
||
262 | */ |
||
263 | public function hasColumn(string $name): bool |
||
267 | |||
268 | /** |
||
269 | * {@inheritdoc} |
||
270 | * |
||
271 | * @return ColumnInterface[]|AbstractColumn[] |
||
272 | */ |
||
273 | public function getColumns(): array |
||
277 | |||
278 | /** |
||
279 | * {@inheritdoc} |
||
280 | */ |
||
281 | public function hasIndex(array $columns = []): bool |
||
285 | |||
286 | /** |
||
287 | * {@inheritdoc} |
||
288 | * |
||
289 | * @return IndexInterface[]|AbstractIndex[] |
||
290 | */ |
||
291 | public function getIndexes(): array |
||
295 | |||
296 | /** |
||
297 | * {@inheritdoc} |
||
298 | */ |
||
299 | public function hasForeign(string $column): bool |
||
303 | |||
304 | /** |
||
305 | * {@inheritdoc} |
||
306 | * |
||
307 | * @return ReferenceInterface[]|AbstractReference[] |
||
308 | */ |
||
309 | public function getForeigns(): array |
||
313 | |||
314 | /** |
||
315 | * {@inheritdoc} |
||
316 | */ |
||
317 | public function getDependencies(): array |
||
326 | |||
327 | /** |
||
328 | * Get/create instance of AbstractColumn associated with current table. |
||
329 | * |
||
330 | * Examples: |
||
331 | * $table->column('name')->string(); |
||
332 | * |
||
333 | * @param string $name |
||
334 | * |
||
335 | * @return AbstractColumn |
||
336 | */ |
||
337 | public function column(string $name): AbstractColumn |
||
349 | |||
350 | /** |
||
351 | * Shortcut for column() method. |
||
352 | * |
||
353 | * @param string $column |
||
354 | * |
||
355 | * @return AbstractColumn |
||
356 | */ |
||
357 | public function __get(string $column) |
||
361 | |||
362 | /** |
||
363 | * Column creation/altering shortcut, call chain is identical to: |
||
364 | * AbstractTable->column($name)->$type($arguments). |
||
365 | * |
||
366 | * Example: |
||
367 | * $table->string("name"); |
||
368 | * $table->text("some_column"); |
||
369 | * |
||
370 | * @param string $type |
||
371 | * @param array $arguments Type specific parameters. |
||
372 | * |
||
373 | * @return AbstractColumn |
||
374 | */ |
||
375 | public function __call(string $type, array $arguments) |
||
382 | |||
383 | /** |
||
384 | * Get/create instance of AbstractIndex associated with current table based on list of forming |
||
385 | * column names. |
||
386 | * |
||
387 | * Example: |
||
388 | * $table->index('key'); |
||
389 | * $table->index('key', 'key2'); |
||
390 | * $table->index(['key', 'key2']); |
||
391 | * |
||
392 | * @param mixed $columns Column name, or array of columns. |
||
393 | * |
||
394 | * @return AbstractIndex |
||
395 | * |
||
396 | * @throws SchemaException |
||
397 | */ |
||
398 | public function index($columns): AbstractIndex |
||
418 | |||
419 | /** |
||
420 | * Get/create instance of AbstractReference associated with current table based on local column |
||
421 | * name. |
||
422 | * |
||
423 | * @param string $column |
||
424 | * |
||
425 | * @return AbstractReference |
||
426 | * |
||
427 | * @throws SchemaException |
||
428 | */ |
||
429 | public function foreign(string $column): AbstractReference |
||
449 | |||
450 | /** |
||
451 | * Rename column (only if column exists). |
||
452 | * |
||
453 | * @param string $column |
||
454 | * @param string $name New column name. |
||
455 | * |
||
456 | * @return self |
||
457 | * |
||
458 | * @throws SchemaException |
||
459 | */ |
||
460 | public function renameColumn(string $column, string $name): AbstractTable |
||
471 | |||
472 | /** |
||
473 | * Rename index (only if index exists). |
||
474 | * |
||
475 | * @param array $columns Index forming columns. |
||
476 | * @param string $name New index name. |
||
477 | * |
||
478 | * @return self |
||
479 | * |
||
480 | * @throws SchemaException |
||
481 | */ |
||
482 | public function renameIndex(array $columns, string $name): AbstractTable |
||
495 | |||
496 | /** |
||
497 | * Drop column by it's name. |
||
498 | * |
||
499 | * @param string $column |
||
500 | * |
||
501 | * @return self |
||
502 | * |
||
503 | * @throws SchemaException |
||
504 | */ |
||
505 | View Code Duplication | public function dropColumn(string $column): AbstractTable |
|
516 | |||
517 | /** |
||
518 | * Drop index by it's forming columns. |
||
519 | * |
||
520 | * @param array $columns |
||
521 | * |
||
522 | * @return self |
||
523 | * |
||
524 | * @throws SchemaException |
||
525 | */ |
||
526 | public function dropIndex(array $columns): AbstractTable |
||
539 | |||
540 | /** |
||
541 | * Drop foreign key by it's name. |
||
542 | * |
||
543 | * @param string $column |
||
544 | * |
||
545 | * @return self |
||
546 | * |
||
547 | * @throws SchemaException |
||
548 | */ |
||
549 | View Code Duplication | public function dropForeign($column): AbstractTable |
|
562 | |||
563 | /** |
||
564 | * Reset table state to new form. |
||
565 | * |
||
566 | * @param TableState $status Use null to flush table schema. |
||
567 | * |
||
568 | * @return self|$this |
||
569 | */ |
||
570 | public function setStatus(TableState $status = null): AbstractTable |
||
581 | |||
582 | /** |
||
583 | * Reset table state to it initial form. |
||
584 | * |
||
585 | * @return self|$this |
||
586 | */ |
||
587 | public function resetState(): AbstractTable |
||
593 | |||
594 | /** |
||
595 | * Save table schema including every column, index, foreign key creation/altering. If table |
||
596 | * does not exist it must be created. If table declared as dropped it will be removed from |
||
597 | * the database. |
||
598 | * |
||
599 | * @param int $behaviour Operation to be performed while table being saved. In some |
||
600 | * cases (when multiple tables are being updated) it is |
||
601 | * reasonable to drop foreing keys and indexes prior to |
||
602 | * dropping related columns. See sync bus class to get more |
||
603 | * details. |
||
604 | * @param LoggerInterface $logger Optional, aggregates messages for data syncing. |
||
605 | * |
||
606 | * @throws HandlerException |
||
607 | */ |
||
608 | public function save(int $behaviour = AbstractHandler::DO_ALL, LoggerInterface $logger = null) |
||
639 | |||
640 | /** |
||
641 | * @return AbstractColumn|string |
||
642 | */ |
||
643 | public function __toString(): string |
||
647 | |||
648 | /** |
||
649 | * @return array |
||
650 | */ |
||
651 | public function __debugInfo() |
||
661 | |||
662 | /** |
||
663 | * Populate table schema with values from database. |
||
664 | * |
||
665 | * @param TableState $state |
||
666 | */ |
||
667 | protected function initSchema(TableState $state) |
||
685 | |||
686 | /** |
||
687 | * Fetch index declarations from database. |
||
688 | * |
||
689 | * @return ColumnInterface[] |
||
690 | */ |
||
691 | abstract protected function fetchColumns(): array; |
||
692 | |||
693 | /** |
||
694 | * Fetch index declarations from database. |
||
695 | * |
||
696 | * @return IndexInterface[] |
||
697 | */ |
||
698 | abstract protected function fetchIndexes(): array; |
||
699 | |||
700 | /** |
||
701 | * Fetch references declaration from database. |
||
702 | * |
||
703 | * @return ReferenceInterface[] |
||
704 | */ |
||
705 | abstract protected function fetchReferences(): array; |
||
706 | |||
707 | /** |
||
708 | * Fetch names of primary keys from table. |
||
709 | * |
||
710 | * @return array |
||
711 | */ |
||
712 | abstract protected function fetchPrimaryKeys(): array; |
||
713 | |||
714 | /** |
||
715 | * Create column with a given name. |
||
716 | * |
||
717 | * @param string $name |
||
718 | * |
||
719 | * @return AbstractColumn |
||
720 | */ |
||
721 | abstract protected function createColumn(string $name): AbstractColumn; |
||
722 | |||
723 | /** |
||
724 | * Create index for a given set of columns. |
||
725 | * |
||
726 | * @param string $name |
||
727 | * |
||
728 | * @return AbstractIndex |
||
729 | */ |
||
730 | abstract protected function createIndex(string $name): AbstractIndex; |
||
731 | |||
732 | /** |
||
733 | * Create reference on a given column set. |
||
734 | * |
||
735 | * @param string $name |
||
736 | * |
||
737 | * @return AbstractReference |
||
738 | */ |
||
739 | abstract protected function createForeign(string $name): AbstractReference; |
||
740 | |||
741 | /** |
||
742 | * Generate unique name for indexes and foreign keys. |
||
743 | * |
||
744 | * @param string $type |
||
745 | * @param array $columns |
||
746 | * |
||
747 | * @return string |
||
748 | */ |
||
749 | protected function createIdentifier(string $type, array $columns): string |
||
760 | |||
761 | /** |
||
762 | * @return ContainerInterface |
||
763 | */ |
||
764 | protected function iocContainer() |
||
769 | } |
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.