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 DoctrineDatabase 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 DoctrineDatabase, and based on these observations, apply Extract Interface, too.
1 | <?php |
||
27 | class DoctrineDatabase extends Gateway |
||
28 | { |
||
29 | /** |
||
30 | * 2^30, since PHP_INT_MAX can cause overflows in DB systems, if PHP is run |
||
31 | * on 64 bit systems. |
||
32 | */ |
||
33 | const MAX_LIMIT = 1073741824; |
||
34 | |||
35 | /** |
||
36 | * Columns of database tables. |
||
37 | * |
||
38 | * @var array |
||
39 | * |
||
40 | * @todo remove after testing |
||
41 | */ |
||
42 | protected $columns = [ |
||
43 | 'ezurlalias_ml' => [ |
||
44 | 'action', |
||
45 | 'action_type', |
||
46 | 'alias_redirects', |
||
47 | 'id', |
||
48 | 'is_alias', |
||
49 | 'is_original', |
||
50 | 'lang_mask', |
||
51 | 'link', |
||
52 | 'parent', |
||
53 | 'text', |
||
54 | 'text_md5', |
||
55 | ], |
||
56 | ]; |
||
57 | |||
58 | /** |
||
59 | * Doctrine database handler. |
||
60 | * |
||
61 | * @var \eZ\Publish\Core\Persistence\Database\DatabaseHandler |
||
62 | * @deprecated Start to use DBAL $connection instead. |
||
63 | */ |
||
64 | protected $dbHandler; |
||
65 | |||
66 | /** |
||
67 | * Language mask generator. |
||
68 | * |
||
69 | * @var \eZ\Publish\Core\Persistence\Legacy\Content\Language\MaskGenerator |
||
70 | */ |
||
71 | protected $languageMaskGenerator; |
||
72 | |||
73 | /** |
||
74 | * Main URL database table name. |
||
75 | * |
||
76 | * @var string |
||
77 | */ |
||
78 | protected $table; |
||
79 | |||
80 | /** @var \Doctrine\DBAL\Connection */ |
||
81 | private $connection; |
||
82 | |||
83 | /** |
||
84 | * Creates a new DoctrineDatabase UrlAlias Gateway. |
||
85 | * |
||
86 | * @param \eZ\Publish\Core\Persistence\Database\DatabaseHandler $dbHandler |
||
87 | * @param \eZ\Publish\Core\Persistence\Legacy\Content\Language\MaskGenerator $languageMaskGenerator |
||
88 | */ |
||
89 | public function __construct( |
||
98 | |||
99 | public function setTable($name) |
||
103 | |||
104 | /** |
||
105 | * Loads list of aliases by given $locationId. |
||
106 | * |
||
107 | * @param mixed $locationId |
||
108 | * @param bool $custom |
||
109 | * @param mixed $languageId |
||
110 | * |
||
111 | * @return array |
||
112 | */ |
||
113 | public function loadLocationEntries($locationId, $custom = false, $languageId = false) |
||
168 | |||
169 | /** |
||
170 | * Loads paged list of global aliases. |
||
171 | * |
||
172 | * @param string|null $languageCode |
||
173 | * @param int $offset |
||
174 | * @param int $limit |
||
175 | * |
||
176 | * @return array |
||
177 | */ |
||
178 | public function listGlobalEntries($languageCode = null, $offset = 0, $limit = -1) |
||
235 | |||
236 | /** |
||
237 | * Returns boolean indicating if the row with given $id is special root entry. |
||
238 | * |
||
239 | * Special root entry entry will have parentId=0 and text=''. |
||
240 | * In standard installation this entry will point to location with id=2. |
||
241 | * |
||
242 | * @param mixed $id |
||
243 | * |
||
244 | * @return bool |
||
245 | */ |
||
246 | public function isRootEntry($id) |
||
267 | |||
268 | /** |
||
269 | * Downgrades autogenerated entry matched by given $action and $languageId and negatively matched by |
||
270 | * composite primary key. |
||
271 | * |
||
272 | * If language mask of the found entry is composite (meaning it consists of multiple language ids) given |
||
273 | * $languageId will be removed from mask. Otherwise entry will be marked as history. |
||
274 | * |
||
275 | * @param string $action |
||
276 | * @param mixed $languageId |
||
277 | * @param mixed $newId |
||
278 | * @param mixed $parentId |
||
279 | * @param string $textMD5 |
||
280 | */ |
||
281 | public function cleanupAfterPublish($action, $languageId, $newId, $parentId, $textMD5) |
||
337 | |||
338 | /** |
||
339 | * Archive (remove or historize) obsolete URL aliases (for translations that were removed). |
||
340 | * |
||
341 | * @param int $languageMask all languages bit mask |
||
342 | * @param int $languageId removed language Id |
||
343 | * @param int $parent |
||
344 | * @param string $textMD5 checksum |
||
345 | * @param $linkId |
||
346 | */ |
||
347 | private function archiveUrlAliasForDeletedTranslation($languageMask, $languageId, $parent, $textMD5, $linkId) |
||
357 | |||
358 | public function historizeBeforeSwap($action, $languageMask) |
||
395 | |||
396 | /** |
||
397 | * Updates single row matched by composite primary key. |
||
398 | * |
||
399 | * Sets "is_original" to 0 thus marking entry as history. |
||
400 | * |
||
401 | * Re-links history entries. |
||
402 | * |
||
403 | * When location alias is published we need to check for new history entries created with self::downgrade() |
||
404 | * with the same action and language, update their "link" column with id of the published entry. |
||
405 | * History entry "id" column is moved to next id value so that all active (non-history) entries are kept |
||
406 | * under the same id. |
||
407 | * |
||
408 | * @param int $parentId |
||
409 | * @param string $textMD5 |
||
410 | * @param int $newId |
||
411 | */ |
||
412 | protected function historize($parentId, $textMD5, $newId) |
||
445 | |||
446 | /** |
||
447 | * Updates single row data matched by composite primary key. |
||
448 | * |
||
449 | * Removes given $languageId from entry's language mask |
||
450 | * |
||
451 | * @param mixed $parentId |
||
452 | * @param string $textMD5 |
||
453 | * @param mixed $languageId |
||
454 | */ |
||
455 | protected function removeTranslation($parentId, $textMD5, $languageId) |
||
456 | { |
||
457 | /** @var $query \eZ\Publish\Core\Persistence\Database\UpdateQuery */ |
||
458 | $query = $this->dbHandler->createUpdateQuery(); |
||
459 | $query->update( |
||
460 | $this->dbHandler->quoteTable($this->table) |
||
461 | )->set( |
||
462 | $this->dbHandler->quoteColumn('lang_mask'), |
||
463 | $query->expr->bitAnd( |
||
464 | $this->dbHandler->quoteColumn('lang_mask'), |
||
465 | $query->bindValue(~$languageId, null, \PDO::PARAM_INT) |
||
466 | ) |
||
467 | )->where( |
||
468 | $query->expr->lAnd( |
||
469 | $query->expr->eq( |
||
470 | $this->dbHandler->quoteColumn('parent'), |
||
471 | $query->bindValue($parentId, null, \PDO::PARAM_INT) |
||
472 | ), |
||
473 | $query->expr->eq( |
||
474 | $this->dbHandler->quoteColumn('text_md5'), |
||
475 | $query->bindValue($textMD5, null, \PDO::PARAM_STR) |
||
476 | ) |
||
477 | ) |
||
478 | ); |
||
479 | $query->prepare()->execute(); |
||
480 | } |
||
481 | |||
482 | /** |
||
483 | * Marks all entries with given $id as history entries. |
||
484 | * |
||
485 | * This method is used by Handler::locationMoved(). Each row is separately historized |
||
486 | * because future publishing needs to be able to take over history entries safely. |
||
487 | * |
||
488 | * @param mixed $id |
||
489 | * @param mixed $link |
||
490 | */ |
||
491 | public function historizeId($id, $link) |
||
530 | |||
531 | /** |
||
532 | * Updates parent id of autogenerated entries. |
||
533 | * |
||
534 | * Update includes history entries. |
||
535 | * |
||
536 | * @param mixed $oldParentId |
||
537 | * @param mixed $newParentId |
||
538 | */ |
||
539 | public function reparent($oldParentId, $newParentId) |
||
540 | { |
||
541 | /** @var $query \eZ\Publish\Core\Persistence\Database\UpdateQuery */ |
||
542 | $query = $this->dbHandler->createUpdateQuery(); |
||
543 | $query->update( |
||
544 | $this->dbHandler->quoteTable($this->table) |
||
545 | )->set( |
||
546 | $this->dbHandler->quoteColumn('parent'), |
||
547 | $query->bindValue($newParentId, null, \PDO::PARAM_INT) |
||
548 | )->where( |
||
549 | $query->expr->lAnd( |
||
550 | $query->expr->eq( |
||
551 | $this->dbHandler->quoteColumn('is_alias'), |
||
552 | $query->bindValue(0, null, \PDO::PARAM_INT) |
||
553 | ), |
||
554 | $query->expr->eq( |
||
555 | $this->dbHandler->quoteColumn('parent'), |
||
556 | $query->bindValue($oldParentId, null, \PDO::PARAM_INT) |
||
557 | ) |
||
558 | ) |
||
559 | ); |
||
560 | |||
561 | $query->prepare()->execute(); |
||
562 | } |
||
563 | |||
564 | /** |
||
565 | * Updates single row data matched by composite primary key. |
||
566 | * |
||
567 | * Use optional parameter $languageMaskMatch to additionally limit the query match with languages. |
||
568 | * |
||
569 | * @param mixed $parentId |
||
570 | * @param string $textMD5 |
||
571 | * @param array $values associative array with column names as keys and column values as values |
||
572 | */ |
||
573 | View Code Duplication | public function updateRow($parentId, $textMD5, array $values) |
|
593 | |||
594 | /** |
||
595 | * Inserts new row in urlalias_ml table. |
||
596 | * |
||
597 | * @param array $values |
||
598 | * |
||
599 | * @return mixed |
||
600 | */ |
||
601 | public function insertRow(array $values) |
||
647 | |||
648 | /** |
||
649 | * Sets value for insert or update query. |
||
650 | * |
||
651 | * @param \eZ\Publish\Core\Persistence\Database\Query|\eZ\Publish\Core\Persistence\Database\InsertQuery|\eZ\Publish\Core\Persistence\Database\UpdateQuery $query |
||
652 | * @param array $values |
||
653 | * |
||
654 | * @throws \Exception |
||
655 | */ |
||
656 | protected function setQueryValues(Query $query, $values) |
||
679 | |||
680 | /** |
||
681 | * Returns next value for "id" column. |
||
682 | * |
||
683 | * @return mixed |
||
684 | */ |
||
685 | View Code Duplication | public function getNextId() |
|
714 | |||
715 | /** |
||
716 | * Loads single row data matched by composite primary key. |
||
717 | * |
||
718 | * @param mixed $parentId |
||
719 | * @param string $textMD5 |
||
720 | * |
||
721 | * @return array |
||
722 | */ |
||
723 | View Code Duplication | public function loadRow($parentId, $textMD5) |
|
747 | |||
748 | /** |
||
749 | * Loads complete URL alias data by given array of path hashes. |
||
750 | * |
||
751 | * @param string[] $urlHashes URL string hashes |
||
752 | * |
||
753 | * @return array |
||
754 | */ |
||
755 | public function loadUrlAliasData(array $urlHashes) |
||
821 | |||
822 | /** |
||
823 | * Loads autogenerated entry id by given $action and optionally $parentId. |
||
824 | * |
||
825 | * @param string $action |
||
826 | * @param mixed|null $parentId |
||
827 | * |
||
828 | * @return array |
||
829 | */ |
||
830 | View Code Duplication | public function loadAutogeneratedEntry($action, $parentId = null) |
|
869 | |||
870 | /** |
||
871 | * Loads all data for the path identified by given $id. |
||
872 | * |
||
873 | * @throws \eZ\Publish\API\Repository\Exceptions\BadStateException |
||
874 | * |
||
875 | * @param int $id |
||
876 | * |
||
877 | * @return array |
||
878 | */ |
||
879 | public function loadPathData($id) |
||
928 | |||
929 | /** |
||
930 | * Loads path data identified by given ordered array of hierarchy data. |
||
931 | * |
||
932 | * The first entry in $hierarchyData corresponds to the top-most path element in the path, the second entry the |
||
933 | * child of the first path element and so on. |
||
934 | * This method is faster than self::getPath() since it can fetch all elements using only one query, but can be used |
||
935 | * only for autogenerated paths. |
||
936 | * |
||
937 | * @param array $hierarchyData |
||
938 | * |
||
939 | * @return array |
||
940 | */ |
||
941 | public function loadPathDataByHierarchy(array $hierarchyData) |
||
1006 | |||
1007 | /** |
||
1008 | * Deletes single custom alias row matched by composite primary key. |
||
1009 | * |
||
1010 | * @param mixed $parentId |
||
1011 | * @param string $textMD5 |
||
1012 | * |
||
1013 | * @return bool |
||
1014 | */ |
||
1015 | public function removeCustomAlias($parentId, $textMD5) |
||
1016 | { |
||
1017 | /** @var $query \eZ\Publish\Core\Persistence\Database\DeleteQuery */ |
||
1018 | $query = $this->dbHandler->createDeleteQuery(); |
||
1019 | $query->deleteFrom( |
||
1020 | $this->dbHandler->quoteTable($this->table) |
||
1021 | )->where( |
||
1022 | $query->expr->lAnd( |
||
1023 | $query->expr->eq( |
||
1024 | $this->dbHandler->quoteColumn('parent'), |
||
1025 | $query->bindValue($parentId, null, \PDO::PARAM_INT) |
||
1026 | ), |
||
1027 | $query->expr->eq( |
||
1028 | $this->dbHandler->quoteColumn('text_md5'), |
||
1029 | $query->bindValue($textMD5, null, \PDO::PARAM_STR) |
||
1030 | ), |
||
1031 | $query->expr->eq( |
||
1032 | $this->dbHandler->quoteColumn('is_alias'), |
||
1033 | $query->bindValue(1, null, \PDO::PARAM_INT) |
||
1034 | ) |
||
1035 | ) |
||
1036 | ); |
||
1037 | $statement = $query->prepare(); |
||
1038 | $statement->execute(); |
||
1039 | |||
1040 | return $statement->rowCount() === 1 ?: false; |
||
1041 | } |
||
1042 | |||
1043 | /** |
||
1044 | * Deletes all rows with given $action and optionally $id. |
||
1045 | * |
||
1046 | * If $id is set only autogenerated entries will be removed. |
||
1047 | * |
||
1048 | * @param mixed $action |
||
1049 | * @param mixed|null $id |
||
1050 | * |
||
1051 | * @return bool |
||
1052 | */ |
||
1053 | public function remove($action, $id = null) |
||
1054 | { |
||
1055 | /** @var $query \eZ\Publish\Core\Persistence\Database\DeleteQuery */ |
||
1056 | $query = $this->dbHandler->createDeleteQuery(); |
||
1057 | $query->deleteFrom( |
||
1058 | $this->dbHandler->quoteTable($this->table) |
||
1059 | )->where( |
||
1060 | $query->expr->eq( |
||
1061 | $this->dbHandler->quoteColumn('action'), |
||
1062 | $query->bindValue($action, null, \PDO::PARAM_STR) |
||
1063 | ) |
||
1064 | ); |
||
1065 | |||
1066 | if ($id !== null) { |
||
1067 | $query->where( |
||
1068 | $query->expr->lAnd( |
||
1069 | $query->expr->eq( |
||
1070 | $this->dbHandler->quoteColumn('is_alias'), |
||
1071 | $query->bindValue(0, null, \PDO::PARAM_INT) |
||
1072 | ), |
||
1073 | $query->expr->eq( |
||
1074 | $this->dbHandler->quoteColumn('id'), |
||
1075 | $query->bindValue($id, null, \PDO::PARAM_INT) |
||
1076 | ) |
||
1077 | ) |
||
1078 | ); |
||
1079 | } |
||
1080 | |||
1081 | $query->prepare()->execute(); |
||
1082 | } |
||
1083 | |||
1084 | /** |
||
1085 | * Loads all autogenerated entries with given $parentId with optionally included history entries. |
||
1086 | * |
||
1087 | * @param mixed $parentId |
||
1088 | * @param bool $includeHistory |
||
1089 | * |
||
1090 | * @return array |
||
1091 | */ |
||
1092 | View Code Duplication | public function loadAutogeneratedEntries($parentId, $includeHistory = false) |
|
1131 | |||
1132 | public function getLocationContentMainLanguageId($locationId) |
||
1133 | { |
||
1155 | |||
1156 | /** |
||
1157 | * Removes languageId of removed translation from lang_mask and deletes single language rows for multiple Locations. |
||
1158 | * |
||
1159 | * Note: URL aliases are not historized as translation removal from all Versions is permanent w/o preserving history. |
||
1160 | * |
||
1161 | * @param int $languageId Language Id to be removed |
||
1162 | * @param string[] $actions actions for which to perform the update |
||
1163 | */ |
||
1164 | public function bulkRemoveTranslation($languageId, $actions) |
||
1188 | |||
1189 | /** |
||
1190 | * Archive (remove or historize) URL aliases for removed Translations. |
||
1191 | * |
||
1192 | * @param int $locationId |
||
1193 | * @param int $parentId |
||
1194 | * @param int[] $languageIds Language IDs of removed Translations |
||
1195 | */ |
||
1196 | public function archiveUrlAliasesForDeletedTranslations($locationId, $parentId, array $languageIds) |
||
1238 | |||
1239 | /** |
||
1240 | * Load list of aliases for given $locationId matching any of the Languages specified by $languageMask. |
||
1241 | * |
||
1242 | * @param int $locationId |
||
1243 | * @param int[] $languageIds |
||
1244 | * |
||
1245 | * @return array[]|\Generator |
||
1246 | */ |
||
1247 | private function loadLocationEntriesMatchingMultipleLanguages($locationId, array $languageIds) |
||
1273 | |||
1274 | /** |
||
1275 | * Delete URL aliases pointing to non-existent Locations. |
||
1276 | * |
||
1277 | * @return int Number of affected rows. |
||
1278 | * |
||
1279 | * @throws \Doctrine\DBAL\DBALException |
||
1280 | */ |
||
1281 | public function deleteUrlAliasesWithoutLocation(): int |
||
1317 | |||
1318 | /** |
||
1319 | * Delete URL aliases pointing to non-existent parent nodes. |
||
1320 | * |
||
1321 | * @return int Number of affected rows. |
||
1322 | */ |
||
1323 | View Code Duplication | public function deleteUrlAliasesWithoutParent(): int |
|
1346 | |||
1347 | /** |
||
1348 | * Delete URL aliases which do not link to any existing URL alias node. |
||
1349 | * |
||
1350 | * Note: Typically link column value is used to determine original alias for an archived entries. |
||
1351 | */ |
||
1352 | View Code Duplication | public function deleteUrlAliasesWithBrokenLink() |
|
1372 | |||
1373 | /** |
||
1374 | * Attempt repairing data corruption for broken archived URL aliases for Location, |
||
1375 | * assuming there exists restored original (current) entry. |
||
1376 | * |
||
1377 | * @param int $locationId |
||
1378 | */ |
||
1379 | public function repairBrokenUrlAliasesForLocation(int $locationId) |
||
1445 | |||
1446 | /** |
||
1447 | * @throws \Doctrine\DBAL\DBALException |
||
1448 | */ |
||
1449 | public function deleteUrlNopAliasesWithoutChildren(): int |
||
1495 | |||
1496 | /** |
||
1497 | * @throws \Doctrine\DBAL\DBALException |
||
1498 | */ |
||
1499 | public function getAllChildrenAliases(int $parentId): array |
||
1500 | { |
||
1501 | $queryBuilder = $this->connection->createQueryBuilder(); |
||
1502 | $expressionBuilder = $queryBuilder->expr(); |
||
1503 | |||
1504 | $queryBuilder |
||
1505 | ->select('parent', 'text_md5') |
||
1506 | ->from($this->table) |
||
1507 | ->where( |
||
1508 | $expressionBuilder->eq( |
||
1509 | 'parent', |
||
1510 | $queryBuilder->createPositionalParameter($parentId, ParameterType::INTEGER) |
||
1511 | ) |
||
1512 | )->andWhere( |
||
1513 | $expressionBuilder->eq( |
||
1514 | 'is_alias', |
||
1515 | $queryBuilder->createPositionalParameter(1, ParameterType::INTEGER) |
||
1516 | ) |
||
1517 | ); |
||
1518 | |||
1519 | return $queryBuilder->execute()->fetchAll(); |
||
1520 | } |
||
1521 | |||
1522 | /** |
||
1523 | * Filter from the given result set original (current) only URL aliases and index them by language_mask. |
||
1524 | * |
||
1525 | * Note: each language_mask can have one URL Alias. |
||
1526 | * |
||
1527 | * @param array $urlAliasesData |
||
1528 | * |
||
1529 | * @return array |
||
1530 | */ |
||
1531 | private function filterOriginalAliases(array $urlAliasesData): array |
||
1546 | |||
1547 | /** |
||
1548 | * Get subquery for IDs of all URL aliases. |
||
1549 | * |
||
1550 | * @return string Query |
||
1551 | */ |
||
1552 | private function getAllUrlAliasesQuery(): string |
||
1566 | |||
1567 | /** |
||
1568 | * Get DBMS-specific integer type. |
||
1569 | * |
||
1570 | * @param \Doctrine\DBAL\Platforms\AbstractPlatform $databasePlatform |
||
1571 | * |
||
1572 | * @return string |
||
1573 | */ |
||
1574 | private function getIntegerType(AbstractPlatform $databasePlatform): string |
||
1583 | |||
1584 | /** |
||
1585 | * Get all URL aliases for the given Location (including archived ones). |
||
1586 | * |
||
1587 | * @param int $locationId |
||
1588 | * |
||
1589 | * @return array |
||
1590 | */ |
||
1591 | protected function getUrlAliasesForLocation(int $locationId): array |
||
1617 | |||
1618 | /** |
||
1619 | * Delete URL alias row by its primary composite key. |
||
1620 | * |
||
1621 | * @param int $parentId |
||
1622 | * @param string $textMD5 |
||
1623 | * |
||
1624 | * @return int number of affected rows |
||
1625 | */ |
||
1626 | private function deleteRow(int $parentId, string $textMD5): int |
||
1647 | } |
||
1648 |
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.