Completed
Pull Request — master (#252)
by Kristof
09:20 queued 04:17
created

EventLDProjector::applyLabelsMerged()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 16
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 16
rs 9.4285
c 0
b 0
f 0
cc 2
eloc 8
nc 2
nop 1
1
<?php
2
3
namespace CultuurNet\UDB3\Event\ReadModel\JSONLD;
4
5
use Broadway\Domain\DateTime;
6
use Broadway\Domain\DomainMessage;
7
use Broadway\Domain\Metadata;
8
use Broadway\EventHandling\EventListenerInterface;
9
use CultuurNet\UDB3\CalendarFactoryInterface;
10
use CultuurNet\UDB3\Cdb\CdbId\EventCdbIdExtractorInterface;
11
use CultuurNet\UDB3\Cdb\EventItemFactory;
12
use CultuurNet\UDB3\EntityNotFoundException;
13
use CultuurNet\UDB3\Event\Events\BookingInfoUpdated;
14
use CultuurNet\UDB3\Event\Events\ContactPointUpdated;
15
use CultuurNet\UDB3\Event\Events\DescriptionTranslated;
16
use CultuurNet\UDB3\Event\Events\DescriptionUpdated;
17
use CultuurNet\UDB3\Event\Events\EventCreated;
18
use CultuurNet\UDB3\Event\Events\EventCreatedFromCdbXml;
19
use CultuurNet\UDB3\Event\Events\EventDeleted;
20
use CultuurNet\UDB3\Event\Events\EventImportedFromUDB2;
21
use CultuurNet\UDB3\Event\Events\EventUpdatedFromCdbXml;
22
use CultuurNet\UDB3\Event\Events\EventUpdatedFromUDB2;
23
use CultuurNet\UDB3\Event\Events\ImageAdded;
24
use CultuurNet\UDB3\Event\Events\ImageRemoved;
25
use CultuurNet\UDB3\Event\Events\ImageUpdated;
26
use CultuurNet\UDB3\Event\Events\LabelAdded;
27
use CultuurNet\UDB3\Event\Events\LabelRemoved;
28
use CultuurNet\UDB3\Event\Events\MainImageSelected;
29
use CultuurNet\UDB3\Event\Events\MajorInfoUpdated;
30
use CultuurNet\UDB3\Event\Events\Moderation\Approved;
31
use CultuurNet\UDB3\Event\Events\Moderation\FlaggedAsDuplicate;
32
use CultuurNet\UDB3\Event\Events\Moderation\FlaggedAsInappropriate;
33
use CultuurNet\UDB3\Event\Events\Moderation\Published;
34
use CultuurNet\UDB3\Event\Events\Moderation\Rejected;
35
use CultuurNet\UDB3\Event\Events\OrganizerDeleted;
36
use CultuurNet\UDB3\Event\Events\OrganizerUpdated;
37
use CultuurNet\UDB3\Event\Events\PriceInfoUpdated;
38
use CultuurNet\UDB3\Event\Events\TitleTranslated;
39
use CultuurNet\UDB3\Event\Events\TranslationApplied;
40
use CultuurNet\UDB3\Event\Events\TranslationDeleted;
41
use CultuurNet\UDB3\Event\Events\TypicalAgeRangeDeleted;
42
use CultuurNet\UDB3\Event\Events\TypicalAgeRangeUpdated;
43
use CultuurNet\UDB3\Event\EventType;
44
use CultuurNet\UDB3\Event\ReadModel\DocumentGoneException;
45
use CultuurNet\UDB3\Event\ReadModel\DocumentRepositoryInterface;
46
use CultuurNet\UDB3\Event\EventServiceInterface;
47
use CultuurNet\UDB3\Iri\IriGeneratorInterface;
48
use CultuurNet\UDB3\LabelCollection;
49
use CultuurNet\UDB3\Offer\AvailableTo;
50
use CultuurNet\UDB3\Offer\IriOfferIdentifierFactoryInterface;
51
use CultuurNet\UDB3\Offer\ReadModel\JSONLD\OfferLDProjector;
52
use CultuurNet\UDB3\Offer\ReadModel\JSONLD\OfferUpdate;
53
use CultuurNet\UDB3\Offer\WorkflowStatus;
54
use CultuurNet\UDB3\Organizer\OrganizerProjectedToJSONLD;
55
use CultuurNet\UDB3\OrganizerService;
56
use CultuurNet\UDB3\Place\Events\PlaceProjectedToJSONLD;
57
use CultuurNet\UDB3\PlaceService;
58
use CultuurNet\UDB3\ReadModel\JsonDocument;
59
use CultuurNet\UDB3\StringFilter\StringFilterInterface;
60
use CultuurNet\UDB3\Theme;
61
use Symfony\Component\Serializer\SerializerInterface;
62
use ValueObjects\String\String;
63
use ValueObjects\Web\Url;
64
65
/**
66
 * Projects state changes on Event entities to a JSON-LD read model in a
67
 * document repository.
68
 *
69
 * Implements PlaceServiceInterface and OrganizerServiceInterface to do a double
70
 * dispatch with CdbXMLImporter.
71
 */
72
class EventLDProjector extends OfferLDProjector implements
73
    EventListenerInterface,
74
    PlaceServiceInterface,
75
    OrganizerServiceInterface
76
{
77
    /**
78
     * @var PlaceService
79
     */
80
    protected $placeService;
81
82
    /**
83
     * @var EventServiceInterface
84
     */
85
    protected $eventService;
86
87
    /**
88
     * @var IriOfferIdentifierFactoryInterface
89
     */
90
    protected $iriOfferIdentifierFactory;
91
92
    /**
93
     * @param DocumentRepositoryInterface $repository
94
     * @param IriGeneratorInterface $iriGenerator
95
     * @param EventServiceInterface $eventService
96
     * @param PlaceService $placeService
97
     * @param OrganizerService $organizerService
98
     * @param SerializerInterface $mediaObjectSerializer
99
     * @param IriOfferIdentifierFactoryInterface $iriOfferIdentifierFactory
100
     * @param EventCdbIdExtractorInterface $eventCdbIdExtractor
101
     * @param CalendarFactoryInterface $calendarFactory
102
     */
103
    public function __construct(
104
        DocumentRepositoryInterface $repository,
105
        IriGeneratorInterface $iriGenerator,
106
        EventServiceInterface $eventService,
107
        PlaceService $placeService,
108
        OrganizerService $organizerService,
109
        SerializerInterface $mediaObjectSerializer,
110
        IriOfferIdentifierFactoryInterface $iriOfferIdentifierFactory,
111
        EventCdbIdExtractorInterface $eventCdbIdExtractor,
112
        CalendarFactoryInterface $calendarFactory
113
    ) {
114
        parent::__construct(
115
            $repository,
116
            $iriGenerator,
117
            $organizerService,
118
            $mediaObjectSerializer,
119
            $eventCdbIdExtractor,
120
            $calendarFactory
121
        );
122
123
        $this->placeService = $placeService;
124
        $this->eventService = $eventService;
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
     * @param EventCreatedFromCdbXml $eventCreatedFromCdbXml
234
     * @param DomainMessage $domainMessage
235
     */
236 View Code Duplication
    protected function applyEventCreatedFromCdbXml(
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...
237
        EventCreatedFromCdbXml $eventCreatedFromCdbXml,
238
        DomainMessage $domainMessage
239
    ) {
240
        $cdbXmlNamespaceUri = $eventCreatedFromCdbXml->getCdbXmlNamespaceUri()->toNative();
241
        $cdbXml = $eventCreatedFromCdbXml->getEventXmlString()->toEventXmlString();
242
        $eventId = $eventCreatedFromCdbXml->getEventId()->toNative();
243
244
        $this->applyEventFromCdbXml(
245
            $eventId,
246
            $cdbXmlNamespaceUri,
247
            $cdbXml,
248
            $domainMessage
249
        );
250
    }
251
252
    /**
253
     * @param EventUpdatedFromCdbXml $eventUpdatedFromCdbXml
254
     * @param DomainMessage $domainMessage
255
     */
256 View Code Duplication
    protected function applyEventUpdatedFromCdbXml(
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...
257
        EventUpdatedFromCdbXml $eventUpdatedFromCdbXml,
258
        DomainMessage $domainMessage
259
    ) {
260
        $cdbXmlNamespaceUri = $eventUpdatedFromCdbXml->getCdbXmlNamespaceUri()->toNative();
261
        $cdbXml = $eventUpdatedFromCdbXml->getEventXmlString()->toEventXmlString();
262
        $eventId = $eventUpdatedFromCdbXml->getEventId()->toNative();
263
264
        $this->applyEventFromCdbXml(
265
            $eventId,
266
            $cdbXmlNamespaceUri,
267
            $cdbXml,
268
            $domainMessage
269
        );
270
    }
271
272
    /**
273
     * Helper function to save JSONLD document from entryapi cdbxml.
274
     *
275
     * @param string $eventId
276
     * @param string $cdbXmlNamespaceUri
277
     * @param string $cdbXml
278
     * @param DomainMessage $domainMessage
279
     */
280
    protected function applyEventFromCdbXml(
281
        $eventId,
282
        $cdbXmlNamespaceUri,
283
        $cdbXml,
284
        $domainMessage
285
    ) {
286
        $this->saveNewDocument(
287
            $eventId,
288
            function (\stdClass $eventLd) use ($eventId, $cdbXmlNamespaceUri, $cdbXml, $domainMessage) {
289
                $eventLd = $this->projectEventCdbXmlToObject(
290
                    $eventLd,
291
                    $eventId,
292
                    $cdbXmlNamespaceUri,
293
                    $cdbXml
294
                );
295
296
                // Add creation date and update date from metadata.
297
                $eventCreationDate = $domainMessage->getRecordedOn();
298
299
                $eventCreationString = $eventCreationDate->toString();
300
                $eventCreationDateTime = \DateTime::createFromFormat(
301
                    DateTime::FORMAT_STRING,
302
                    $eventCreationString
303
                );
304
                $eventLd->created = $eventCreationDateTime->format('c');
305
                $eventLd->modified = $eventCreationDateTime->format('c');
306
307
                // Add creator.
308
                $eventLd->creator = $this->getAuthorFromMetadata($domainMessage->getMetadata())->toNative();
309
310
                // Add publisher, which is the consumer name.
311
                $eventLd->publisher = $this->getConsumerFromMetadata($domainMessage->getMetadata())->toNative();
312
313
                return $eventLd;
314
            }
315
        );
316
    }
317
318
    /**
319
     * @param string $eventId
320
     * @param callable $fn
321
     */
322
    protected function saveNewDocument($eventId, callable $fn)
323
    {
324
        $document = $this
325
            ->newDocument($eventId)
326
            ->apply($fn);
327
328
        $this->repository->save($document);
329
    }
330
331
    /**
332
     * Helper function to save a JSON-LD document from cdbxml coming from UDB2.
333
     *
334
     * @param string $eventId
335
     * @param string $cdbXmlNamespaceUri
336
     * @param string $cdbXml
337
     */
338
    protected function applyEventCdbXmlFromUDB2(
339
        $eventId,
340
        $cdbXmlNamespaceUri,
341
        $cdbXml
342
    ) {
343
        $this->saveNewDocument(
344
            $eventId,
345
            function (\stdClass $eventLd) use ($cdbXmlNamespaceUri, $eventId, $cdbXml) {
346
                return $this->projectEventCdbXmlToObject(
347
                    $eventLd,
348
                    $eventId,
349
                    $cdbXmlNamespaceUri,
350
                    $cdbXml
351
                ) ;
352
            }
353
        );
354
    }
355
356
    /**
357
     * @param \stdClass $jsonLd
358
     * @param string $eventId
359
     * @param string $cdbXmlNamespaceUri
360
     * @param string $cdbXml
361
     *
362
     * @return \stdClass
363
     */
364
    protected function projectEventCdbXmlToObject(
365
        \stdClass $jsonLd,
366
        $eventId,
367
        $cdbXmlNamespaceUri,
368
        $cdbXml
369
    ) {
370
        $udb2Event = EventItemFactory::createEventFromCdbXml(
371
            $cdbXmlNamespaceUri,
372
            $cdbXml
373
        );
374
375
        $jsonLd = $this->cdbXMLImporter->documentWithCdbXML(
376
            $jsonLd,
377
            $udb2Event,
378
            $this,
379
            $this,
380
            $this->slugger
381
        );
382
383
        // Because we can not properly track media coming from UDB2 we simply
384
        // ignore it and give priority to content added through UDB3.
385
        // It's possible that an event has been deleted in udb3, but never
386
        // in udb2. If an update comes for that event from udb2, it should
387
        // be imported again. This is intended by design.
388
        // @see https://jira.uitdatabank.be/browse/III-1092
389
        try {
390
            $document = $this->loadDocumentFromRepositoryByItemId($eventId);
391
        } catch (DocumentGoneException $documentGoneException) {
392
            $document = $this->newDocument($eventId);
393
        }
394
395
        $media = $this->UDB3Media($document);
396
        if (!empty($media)) {
397
            $jsonLd->mediaObject = $media;
398
        }
399
400
        // Because UDB2 cannot keep track of UDB3 places as a location
401
        // ignore it and give priority to content added through UDB3.
402
        $location = $this->UDB3Location($document);
403
        if (!empty($location)) {
404
            $jsonLd->location = $location;
405
        }
406
407
        return $jsonLd;
408
    }
409
410
    /**
411
     * Return the media of an event if it already exists.
412
     *
413
     * @param JsonDocument $document The JsonDocument.
414
     *
415
     * @return array
416
     *  A list of media objects.
417
     */
418
    private function UDB3Media($document)
419
    {
420
        $media = [];
421
422
        if ($document) {
423
            $item = $document->getBody();
424
            // At the moment we do not include any media coming from UDB2.
425
            // If the mediaObject property contains data it's coming from UDB3.
426
            $item->mediaObject = isset($item->mediaObject) ? $item->mediaObject : [];
427
        }
428
429
        return $media;
430
    }
431
432
    /**
433
     * Return the location of an event if it already exists.
434
     *
435
     * @param JsonDocument $document The JsonDocument.
436
     *
437
     * @return array|null
438
     *  The location
439
     */
440
    private function UDB3Location($document)
441
    {
442
        $location = null;
443
444
        if ($document) {
445
            $item = $document->getBody();
446
            $location = isset($item->location) ? $item->location : null;
447
        }
448
449
        return $location;
450
    }
451
452
    /**
453
     * @param EventCreated $eventCreated
454
     * @param DomainMessage $domainMessage
455
     */
456
    protected function applyEventCreated(
457
        EventCreated $eventCreated,
458
        DomainMessage $domainMessage
459
    ) {
460
        $this->saveNewDocument(
461
            $eventCreated->getEventId(),
462
            function (\stdClass $jsonLD) use ($eventCreated, $domainMessage) {
463
                $jsonLD->{'@id'} = $this->iriGenerator->iri(
464
                    $eventCreated->getEventId()
465
                );
466
                $jsonLD->name['nl'] = $eventCreated->getTitle();
467
                $jsonLD->location = array(
468
                        '@type' => 'Place',
469
                    ) + (array)$this->placeJSONLD(
470
                        $eventCreated->getLocation()->getCdbid()
471
                    );
472
473
                $calendarJsonLD = $eventCreated->getCalendar()->toJsonLd();
474
                $jsonLD = (object)array_merge((array)$jsonLD, $calendarJsonLD);
475
476
                $availableTo = AvailableTo::createFromCalendar($eventCreated->getCalendar());
477
                $jsonLD->availableTo = (string)$availableTo;
478
479
                // Same as.
480
                $jsonLD->sameAs = $this->generateSameAs(
481
                    $eventCreated->getEventId(),
482
                    reset($jsonLD->name)
483
                );
484
485
                $eventType = $eventCreated->getEventType();
486
                $jsonLD->terms = [
487
                    $eventType->toJsonLd()
488
                ];
489
490
                $theme = $eventCreated->getTheme();
491
                if (!empty($theme)) {
492
                    $jsonLD->terms[] = $theme->toJsonLd();
493
                }
494
495
                $recordedOn = $domainMessage->getRecordedOn()->toString();
496
                $jsonLD->created = \DateTime::createFromFormat(
497
                    DateTime::FORMAT_STRING,
498
                    $recordedOn
499
                )->format('c');
500
                $jsonLD->modified = $jsonLD->created;
501
502
                $metaData = $domainMessage->getMetadata()->serialize();
503 View Code Duplication
                if (isset($metaData['user_email'])) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across 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...
504
                    $jsonLD->creator = $metaData['user_email'];
505
                } elseif (isset($metaData['user_nick'])) {
506
                    $jsonLD->creator = $metaData['user_nick'];
507
                }
508
509
                $jsonLD->workflowStatus = WorkflowStatus::DRAFT()->getName();
510
511
                return $jsonLD;
512
            }
513
        );
514
    }
515
516
    /**
517
     * @param EventDeleted $eventDeleted
518
     */
519
    protected function applyEventDeleted(EventDeleted $eventDeleted)
520
    {
521
        $this->repository->remove($eventDeleted->getItemId());
522
    }
523
524
    /**
525
     * Apply the major info updated command to the projector.
526
     */
527
    protected function applyMajorInfoUpdated(MajorInfoUpdated $majorInfoUpdated)
528
    {
529
        $document = $this
530
            ->loadDocumentFromRepository($majorInfoUpdated)
531
            ->apply(OfferUpdate::calendar($majorInfoUpdated->getCalendar()));
0 ignored issues
show
Compatibility introduced by
$majorInfoUpdated->getCalendar() of type object<CultuurNet\UDB3\CalendarInterface> is not a sub-type of object<CultuurNet\UDB3\Calendar>. It seems like you assume a concrete implementation of the interface CultuurNet\UDB3\CalendarInterface to be always present.

This check looks for parameters that are defined as one type in their type hint or doc comment but seem to be used as a narrower type, i.e an implementation of an interface or a subclass.

Consider changing the type of the parameter or doing an instanceof check before assuming your parameter is of the expected type.

Loading history...
532
533
        $jsonLD = $document->getBody();
534
535
        $jsonLD->name->nl = $majorInfoUpdated->getTitle();
536
        $jsonLD->location = array(
537
          '@type' => 'Place',
538
        ) + (array)$this->placeJSONLD($majorInfoUpdated->getLocation()->getCdbid());
539
540
        $availableTo = AvailableTo::createFromCalendar($majorInfoUpdated->getCalendar());
541
        $jsonLD->availableTo = (string)$availableTo;
542
543
        // Remove old theme and event type.
544
        $jsonLD->terms = array_filter($jsonLD->terms, function ($term) {
545
            return $term->domain !== EventType::DOMAIN &&  $term->domain !== Theme::DOMAIN;
546
        });
547
548
        $eventType = $majorInfoUpdated->getEventType();
549
        $jsonLD->terms = [
550
            $eventType->toJsonLd()
551
        ];
552
553
        $theme = $majorInfoUpdated->getTheme();
554
        if (!empty($theme)) {
555
            $jsonLD->terms[] = $theme->toJsonLd();
556
        }
557
558
        $this->repository->save($document->withBody($jsonLD));
559
    }
560
561
    /**
562
     * @inheritdoc
563
     */
564
    public function placeJSONLD($placeId)
565
    {
566
        if (empty($placeId)) {
567
            return array();
0 ignored issues
show
Bug Best Practice introduced by
The return type of return array(); (array) is incompatible with the return type declared by the interface CultuurNet\UDB3\Event\Re...eInterface::placeJSONLD 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...
568
        }
569
570
        try {
571
            $placeJSONLD = $this->placeService->getEntity(
572
                $placeId
573
            );
574
575
            return json_decode($placeJSONLD);
576
        } catch (EntityNotFoundException $e) {
577
            // In case the place can not be found at the moment, just add its ID
578
            return array(
0 ignored issues
show
Bug Best Practice introduced by
The return type of return array('@id' => $t...ervice->iri($placeId)); (array<string,string>) is incompatible with the return type declared by the interface CultuurNet\UDB3\Event\Re...eInterface::placeJSONLD 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...
579
                '@id' => $this->placeService->iri($placeId)
580
            );
581
        } catch (DocumentGoneException $e) {
582
            // In case the place can not be found at the moment, just add its ID
583
            return array(
0 ignored issues
show
Bug Best Practice introduced by
The return type of return array('@id' => $t...ervice->iri($placeId)); (array<string,string>) is incompatible with the return type declared by the interface CultuurNet\UDB3\Event\Re...eInterface::placeJSONLD 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...
584
                '@id' => $this->placeService->iri($placeId)
585
            );
586
        }
587
    }
588
589
    protected function applyTranslationApplied(
590
        TranslationApplied $translationApplied
591
    ) {
592
        $document = $this->loadDocumentFromRepositoryByItemId($translationApplied->getEventId()->toNative());
593
594
        $eventLd = $document->getBody();
595
596 View Code Duplication
        if ($translationApplied->getTitle() !== null) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across 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...
597
            $eventLd->name->{$translationApplied->getLanguage()->getCode(
598
            )} = $translationApplied->getTitle()->toNative();
599
        }
600
601 View Code Duplication
        if ($translationApplied->getLongDescription() !== null) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across 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...
602
            $eventLd->description->{$translationApplied->getLanguage()->getCode(
603
            )} = $translationApplied->getLongDescription()->toNative();
604
        }
605
606
        $this->repository->save($document->withBody($eventLd));
607
    }
