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 ContentManager 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 ContentManager, and based on these observations, apply Extract Interface, too.
| 1 | <?php |
||
| 26 | class ContentManager extends RepositoryExecutor implements MigrationGeneratorInterface |
||
| 27 | 20 | { |
|
| 28 | protected $supportedStepTypes = array('content'); |
||
| 29 | 20 | protected $supportedActions = array('create', 'load', 'update', 'delete'); |
|
| 30 | 20 | ||
| 31 | 20 | protected $contentMatcher; |
|
| 32 | protected $sectionMatcher; |
||
| 33 | protected $userMatcher; |
||
| 34 | protected $objectStateMatcher; |
||
| 35 | protected $complexFieldManager; |
||
| 36 | 4 | protected $locationManager; |
|
| 37 | protected $sortConverter; |
||
| 38 | 4 | ||
| 39 | 4 | public function __construct( |
|
| 56 | 4 | ||
| 57 | 4 | /** |
|
| 58 | 1 | * Handles the content create migration action type |
|
| 59 | 1 | */ |
|
| 60 | 4 | protected function create() |
|
| 61 | 4 | { |
|
| 62 | 1 | $contentService = $this->repository->getContentService(); |
|
| 63 | 1 | $locationService = $this->repository->getLocationService(); |
|
| 64 | $contentTypeService = $this->repository->getContentTypeService(); |
||
| 65 | 4 | ||
| 66 | 1 | $contentTypeIdentifier = $this->dsl['content_type']; |
|
| 67 | 1 | $contentTypeIdentifier = $this->referenceResolver->resolveReference($contentTypeIdentifier); |
|
| 68 | /// @todo use a contenttypematcher |
||
| 69 | 4 | $contentType = $contentTypeService->loadContentTypeByIdentifier($contentTypeIdentifier); |
|
| 70 | |||
| 71 | 4 | $contentCreateStruct = $contentService->newContentCreateStruct($contentType, $this->getLanguageCode()); |
|
| 72 | |||
| 73 | $this->setFields($contentCreateStruct, $this->dsl['attributes'], $contentType); |
||
| 74 | |||
| 75 | if (isset($this->dsl['always_available'])) { |
||
| 76 | $contentCreateStruct->alwaysAvailable = $this->dsl['always_available']; |
||
| 77 | } else { |
||
| 78 | // Could be removed when https://github.com/ezsystems/ezpublish-kernel/pull/1874 is merged, |
||
| 79 | // but we strive to support old eZ kernel versions as well... |
||
| 80 | $contentCreateStruct->alwaysAvailable = $contentType->defaultAlwaysAvailable; |
||
| 81 | } |
||
| 82 | |||
| 83 | 4 | if (isset($this->dsl['remote_id'])) { |
|
| 84 | 3 | $contentCreateStruct->remoteId = $this->dsl['remote_id']; |
|
| 85 | } |
||
| 86 | 3 | ||
| 87 | 3 | if (isset($this->dsl['section'])) { |
|
| 88 | $sectionKey = $this->referenceResolver->resolveReference($this->dsl['section']); |
||
| 89 | $section = $this->sectionMatcher->matchOneByKey($sectionKey); |
||
| 90 | $contentCreateStruct->sectionId = $section->id; |
||
| 91 | } |
||
| 92 | |||
| 93 | View Code Duplication | if (isset($this->dsl['owner'])) { |
|
|
|
|||
| 94 | 1 | $owner = $this->getUser($this->dsl['owner']); |
|
| 95 | $contentCreateStruct->ownerId = $owner->id; |
||
| 96 | 1 | } |
|
| 97 | 1 | ||
| 98 | // This is a bit tricky, as the eZPublish API does not support having a different creator and owner with only 1 version. |
||
| 99 | // We allow it, hoping that nothing gets broken because of it |
||
| 100 | if (isset($this->dsl['version_creator'])) { |
||
| 101 | $realContentOwnerId = $contentCreateStruct->ownerId; |
||
| 102 | if ($realContentOwnerId == null) { |
||
| 103 | $realContentOwnerId = $this->repository->getCurrentUser()->id; |
||
| 104 | } |
||
| 105 | $versionCreator = $this->getUser($this->dsl['version_creator']); |
||
| 106 | $contentCreateStruct->ownerId = $versionCreator->id; |
||
| 107 | } |
||
| 108 | |||
| 109 | if (isset($this->dsl['modification_date'])) { |
||
| 110 | $contentCreateStruct->modificationDate = $this->toDateTime($this->dsl['modification_date']); |
||
| 111 | } |
||
| 112 | |||
| 113 | // instantiate a location create struct from the parent location: |
||
| 114 | // BC |
||
| 115 | $locationId = isset($this->dsl['parent_location']) ? $this->dsl['parent_location'] : ( |
||
| 116 | isset($this->dsl['main_location']) ? $this->dsl['main_location'] : null |
||
| 117 | ); |
||
| 118 | // 1st resolve references |
||
| 119 | $locationId = $this->referenceResolver->resolveReference($locationId); |
||
| 120 | // 2nd allow to specify the location via remote_id |
||
| 121 | 1 | $locationId = $this->locationManager->matchLocationByKey($locationId)->id; |
|
| 122 | $locationCreateStruct = $locationService->newLocationCreateStruct($locationId); |
||
| 123 | 1 | ||
| 124 | if (isset($this->dsl['location_remote_id'])) { |
||
| 125 | $locationCreateStruct->remoteId = $this->dsl['location_remote_id']; |
||
| 126 | } |
||
| 127 | 1 | ||
| 128 | if (isset($this->dsl['priority'])) { |
||
| 129 | 1 | $locationCreateStruct->priority = $this->dsl['priority']; |
|
| 130 | 1 | } |
|
| 131 | |||
| 132 | 1 | if (isset($this->dsl['is_hidden'])) { |
|
| 133 | 1 | $locationCreateStruct->hidden = $this->dsl['is_hidden']; |
|
| 134 | 1 | } |
|
| 135 | |||
| 136 | 1 | if (isset($this->dsl['sort_field'])) { |
|
| 137 | $locationCreateStruct->sortField = $this->sortConverter->hash2SortField($this->dsl['sort_field']); |
||
| 138 | 1 | } else { |
|
| 139 | 1 | $locationCreateStruct->sortField = $contentType->defaultSortField; |
|
| 140 | 1 | } |
|
| 141 | |||
| 142 | 1 | if (isset($this->dsl['sort_order'])) { |
|
| 143 | 1 | $locationCreateStruct->sortOrder = $this->sortConverter->hash2SortOrder($this->dsl['sort_order']); |
|
| 144 | 1 | } else { |
|
| 145 | $locationCreateStruct->sortOrder = $contentType->defaultSortOrder; |
||
| 146 | 1 | } |
|
| 147 | |||
| 148 | $locations = array($locationCreateStruct); |
||
| 149 | |||
| 150 | // BC |
||
| 151 | $other_locations = isset($this->dsl['other_parent_locations']) ? $this->dsl['other_parent_locations'] : ( |
||
| 152 | isset($this->dsl['other_locations']) ? $this->dsl['other_locations'] : null |
||
| 153 | ); |
||
| 154 | if (isset($other_locations)) { |
||
| 155 | foreach ($other_locations as $locationId) { |
||
| 156 | $locationId = $this->referenceResolver->resolveReference($locationId); |
||
| 157 | $locationId = $this->locationManager->matchLocationByKey($locationId)->id; |
||
| 158 | $secondaryLocationCreateStruct = $locationService->newLocationCreateStruct($locationId); |
||
| 159 | array_push($locations, $secondaryLocationCreateStruct); |
||
| 160 | } |
||
| 161 | 1 | } |
|
| 162 | 1 | ||
| 163 | 1 | // create a draft using the content and location create struct and publish it |
|
| 164 | $draft = $contentService->createContent($contentCreateStruct, $locations); |
||
| 165 | $content = $contentService->publishVersion($draft->versionInfo); |
||
| 166 | |||
| 167 | if (isset($this->dsl['object_states'])) { |
||
| 168 | 3 | $this->setObjectStates($content, $this->dsl['object_states']); |
|
| 169 | } |
||
| 170 | 3 | ||
| 171 | // 2nd part of the hack: re-set the content owner to its intended value |
||
| 172 | 3 | if (isset($this->dsl['version_creator']) || isset($this->dsl['publication_date'])) { |
|
| 173 | $contentMetaDataUpdateStruct = $contentService->newContentMetadataUpdateStruct(); |
||
| 174 | 3 | ||
| 175 | if (isset($this->dsl['version_creator'])) { |
||
| 176 | 3 | $contentMetaDataUpdateStruct->ownerId = $realContentOwnerId; |
|
| 177 | 3 | } |
|
| 178 | if (isset($this->dsl['publication_date'])) { |
||
| 179 | $contentMetaDataUpdateStruct->publishedDate = $this->toDateTime($this->dsl['publication_date']); |
||
| 180 | } |
||
| 181 | 3 | ||
| 182 | 3 | $contentService->updateContentMetadata($content->contentInfo, $contentMetaDataUpdateStruct); |
|
| 183 | } |
||
| 184 | |||
| 185 | $this->setReferences($content); |
||
| 186 | |||
| 187 | return $content; |
||
| 188 | } |
||
| 189 | 3 | ||
| 190 | View Code Duplication | protected function load() |
|
| 202 | 1 | ||
| 203 | /** |
||
| 204 | 3 | * Handles the content update migration action type |
|
| 205 | * |
||
| 206 | * @todo handle updating of more metadata fields |
||
| 207 | 3 | */ |
|
| 208 | 3 | protected function update() |
|
| 290 | 1 | ||
| 291 | /** |
||
| 292 | * Handles the content delete migration action type |
||
| 293 | */ |
||
| 294 | protected function delete() |
||
| 311 | |||
| 312 | /** |
||
| 313 | * @param string $action |
||
| 314 | * @return ContentCollection |
||
| 315 | 3 | * @throws \Exception |
|
| 316 | */ |
||
| 317 | 3 | protected function matchContents($action) |
|
| 337 | |||
| 338 | 3 | /** |
|
| 339 | * Sets references to certain content attributes. |
||
| 340 | * |
||
| 341 | * @param \eZ\Publish\API\Repository\Values\Content\Content|ContentCollection $content |
||
| 342 | * @throws \InvalidArgumentException When trying to set a reference to an unsupported attribute |
||
| 343 | * @return boolean |
||
| 344 | * |
||
| 345 | * @todo add support for other attributes: contentTypeId, contentTypeIdentifier, section, etc... ? |
||
| 346 | */ |
||
| 347 | protected function setReferences($content) |
||
| 447 | |||
| 448 | /** |
||
| 449 | * @param array $matchCondition |
||
| 450 | * @param string $mode |
||
| 451 | * @throws \Exception |
||
| 452 | * @return array |
||
| 453 | * |
||
| 454 | * @todo add support for dumping all object languages |
||
| 455 | * @todo add 2ndary locations when in 'update' mode |
||
| 456 | * @todo add dumping of sort_field and sort_order for 2ndary locations |
||
| 457 | */ |
||
| 458 | public function generateMigration(array $matchCondition, $mode) |
||
| 559 | |||
| 560 | /** |
||
| 561 | * Helper function to set the fields of a ContentCreateStruct based on the DSL attribute settings. |
||
| 562 | * |
||
| 563 | * @param ContentCreateStruct|ContentUpdateStruct $createOrUpdateStruct |
||
| 564 | * @param ContentType $contentType |
||
| 565 | * @param array $fields see description of expected format in code below |
||
| 566 | * @throws \Exception |
||
| 567 | */ |
||
| 568 | protected function setFields($createOrUpdateStruct, array $fields, ContentType $contentType) |
||
| 598 | |||
| 599 | View Code Duplication | protected function setSection(Content $content, $sectionKey) |
|
| 607 | |||
| 608 | protected function setObjectStates(Content $content, array $stateKeys) |
||
| 619 | |||
| 620 | /** |
||
| 621 | * Create the field value from the migration definition hash |
||
| 622 | * |
||
| 623 | * @param mixed $value |
||
| 624 | * @param FieldDefinition $fieldDefinition |
||
| 625 | * @param string $contentTypeIdentifier |
||
| 626 | * @param array $context |
||
| 627 | * @throws \InvalidArgumentException |
||
| 628 | * @return mixed |
||
| 629 | */ |
||
| 630 | protected function getFieldValue($value, FieldDefinition $fieldDefinition, $contentTypeIdentifier, array $context = array()) |
||
| 642 | |||
| 643 | /** |
||
| 644 | * Create the field value for a primitive field from the migration definition hash |
||
| 645 | * |
||
| 646 | * @param mixed $value |
||
| 647 | * @param FieldDefinition $fieldDefinition |
||
| 648 | * @param string $contentTypeIdentifier |
||
| 649 | * @param array $context |
||
| 650 | * @throws \InvalidArgumentException |
||
| 651 | * @return mixed |
||
| 652 | */ |
||
| 653 | protected function getSingleFieldValue($value, FieldDefinition $fieldDefinition, $contentTypeIdentifier, array $context = array()) |
||
| 663 | |||
| 664 | /** |
||
| 665 | * Load user using either login, email, id - resolving eventual references |
||
| 666 | * @param int|string $userKey |
||
| 667 | * @return \eZ\Publish\API\Repository\Values\User\User |
||
| 668 | */ |
||
| 669 | protected function getUser($userKey) |
||
| 674 | |||
| 675 | /** |
||
| 676 | * @param int|string $date if integer, we assume a timestamp |
||
| 677 | * @return \DateTime |
||
| 678 | */ |
||
| 679 | protected function toDateTime($date) |
||
| 687 | } |
||
| 688 |
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.