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 Mapper 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 Mapper, and based on these observations, apply Extract Interface, too.
1 | <?php |
||
27 | class Mapper |
||
28 | { |
||
29 | /** |
||
30 | * FieldValue converter registry. |
||
31 | * |
||
32 | * @var \eZ\Publish\Core\Persistence\Legacy\Content\FieldValue\ConverterRegistry |
||
33 | */ |
||
34 | protected $converterRegistry; |
||
35 | |||
36 | /** |
||
37 | * Caching language handler. |
||
38 | * |
||
39 | * @var \eZ\Publish\SPI\Persistence\Content\Language\Handler |
||
40 | */ |
||
41 | protected $languageHandler; |
||
42 | |||
43 | /** |
||
44 | * Creates a new mapper. |
||
45 | * |
||
46 | * @param \eZ\Publish\Core\Persistence\Legacy\Content\FieldValue\ConverterRegistry $converterRegistry |
||
47 | * @param \eZ\Publish\SPI\Persistence\Content\Language\Handler $languageHandler |
||
48 | */ |
||
49 | public function __construct(Registry $converterRegistry, LanguageHandler $languageHandler) |
||
54 | |||
55 | /** |
||
56 | * Creates a Content from the given $struct and $currentVersionNo. |
||
57 | * |
||
58 | * @param \eZ\Publish\SPI\Persistence\Content\CreateStruct $struct |
||
59 | * @param mixed $currentVersionNo |
||
60 | * |
||
61 | * @return \eZ\Publish\SPI\Persistence\Content\ContentInfo |
||
62 | */ |
||
63 | private function createContentInfoFromCreateStruct(CreateStruct $struct, $currentVersionNo = 1) |
||
64 | { |
||
65 | $contentInfo = new ContentInfo(); |
||
66 | |||
67 | $contentInfo->id = null; |
||
68 | $contentInfo->contentTypeId = $struct->typeId; |
||
69 | $contentInfo->sectionId = $struct->sectionId; |
||
70 | $contentInfo->ownerId = $struct->ownerId; |
||
71 | $contentInfo->alwaysAvailable = $struct->alwaysAvailable; |
||
72 | $contentInfo->remoteId = $struct->remoteId; |
||
73 | $contentInfo->mainLanguageCode = $this->languageHandler |
||
74 | ->load(isset($struct->mainLanguageId) ? $struct->mainLanguageId : $struct->initialLanguageId) |
||
75 | ->languageCode; |
||
76 | $contentInfo->name = isset($struct->name[$contentInfo->mainLanguageCode]) |
||
77 | ? $struct->name[$contentInfo->mainLanguageCode] |
||
78 | : ''; |
||
79 | // For drafts published and modified timestamps should be 0 |
||
80 | $contentInfo->publicationDate = 0; |
||
81 | $contentInfo->modificationDate = 0; |
||
82 | $contentInfo->currentVersionNo = $currentVersionNo; |
||
83 | $contentInfo->isPublished = false; |
||
84 | |||
85 | return $contentInfo; |
||
86 | } |
||
87 | |||
88 | /** |
||
89 | * Creates a new version for the given $struct and $versionNo. |
||
90 | * |
||
91 | * @param \eZ\Publish\SPI\Persistence\Content\CreateStruct $struct |
||
92 | * @param mixed $versionNo |
||
93 | * |
||
94 | * @return \eZ\Publish\SPI\Persistence\Content\VersionInfo |
||
95 | */ |
||
96 | public function createVersionInfoFromCreateStruct(CreateStruct $struct, $versionNo) |
||
97 | { |
||
98 | $versionInfo = new VersionInfo(); |
||
99 | |||
100 | $versionInfo->id = null; |
||
101 | $versionInfo->contentInfo = $this->createContentInfoFromCreateStruct($struct, $versionNo); |
||
102 | $versionInfo->versionNo = $versionNo; |
||
103 | $versionInfo->creatorId = $struct->ownerId; |
||
104 | $versionInfo->status = VersionInfo::STATUS_DRAFT; |
||
105 | $versionInfo->initialLanguageCode = $this->languageHandler->load($struct->initialLanguageId)->languageCode; |
||
106 | $versionInfo->creationDate = $struct->modified; |
||
107 | $versionInfo->modificationDate = $struct->modified; |
||
108 | $versionInfo->names = $struct->name; |
||
109 | |||
110 | $languages = []; |
||
111 | View Code Duplication | foreach ($struct->fields as $field) { |
|
|
|||
112 | if (!isset($languages[$field->languageCode])) { |
||
113 | $languages[$field->languageCode] = true; |
||
114 | } |
||
115 | } |
||
116 | $versionInfo->languageCodes = array_keys($languages); |
||
117 | |||
118 | return $versionInfo; |
||
119 | } |
||
120 | |||
121 | /** |
||
122 | * Creates a new version for the given $content. |
||
123 | * |
||
124 | * @param \eZ\Publish\SPI\Persistence\Content $content |
||
125 | * @param mixed $versionNo |
||
126 | * @param mixed $userId |
||
127 | * |
||
128 | * @return \eZ\Publish\SPI\Persistence\Content\VersionInfo |
||
129 | */ |
||
130 | public function createVersionInfoForContent(Content $content, $versionNo, $userId) |
||
131 | { |
||
132 | $versionInfo = new VersionInfo(); |
||
133 | |||
134 | $versionInfo->contentInfo = $content->versionInfo->contentInfo; |
||
135 | $versionInfo->versionNo = $versionNo; |
||
136 | $versionInfo->creatorId = $userId; |
||
137 | $versionInfo->status = VersionInfo::STATUS_DRAFT; |
||
138 | $versionInfo->initialLanguageCode = $content->versionInfo->initialLanguageCode; |
||
139 | $versionInfo->creationDate = time(); |
||
140 | $versionInfo->modificationDate = $versionInfo->creationDate; |
||
141 | $versionInfo->names = is_object($content->versionInfo) ? $content->versionInfo->names : array(); |
||
142 | $versionInfo->languageCodes = $content->versionInfo->languageCodes; |
||
143 | |||
144 | return $versionInfo; |
||
145 | } |
||
146 | |||
147 | /** |
||
148 | * Converts value of $field to storage value. |
||
149 | * |
||
150 | * @param \eZ\Publish\SPI\Persistence\Content\Field $field |
||
151 | * |
||
152 | * @return \eZ\Publish\Core\Persistence\Legacy\Content\StorageFieldValue |
||
153 | */ |
||
154 | public function convertToStorageValue(Field $field) |
||
155 | { |
||
156 | $converter = $this->converterRegistry->getConverter( |
||
157 | $field->type |
||
158 | ); |
||
159 | $storageValue = new StorageFieldValue(); |
||
160 | $converter->toStorageValue( |
||
161 | $field->value, |
||
162 | $storageValue |
||
163 | ); |
||
164 | |||
165 | return $storageValue; |
||
166 | } |
||
167 | |||
168 | /** |
||
169 | * Extracts Content objects (and nested) from database result $rows. |
||
170 | * |
||
171 | * Expects database rows to be indexed by keys of the format |
||
172 | * |
||
173 | * "$tableName_$columnName" |
||
174 | * |
||
175 | * @param array $rows |
||
176 | * @param array $nameRows |
||
177 | * |
||
178 | * @return \eZ\Publish\SPI\Persistence\Content[] |
||
179 | */ |
||
180 | public function extractContentFromRows(array $rows, array $nameRows) |
||
181 | { |
||
182 | $versionedNameData = array(); |
||
183 | foreach ($nameRows as $row) { |
||
184 | $contentId = (int)$row['ezcontentobject_name_contentobject_id']; |
||
185 | $versionNo = (int)$row['ezcontentobject_name_content_version']; |
||
186 | $versionedNameData[$contentId][$versionNo][$row['ezcontentobject_name_content_translation']] = $row['ezcontentobject_name_name']; |
||
187 | } |
||
188 | |||
189 | $contentInfos = array(); |
||
190 | $versionInfos = array(); |
||
191 | $fields = array(); |
||
192 | |||
193 | foreach ($rows as $row) { |
||
194 | $contentId = (int)$row['ezcontentobject_id']; |
||
195 | if (!isset($contentInfos[$contentId])) { |
||
196 | $contentInfos[$contentId] = $this->extractContentInfoFromRow($row, 'ezcontentobject_'); |
||
197 | } |
||
198 | if (!isset($versionInfos[$contentId])) { |
||
199 | $versionInfos[$contentId] = array(); |
||
200 | } |
||
201 | |||
202 | $versionId = (int)$row['ezcontentobject_version_id']; |
||
203 | if (!isset($versionInfos[$contentId][$versionId])) { |
||
204 | $versionInfos[$contentId][$versionId] = $this->extractVersionInfoFromRow($row); |
||
205 | } |
||
206 | |||
207 | $fieldId = (int)$row['ezcontentobject_attribute_id']; |
||
208 | View Code Duplication | if (!isset($fields[$contentId][$versionId][$fieldId])) { |
|
209 | $fields[$contentId][$versionId][$fieldId] = $this->extractFieldFromRow($row); |
||
210 | } |
||
211 | } |
||
212 | |||
213 | $results = array(); |
||
214 | foreach ($contentInfos as $contentId => $contentInfo) { |
||
215 | foreach ($versionInfos[$contentId] as $versionId => $versionInfo) { |
||
216 | // Fallback to just main language name if versioned name data is missing |
||
217 | View Code Duplication | if (isset($versionedNameData[$contentId][$versionInfo->versionNo])) { |
|
218 | $names = $versionedNameData[$contentId][$versionInfo->versionNo]; |
||
219 | } else { |
||
220 | $names = [$contentInfo->mainLanguageCode => $contentInfo->name]; |
||
221 | } |
||
222 | |||
223 | $content = new Content(); |
||
224 | $content->versionInfo = $versionInfo; |
||
225 | $content->versionInfo->names = $names; |
||
226 | $content->versionInfo->contentInfo = $contentInfo; |
||
227 | $content->fields = array_values($fields[$contentId][$versionId]); |
||
228 | $results[] = $content; |
||
229 | } |
||
230 | } |
||
231 | |||
232 | return $results; |
||
233 | } |
||
234 | |||
235 | /** |
||
236 | * Extracts a ContentInfo object from $row. |
||
237 | * |
||
238 | * @param array $row |
||
239 | * @param string $prefix Prefix for row keys, which are initially mapped by ezcontentobject fields |
||
240 | * @param string $treePrefix Prefix for tree row key, which are initially mapped by ezcontentobject_tree_ fields |
||
241 | * |
||
242 | * @return \eZ\Publish\SPI\Persistence\Content\ContentInfo |
||
243 | */ |
||
244 | public function extractContentInfoFromRow(array $row, $prefix = '', $treePrefix = 'ezcontentobject_tree_') |
||
245 | { |
||
246 | $contentInfo = new ContentInfo(); |
||
247 | $contentInfo->id = (int)$row["{$prefix}id"]; |
||
248 | $contentInfo->name = $row["{$prefix}name"]; |
||
249 | $contentInfo->contentTypeId = (int)$row["{$prefix}contentclass_id"]; |
||
250 | $contentInfo->sectionId = (int)$row["{$prefix}section_id"]; |
||
251 | $contentInfo->currentVersionNo = (int)$row["{$prefix}current_version"]; |
||
252 | $contentInfo->isPublished = (bool)($row["{$prefix}status"] == ContentInfo::STATUS_PUBLISHED); |
||
253 | $contentInfo->ownerId = (int)$row["{$prefix}owner_id"]; |
||
254 | $contentInfo->publicationDate = (int)$row["{$prefix}published"]; |
||
255 | $contentInfo->modificationDate = (int)$row["{$prefix}modified"]; |
||
256 | $contentInfo->alwaysAvailable = (int)$row["{$prefix}language_mask"] & 1; |
||
257 | $contentInfo->mainLanguageCode = $this->languageHandler->load($row["{$prefix}initial_language_id"])->languageCode; |
||
258 | $contentInfo->remoteId = $row["{$prefix}remote_id"]; |
||
259 | $contentInfo->mainLocationId = ($row["{$treePrefix}main_node_id"] !== null ? (int)$row["{$treePrefix}main_node_id"] : null); |
||
260 | |||
261 | return $contentInfo; |
||
262 | } |
||
263 | |||
264 | /** |
||
265 | * Extracts ContentInfo objects from $rows. |
||
266 | * |
||
267 | * @param array $rows |
||
268 | * @param string $prefix Prefix for row keys, which are initially mapped by ezcontentobject fields |
||
269 | * @param string $treePrefix Prefix for tree row key, which are initially mapped by ezcontentobject_tree_ fields |
||
270 | * |
||
271 | * @return \eZ\Publish\SPI\Persistence\Content\ContentInfo[] |
||
272 | */ |
||
273 | public function extractContentInfoFromRows(array $rows, $prefix = '', $treePrefix = 'ezcontentobject_tree_') |
||
274 | { |
||
275 | $contentInfoObjects = array(); |
||
276 | foreach ($rows as $row) { |
||
277 | $contentInfoObjects[] = $this->extractContentInfoFromRow($row, $prefix, $treePrefix); |
||
278 | } |
||
279 | |||
280 | return $contentInfoObjects; |
||
281 | } |
||
282 | |||
283 | /** |
||
284 | * Extracts a VersionInfo object from $row. |
||
285 | * |
||
286 | * This method will return VersionInfo with incomplete data. It is intended to be used only by |
||
287 | * {@link self::extractContentFromRows} where missing data will be filled in. |
||
288 | * |
||
289 | * @param array $row |
||
290 | * @param array $names |
||
291 | * |
||
292 | * @return \eZ\Publish\SPI\Persistence\Content\VersionInfo |
||
293 | */ |
||
294 | private function extractVersionInfoFromRow(array $row, array $names = array()) |
||
295 | { |
||
296 | $versionInfo = new VersionInfo(); |
||
297 | $versionInfo->id = (int)$row['ezcontentobject_version_id']; |
||
298 | $versionInfo->contentInfo = null; |
||
299 | $versionInfo->versionNo = (int)$row['ezcontentobject_version_version']; |
||
300 | $versionInfo->creatorId = (int)$row['ezcontentobject_version_creator_id']; |
||
301 | $versionInfo->creationDate = (int)$row['ezcontentobject_version_created']; |
||
302 | $versionInfo->modificationDate = (int)$row['ezcontentobject_version_modified']; |
||
303 | $versionInfo->initialLanguageCode = $this->languageHandler->load($row['ezcontentobject_version_initial_language_id'])->languageCode; |
||
304 | $versionInfo->languageCodes = $this->extractLanguageCodesFromMask($row['ezcontentobject_version_language_mask']); |
||
305 | $versionInfo->status = (int)$row['ezcontentobject_version_status']; |
||
306 | $versionInfo->names = $names; |
||
307 | |||
308 | return $versionInfo; |
||
309 | } |
||
310 | |||
311 | /** |
||
312 | * Extracts a VersionInfo object from $row. |
||
313 | * |
||
314 | * @param array $rows |
||
315 | * @param array $nameRows |
||
316 | * |
||
317 | * @return \eZ\Publish\SPI\Persistence\Content\VersionInfo[] |
||
318 | */ |
||
319 | public function extractVersionInfoListFromRows(array $rows, array $nameRows) |
||
320 | { |
||
321 | $nameData = array(); |
||
322 | foreach ($nameRows as $row) { |
||
323 | $versionId = $row['ezcontentobject_name_contentobject_id'] . '_' . $row['ezcontentobject_name_content_version']; |
||
324 | $nameData[$versionId][$row['ezcontentobject_name_content_translation']] = $row['ezcontentobject_name_name']; |
||
325 | } |
||
326 | |||
327 | $versionInfoList = array(); |
||
328 | foreach ($rows as $row) { |
||
329 | $versionId = $row['ezcontentobject_id'] . '_' . $row['ezcontentobject_version_version']; |
||
330 | if (!isset($versionInfoList[$versionId])) { |
||
331 | $versionInfo = new VersionInfo(); |
||
332 | $versionInfo->id = (int)$row['ezcontentobject_version_id']; |
||
333 | $versionInfo->contentInfo = $this->extractContentInfoFromRow($row, 'ezcontentobject_'); |
||
334 | $versionInfo->versionNo = (int)$row['ezcontentobject_version_version']; |
||
335 | $versionInfo->creatorId = (int)$row['ezcontentobject_version_creator_id']; |
||
336 | $versionInfo->creationDate = (int)$row['ezcontentobject_version_created']; |
||
337 | $versionInfo->modificationDate = (int)$row['ezcontentobject_version_modified']; |
||
338 | $versionInfo->initialLanguageCode = $this->languageHandler->load($row['ezcontentobject_version_initial_language_id'])->languageCode; |
||
339 | $versionInfo->languageCodes = $this->extractLanguageCodesFromMask((int)$row['ezcontentobject_version_language_mask']); |
||
340 | $versionInfo->status = (int)$row['ezcontentobject_version_status']; |
||
341 | $versionInfo->names = $nameData[$versionId]; |
||
342 | $versionInfoList[$versionId] = $versionInfo; |
||
343 | } |
||
344 | } |
||
345 | |||
346 | return array_values($versionInfoList); |
||
347 | } |
||
348 | |||
349 | /** |
||
350 | * @param int $languageMask |
||
351 | * |
||
352 | * @return string[] |
||
353 | */ |
||
354 | View Code Duplication | public function extractLanguageCodesFromMask($languageMask) |
|
355 | { |
||
356 | $exp = 2; |
||
357 | $result = []; |
||
358 | |||
359 | // Decomposition of $languageMask into its binary components. |
||
360 | while ($exp <= $languageMask) { |
||
361 | if ($languageMask & $exp) { |
||
362 | $result[] = $this->languageHandler->load($exp)->languageCode; |
||
363 | } |
||
364 | |||
365 | $exp *= 2; |
||
366 | } |
||
367 | |||
368 | return $result; |
||
369 | } |
||
370 | |||
371 | /** |
||
372 | * Extracts a Field from $row. |
||
373 | * |
||
374 | * @param array $row |
||
375 | * |
||
376 | * @return Field |
||
377 | */ |
||
378 | protected function extractFieldFromRow(array $row) |
||
379 | { |
||
380 | $field = new Field(); |
||
381 | |||
382 | $field->id = (int)$row['ezcontentobject_attribute_id']; |
||
383 | $field->fieldDefinitionId = (int)$row['ezcontentobject_attribute_contentclassattribute_id']; |
||
384 | $field->type = $row['ezcontentobject_attribute_data_type_string']; |
||
385 | $field->value = $this->extractFieldValueFromRow($row, $field->type); |
||
386 | $field->languageCode = $row['ezcontentobject_attribute_language_code']; |
||
387 | $field->versionNo = (int)$row['ezcontentobject_attribute_version']; |
||
388 | |||
389 | return $field; |
||
390 | } |
||
391 | |||
392 | /** |
||
393 | * Extracts a FieldValue of $type from $row. |
||
394 | * |
||
395 | * @param array $row |
||
396 | * @param string $type |
||
397 | * |
||
398 | * @return \eZ\Publish\SPI\Persistence\Content\FieldValue |
||
399 | * |
||
400 | * @throws \eZ\Publish\Core\Persistence\Legacy\Content\FieldValue\Converter\Exception\NotFound |
||
401 | * if the necessary converter for $type could not be found. |
||
402 | */ |
||
403 | protected function extractFieldValueFromRow(array $row, $type) |
||
404 | { |
||
405 | $storageValue = new StorageFieldValue(); |
||
406 | |||
407 | // Nullable field |
||
408 | $storageValue->dataFloat = isset($row['ezcontentobject_attribute_data_float']) |
||
409 | ? (float)$row['ezcontentobject_attribute_data_float'] |
||
410 | : null; |
||
411 | // Nullable field |
||
412 | $storageValue->dataInt = isset($row['ezcontentobject_attribute_data_int']) |
||
413 | ? (int)$row['ezcontentobject_attribute_data_int'] |
||
414 | : null; |
||
415 | $storageValue->dataText = $row['ezcontentobject_attribute_data_text']; |
||
416 | // Not nullable field |
||
417 | $storageValue->sortKeyInt = (int)$row['ezcontentobject_attribute_sort_key_int']; |
||
418 | $storageValue->sortKeyString = $row['ezcontentobject_attribute_sort_key_string']; |
||
419 | |||
420 | $fieldValue = new FieldValue(); |
||
421 | |||
422 | $converter = $this->converterRegistry->getConverter($type); |
||
423 | $converter->toFieldValue($storageValue, $fieldValue); |
||
424 | |||
425 | return $fieldValue; |
||
426 | } |
||
427 | |||
428 | /** |
||
429 | * Creates CreateStruct from $content. |
||
430 | * |
||
431 | * @param \eZ\Publish\SPI\Persistence\Content $content |
||
432 | * |
||
433 | * @return \eZ\Publish\SPI\Persistence\Content\CreateStruct |
||
434 | */ |
||
435 | public function createCreateStructFromContent(Content $content) |
||
436 | { |
||
437 | $struct = new CreateStruct(); |
||
438 | $struct->name = $content->versionInfo->names; |
||
439 | $struct->typeId = $content->versionInfo->contentInfo->contentTypeId; |
||
440 | $struct->sectionId = $content->versionInfo->contentInfo->sectionId; |
||
441 | $struct->ownerId = $content->versionInfo->contentInfo->ownerId; |
||
442 | $struct->locations = array(); |
||
443 | $struct->alwaysAvailable = $content->versionInfo->contentInfo->alwaysAvailable; |
||
444 | $struct->remoteId = md5(uniqid(get_class($this), true)); |
||
445 | $struct->initialLanguageId = $this->languageHandler->loadByLanguageCode($content->versionInfo->initialLanguageCode)->id; |
||
446 | $struct->mainLanguageId = $this->languageHandler->loadByLanguageCode($content->versionInfo->contentInfo->mainLanguageCode)->id; |
||
447 | $struct->modified = time(); |
||
448 | |||
449 | foreach ($content->fields as $field) { |
||
450 | $newField = clone $field; |
||
451 | $newField->id = null; |
||
452 | $struct->fields[] = $newField; |
||
453 | } |
||
454 | |||
455 | return $struct; |
||
456 | } |
||
457 | |||
458 | /** |
||
459 | * Extracts relation objects from $rows. |
||
460 | */ |
||
461 | public function extractRelationsFromRows(array $rows) |
||
474 | |||
475 | /** |
||
476 | * Extracts a Relation object from a $row. |
||
477 | * |
||
478 | * @param array $row Associative array representing a relation |
||
479 | * |
||
480 | * @return \eZ\Publish\SPI\Persistence\Content\Relation |
||
481 | */ |
||
482 | protected function extractRelationFromRow(array $row) |
||
498 | |||
499 | /** |
||
500 | * Creates a Content from the given $struct. |
||
501 | * |
||
502 | * @param \eZ\Publish\SPI\Persistence\Content\Relation\CreateStruct $struct |
||
503 | * |
||
504 | * @return \eZ\Publish\SPI\Persistence\Content\Relation |
||
505 | */ |
||
506 | public function createRelationFromCreateStruct(RelationCreateStruct $struct) |
||
518 | } |
||
519 |
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.