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 |
||
| 22 | class ContentManager extends RepositoryExecutor |
||
| 23 | { |
||
| 24 | protected $supportedStepTypes = array('content'); |
||
| 25 | |||
| 26 | protected $complexFieldManager; |
||
| 27 | 20 | protected $contentMatcher; |
|
| 28 | protected $sectionMatcher; |
||
| 29 | 20 | protected $locationManager; |
|
| 30 | 20 | ||
| 31 | 20 | public function __construct(ContentMatcher $contentMatcher, SectionMatcher $sectionMatcher, $complexFieldManager, $locationManager) |
|
| 32 | { |
||
| 33 | $this->contentMatcher = $contentMatcher; |
||
| 34 | $this->sectionMatcher = $sectionMatcher; |
||
| 35 | $this->complexFieldManager = $complexFieldManager; |
||
| 36 | 4 | $this->locationManager = $locationManager; |
|
| 37 | } |
||
| 38 | 4 | ||
| 39 | 4 | /** |
|
| 40 | 4 | * Handle the content create migration action type |
|
| 41 | */ |
||
| 42 | 4 | protected function create() |
|
| 43 | 4 | { |
|
| 44 | 3 | $contentService = $this->repository->getContentService(); |
|
| 45 | 3 | $locationService = $this->repository->getLocationService(); |
|
| 46 | 4 | $contentTypeService = $this->repository->getContentTypeService(); |
|
| 47 | |||
| 48 | 4 | $contentTypeIdentifier = $this->dsl['content_type']; |
|
| 49 | 4 | if ($this->referenceResolver->isReference($contentTypeIdentifier)) { |
|
| 50 | $contentTypeIdentifier = $this->referenceResolver->getReferenceValue($contentTypeIdentifier); |
||
| 51 | 4 | } |
|
| 52 | 1 | $contentType = $contentTypeService->loadContentTypeByIdentifier($contentTypeIdentifier); |
|
| 53 | 1 | ||
| 54 | $contentCreateStruct = $contentService->newContentCreateStruct($contentType, $this->getLanguageCode()); |
||
| 55 | $this->setFields($contentCreateStruct, $this->dsl['attributes'], $contentType); |
||
| 56 | 4 | ||
| 57 | 4 | if (array_key_exists('remote_id', $this->dsl)) { |
|
| 58 | 1 | $contentCreateStruct->remoteId = $this->dsl['remote_id']; |
|
| 59 | 1 | } |
|
| 60 | 4 | ||
| 61 | 4 | if (isset($this->dsl['section'])) { |
|
| 62 | 1 | $sectionId = $this->dsl['section']; |
|
| 63 | 1 | if ($this->referenceResolver->isReference($sectionId)) { |
|
| 64 | $sectionId = $this->referenceResolver->getReferenceValue($sectionId); |
||
| 65 | 4 | } |
|
| 66 | 1 | $contentCreateStruct->sectionId = $sectionId; |
|
| 67 | 1 | } |
|
| 68 | |||
| 69 | 4 | // instantiate a location create struct from the parent location |
|
| 70 | $locationId = $this->dsl['main_location']; |
||
| 71 | 4 | if ($this->referenceResolver->isReference($locationId)) { |
|
| 72 | $locationId = $this->referenceResolver->getReferenceValue($locationId); |
||
| 73 | } |
||
| 74 | $locationCreateStruct = $locationService->newLocationCreateStruct($locationId); |
||
| 75 | |||
| 76 | if (array_key_exists('priority', $this->dsl)) { |
||
| 77 | $locationCreateStruct->priority = $this->dsl['priority']; |
||
| 78 | } |
||
| 79 | |||
| 80 | if (isset($this->dsl['is_hidden'])) { |
||
| 81 | $locationCreateStruct->hidden = $this->dsl['is_hidden']; |
||
| 82 | } |
||
| 83 | 4 | ||
| 84 | 3 | if (isset($this->dsl['sort_order'])) { |
|
| 85 | $locationCreateStruct->sortOrder = $this->locationManager->getSortOrder($this->dsl['sort_order']); |
||
| 86 | 3 | } |
|
| 87 | 3 | ||
| 88 | if (isset($this->dsl['sort_field'])) { |
||
| 89 | $locationCreateStruct->sortField = $this->locationManager->getSortField($this->dsl['sort_field']); |
||
| 90 | } |
||
| 91 | |||
| 92 | $locations = array($locationCreateStruct); |
||
| 93 | |||
| 94 | 1 | if (array_key_exists('other_locations', $this->dsl)) { |
|
| 95 | View Code Duplication | foreach ($this->dsl['other_locations'] as $otherLocation) { |
|
|
|
|||
| 96 | 1 | $locationId = $otherLocation; |
|
| 97 | 1 | if ($this->referenceResolver->isReference($locationId)) { |
|
| 98 | $locationId = $this->referenceResolver->getReferenceValue($otherLocation); |
||
| 99 | } |
||
| 100 | $secondaryLocationCreateStruct = $locationService->newLocationCreateStruct($locationId); |
||
| 101 | array_push($locations, $secondaryLocationCreateStruct); |
||
| 102 | } |
||
| 103 | } |
||
| 104 | |||
| 105 | // create a draft using the content and location create struct and publish it |
||
| 106 | $draft = $contentService->createContent($contentCreateStruct, $locations); |
||
| 107 | $content = $contentService->publishVersion($draft->versionInfo); |
||
| 108 | |||
| 109 | $this->setReferences($content); |
||
| 110 | |||
| 111 | return $content; |
||
| 112 | } |
||
| 113 | |||
| 114 | /** |
||
| 115 | * Handle the content update migration action type |
||
| 116 | * |
||
| 117 | * @todo handle updating of more metadata fields |
||
| 118 | */ |
||
| 119 | protected function update() |
||
| 120 | { |
||
| 121 | 1 | $contentService = $this->repository->getContentService(); |
|
| 122 | $contentTypeService = $this->repository->getContentTypeService(); |
||
| 123 | 1 | ||
| 124 | /*if (isset($this->dsl['object_id'])) { |
||
| 125 | $objectId = $this->dsl['object_id']; |
||
| 126 | if ($this->referenceResolver->isReference($objectId)) { |
||
| 127 | 1 | $objectId = $this->referenceResolver->getReferenceValue($objectId); |
|
| 128 | } |
||
| 129 | 1 | $contentToUpdate = $contentService->loadContent($objectId); |
|
| 130 | 1 | $contentInfo = $contentToUpdate->contentInfo; |
|
| 131 | } else { |
||
| 132 | 1 | $remoteId = $this->dsl['remote_id']; |
|
| 133 | 1 | if ($this->referenceResolver->isReference($remoteId)) { |
|
| 134 | 1 | $remoteId = $this->referenceResolver->getReferenceValue($remoteId); |
|
| 135 | } |
||
| 136 | 1 | ||
| 137 | //try { |
||
| 138 | 1 | $contentInfo = $contentService->loadContentInfoByRemoteId($remoteId); |
|
| 139 | 1 | // disabled in v2: we disallow this. For matching location-remote-id, use the 'match' keyword |
|
| 140 | 1 | //} catch (\eZ\Publish\API\Repository\Exceptions\NotFoundException $e) { |
|
| 141 | // $location = $this->repository->getLocationService()->loadLocationByRemoteId($remoteId); |
||
| 142 | 1 | // $contentInfo = $location->contentInfo; |
|
| 143 | 1 | //} |
|
| 144 | 1 | }*/ |
|
| 145 | |||
| 146 | 1 | $contentCollection = $this->matchContents('update'); |
|
| 147 | |||
| 148 | if (count($contentCollection) > 1 && array_key_exists('references', $this->dsl)) { |
||
| 149 | throw new \Exception("Can not execute Content update because multiple contents match, and a references section is specified in the dsl. References can be set when only 1 content matches"); |
||
| 150 | } |
||
| 151 | |||
| 152 | $contentType = null; |
||
| 153 | |||
| 154 | foreach ($contentCollection as $key => $content) { |
||
| 155 | $contentInfo = $content->contentInfo; |
||
| 156 | |||
| 157 | if ($contentType == null) { |
||
| 158 | $contentType = $contentTypeService->loadContentType($contentInfo->contentTypeId); |
||
| 159 | } |
||
| 160 | |||
| 161 | 1 | $contentUpdateStruct = $contentService->newContentUpdateStruct(); |
|
| 162 | 1 | ||
| 163 | 1 | if (array_key_exists('attributes', $this->dsl)) { |
|
| 164 | $this->setFields($contentUpdateStruct, $this->dsl['attributes'], $contentType); |
||
| 165 | } |
||
| 166 | |||
| 167 | $draft = $contentService->createContentDraft($contentInfo); |
||
| 168 | 3 | $contentService->updateContent($draft->versionInfo,$contentUpdateStruct); |
|
| 169 | $content = $contentService->publishVersion($draft->versionInfo); |
||
| 170 | 3 | ||
| 171 | if (array_key_exists('new_remote_id', $this->dsl)) { |
||
| 172 | 3 | // Update object remote ID |
|
| 173 | $contentMetaDataUpdateStruct = $contentService->newContentMetadataUpdateStruct(); |
||
| 174 | 3 | $contentMetaDataUpdateStruct->remoteId = $this->dsl['new_remote_id']; |
|
| 175 | $content = $contentService->updateContentMetadata($content->contentInfo, $contentMetaDataUpdateStruct); |
||
| 176 | 3 | ||
| 177 | 3 | } |
|
| 178 | |||
| 179 | if (isset($this->dsl['section'])) { |
||
| 180 | $this->setSection($content, $this->dsl['section']); |
||
| 181 | 3 | } |
|
| 182 | 3 | ||
| 183 | $contentCollection[$key] = $content; |
||
| 184 | } |
||
| 185 | |||
| 186 | $this->setReferences($contentCollection); |
||
| 187 | |||
| 188 | return $contentCollection; |
||
| 189 | 3 | } |
|
| 190 | |||
| 191 | 3 | /** |
|
| 192 | * Handle the content delete migration action type |
||
| 193 | */ |
||
| 194 | protected function delete() |
||
| 211 | 1 | ||
| 212 | 1 | /** |
|
| 213 | 1 | * @param string $action |
|
| 214 | 1 | * @return ContentCollection |
|
| 215 | 3 | * @throws \Exception |
|
| 216 | 3 | */ |
|
| 217 | 3 | View Code Duplication | protected function matchContents($action) |
| 251 | |||
| 252 | /** |
||
| 253 | * Helper function to set the fields of a ContentCreateStruct based on the DSL attribute settings. |
||
| 254 | * |
||
| 255 | * @param ContentCreateStruct|ContentUpdateStruct $createOrUpdateStruct |
||
| 256 | * @param ContentType $contentType |
||
| 257 | * @param array $fields |
||
| 258 | */ |
||
| 259 | protected function setFields(&$createOrUpdateStruct, array $fields, ContentType $contentType) |
||
| 279 | |||
| 280 | protected function setSection(Content $content, $sectionId) |
||
| 290 | 1 | ||
| 291 | /** |
||
| 292 | * Create the field value for a primitive field |
||
| 293 | * This function is needed to get past validation on Checkbox fieldtype (eZP bug) |
||
| 294 | * |
||
| 295 | * @param mixed $value |
||
| 296 | * @param FieldDefinition $fieldDefinition |
||
| 297 | * @param array $context |
||
| 298 | * @throws \InvalidArgumentException |
||
| 299 | * @return object |
||
| 300 | */ |
||
| 301 | protected function getSingleFieldValue($value, FieldDefinition $fieldDefinition, array $context = array()) |
||
| 317 | 3 | ||
| 318 | 3 | /** |
|
| 319 | 3 | * Create the field value for a complex field eg.: ezimage, ezfile |
|
| 320 | 3 | * |
|
| 321 | 3 | * @param FieldDefinition $fieldDefinition |
|
| 322 | 3 | * @param array $fieldValueArray |
|
| 323 | 2 | * @param array $context |
|
| 324 | 2 | * @return object |
|
| 325 | 2 | */ |
|
| 326 | 1 | protected function getComplexFieldValue(array $fieldValueArray, FieldDefinition $fieldDefinition, array $context = array()) |
|
| 330 | |||
| 331 | /** |
||
| 332 | * Sets references to certain content attributes. |
||
| 333 | 3 | * The Content Manager currently supports setting references to object_id and location_id |
|
| 334 | * |
||
| 335 | 3 | * @param \eZ\Publish\API\Repository\Values\Content\Content|ContentCollection $content |
|
| 336 | 3 | * @throws \InvalidArgumentException When trying to set a reference to an unsupported attribute |
|
| 337 | * @return boolean |
||
| 338 | 3 | * |
|
| 339 | * @todo add support for other attributes: remote ids, contentTypeId, contentTypeIdentifier, section, etc... |
||
| 340 | */ |
||
| 341 | protected function setReferences($content) |
||
| 382 | } |
||
| 383 |
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.