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 EventLDProjector 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 EventLDProjector, and based on these observations, apply Extract Interface, too.
1 | <?php |
||
70 | class EventLDProjector extends OfferLDProjector implements |
||
71 | EventListenerInterface, |
||
72 | PlaceServiceInterface, |
||
73 | OrganizerServiceInterface |
||
74 | { |
||
75 | /** |
||
76 | * @var PlaceService |
||
77 | */ |
||
78 | protected $placeService; |
||
79 | |||
80 | /** |
||
81 | * @var EventServiceInterface |
||
82 | */ |
||
83 | protected $eventService; |
||
84 | |||
85 | /** |
||
86 | * @var IriOfferIdentifierFactoryInterface |
||
87 | */ |
||
88 | protected $iriOfferIdentifierFactory; |
||
89 | |||
90 | /** |
||
91 | * @var CdbXMLImporter |
||
92 | */ |
||
93 | protected $cdbXmlImporter; |
||
94 | |||
95 | /** |
||
96 | * @param DocumentRepositoryInterface $repository |
||
97 | * @param IriGeneratorInterface $iriGenerator |
||
98 | * @param EventServiceInterface $eventService |
||
99 | * @param PlaceService $placeService |
||
100 | * @param OrganizerService $organizerService |
||
101 | * @param SerializerInterface $mediaObjectSerializer |
||
102 | * @param IriOfferIdentifierFactoryInterface $iriOfferIdentifierFactory |
||
103 | * @param CdbXMLImporter $cdbXMLImporter |
||
104 | */ |
||
105 | public function __construct( |
||
106 | DocumentRepositoryInterface $repository, |
||
107 | IriGeneratorInterface $iriGenerator, |
||
108 | EventServiceInterface $eventService, |
||
109 | PlaceService $placeService, |
||
110 | OrganizerService $organizerService, |
||
111 | SerializerInterface $mediaObjectSerializer, |
||
112 | IriOfferIdentifierFactoryInterface $iriOfferIdentifierFactory, |
||
113 | CdbXMLImporter $cdbXMLImporter |
||
114 | ) { |
||
115 | parent::__construct( |
||
116 | $repository, |
||
117 | $iriGenerator, |
||
118 | $organizerService, |
||
119 | $mediaObjectSerializer |
||
120 | ); |
||
121 | |||
122 | $this->placeService = $placeService; |
||
123 | $this->eventService = $eventService; |
||
124 | $this->cdbXMLImporter = $cdbXMLImporter; |
||
|
|||
125 | |||
126 | $this->iriOfferIdentifierFactory = $iriOfferIdentifierFactory; |
||
127 | } |
||
128 | |||
129 | protected function applyOrganizerProjectedToJSONLD(OrganizerProjectedToJSONLD $organizerProjectedToJSONLD) |
||
130 | { |
||
131 | $eventIds = $this->eventsOrganizedByOrganizer( |
||
132 | $organizerProjectedToJSONLD->getId() |
||
133 | ); |
||
134 | |||
135 | $organizer = $this->organizerService->getEntity( |
||
136 | $organizerProjectedToJSONLD->getId() |
||
137 | ); |
||
138 | |||
139 | foreach ($eventIds as $eventId) { |
||
140 | $document = $this->loadDocumentFromRepositoryByItemId( |
||
141 | $eventId |
||
142 | ); |
||
143 | $eventLD = $document->getBody(); |
||
144 | |||
145 | $newEventLD = clone $eventLD; |
||
146 | $newEventLD->organizer = json_decode($organizer); |
||
147 | |||
148 | if ($newEventLD != $eventLD) { |
||
149 | $this->repository->save($document->withBody($newEventLD)); |
||
150 | } |
||
151 | } |
||
152 | } |
||
153 | |||
154 | protected function applyPlaceProjectedToJSONLD( |
||
155 | PlaceProjectedToJSONLD $placeProjectedToJSONLD |
||
156 | ) { |
||
157 | $identifier = $this->iriOfferIdentifierFactory->fromIri( |
||
158 | Url::fromNative($placeProjectedToJSONLD->getIri()) |
||
159 | ); |
||
160 | |||
161 | $eventsLocatedAtPlace = $this->eventsLocatedAtPlace( |
||
162 | $identifier->getId() |
||
163 | ); |
||
164 | |||
165 | $placeJSONLD = $this->placeService->getEntity( |
||
166 | $identifier->getId() |
||
167 | ); |
||
168 | |||
169 | foreach ($eventsLocatedAtPlace as $eventId) { |
||
170 | $document = $this->loadDocumentFromRepositoryByItemId( |
||
171 | $eventId |
||
172 | ); |
||
173 | $eventLD = $document->getBody(); |
||
174 | |||
175 | $newEventLD = clone $eventLD; |
||
176 | $newEventLD->location = json_decode($placeJSONLD); |
||
177 | |||
178 | if ($newEventLD != $eventLD) { |
||
179 | $this->repository->save($document->withBody($newEventLD)); |
||
180 | } |
||
181 | } |
||
182 | } |
||
183 | |||
184 | /** |
||
185 | * @param string $organizerId |
||
186 | * @return string[] |
||
187 | */ |
||
188 | protected function eventsOrganizedByOrganizer($organizerId) |
||
189 | { |
||
190 | return $this->eventService->eventsOrganizedByOrganizer( |
||
191 | $organizerId |
||
192 | ); |
||
193 | } |
||
194 | |||
195 | /** |
||
196 | * @param string $placeId |
||
197 | * @return string[] |
||
198 | */ |
||
199 | protected function eventsLocatedAtPlace($placeId) |
||
200 | { |
||
201 | return $this->eventService->eventsLocatedAtPlace( |
||
202 | $placeId |
||
203 | ); |
||
204 | } |
||
205 | |||
206 | /** |
||
207 | * @param EventImportedFromUDB2 $eventImportedFromUDB2 |
||
208 | */ |
||
209 | protected function applyEventImportedFromUDB2( |
||
210 | EventImportedFromUDB2 $eventImportedFromUDB2 |
||
211 | ) { |
||
212 | $this->applyEventCdbXmlFromUDB2( |
||
213 | $eventImportedFromUDB2->getEventId(), |
||
214 | $eventImportedFromUDB2->getCdbXmlNamespaceUri(), |
||
215 | $eventImportedFromUDB2->getCdbXml() |
||
216 | ); |
||
217 | } |
||
218 | |||
219 | /** |
||
220 | * @param EventUpdatedFromUDB2 $eventUpdatedFromUDB2 |
||
221 | */ |
||
222 | protected function applyEventUpdatedFromUDB2( |
||
223 | EventUpdatedFromUDB2 $eventUpdatedFromUDB2 |
||
224 | ) { |
||
225 | $this->applyEventCdbXmlFromUDB2( |
||
226 | $eventUpdatedFromUDB2->getEventId(), |
||
227 | $eventUpdatedFromUDB2->getCdbXmlNamespaceUri(), |
||
228 | $eventUpdatedFromUDB2->getCdbXml() |
||
229 | ); |
||
230 | } |
||
231 | |||
232 | /** |
||
233 | * Helper function to save JSONLD document from entryapi cdbxml. |
||
234 | * |
||
235 | * @param string $eventId |
||
236 | * @param string $cdbXmlNamespaceUri |
||
237 | * @param string $cdbXml |
||
238 | * @param DomainMessage $domainMessage |
||
239 | */ |
||
240 | protected function applyEventFromCdbXml( |
||
241 | $eventId, |
||
242 | $cdbXmlNamespaceUri, |
||
243 | $cdbXml, |
||
244 | $domainMessage |
||
245 | ) { |
||
246 | $this->saveNewDocument( |
||
247 | $eventId, |
||
248 | function (\stdClass $eventLd) use ($eventId, $cdbXmlNamespaceUri, $cdbXml, $domainMessage) { |
||
249 | $eventLd = $this->projectEventCdbXmlToObject( |
||
250 | $eventLd, |
||
251 | $eventId, |
||
252 | $cdbXmlNamespaceUri, |
||
253 | $cdbXml |
||
254 | ); |
||
255 | |||
256 | // Add creation date and update date from metadata. |
||
257 | $eventCreationDate = $domainMessage->getRecordedOn(); |
||
258 | |||
259 | $eventCreationString = $eventCreationDate->toString(); |
||
260 | $eventCreationDateTime = \DateTime::createFromFormat( |
||
261 | DateTime::FORMAT_STRING, |
||
262 | $eventCreationString |
||
263 | ); |
||
264 | $eventLd->created = $eventCreationDateTime->format('c'); |
||
265 | $eventLd->modified = $eventCreationDateTime->format('c'); |
||
266 | |||
267 | // Add creator. |
||
268 | $eventLd->creator = $this->getAuthorFromMetadata($domainMessage->getMetadata())->toNative(); |
||
269 | |||
270 | // Add publisher, which is the consumer name. |
||
271 | $eventLd->publisher = $this->getConsumerFromMetadata($domainMessage->getMetadata())->toNative(); |
||
272 | |||
273 | return $eventLd; |
||
274 | } |
||
275 | ); |
||
276 | } |
||
277 | |||
278 | /** |
||
279 | * @param string $eventId |
||
280 | * @param callable $fn |
||
281 | */ |
||
282 | protected function saveNewDocument($eventId, callable $fn) |
||
283 | { |
||
284 | $document = $this |
||
285 | ->newDocument($eventId) |
||
286 | ->apply($fn); |
||
287 | |||
288 | $this->repository->save($document); |
||
289 | } |
||
290 | |||
291 | /** |
||
292 | * Helper function to save a JSON-LD document from cdbxml coming from UDB2. |
||
293 | * |
||
294 | * @param string $eventId |
||
295 | * @param string $cdbXmlNamespaceUri |
||
296 | * @param string $cdbXml |
||
297 | */ |
||
298 | protected function applyEventCdbXmlFromUDB2( |
||
299 | $eventId, |
||
300 | $cdbXmlNamespaceUri, |
||
301 | $cdbXml |
||
302 | ) { |
||
303 | $this->saveNewDocument( |
||
304 | $eventId, |
||
305 | function (\stdClass $eventLd) use ($cdbXmlNamespaceUri, $eventId, $cdbXml) { |
||
306 | return $this->projectEventCdbXmlToObject( |
||
307 | $eventLd, |
||
308 | $eventId, |
||
309 | $cdbXmlNamespaceUri, |
||
310 | $cdbXml |
||
311 | ) ; |
||
312 | } |
||
313 | ); |
||
314 | } |
||
315 | |||
316 | /** |
||
317 | * @param \stdClass $jsonLd |
||
318 | * @param string $eventId |
||
319 | * @param string $cdbXmlNamespaceUri |
||
320 | * @param string $cdbXml |
||
321 | * |
||
322 | * @return \stdClass |
||
323 | */ |
||
324 | protected function projectEventCdbXmlToObject( |
||
325 | \stdClass $jsonLd, |
||
326 | $eventId, |
||
327 | $cdbXmlNamespaceUri, |
||
328 | $cdbXml |
||
329 | ) { |
||
330 | $udb2Event = EventItemFactory::createEventFromCdbXml( |
||
331 | $cdbXmlNamespaceUri, |
||
332 | $cdbXml |
||
333 | ); |
||
334 | |||
335 | $jsonLd = $this->cdbXMLImporter->documentWithCdbXML( |
||
336 | $jsonLd, |
||
337 | $udb2Event, |
||
338 | $this, |
||
339 | $this, |
||
340 | $this->slugger |
||
341 | ); |
||
342 | |||
343 | // Because we can not properly track media coming from UDB2 we simply |
||
344 | // ignore it and give priority to content added through UDB3. |
||
345 | // It's possible that an event has been deleted in udb3, but never |
||
346 | // in udb2. If an update comes for that event from udb2, it should |
||
347 | // be imported again. This is intended by design. |
||
348 | // @see https://jira.uitdatabank.be/browse/III-1092 |
||
349 | try { |
||
350 | $document = $this->loadDocumentFromRepositoryByItemId($eventId); |
||
351 | } catch (DocumentGoneException $documentGoneException) { |
||
352 | $document = $this->newDocument($eventId); |
||
353 | } |
||
354 | |||
355 | $media = $this->UDB3Media($document); |
||
356 | if (!empty($media)) { |
||
357 | $jsonLd->mediaObject = $media; |
||
358 | } |
||
359 | |||
360 | // Because UDB2 cannot keep track of UDB3 places as a location |
||
361 | // ignore it and give priority to content added through UDB3. |
||
362 | $location = $this->UDB3Location($document); |
||
363 | if (!empty($location)) { |
||
364 | $jsonLd->location = $location; |
||
365 | } |
||
366 | |||
367 | return $jsonLd; |
||
368 | } |
||
369 | |||
370 | /** |
||
371 | * Return the media of an event if it already exists. |
||
372 | * |
||
373 | * @param JsonDocument $document The JsonDocument. |
||
374 | * |
||
375 | * @return array |
||
376 | * A list of media objects. |
||
377 | */ |
||
378 | private function UDB3Media($document) |
||
379 | { |
||
380 | $media = []; |
||
381 | |||
382 | if ($document) { |
||
383 | $item = $document->getBody(); |
||
384 | // At the moment we do not include any media coming from UDB2. |
||
385 | // If the mediaObject property contains data it's coming from UDB3. |
||
386 | $item->mediaObject = isset($item->mediaObject) ? $item->mediaObject : []; |
||
387 | } |
||
388 | |||
389 | return $media; |
||
390 | } |
||
391 | |||
392 | /** |
||
393 | * Return the location of an event if it already exists. |
||
394 | * |
||
395 | * @param JsonDocument $document The JsonDocument. |
||
396 | * |
||
397 | * @return array|null |
||
398 | * The location |
||
399 | */ |
||
400 | private function UDB3Location($document) |
||
401 | { |
||
402 | $location = null; |
||
403 | |||
404 | if ($document) { |
||
405 | $item = $document->getBody(); |
||
406 | $location = isset($item->location) ? $item->location : null; |
||
407 | } |
||
408 | |||
409 | return $location; |
||
410 | } |
||
411 | |||
412 | /** |
||
413 | * @param EventCreated $eventCreated |
||
414 | * @param DomainMessage $domainMessage |
||
415 | */ |
||
416 | protected function applyEventCreated( |
||
417 | EventCreated $eventCreated, |
||
418 | DomainMessage $domainMessage |
||
419 | ) { |
||
420 | $this->saveNewDocument( |
||
421 | $eventCreated->getEventId(), |
||
422 | function (\stdClass $jsonLD) use ($eventCreated, $domainMessage) { |
||
423 | $jsonLD->{'@id'} = $this->iriGenerator->iri( |
||
424 | $eventCreated->getEventId() |
||
425 | ); |
||
426 | $jsonLD->name['nl'] = $eventCreated->getTitle(); |
||
427 | $jsonLD->location = array( |
||
428 | '@type' => 'Place', |
||
429 | ) + (array)$this->placeJSONLD( |
||
430 | $eventCreated->getLocation()->getCdbid() |
||
431 | ); |
||
432 | |||
433 | $calendarJsonLD = $eventCreated->getCalendar()->toJsonLd(); |
||
434 | $jsonLD = (object)array_merge((array)$jsonLD, $calendarJsonLD); |
||
435 | |||
436 | $availableTo = AvailableTo::createFromCalendar($eventCreated->getCalendar()); |
||
437 | $jsonLD->availableTo = (string)$availableTo; |
||
438 | |||
439 | // Same as. |
||
440 | $jsonLD->sameAs = $this->generateSameAs( |
||
441 | $eventCreated->getEventId(), |
||
442 | reset($jsonLD->name) |
||
443 | ); |
||
444 | |||
445 | $eventType = $eventCreated->getEventType(); |
||
446 | $jsonLD->terms = [ |
||
447 | $eventType->toJsonLd() |
||
448 | ]; |
||
449 | |||
450 | $theme = $eventCreated->getTheme(); |
||
451 | if (!empty($theme)) { |
||
452 | $jsonLD->terms[] = $theme->toJsonLd(); |
||
453 | } |
||
454 | |||
455 | $recordedOn = $domainMessage->getRecordedOn()->toString(); |
||
456 | $jsonLD->created = \DateTime::createFromFormat( |
||
457 | DateTime::FORMAT_STRING, |
||
458 | $recordedOn |
||
459 | )->format('c'); |
||
460 | $jsonLD->modified = $jsonLD->created; |
||
461 | |||
462 | $metaData = $domainMessage->getMetadata()->serialize(); |
||
463 | View Code Duplication | if (isset($metaData['user_email'])) { |
|
464 | $jsonLD->creator = $metaData['user_email']; |
||
465 | } elseif (isset($metaData['user_nick'])) { |
||
466 | $jsonLD->creator = $metaData['user_nick']; |
||
467 | } |
||
468 | |||
469 | $jsonLD->workflowStatus = WorkflowStatus::DRAFT()->getName(); |
||
470 | |||
471 | $defaultAudience = new Audience(AudienceType::EVERYONE()); |
||
472 | $jsonLD->audience = $defaultAudience->serialize(); |
||
473 | |||
474 | return $jsonLD; |
||
475 | } |
||
476 | ); |
||
477 | } |
||
478 | |||
479 | /** |
||
480 | * @param EventCopied $eventCopied |
||
481 | * @param DomainMessage $domainMessage |
||
482 | */ |
||
483 | protected function applyEventCopied( |
||
484 | EventCopied $eventCopied, |
||
485 | DomainMessage $domainMessage |
||
486 | ) { |
||
487 | $originalDocument = $this->repository->get($eventCopied->getOriginalEventId()); |
||
488 | |||
489 | // Set new calendar. |
||
490 | $originalDocument->apply(OfferUpdate::calendar($eventCopied->getCalendar())); |
||
491 | |||
492 | $eventJsonLD = $originalDocument->getBody(); |
||
493 | |||
494 | // Set the id. |
||
495 | $eventJsonLD->{'@id'} = $this->iriGenerator->iri($eventCopied->getItemId()); |
||
496 | |||
497 | // Set workflow status. |
||
498 | $eventJsonLD->workflowStatus = WorkflowStatus::DRAFT()->getName(); |
||
499 | |||
500 | // Remove labels. |
||
501 | unset($eventJsonLD->labels); |
||
502 | unset($eventJsonLD->hiddenLabels); |
||
503 | |||
504 | // Set available to and from. |
||
505 | $availableTo = AvailableTo::createFromCalendar($eventCopied->getCalendar()); |
||
506 | $eventJsonLD->availableTo = (string) $availableTo; |
||
507 | unset($eventJsonLD->availableFrom); |
||
508 | |||
509 | $newDocument = new JsonDocument($eventCopied->getItemId()); |
||
510 | $newDocument = $newDocument->withBody($eventJsonLD); |
||
511 | $this->repository->save($newDocument); |
||
512 | } |
||
513 | |||
514 | /** |
||
515 | * @param EventDeleted $eventDeleted |
||
516 | */ |
||
517 | protected function applyEventDeleted(EventDeleted $eventDeleted) |
||
521 | |||
522 | /** |
||
523 | * Apply the major info updated command to the projector. |
||
524 | */ |
||
525 | protected function applyMajorInfoUpdated(MajorInfoUpdated $majorInfoUpdated) |
||
526 | { |
||
527 | $document = $this |
||
528 | ->loadDocumentFromRepository($majorInfoUpdated) |
||
529 | ->apply(OfferUpdate::calendar($majorInfoUpdated->getCalendar())); |
||
530 | |||
531 | $jsonLD = $document->getBody(); |
||
532 | |||
533 | $jsonLD->name->nl = $majorInfoUpdated->getTitle(); |
||
534 | $jsonLD->location = array( |
||
535 | '@type' => 'Place', |
||
536 | ) + (array)$this->placeJSONLD($majorInfoUpdated->getLocation()->getCdbid()); |
||
557 | |||
558 | /** |
||
559 | * @param AudienceUpdated $audienceUpdated |
||
560 | */ |
||
561 | protected function applyAudienceUpdated(AudienceUpdated $audienceUpdated) |
||
570 | |||
571 | /** |
||
572 | * @inheritdoc |
||
573 | */ |
||
574 | public function placeJSONLD($placeId) |
||
598 | |||
599 | private function generateSameAs($eventId, $name) |
||
606 | |||
607 | View Code Duplication | private function getAuthorFromMetadata(Metadata $metadata) |
|
615 | |||
616 | View Code Duplication | private function getConsumerFromMetadata(Metadata $metadata) |
|
624 | |||
625 | /** |
||
626 | * @return string |
||
627 | */ |
||
628 | protected function getLabelAddedClassName() |
||
632 | |||
633 | /** |
||
634 | * @return string |
||
635 | */ |
||
636 | protected function getLabelRemovedClassName() |
||
640 | |||
641 | /** |
||
642 | * @return string |
||
643 | */ |
||
644 | protected function getImageAddedClassName() |
||
648 | |||
649 | /** |
||
650 | * @return string |
||
651 | */ |
||
652 | protected function getImageRemovedClassName() |
||
656 | |||
657 | /** |
||
658 | * @return string |
||
659 | */ |
||
660 | protected function getImageUpdatedClassName() |
||
664 | |||
665 | protected function getMainImageSelectedClassName() |
||
669 | |||
670 | /** |
||
671 | * @return string |
||
672 | */ |
||
673 | protected function getTitleTranslatedClassName() |
||
677 | |||
678 | /** |
||
679 | * @return string |
||
680 | */ |
||
681 | protected function getDescriptionTranslatedClassName() |
||
685 | |||
686 | /** |
||
687 | * @return string |
||
688 | */ |
||
689 | protected function getOrganizerUpdatedClassName() |
||
693 | |||
694 | /** |
||
695 | * @return string |
||
696 | */ |
||
697 | protected function getOrganizerDeletedClassName() |
||
701 | |||
702 | protected function getBookingInfoUpdatedClassName() |
||
706 | |||
707 | /** |
||
708 | * @return string |
||
709 | */ |
||
710 | protected function getPriceInfoUpdatedClassName() |
||
714 | |||
715 | protected function getContactPointUpdatedClassName() |
||
719 | |||
720 | protected function getDescriptionUpdatedClassName() |
||
724 | |||
725 | protected function getTypicalAgeRangeUpdatedClassName() |
||
729 | |||
730 | protected function getTypicalAgeRangeDeletedClassName() |
||
734 | |||
735 | protected function getPublishedClassName() |
||
739 | |||
740 | protected function getApprovedClassName() |
||
744 | |||
745 | protected function getRejectedClassName() |
||
749 | |||
750 | protected function getFlaggedAsDuplicateClassName() |
||
754 | |||
755 | protected function getFlaggedAsInappropriateClassName() |
||
759 | |||
760 | protected function getImagesImportedFromUdb2ClassName() |
||
764 | |||
765 | protected function getImagesUpdatedFromUdb2ClassName() |
||
769 | } |
||
770 |
An attempt at access to an undefined property has been detected. This may either be a typographical error or the property has been renamed but there are still references to its old name.
If you really want to allow access to undefined properties, you can define magic methods to allow access. See the php core documentation on Overloading.