Completed
Push — master ( 65255d...9dfb0b )
by Luc
07:57 queued 02:17
created

OfferLDProjector   F

Complexity

Total Complexity 72

Size/Duplication

Total Lines 864
Duplicated Lines 11.34 %

Coupling/Cohesion

Components 1
Dependencies 45

Importance

Changes 0
Metric Value
wmc 72
lcom 1
cbo 45
dl 98
loc 864
rs 1.0434
c 0
b 0
f 0

59 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 15 1
B handle() 0 29 6
A getEventHandlers() 20 20 4
getLabelAddedClassName() 0 1 ?
getLabelRemovedClassName() 0 1 ?
getImageAddedClassName() 0 1 ?
getImageRemovedClassName() 0 1 ?
getImageUpdatedClassName() 0 1 ?
getMainImageSelectedClassName() 0 1 ?
getTitleTranslatedClassName() 0 1 ?
getTitleUpdatedClassName() 0 1 ?
getDescriptionTranslatedClassName() 0 1 ?
getOrganizerUpdatedClassName() 0 1 ?
getOrganizerDeletedClassName() 0 1 ?
getBookingInfoUpdatedClassName() 0 1 ?
getPriceInfoUpdatedClassName() 0 1 ?
getContactPointUpdatedClassName() 0 1 ?
getDescriptionUpdatedClassName() 0 1 ?
getCalendarUpdatedClassName() 0 1 ?
getTypicalAgeRangeUpdatedClassName() 0 1 ?
getTypicalAgeRangeDeletedClassName() 0 1 ?
getPublishedClassName() 0 1 ?
getApprovedClassName() 0 1 ?
getRejectedClassName() 0 1 ?
getFlaggedAsDuplicateClassName() 0 1 ?
getFlaggedAsInappropriateClassName() 0 1 ?
getImagesImportedFromUdb2ClassName() 0 1 ?
getImagesUpdatedFromUdb2ClassName() 0 1 ?
A applyLabelAdded() 0 17 3
B applyLabelRemoved() 32 32 5
A applyImageAdded() 0 17 3
B applyImageUpdated() 0 34 5
C applyImageRemoved() 0 51 7
A applyMainImageSelected() 0 21 3
A mediaObjectMatchesId() 0 4 1
A applyTitleTranslated() 0 10 1
A applyTitleUpdated() 0 10 2
A applyDescriptionTranslated() 0 15 2
A applyCalendarUpdated() 0 12 1
A applyOrganizerUpdated() 0 12 1
A applyOrganizerDeleted() 0 10 1
A applyBookingInfoUpdated() 0 9 1
B applyPriceInfoUpdated() 0 27 2
A applyContactPointUpdated() 0 9 1
A applyDescriptionUpdated() 0 13 2
A applyTypicalAgeRangeUpdated() 0 10 1
A applyTypicalAgeRangeDeleted() 0 11 1
A applyPublished() 0 13 1
A applyApproved() 0 7 1
A applyRejected() 7 7 1
A applyFlaggedAsDuplicate() 8 8 1
A applyFlaggedAsInappropriate() 8 8 1
A applyImagesImportedFromUdb2() 7 7 1
A applyImagesUpdatedFromUdb2() 7 7 1
B applyUdb2ImagesEvent() 0 28 5
A newDocument() 9 9 1
A loadDocumentFromRepository() 0 4 1
A loadDocumentFromRepositoryByItemId() 0 10 2
A organizerJSONLD() 0 15 2

How to fix   Duplicated Code    Complexity   

Duplicated Code

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 Class

 Tip:   Before tackling complexity, make sure that you eliminate any duplication first. This often can reduce the size of classes significantly.