608
609
    /**
610
     * Apply the translation deleted event to the event repository.
611
     * @param TranslationDeleted $translationDeleted
612
     */
613
    protected function applyTranslationDeleted(
614
        TranslationDeleted $translationDeleted
615
    ) {
616
        $document = $this->loadDocumentFromRepositoryByItemId($translationDeleted->getEventId()->toNative());
617
618
        $eventLd = $document->getBody();
619
620
        unset($eventLd->name->{$translationDeleted->getLanguage()->getCode()});
621
622
        unset($eventLd->description->{$translationDeleted->getLanguage()->getCode()});
623
624
        $this->repository->save($document->withBody($eventLd));
625
    }
626
627
    private function generateSameAs($eventId, $name)
628
    {
629
        $eventSlug = $this->slugger->slug($name);
630
        return array(
631
            'http://www.uitinvlaanderen.be/agenda/e/' . $eventSlug . '/' . $eventId,
632
        );
633
    }
634
635 View Code Duplication
    private function getAuthorFromMetadata(Metadata $metadata)
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...
636
    {
637
        $properties = $metadata->serialize();
638
639
        if (isset($properties['user_nick'])) {
640
            return new String($properties['user_nick']);
641
        }
642
    }
643
644 View Code Duplication
    private function getConsumerFromMetadata(Metadata $metadata)
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...
645
    {
646
        $properties = $metadata->serialize();
647
648
        if (isset($properties['consumer']['name'])) {
649
            return new String($properties['consumer']['name']);
650
        }
651
    }
