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 Offer 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 Offer, and based on these observations, apply Extract Interface, too.
1 | <?php |
||
41 | abstract class Offer extends EventSourcedAggregateRoot |
||
42 | { |
||
43 | const DUPLICATE_REASON = 'duplicate'; |
||
44 | const INAPPROPRIATE_REASON = 'inappropriate'; |
||
45 | |||
46 | /** |
||
47 | * @var LabelCollection |
||
48 | */ |
||
49 | protected $labels; |
||
50 | |||
51 | /** |
||
52 | * @var UUID[] |
||
53 | */ |
||
54 | protected $mediaObjects = []; |
||
55 | |||
56 | /** |
||
57 | * @var UUID |
||
58 | */ |
||
59 | protected $mainImageId; |
||
60 | |||
61 | /** |
||
62 | * @var string |
||
63 | * |
||
64 | * Organizer ids can come from UDB2 which does not strictly use UUIDs. |
||
65 | */ |
||
66 | protected $organizerId; |
||
67 | |||
68 | /** |
||
69 | * @var WorkflowStatus |
||
70 | */ |
||
71 | protected $workflowStatus; |
||
72 | |||
73 | /** |
||
74 | * @var StringLiteral|null |
||
75 | */ |
||
76 | protected $rejectedReason; |
||
77 | |||
78 | /** |
||
79 | * @var PriceInfo |
||
80 | */ |
||
81 | protected $priceInfo; |
||
82 | |||
83 | /** |
||
84 | * Offer constructor. |
||
85 | */ |
||
86 | public function __construct() |
||
87 | { |
||
88 | $this->resetLabels(); |
||
89 | } |
||
90 | |||
91 | /** |
||
92 | * @return LabelCollection |
||
93 | */ |
||
94 | public function getLabels() |
||
98 | |||
99 | /** |
||
100 | * @param \CultureFeed_Cdb_Item_Base $udb2Item |
||
101 | */ |
||
102 | View Code Duplication | protected function setLabelsFromUDB2Item(\CultureFeed_Cdb_Item_Base $udb2Item) |
|
116 | |||
117 | /** |
||
118 | * Get the id of the main image if one is selected for this offer. |
||
119 | * |
||
120 | * @return UUID|null |
||
121 | */ |
||
122 | protected function getMainImageId() |
||
126 | |||
127 | /** |
||
128 | * @param Label $label |
||
129 | */ |
||
130 | public function addLabel(Label $label) |
||
138 | |||
139 | /** |
||
140 | * @param Label $label |
||
141 | */ |
||
142 | public function deleteLabel(Label $label) |
||
150 | |||
151 | /** |
||
152 | * @param Language $language |
||
153 | * @param StringLiteral $title |
||
154 | */ |
||
155 | public function translateTitle(Language $language, StringLiteral $title) |
||
161 | |||
162 | /** |
||
163 | * @param Language $language |
||
164 | * @param StringLiteral $description |
||
165 | */ |
||
166 | public function translateDescription(Language $language, StringLiteral $description) |
||
172 | |||
173 | |||
174 | /** |
||
175 | * @param string $description |
||
176 | */ |
||
177 | public function updateDescription($description) |
||
183 | |||
184 | /** |
||
185 | * @param string $typicalAgeRange |
||
186 | */ |
||
187 | public function updateTypicalAgeRange($typicalAgeRange) |
||
193 | |||
194 | public function deleteTypicalAgeRange() |
||
200 | |||
201 | /** |
||
202 | * @param string $organizerId |
||
203 | */ |
||
204 | public function updateOrganizer($organizerId) |
||
212 | |||
213 | /** |
||
214 | * Delete the given organizer. |
||
215 | * |
||
216 | * @param string $organizerId |
||
217 | */ |
||
218 | public function deleteOrganizer($organizerId) |
||
226 | |||
227 | /** |
||
228 | * Updated the contact info. |
||
229 | * @param ContactPoint $contactPoint |
||
230 | */ |
||
231 | public function updateContactPoint(ContactPoint $contactPoint) |
||
237 | |||
238 | /** |
||
239 | * Updated the booking info. |
||
240 | * |
||
241 | * @param BookingInfo $bookingInfo |
||
242 | */ |
||
243 | public function updateBookingInfo(BookingInfo $bookingInfo) |
||
249 | |||
250 | /** |
||
251 | * @param PriceInfo $priceInfo |
||
252 | */ |
||
253 | public function updatePriceInfo(PriceInfo $priceInfo) |
||
261 | |||
262 | /** |
||
263 | * @param AbstractPriceInfoUpdated $priceInfoUpdated |
||
264 | */ |
||
265 | protected function applyPriceInfoUpdated(AbstractPriceInfoUpdated $priceInfoUpdated) |
||
269 | |||
270 | /** |
||
271 | * @param AbstractLabelAdded $labelAdded |
||
272 | */ |
||
273 | protected function applyLabelAdded(AbstractLabelAdded $labelAdded) |
||
281 | |||
282 | /** |
||
283 | * @param AbstractLabelDeleted $labelDeleted |
||
284 | */ |
||
285 | protected function applyLabelDeleted(AbstractLabelDeleted $labelDeleted) |
||
291 | |||
292 | protected function resetLabels() |
||
296 | |||
297 | /** |
||
298 | * @param Image $image |
||
299 | * @return boolean |
||
300 | */ |
||
301 | private function containsImage(Image $image) |
||
314 | |||
315 | /** |
||
316 | * Add a new image. |
||
317 | * |
||
318 | * @param Image $image |
||
319 | */ |
||
320 | public function addImage(Image $image) |
||
328 | |||
329 | /** |
||
330 | * @param AbstractUpdateImage $updateImageCommand |
||
331 | */ |
||
332 | public function updateImage(AbstractUpdateImage $updateImageCommand) |
||
338 | |||
339 | /** |
||
340 | * Remove an image. |
||
341 | * |
||
342 | * @param Image $image |
||
343 | */ |
||
344 | public function removeImage(Image $image) |
||
352 | |||
353 | /** |
||
354 | * Make an existing image of the item the main image. |
||
355 | * |
||
356 | * @param Image $image |
||
357 | */ |
||
358 | public function selectMainImage(Image $image) |
||
370 | |||
371 | /** |
||
372 | * Delete the offer. |
||
373 | */ |
||
374 | public function delete() |
||
380 | |||
381 | /** |
||
382 | * @param CultureFeed_Cdb_Item_Base $cdbItem |
||
383 | */ |
||
384 | protected function importWorkflowStatus(CultureFeed_Cdb_Item_Base $cdbItem) |
||
393 | |||
394 | /** |
||
395 | * Publish the offer when it has workflowstatus draft. |
||
396 | * @param \DateTimeInterface $publicationDate |
||
397 | */ |
||
398 | public function publish(\DateTimeInterface $publicationDate) |
||
404 | |||
405 | /** |
||
406 | * @return bool |
||
407 | * @throws Exception |
||
408 | */ |
||
409 | View Code Duplication | private function guardPublish() |
|
421 | |||
422 | /** |
||
423 | * Approve the offer when it's waiting for validation. |
||
424 | */ |
||
425 | public function approve() |
||
429 | |||
430 | /** |
||
431 | * @return bool |
||
432 | * @throws Exception |
||
433 | */ |
||
434 | View Code Duplication | private function guardApprove() |
|
446 | |||
447 | /** |
||
448 | * Reject an offer that is waiting for validation with a given reason. |
||
449 | * @param StringLiteral $reason |
||
450 | */ |
||
451 | public function reject(StringLiteral $reason) |
||
455 | |||
456 | public function flagAsDuplicate() |
||
461 | |||
462 | public function flagAsInappropriate() |
||
467 | |||
468 | /** |
||
469 | * @param StringLiteral $reason |
||
470 | * @return bool |
||
471 | * false when the offer can still be rejected, true when the offer is already rejected for the same reason |
||
472 | * @throws Exception |
||
473 | */ |
||
474 | private function guardRejection(StringLiteral $reason) |
||
490 | |||
491 | /** |
||
492 | * @param AbstractPublished $published |
||
493 | */ |
||
494 | protected function applyPublished(AbstractPublished $published) |
||
498 | |||
499 | /** |
||
500 | * @param AbstractApproved $approved |
||
501 | */ |
||
502 | protected function applyApproved(AbstractApproved $approved) |
||
506 | |||
507 | /** |
||
508 | * @param AbstractRejected $rejected |
||
509 | */ |
||
510 | protected function applyRejected(AbstractRejected $rejected) |
||
515 | |||
516 | /** |
||
517 | * @param AbstractFlaggedAsDuplicate $flaggedAsDuplicate |
||
518 | */ |
||
519 | protected function applyFlaggedAsDuplicate(AbstractFlaggedAsDuplicate $flaggedAsDuplicate) |
||
524 | |||
525 | /** |
||
526 | * @param AbstractFlaggedAsInappropriate $flaggedAsInappropriate |
||
527 | */ |
||
528 | protected function applyFlaggedAsInappropriate(AbstractFlaggedAsInappropriate $flaggedAsInappropriate) |
||
533 | |||
534 | View Code Duplication | protected function applyImageAdded(AbstractImageAdded $imageAdded) |
|
543 | |||
544 | View Code Duplication | protected function applyImageRemoved(AbstractImageRemoved $imageRemoved) |
|
556 | |||
557 | protected function applyMainImageSelected(AbstractMainImageSelected $mainImageSelected) |
||
561 | |||
562 | protected function applyOrganizerUpdated(AbstractOrganizerUpdated $organizerUpdated) |
||
566 | |||
567 | protected function applyOrganizerDeleted(AbstractOrganizerDeleted $organizerDeleted) |
||
571 | |||
572 | /** |
||
573 | * @param Label $label |
||
574 | * @return AbstractLabelAdded |
||
575 | */ |
||
576 | abstract protected function createLabelAddedEvent(Label $label); |
||
577 | |||
578 | /** |
||
579 | * @param Label $label |
||
580 | * @return AbstractLabelDeleted |
||
581 | */ |
||
582 | abstract protected function createLabelDeletedEvent(Label $label); |
||
583 | |||
584 | /** |
||
585 | * @param Language $language |
||
586 | * @param StringLiteral $title |
||
587 | * @return AbstractTitleTranslated |
||
588 | */ |
||
589 | abstract protected function createTitleTranslatedEvent(Language $language, StringLiteral $title); |
||
590 | |||
591 | /** |
||
592 | * @param Language $language |
||
593 | * @param StringLiteral $description |
||
594 | * @return AbstractDescriptionTranslated |
||
595 | */ |
||
596 | abstract protected function createDescriptionTranslatedEvent(Language $language, StringLiteral $description); |
||
597 | |||
598 | /** |
||
599 | * @param Image $image |
||
600 | * @return AbstractImageAdded |
||
601 | */ |
||
602 | abstract protected function createImageAddedEvent(Image $image); |
||
603 | |||
604 | /** |
||
605 | * @param Image $image |
||
606 | * @return AbstractImageRemoved |
||
607 | */ |
||
608 | abstract protected function createImageRemovedEvent(Image $image); |
||
609 | |||
610 | /** |
||
611 | * @param AbstractUpdateImage $updateImageCommand |
||
612 | * @return AbstractImageUpdated |
||
613 | */ |
||
614 | abstract protected function createImageUpdatedEvent( |
||
617 | |||
618 | /** |
||
619 | * @param Image $image |
||
620 | * @return AbstractMainImageSelected |
||
621 | */ |
||
622 | abstract protected function createMainImageSelectedEvent(Image $image); |
||
623 | |||
624 | /** |
||
625 | * @return AbstractOfferDeleted |
||
626 | */ |
||
627 | abstract protected function createOfferDeletedEvent(); |
||
628 | |||
629 | /** |
||
630 | * @param string $description |
||
631 | * @return AbstractDescriptionUpdated |
||
632 | */ |
||
633 | abstract protected function createDescriptionUpdatedEvent($description); |
||
634 | |||
635 | /** |
||
636 | * @param string $typicalAgeRange |
||
637 | * @return AbstractTypicalAgeRangeUpdated |
||
638 | */ |
||
639 | abstract protected function createTypicalAgeRangeUpdatedEvent($typicalAgeRange); |
||
640 | |||
641 | /** |
||
642 | * @return AbstractTypicalAgeRangeDeleted |
||
643 | */ |
||
644 | abstract protected function createTypicalAgeRangeDeletedEvent(); |
||
645 | |||
646 | /** |
||
647 | * @param string $organizerId |
||
648 | * @return AbstractOrganizerUpdated |
||
649 | */ |
||
650 | abstract protected function createOrganizerUpdatedEvent($organizerId); |
||
651 | |||
652 | /** |
||
653 | * @param string $organizerId |
||
654 | * @return AbstractOrganizerDeleted |
||
655 | */ |
||
656 | abstract protected function createOrganizerDeletedEvent($organizerId); |
||
657 | |||
658 | /** |
||
659 | * @param ContactPoint $contactPoint |
||
660 | * @return AbstractContactPointUpdated |
||
661 | */ |
||
662 | abstract protected function createContactPointUpdatedEvent(ContactPoint $contactPoint); |
||
663 | |||
664 | /** |
||
665 | * @param BookingInfo $bookingInfo |
||
666 | * @return AbstractBookingInfoUpdated |
||
667 | */ |
||
668 | abstract protected function createBookingInfoUpdatedEvent(BookingInfo $bookingInfo); |
||
669 | |||
670 | /** |
||
671 | * @param PriceInfo $priceInfo |
||
672 | * @return AbstractPriceInfoUpdated |
||
673 | */ |
||
674 | abstract protected function createPriceInfoUpdatedEvent(PriceInfo $priceInfo); |
||
675 | |||
676 | /** |
||
677 | * @param \DateTimeInterface $publicationDate |
||
678 | * @return AbstractPublished |
||
679 | */ |
||
680 | abstract protected function createPublishedEvent(\DateTimeInterface $publicationDate); |
||
681 | |||
682 | /** |
||
683 | * @return AbstractApproved |
||
684 | */ |
||
685 | abstract protected function createApprovedEvent(); |
||
686 | |||
687 | /** |
||
688 | * @param StringLiteral $reason |
||
689 | * @return AbstractRejected |
||
690 | */ |
||
691 | abstract protected function createRejectedEvent(StringLiteral $reason); |
||
692 | |||
693 | /** |
||
694 | * @return AbstractFlaggedAsDuplicate |
||
695 | */ |
||
696 | abstract protected function createFlaggedAsDuplicate(); |
||
697 | |||
698 | /** |
||
699 | * @return AbstractFlaggedAsInappropriate |
||
700 | */ |
||
701 | abstract protected function createFlaggedAsInappropriate(); |
||
702 | } |
||
703 |
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.