Complex classes like OfferLDProjector 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 OfferLDProjector, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
namespace CultuurNet\UDB3\Offer\ReadModel\JSONLD;
4
5
use Broadway\Domain\DomainMessage;
6
use CultuurNet\UDB3\CulturefeedSlugger;
7
use CultuurNet\UDB3\EntityNotFoundException;
8
use CultuurNet\UDB3\EntityServiceInterface;
9
use CultuurNet\UDB3\Event\ReadModel\DocumentRepositoryInterface;
10
use CultuurNet\UDB3\Event\ReadModel\JSONLD\OrganizerServiceInterface;
11
use CultuurNet\UDB3\EventHandling\DelegateEventHandlingToSpecificMethodTrait;
12
use CultuurNet\UDB3\Iri\IriGeneratorInterface;
13
use CultuurNet\UDB3\Label;
14
use CultuurNet\UDB3\Media\Image;
15
use CultuurNet\UDB3\Offer\AvailableTo;
16
use CultuurNet\UDB3\Offer\Events\AbstractBookingInfoUpdated;
17
use CultuurNet\UDB3\Offer\Events\AbstractCalendarUpdated;
18
use CultuurNet\UDB3\Offer\Events\AbstractContactPointUpdated;
19
use CultuurNet\UDB3\Offer\Events\AbstractDescriptionTranslated;
20
use CultuurNet\UDB3\Offer\Events\AbstractDescriptionUpdated;
21
use CultuurNet\UDB3\Offer\Events\AbstractEvent;
22
use CultuurNet\UDB3\Offer\Events\AbstractLabelAdded;
23
use CultuurNet\UDB3\Offer\Events\AbstractLabelRemoved;
24
use CultuurNet\UDB3\Offer\Events\AbstractOrganizerDeleted;
25
use CultuurNet\UDB3\Offer\Events\AbstractOrganizerUpdated;
26
use CultuurNet\UDB3\Offer\Events\AbstractPriceInfoUpdated;
27
use CultuurNet\UDB3\Offer\Events\AbstractTitleTranslated;
28
use CultuurNet\UDB3\Offer\Events\AbstractTitleUpdated;
29
use CultuurNet\UDB3\Offer\Events\AbstractTypicalAgeRangeDeleted;
30
use CultuurNet\UDB3\Offer\Events\AbstractTypicalAgeRangeUpdated;
31
use CultuurNet\UDB3\Offer\Events\Image\AbstractImageAdded;
32
use CultuurNet\UDB3\Offer\Events\Image\AbstractImageRemoved;
33
use CultuurNet\UDB3\Offer\Events\Image\AbstractImagesEvent;
34
use CultuurNet\UDB3\Offer\Events\Image\AbstractImagesImportedFromUDB2;
35
use CultuurNet\UDB3\Offer\Events\Image\AbstractImagesUpdatedFromUDB2;
36
use CultuurNet\UDB3\Offer\Events\Image\AbstractImageUpdated;
37
use CultuurNet\UDB3\Offer\Events\Image\AbstractMainImageSelected;
38
use CultuurNet\UDB3\Offer\Events\Moderation\AbstractApproved;
39
use CultuurNet\UDB3\Offer\Events\Moderation\AbstractFlaggedAsDuplicate;
40
use CultuurNet\UDB3\Offer\Events\Moderation\AbstractFlaggedAsInappropriate;
41
use CultuurNet\UDB3\Offer\Events\Moderation\AbstractPublished;
42
use CultuurNet\UDB3\Offer\Events\Moderation\AbstractRejected;
43
use CultuurNet\UDB3\Offer\WorkflowStatus;
44
use CultuurNet\UDB3\ReadModel\JsonDocument;
45
use CultuurNet\UDB3\ReadModel\JsonDocumentMetaDataEnricherInterface;
46
use CultuurNet\UDB3\ReadModel\MultilingualJsonLDProjectorTrait;
47
use CultuurNet\UDB3\SluggerInterface;
48
use Symfony\Component\Serializer\SerializerInterface;
49
use ValueObjects\Identity\UUID;
50
51
abstract class OfferLDProjector implements OrganizerServiceInterface
52
{
53
    use MultilingualJsonLDProjectorTrait;
54
    use DelegateEventHandlingToSpecificMethodTrait {
55
        DelegateEventHandlingToSpecificMethodTrait::handle as handleUnknownEvents;
56
    }
57
58
    /**
59
     * @var DocumentRepositoryInterface
60
     */
61
    protected $repository;
62
63
    /**
64
     * @var IriGeneratorInterface
65
     */
66
    protected $iriGenerator;
67
68
    /**
69
     * @var EntityServiceInterface
70
     */
71
    protected $organizerService;
72
73
    /**
74
     * @var JsonDocumentMetaDataEnricherInterface
75
     */
76
    protected $jsonDocumentMetaDataEnricher;
77
78
    /**
79
     * @var SerializerInterface
80
     */
81
    protected $mediaObjectSerializer;
82
83
    /**
84
     * @var SluggerInterface
85
     */
86
    protected $slugger;
87
88
    /**
89
     * @param DocumentRepositoryInterface $repository
90
     * @param IriGeneratorInterface $iriGenerator
91
     * @param EntityServiceInterface $organizerService
92
     * @param SerializerInterface $mediaObjectSerializer
93
     * @param JsonDocumentMetaDataEnricherInterface $jsonDocumentMetaDataEnricher
94
     */
95
    public function __construct(
96
        DocumentRepositoryInterface $repository,
97
        IriGeneratorInterface $iriGenerator,
98
        EntityServiceInterface $organizerService,
99
        SerializerInterface $mediaObjectSerializer,
100
        JsonDocumentMetaDataEnricherInterface $jsonDocumentMetaDataEnricher
101
    ) {
102
        $this->repository = $repository;
103
        $this->iriGenerator = $iriGenerator;
104
        $this->organizerService = $organizerService;
105
        $this->jsonDocumentMetaDataEnricher = $jsonDocumentMetaDataEnricher;
106
        $this->mediaObjectSerializer = $mediaObjectSerializer;
107
108
        $this->slugger = new CulturefeedSlugger();
109
    }
110
111
    /**
112
     * {@inheritdoc}
113
     */
114
    public function handle(DomainMessage $domainMessage)
115
    {
116
        $event = $domainMessage->getPayload();
117
118
        $eventName = get_class($event);
119
        $eventHandlers = $this->getEventHandlers();
120
121
        if (isset($eventHandlers[$eventName])) {
122
            $handler = $eventHandlers[$eventName];
123
            $jsonDocuments = call_user_func(array($this, $handler), $event, $domainMessage);
124
        } elseif ($methodName = $this->getHandleMethodName($event)) {
125
            $jsonDocuments = $this->{$methodName}($event, $domainMessage);
126
        } else {
127
            return;
128
        }
129
130
        if (!$jsonDocuments) {
131
            return;
132
        }
133
134
        if (!is_array($jsonDocuments)) {
135
            $jsonDocuments = [$jsonDocuments];
136
        }
137
138
        foreach ($jsonDocuments as $jsonDocument) {
139
            $jsonDocument = $this->jsonDocumentMetaDataEnricher->enrich($jsonDocument, $domainMessage->getMetadata());
140
            $this->repository->save($jsonDocument);
141
        }
142
    }
143
144
    /**
145
     * @return string[]
146
     *   An associative array of commands and their handler methods.
147
     */
148 View Code Duplication
    private function getEventHandlers()
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

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.

Loading history...
149
    {
150
        $events = [];
151
152
        foreach (get_class_methods($this) as $method) {
153
            $matches = [];
154
155
            if (preg_match('/^apply(.+)$/', $method, $matches)) {
156
                $event = $matches[1];
157
                $classNameMethod = 'get' . $event . 'ClassName';
158
159
                if (method_exists($this, $classNameMethod)) {
160
                    $eventFullClassName = call_user_func(array($this, $classNameMethod));
161
                    $events[$eventFullClassName] = $method;
162
                }
163
            }
164
        }
165
166
        return $events;
167
    }
168
169
    /**
170
     * @return string
171
     */
172
    abstract protected function getLabelAddedClassName();
173
174
    /**
175
     * @return string
176
     */
177
    abstract protected function getLabelRemovedClassName();
178
179
    /**
180
     * @return string
181
     */
182
    abstract protected function getImageAddedClassName();
183
184
    /**
185
     * @return string
186
     */
187
    abstract protected function getImageRemovedClassName();
188
189
    /**
190
     * @return string
191
     */
192
    abstract protected function getImageUpdatedClassName();
193
194
    /**
195
     * @return string
196
     */
197
    abstract protected function getMainImageSelectedClassName();
198
199
    /**
200
     * @return string
201
     */
202
    abstract protected function getTitleTranslatedClassName();
203
204
    /**
205
     * @return string
206
     */
207
    abstract protected function getTitleUpdatedClassName();
208
209
    /**
210
     * @return string
211
     */
212
    abstract protected function getDescriptionTranslatedClassName();
213
214
    /**
215
     * @return string
216
     */
217
    abstract protected function getOrganizerUpdatedClassName();
218
219
    /**
220
     * @return string
221
     */
222
    abstract protected function getOrganizerDeletedClassName();
223
224
    /**
225
     * @return string
226
     */
227
    abstract protected function getBookingInfoUpdatedClassName();
228
229
    /**
230
     * @return string
231
     */
232
    abstract protected function getPriceInfoUpdatedClassName();
233
234
    /**
235
     * @return string
236
     */
237
    abstract protected function getContactPointUpdatedClassName();
238
239
    /**
240
     * @return string
241
     */
242
    abstract protected function getDescriptionUpdatedClassName();
243
244
    /**
245
     * @return string
246
     */
247
    abstract protected function getCalendarUpdatedClassName();
248
249
    /**
250
     * @return string
251
     */
252
    abstract protected function getTypicalAgeRangeUpdatedClassName();
253
254
    /**
255
     * @return string
256
     */
257
    abstract protected function getTypicalAgeRangeDeletedClassName();
258
259
    /**
260
     * @return string
261
     */
262
    abstract protected function getPublishedClassName();
263
264
    /**
265
     * @return string
266
     */
267
    abstract protected function getApprovedClassName();
268
269
    /**
270
     * @return string
271
     */
272
    abstract protected function getRejectedClassName();
273
274
    /**
275
     * @return string
276
     */
277
    abstract protected function getFlaggedAsDuplicateClassName();
278
279
    /**
280
     * @return string
281
     */
282
    abstract protected function getFlaggedAsInappropriateClassName();
283
284
    /**
285
     * @return string
286
     */
287
    abstract protected function getImagesImportedFromUdb2ClassName();
288
289
    /**
290
     * @return string
291
     */
292
    abstract protected function getImagesUpdatedFromUdb2ClassName();
293
294
    /**
295
     * @param AbstractLabelAdded $labelAdded
296
     * @return JsonDocument
297
     */
298
    protected function applyLabelAdded(AbstractLabelAdded $labelAdded)
299
    {
300
        $document = $this->loadDocumentFromRepository($labelAdded);
301
302
        $offerLd = $document->getBody();
303
304
        // Check the visibility of the label to update the right property.
305
        $labelsProperty = $labelAdded->getLabel()->isVisible() ? 'labels' : 'hiddenLabels';
306
307
        $labels = isset($offerLd->{$labelsProperty}) ? $offerLd->{$labelsProperty} : [];
308
        $label = (string) $labelAdded->getLabel();
309
310
        $labels[] = $label;
311
        $offerLd->{$labelsProperty} = array_unique($labels);
312
313
        return $document->withBody($offerLd);
314
    }
315
316
    /**
317
     * @param AbstractLabelRemoved $labelRemoved
318
     * @return JsonDocument
319
     */
320 View Code Duplication
    protected function applyLabelRemoved(AbstractLabelRemoved $labelRemoved)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

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.

Loading history...
321
    {
322
        $document = $this->loadDocumentFromRepository($labelRemoved);
323
324
        $offerLd = $document->getBody();
325
326
        // Don't presume that the label visibility is correct when removing.
327
        // So iterate over both the visible and invisible labels.
328
        $labelsProperties = ['labels', 'hiddenLabels'];
329
330
        foreach ($labelsProperties as $labelsProperty) {
331
            if (isset($offerLd->{$labelsProperty}) && is_array($offerLd->{$labelsProperty})) {
332
                $offerLd->{$labelsProperty} = array_filter(
333
                    $offerLd->{$labelsProperty},
334
                    function ($label) use ($labelRemoved) {
335
                        return !$labelRemoved->getLabel()->equals(
336
                            new Label($label)
337
                        );
338
                    }
339
                );
340
                // Ensure array keys start with 0 so json_encode() does encode it
341
                // as an array and not as an object.
342
                if (count($offerLd->{$labelsProperty}) > 0) {
343
                    $offerLd->{$labelsProperty} = array_values($offerLd->{$labelsProperty});
344
                } else {
345
                    unset($offerLd->{$labelsProperty});
346
                }
347
            }
348
        }
349
350
        return $document->withBody($offerLd);
351
    }
352
353
    /**
354
     * Apply the imageAdded event to the item repository.
355
     *
356
     * @param AbstractImageAdded $imageAdded
357
     * @return JsonDocument
358
     */
359
    protected function applyImageAdded(AbstractImageAdded $imageAdded)
360
    {
361
        $document = $this->loadDocumentFromRepository($imageAdded);
362
363
        $offerLd = $document->getBody();
364
        $offerLd->mediaObject = isset($offerLd->mediaObject) ? $offerLd->mediaObject : [];
365
366
        $imageData = $this->mediaObjectSerializer
367
            ->serialize($imageAdded->getImage(), 'json-ld');
368
        $offerLd->mediaObject[] = $imageData;
369
370
        if (count($offerLd->mediaObject) === 1) {
371
            $offerLd->image = $imageData['contentUrl'];
372
        }
373
374
        return $document->withBody($offerLd);
375
    }
376
377
    /**
378
     * Apply the ImageUpdated event to the item repository.
379
     *
380
     * @param AbstractImageUpdated $imageUpdated
381
     * @return JsonDocument
382
     * @throws \Exception
383
     */
384
    protected function applyImageUpdated(AbstractImageUpdated $imageUpdated)
385
    {
386
        $document = $this->loadDocumentFromRepository($imageUpdated);
387
388
        $offerLd = $document->getBody();
389
390
        if (!isset($offerLd->mediaObject)) {
391
            throw new \Exception('The image to update could not be found.');
392
        }
393
394
        $updatedMediaObjects = [];
395
396
        foreach ($offerLd->mediaObject as $mediaObject) {
397
            $mediaObjectMatches = (
398
                strpos(
399
                    $mediaObject->{'@id'},
400
                    (string)$imageUpdated->getMediaObjectId()
401
                ) > 0
402
            );
403
404
            if ($mediaObjectMatches) {
405
                $mediaObject->description = (string)$imageUpdated->getDescription();
406
                $mediaObject->copyrightHolder = (string)$imageUpdated->getCopyrightHolder();
407
408
                $updatedMediaObjects[] = $mediaObject;
409
            }
410
        };
411
412
        if (empty($updatedMediaObjects)) {
413
            throw new \Exception('The image to update could not be found.');
414
        }
415
416
        return $document->withBody($offerLd);
417
    }
418
419
    /**
420
     * @param AbstractImageRemoved $imageRemoved
421
     * @return JsonDocument
422
     */
423
    protected function applyImageRemoved(AbstractImageRemoved $imageRemoved)
424
    {
425
        $document = $this->loadDocumentFromRepository($imageRemoved);
426
427
        $offerLd = $document->getBody();
428
429
        // Nothing to remove if there are no media objects!
430
        if (!isset($offerLd->mediaObject)) {
431
            return;
432
        }
433
434
        $imageId = (string) $imageRemoved->getImage()->getMediaObjectId();
435
436
        /**
437
         * Matches any object that is not the removed image.
438
         *
439
         * @param Object $mediaObject
440
         *  An existing projection of a media object.
441
         *
442
         * @return bool
443
         *  Returns true when the media object does not match the image to remove.
444
         */
445
        $shouldNotBeRemoved = function ($mediaObject) use ($imageId) {
446
            $containsId = !!strpos($mediaObject->{'@id'}, $imageId);
447
            return !$containsId;
448
        };
449
450
        // Remove any media objects that match the image.
451
        $filteredMediaObjects = array_filter(
452
            $offerLd->mediaObject,
453
            $shouldNotBeRemoved
454
        );
455
456
        // Unset the main image if it matches the removed image
457
        if (isset($offerLd->image) && strpos($offerLd->{'image'}, $imageId)) {
458
            unset($offerLd->{"image"});
459
        }
460
461
        if (!isset($offerLd->image) && count($filteredMediaObjects) > 0) {
462
            $offerLd->image = array_values($filteredMediaObjects)[0]->contentUrl;
463
        }
464
465
        // If no media objects are left remove the attribute.
466
        if (empty($filteredMediaObjects)) {
467
            unset($offerLd->{"mediaObject"});
468
        } else {
469
            $offerLd->mediaObject = array_values($filteredMediaObjects);
470
        }
471
472
        return $document->withBody($offerLd);
473
    }
474
475
    /**
476
     * @param AbstractMainImageSelected $mainImageSelected
477
     * @return JsonDocument
478
     */
479
    protected function applyMainImageSelected(AbstractMainImageSelected $mainImageSelected)
480
    {
481
        $document = $this->loadDocumentFromRepository($mainImageSelected);
482
        $offerLd = $document->getBody();
483
        $imageId = $mainImageSelected->getImage()->getMediaObjectId();
484
        $mediaObjectMatcher = function ($matchingMediaObject, $currentMediaObject) use ($imageId) {
485
            if (!$matchingMediaObject && $this->mediaObjectMatchesId($currentMediaObject, $imageId)) {
486
                $matchingMediaObject = $currentMediaObject;
487
            }
488
489
            return $matchingMediaObject;
490
        };
491
        $mediaObject = array_reduce(
492
            $offerLd->mediaObject,
493
            $mediaObjectMatcher
494
        );
495
496
        $offerLd->image = $mediaObject->contentUrl;
497
498
        return $document->withBody($offerLd);
499
    }
500
501
    /**
502
     * @param Object $mediaObject
503
     * @param UUID $mediaObjectId
504
     *
505
     * @return bool
506
     */
507
    protected function mediaObjectMatchesId($mediaObject, UUID $mediaObjectId)
508
    {
509
        return strpos($mediaObject->{'@id'}, (string) $mediaObjectId) > 0;
510
    }
511
512
    /**
513
     * @param AbstractTitleTranslated $titleTranslated
514
     * @return JsonDocument
515
     */
516
    protected function applyTitleTranslated(AbstractTitleTranslated $titleTranslated)
517
    {
518
        $document = $this->loadDocumentFromRepository($titleTranslated);
519
520
        $offerLd = $document->getBody();
521
        $offerLd->name->{$titleTranslated->getLanguage()->getCode(
522
        )} = $titleTranslated->getTitle()->toNative();
523
524
        return $document->withBody($offerLd);
525
    }
526
527
    /**
528
     * @param AbstractTitleUpdated $titleUpdated
529
     * @return JsonDocument
530
     */
531
    protected function applyTitleUpdated(AbstractTitleUpdated $titleUpdated)
532
    {
533
        $document = $this->loadDocumentFromRepository($titleUpdated);
534
        $offerLd = $document->getBody();
535
        $mainLanguage = isset($offerLd->mainLanguage) ? $offerLd->mainLanguage : 'nl';
536
537
        $offerLd->name->{$mainLanguage} = $titleUpdated->getTitle()->toNative();
538
539
        return $document->withBody($offerLd);
540
    }
541
542
    /**
543
     * @param AbstractDescriptionTranslated $descriptionTranslated
544
     * @return JsonDocument
545
     */
546
    protected function applyDescriptionTranslated(
547
        AbstractDescriptionTranslated $descriptionTranslated
548
    ) {
549
        $document = $this->loadDocumentFromRepository($descriptionTranslated);
550
551
        $offerLd = $document->getBody();
552
        $languageCode = $descriptionTranslated->getLanguage()->getCode();
553
        $description = $descriptionTranslated->getDescription()->toNative();
0 ignored issues
show
Bug introduced by
The method toNative cannot be called on $descriptionTranslated->getDescription() (of type string).

Methods can only be called on objects. This check looks for methods being called on variables that have been inferred to never be objects.

Loading history...
554
        if (empty($offerLd->description)) {
555
            $offerLd->description = new \stdClass();
556
        }
557
        $offerLd->description->{$languageCode} = $description;
558
559
        return $document->withBody($offerLd);
560
    }
561
562
    /**
563
     * @param AbstractCalendarUpdated $calendarUpdated
564
     *
565
     * @return JsonDocument
566
     */
567
    protected function applyCalendarUpdated(AbstractCalendarUpdated $calendarUpdated)
568
    {
569
        $document = $this->loadDocumentFromRepository($calendarUpdated)
570
            ->apply(OfferUpdate::calendar($calendarUpdated->getCalendar()));
571
572
        $offerLd = $document->getBody();
573
574
        $availableTo = AvailableTo::createFromCalendar($calendarUpdated->getCalendar());
575
        $offerLd->availableTo = (string)$availableTo;
576
577
        return $document->withBody($offerLd);
578
    }
579
580
    /**
581
     * Apply the organizer updated event to the offer repository.
582
     * @param AbstractOrganizerUpdated $organizerUpdated
583
     * @return JsonDocument
584
     */
585
    protected function applyOrganizerUpdated(AbstractOrganizerUpdated $organizerUpdated)
586
    {
587
        $document = $this->loadDocumentFromRepository($organizerUpdated);
588
589
        $offerLd = $document->getBody();
590
591
        $offerLd->organizer = array(
592
                '@type' => 'Organizer',
593
            ) + (array)$this->organizerJSONLD($organizerUpdated->getOrganizerId());
594
595
        return $document->withBody($offerLd);
596
    }
597
598
    /**
599
     * Apply the organizer delete event to the offer repository.
600
     * @param AbstractOrganizerDeleted $organizerDeleted
601
     * @return JsonDocument
602
     */
603
    protected function applyOrganizerDeleted(AbstractOrganizerDeleted $organizerDeleted)
604
    {
605
        $document = $this->loadDocumentFromRepository($organizerDeleted);
606
607
        $offerLd = $document->getBody();
608
609
        unset($offerLd->organizer);
610
611
        return $document->withBody($offerLd);
612
    }
613
614
    /**
615
     * Apply the booking info updated event to the offer repository.
616
     * @param AbstractBookingInfoUpdated $bookingInfoUpdated
617
     * @return JsonDocument
618
     */
619
    protected function applyBookingInfoUpdated(AbstractBookingInfoUpdated $bookingInfoUpdated)
620
    {
621
        $document = $this->loadDocumentFromRepository($bookingInfoUpdated);
622
623
        $offerLd = $document->getBody();
624
        $offerLd->bookingInfo = $bookingInfoUpdated->getBookingInfo()->toJsonLd();
625
626
        return $document->withBody($offerLd);
627
    }
628
629
    /**
630
     * @param AbstractPriceInfoUpdated $priceInfoUpdated
631
     * @return JsonDocument
632
     */
633
    protected function applyPriceInfoUpdated(AbstractPriceInfoUpdated $priceInfoUpdated)
634
    {
635
        $document = $this->loadDocumentFromRepository($priceInfoUpdated);
636
637
        $offerLd = $document->getBody();
638
        $offerLd->priceInfo = [];
639
640
        $basePrice = $priceInfoUpdated->getPriceInfo()->getBasePrice();
641
642
        $offerLd->priceInfo[] = [
643
            'category' => 'base',
644
            'name' => 'Basistarief',
645
            'price' => $basePrice->getPrice()->toFloat(),
646
            'priceCurrency' => $basePrice->getCurrency()->getCode()->toNative(),
647
        ];
648
649
        foreach ($priceInfoUpdated->getPriceInfo()->getTariffs() as $tariff) {
650
            $offerLd->priceInfo[] = [
651
                'category' => 'tariff',
652
                'name' => $tariff->getName()->toNative(),
653
                'price' => $tariff->getPrice()->toFloat(),
654
                'priceCurrency' => $tariff->getCurrency()->getCode()->toNative(),
655
            ];
656
        }
657
658
        return $document->withBody($offerLd);
659
    }
660
661
    /**
662
     * Apply the contact point updated event to the offer repository.
663
     * @param AbstractContactPointUpdated $contactPointUpdated
664
     * @return JsonDocument
665
     */
666
    protected function applyContactPointUpdated(AbstractContactPointUpdated $contactPointUpdated)
667
    {
668
        $document = $this->loadDocumentFromRepository($contactPointUpdated);
669
670
        $offerLd = $document->getBody();
671
        $offerLd->contactPoint = $contactPointUpdated->getContactPoint()->toJsonLd();
672
673
        return $document->withBody($offerLd);
674
    }
675
676
    /**
677
     * Apply the description updated event to the offer repository.
678
     * @param AbstractDescriptionUpdated $descriptionUpdated
679
     * @return JsonDocument
680
     */
681
    protected function applyDescriptionUpdated(
682
        AbstractDescriptionUpdated $descriptionUpdated
683
    ) {
684
        $document = $this->loadDocumentFromRepository($descriptionUpdated);
685
686
        $offerLd = $document->getBody();
687
        if (empty($offerLd->description)) {
688
            $offerLd->description = new \stdClass();
689
        }
690
        $offerLd->description->{'nl'} = $descriptionUpdated->getDescription();
691
692
        return $document->withBody($offerLd);
693
    }
694
695
    /**
696
     * Apply the typical age range updated event to the offer repository.
697
     * @param AbstractTypicalAgeRangeUpdated $typicalAgeRangeUpdated
698
     * @return JsonDocument
699
     */
700
    protected function applyTypicalAgeRangeUpdated(
701
        AbstractTypicalAgeRangeUpdated $typicalAgeRangeUpdated
702
    ) {
703
        $document = $this->loadDocumentFromRepository($typicalAgeRangeUpdated);
704
705
        $offerLd = $document->getBody();
706
        $offerLd->typicalAgeRange = (string) $typicalAgeRangeUpdated->getTypicalAgeRange();
707
708
        return $document->withBody($offerLd);
709
    }
710
711
    /**
712
     * Apply the typical age range deleted event to the offer repository.
713
     * @param AbstractTypicalAgeRangeDeleted $typicalAgeRangeDeleted
714
     * @return JsonDocument
715
     */
716
    protected function applyTypicalAgeRangeDeleted(
717
        AbstractTypicalAgeRangeDeleted $typicalAgeRangeDeleted
718
    ) {
719
        $document = $this->loadDocumentFromRepository($typicalAgeRangeDeleted);
720
721
        $offerLd = $document->getBody();
722
723
        unset($offerLd->typicalAgeRange);
724
725
        return $document->withBody($offerLd);
726
    }
727
728
    /**
729
     * @param AbstractPublished $published
730
     * @return JsonDocument
731
     */
732
    protected function applyPublished(AbstractPublished $published)
733
    {
734
        $document = $this->loadDocumentFromRepository($published);
735
736
        $offerLd = $document->getBody();
737
738
        $offerLd->workflowStatus = WorkflowStatus::READY_FOR_VALIDATION()->getName();
739
740
        $publicationDate = $published->getPublicationDate();
741
        $offerLd->availableFrom = $publicationDate->format(\DateTime::ATOM);
742
743
        return $document->withBody($offerLd);
744
    }
745
746
    /**
747
     * @param AbstractApproved $approved
748
     * @return JsonDocument
749
     */
750
    protected function applyApproved(AbstractApproved $approved)
751
    {
752
        $document = $this->loadDocumentFromRepository($approved);
753
        $offerLd = $document->getBody();
754
        $offerLd->workflowStatus = WorkflowStatus::APPROVED()->getName();
755
        return $document->withBody($offerLd);
756
    }
757
758
    /**
759
     * @param AbstractRejected $rejected
760
     * @return JsonDocument
761
     */
762 View Code Duplication
    protected function applyRejected(AbstractRejected $rejected)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

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.

Loading history...
763
    {
764
        $document = $this->loadDocumentFromRepository($rejected);
765
        $offerLd = $document->getBody();
766
        $offerLd->workflowStatus = WorkflowStatus::REJECTED()->getName();
767
        return $document->withBody($offerLd);
768
    }
769
770
    /**
771
     * @param AbstractFlaggedAsDuplicate $flaggedAsDuplicate
772
     * @return JsonDocument
773
     */
774 View Code Duplication
    protected function applyFlaggedAsDuplicate(
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

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.

Loading history...
775
        AbstractFlaggedAsDuplicate $flaggedAsDuplicate
776
    ) {
777
        $document = $this->loadDocumentFromRepository($flaggedAsDuplicate);
778
        $offerLd = $document->getBody();
779
        $offerLd->workflowStatus = WorkflowStatus::REJECTED()->getName();
780
        return $document->withBody($offerLd);
781
    }
782
783
    /**
784
     * @param AbstractFlaggedAsInappropriate $flaggedAsInappropriate
785
     * @return JsonDocument
786
     */
787 View Code Duplication
    protected function applyFlaggedAsInappropriate(
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

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.

Loading history...
788
        AbstractFlaggedAsInappropriate $flaggedAsInappropriate
789
    ) {
790
        $document = $this->loadDocumentFromRepository($flaggedAsInappropriate);
791
        $offerLd = $document->getBody();
792
        $offerLd->workflowStatus = WorkflowStatus::REJECTED()->getName();
793
        return $document->withBody($offerLd);
794
    }
795
796
    /**
797
     * @param AbstractImagesImportedFromUDB2 $imagesImportedFromUDB2
798
     * @return JsonDocument
799
     */
800 View Code Duplication
    protected function applyImagesImportedFromUdb2(AbstractImagesImportedFromUDB2 $imagesImportedFromUDB2)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

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.

Loading history...
801
    {
802
        $document = $this->loadDocumentFromRepository($imagesImportedFromUDB2);
803
        $offerLd = $document->getBody();
804
        $this->applyUdb2ImagesEvent($offerLd, $imagesImportedFromUDB2);
805
        return $document->withBody($offerLd);
806
    }
807
808
    /**
809
     * @param AbstractImagesUpdatedFromUDB2 $imagesUpdatedFromUDB2
810
     * @return JsonDocument
811
     */
812 View Code Duplication
    protected function applyImagesUpdatedFromUdb2(AbstractImagesUpdatedFromUDB2 $imagesUpdatedFromUDB2)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

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.

Loading history...
813
    {
814
        $document = $this->loadDocumentFromRepository($imagesUpdatedFromUDB2);
815
        $offerLd = $document->getBody();
816
        $this->applyUdb2ImagesEvent($offerLd, $imagesUpdatedFromUDB2);
817
        return $document->withBody($offerLd);
818
    }
819
820
    /**
821
     * This indirect apply method can be called internally to deal with images coming from UDB2.
822
     * Imports from UDB2 only contain the native Dutch content.
823
     * @see https://github.com/cultuurnet/udb3-udb2-bridge/blob/db0a7ab2444f55bb3faae3d59b82b39aaeba253b/test/Media/ImageCollectionFactoryTest.php#L79-L103
824
     * Because of this we have to make sure translated images are left in place.
825
     *
826
     * @param \stdClass $offerLd
827
     * @param AbstractImagesEvent $imagesEvent
828
     */
829
    private function applyUdb2ImagesEvent(\stdClass $offerLd, AbstractImagesEvent $imagesEvent)
830
    {
831
        $images = $imagesEvent->getImages();
832
        $currentMediaObjects = isset($offerLd->mediaObject) ? $offerLd->mediaObject : [];
833
        $dutchMediaObjects = array_map(
834
            function (Image $image) {
835
                return $this->mediaObjectSerializer->serialize($image, 'json-ld');
836
            },
837
            $images->toArray()
838
        );
839
        $translatedMediaObjects = array_filter(
840
            $currentMediaObjects,
841
            function ($image) {
842
                return $image->inLanguage !== 'nl';
843
            }
844
        );
845
        $mainImage = $images->getMain();
846
847
        unset($offerLd->mediaObject, $offerLd->image);
848
849
        if (!empty($dutchMediaObjects) || !empty($translatedMediaObjects)) {
850
            $offerLd->mediaObject = array_merge($dutchMediaObjects, $translatedMediaObjects);
851
        }
852
853
        if (isset($mainImage)) {
854
            $offerLd->image = (string) $mainImage->getSourceLocation();
855
        }
856
    }
857
858
    /**
859
     * @param string $id
860
     * @return JsonDocument
861
     */
862 View Code Duplication
    protected function newDocument($id)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

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.

Loading history...
863
    {
864
        $document = new JsonDocument($id);
865
866
        $offerLd = $document->getBody();
867
        $offerLd->{'@id'} = $this->iriGenerator->iri($id);
868
869
        return $document->withBody($offerLd);
870
    }
871
872
    /**
873
     * @param AbstractEvent $event
874
     * @return JsonDocument
875
     */
876
    protected function loadDocumentFromRepository(AbstractEvent $event)
877
    {
878
        return $this->loadDocumentFromRepositoryByItemId($event->getItemId());
879
    }
880
881
    /**
882
     * @param string $itemId
883
     * @return JsonDocument
884
     */
885
    protected function loadDocumentFromRepositoryByItemId($itemId)
886
    {
887
        $document = $this->repository->get($itemId);
888
889
        if (!$document) {
890
            return $this->newDocument($itemId);
891
        }
892
893
        return $document;
894
    }
895
896
    /**
897
     * @inheritdoc
898
     */
899
    public function organizerJSONLD($organizerId)
900
    {
901
        try {
902
            $organizerJSONLD = $this->organizerService->getEntity(
903
                $organizerId
904
            );
905
906
            return json_decode($organizerJSONLD);
907
        } catch (EntityNotFoundException $e) {
908
            // In case the place can not be found at the moment, just add its ID
909
            return array(
0 ignored issues
show
Bug Best Practice introduced by
The return type of return array('@id' => $t...ce->iri($organizerId)); (array) is incompatible with the return type declared by the interface CultuurNet\UDB3\Event\Re...erface::organizerJSONLD of type stdClass.

If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.

Let’s take a look at an example:

class Author {
    private $name;

    public function __construct($name) {
        $this->name = $name;
    }

    public function getName() {
        return $this->name;
    }
}

abstract class Post {
    public function getAuthor() {
        return 'Johannes';
    }
}

class BlogPost extends Post {
    public function getAuthor() {
        return new Author('Johannes');
    }
}

class ForumPost extends Post { /* ... */ }

function my_function(Post $post) {
    echo strtoupper($post->getAuthor());
}

Our function my_function expects a Post object, and outputs the author of the post. The base class Post returns a simple string and outputting a simple string will work just fine. However, the child class BlogPost which is a sub-type of Post instead decided to return an object, and is therefore violating the SOLID principles. If a BlogPost were passed to my_function, PHP would not complain, but ultimately fail when executing the strtoupper call in its body.

Loading history...
910
                '@id' => $this->organizerService->iri($organizerId),
911
            );
912
        }
913
    }
914
}
915