652
653
    /**
654
     * @return string
655
     */
656
    protected function getLabelAddedClassName()
657
    {
658
        return LabelAdded::class;
659
    }
660
661
    /**
662
     * @return string
663
     */
664
    protected function getLabelRemovedClassName()
665
    {
666
        return LabelRemoved::class;
667
    }
668
669
    /**
670
     * @return string
671
     */
672
    protected function getImageAddedClassName()
673
    {
674
        return ImageAdded::class;
675
    }
676
677
    /**
678
     * @return string
679
     */
680
    protected function getImageRemovedClassName()
681
    {
682
        return ImageRemoved::class;
683
    }
684
685
    /**
686
     * @return string
687
     */
688
    protected function getImageUpdatedClassName()
689
    {
690
        return ImageUpdated::class;
691
    }
692
693
    protected function getMainImageSelectedClassName()
694
    {
695
        return MainImageSelected::class;
696
    }
697
698
    /**
699
     * @return string
700
     */
701
    protected function getTitleTranslatedClassName()
702
    {
703
        return TitleTranslated::class;
704
    }
705
706
    /**
707
     * @return string
708
     */
709
    protected function getDescriptionTranslatedClassName()
710
    {
711
        return DescriptionTranslated::class;
712
    }
713
714
    /**
715
     * @return string
716
     */
717
    protected function getOrganizerUpdatedClassName()
718
    {
719
        return OrganizerUpdated::class;
720
    }
721
722
    /**
723
     * @return string
724
     */
725
    protected function getOrganizerDeletedClassName()
726
    {
727
        return OrganizerDeleted::class;
728
    }
729
730
    protected function getBookingInfoUpdatedClassName()
731
    {
732
        return BookingInfoUpdated::class;
733
    }
734
735
    /**
736
     * @return string
737
     */
738
    protected function getPriceInfoUpdatedClassName()
739
    {
740
        return PriceInfoUpdated::class;
741
    }
742
743
    protected function getContactPointUpdatedClassName()
744
    {
745
        return ContactPointUpdated::class;
746
    }
747
748
    protected function getDescriptionUpdatedClassName()
749
    {
750
        return DescriptionUpdated::class;
751
    }
752
753
    protected function getTypicalAgeRangeUpdatedClassName()
754
    {
755
        return TypicalAgeRangeUpdated::class;
756
    }
757
758
    protected function getTypicalAgeRangeDeletedClassName()
759
    {
760
        return TypicalAgeRangeDeleted::class;
761
    }
762
763
    protected function getPublishedClassName()
764
    {
765
        return Published::class;
766
    }
767
768
    protected function getApprovedClassName()
769
    {
770
        return Approved::class;
771
    }
772
773
    protected function getRejectedClassName()
774
    {
775
        return Rejected::class;
776
    }
777
778
    protected function getFlaggedAsDuplicateClassName()
779
    {
780
        return FlaggedAsDuplicate::class;
781
    }
782
783
    protected function getFlaggedAsInappropriateClassName()
784
    {
785
        return FlaggedAsInappropriate::class;
786
    }
787
}
788