Completed
Pull Request — master (#270)
by Luc
04:33
created

EventLDProjector::applyEventCopied()   B

Complexity

Conditions 1
Paths 1

Size

Total Lines 40
Code Lines 22

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 40
rs 8.8571
c 0
b 0
f 0
cc 1
eloc 22
nc 1
nop 2
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\Cdb\EventItemFactory;
10
use CultuurNet\UDB3\EntityNotFoundException;
11
use CultuurNet\UDB3\Event\Events\AudienceUpdated;
12
use CultuurNet\UDB3\Event\Events\BookingInfoUpdated;
13
use CultuurNet\UDB3\Event\Events\ContactPointUpdated;
14
use CultuurNet\UDB3\Event\Events\DescriptionTranslated;
15
use CultuurNet\UDB3\Event\Events\DescriptionUpdated;
16
use CultuurNet\UDB3\Event\Events\EventCopied;
17
use CultuurNet\UDB3\Event\Events\EventCreated;
18
use CultuurNet\UDB3\Event\Events\EventDeleted;
19
use CultuurNet\UDB3\Event\Events\EventImportedFromUDB2;
20
use CultuurNet\UDB3\Event\Events\EventUpdatedFromUDB2;
21
use CultuurNet\UDB3\Event\Events\ImageAdded;
22
use CultuurNet\UDB3\Event\Events\ImageRemoved;
23
use CultuurNet\UDB3\Event\Events\Image\ImagesImportedFromUDB2;
24
use CultuurNet\UDB3\Event\Events\Image\ImagesUpdatedFromUDB2;
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\TypicalAgeRangeDeleted;
40
use CultuurNet\UDB3\Event\Events\TypicalAgeRangeUpdated;
41
use CultuurNet\UDB3\Event\EventType;
42
use CultuurNet\UDB3\Event\ReadModel\DocumentGoneException;
43
use CultuurNet\UDB3\Event\ReadModel\DocumentRepositoryInterface;
44
use CultuurNet\UDB3\Event\EventServiceInterface;
45
use CultuurNet\UDB3\Event\ValueObjects\Audience;
46
use CultuurNet\UDB3\Event\ValueObjects\AudienceType;
47
use CultuurNet\UDB3\Iri\IriGeneratorInterface;
48
use CultuurNet\UDB3\Offer\AvailableTo;
49
use CultuurNet\UDB3\Offer\IriOfferIdentifierFactoryInterface;
50
use CultuurNet\UDB3\Offer\ReadModel\JSONLD\OfferLDProjector;
51
use CultuurNet\UDB3\Offer\ReadModel\JSONLD\OfferUpdate;
52
use CultuurNet\UDB3\Offer\WorkflowStatus;
53
use CultuurNet\UDB3\Organizer\OrganizerProjectedToJSONLD;
54
use CultuurNet\UDB3\OrganizerService;
55
use CultuurNet\UDB3\Place\Events\PlaceProjectedToJSONLD;
56
use CultuurNet\UDB3\PlaceService;
57
use CultuurNet\UDB3\ReadModel\JsonDocument;
58
use CultuurNet\UDB3\Theme;
59
use Symfony\Component\Serializer\SerializerInterface;
60
use ValueObjects\StringLiteral\StringLiteral;
61
use ValueObjects\Web\Url;
62
63
/**
64
 * Projects state changes on Event entities to a JSON-LD read model in a
65
 * document repository.
66
 *
67
 * Implements PlaceServiceInterface and OrganizerServiceInterface to do a double
68
 * dispatch with CdbXMLImporter.
69
 */
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;
0 ignored issues
show
Bug introduced by
The property cdbXMLImporter does not seem to exist. Did you mean cdbXmlImporter?

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.

Loading history...
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
                $created = $this->getCreated($domainMessage);
258
                $eventLd->created = $created;
259
                $eventLd->modified = $created;
260
261
                // Add creator.
262
                $eventLd->creator = $this->getAuthorFromMetadata($domainMessage->getMetadata())->toNative();
263
264
                // Add publisher, which is the consumer name.
265
                $eventLd->publisher = $this->getConsumerFromMetadata($domainMessage->getMetadata())->toNative();
266
267
                return $eventLd;
268
            }
269
        );
270
    }
271
272
    /**
273
     * @param string $eventId
274
     * @param callable $fn
275
     */
276
    protected function saveNewDocument($eventId, callable $fn)
277
    {
278
        $document = $this
279
            ->newDocument($eventId)
280
            ->apply($fn);
281
282
        $this->repository->save($document);
283
    }
284
285
    /**
286
     * Helper function to save a JSON-LD document from cdbxml coming from UDB2.
287
     *
288
     * @param string $eventId
289
     * @param string $cdbXmlNamespaceUri
290
     * @param string $cdbXml
291
     */
292
    protected function applyEventCdbXmlFromUDB2(
293
        $eventId,
294
        $cdbXmlNamespaceUri,
295
        $cdbXml
296
    ) {
297
        $this->saveNewDocument(
298
            $eventId,
299
            function (\stdClass $eventLd) use ($cdbXmlNamespaceUri, $eventId, $cdbXml) {
300
                return $this->projectEventCdbXmlToObject(
301
                    $eventLd,
302
                    $eventId,
303
                    $cdbXmlNamespaceUri,
304
                    $cdbXml
305
                ) ;
306
            }
307
        );
308
    }
309
310
    /**
311
     * @param \stdClass $jsonLd
312
     * @param string $eventId
313
     * @param string $cdbXmlNamespaceUri
314
     * @param string $cdbXml
315
     *
316
     * @return \stdClass
317
     */
318
    protected function projectEventCdbXmlToObject(
319
        \stdClass $jsonLd,
320
        $eventId,
321
        $cdbXmlNamespaceUri,
322
        $cdbXml
323
    ) {
324
        $udb2Event = EventItemFactory::createEventFromCdbXml(
325
            $cdbXmlNamespaceUri,
326
            $cdbXml
327
        );
328
329
        $jsonLd = $this->cdbXMLImporter->documentWithCdbXML(
0 ignored issues
show
Bug introduced by
The property cdbXMLImporter does not seem to exist. Did you mean cdbXmlImporter?

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.

Loading history...
330
            $jsonLd,
331
            $udb2Event,
332
            $this,
333
            $this,
334
            $this->slugger
335
        );
336
337
        // Because we can not properly track media coming from UDB2 we simply
338
        // ignore it and give priority to content added through UDB3.
339
        // It's possible that an event has been deleted in udb3, but never
340
        // in udb2. If an update comes for that event from udb2, it should
341
        // be imported again. This is intended by design.
342
        // @see https://jira.uitdatabank.be/browse/III-1092
343
        try {
344
            $document = $this->loadDocumentFromRepositoryByItemId($eventId);
345
        } catch (DocumentGoneException $documentGoneException) {
346
            $document = $this->newDocument($eventId);
347
        }
348
349
        $media = $this->UDB3Media($document);
350
        if (!empty($media)) {
351
            $jsonLd->mediaObject = $media;
352
        }
353
354
        // Because UDB2 cannot keep track of UDB3 places as a location
355
        // ignore it and give priority to content added through UDB3.
356
        $location = $this->UDB3Location($document);
357
        if (!empty($location)) {
358
            $jsonLd->location = $location;
359
        }
360
361
        return $jsonLd;
362
    }
363
364
    /**
365
     * Return the media of an event if it already exists.
366
     *
367
     * @param JsonDocument $document The JsonDocument.
368
     *
369
     * @return array
370
     *  A list of media objects.
371
     */
372
    private function UDB3Media($document)
373
    {
374
        $media = [];
375
376
        if ($document) {
377
            $item = $document->getBody();
378
            // At the moment we do not include any media coming from UDB2.
379
            // If the mediaObject property contains data it's coming from UDB3.
380
            $item->mediaObject = isset($item->mediaObject) ? $item->mediaObject : [];
381
        }
382
383
        return $media;
384
    }
385
386
    /**
387
     * Return the location of an event if it already exists.
388
     *
389
     * @param JsonDocument $document The JsonDocument.
390
     *
391
     * @return array|null
392
     *  The location
393
     */
394
    private function UDB3Location($document)
395
    {
396
        $location = null;
397
398
        if ($document) {
399
            $item = $document->getBody();
400
            $location = isset($item->location) ? $item->location : null;
401
        }
402
403
        return $location;
404
    }
405
406
    /**
407
     * @param EventCreated $eventCreated
408
     * @param DomainMessage $domainMessage
409
     */
410
    protected function applyEventCreated(
411
        EventCreated $eventCreated,
412
        DomainMessage $domainMessage
413
    ) {
414
        $this->saveNewDocument(
415
            $eventCreated->getEventId(),
416
            function (\stdClass $jsonLD) use ($eventCreated, $domainMessage) {
417
                $jsonLD->{'@id'} = $this->iriGenerator->iri(
418
                    $eventCreated->getEventId()
419
                );
420
                $jsonLD->name['nl'] = $eventCreated->getTitle();
421
                $jsonLD->location = array(
422
                        '@type' => 'Place',
423
                    ) + (array)$this->placeJSONLD(
424
                        $eventCreated->getLocation()->getCdbid()
425
                    );
426
427
                $calendarJsonLD = $eventCreated->getCalendar()->toJsonLd();
428
                $jsonLD = (object)array_merge((array)$jsonLD, $calendarJsonLD);
429
430
                $availableTo = AvailableTo::createFromCalendar($eventCreated->getCalendar());
431
                $jsonLD->availableTo = (string)$availableTo;
432
433
                // Same as.
434
                $jsonLD->sameAs = $this->generateSameAs(
435
                    $eventCreated->getEventId(),
436
                    reset($jsonLD->name)
437
                );
438
439
                $eventType = $eventCreated->getEventType();
440
                $jsonLD->terms = [
441
                    $eventType->toJsonLd()
442
                ];
443
444
                $theme = $eventCreated->getTheme();
445
                if (!empty($theme)) {
446
                    $jsonLD->terms[] = $theme->toJsonLd();
447
                }
448
449
                $created = $this->getCreated($domainMessage);
450
                $jsonLD->created = $created;
451
                $jsonLD->modified = $created;
452
453
                $metaData = $domainMessage->getMetadata()->serialize();
454 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...
455
                    $jsonLD->creator = $metaData['user_email'];
456
                } elseif (isset($metaData['user_nick'])) {
457
                    $jsonLD->creator = $metaData['user_nick'];
458
                }
459
460
                $jsonLD->workflowStatus = WorkflowStatus::DRAFT()->getName();
461
462
                $defaultAudience = new Audience(AudienceType::EVERYONE());
463
                $jsonLD->audience = $defaultAudience->serialize();
464
465
                return $jsonLD;
466
            }
467
        );
468
    }
469
470
    /**
471
     * @param EventCopied $eventCopied
472
     * @param DomainMessage $domainMessage
473
     */
474
    protected function applyEventCopied(
475
        EventCopied $eventCopied,
476
        DomainMessage $domainMessage
477
    ) {
478
        $originalDocument = $this->repository->get($eventCopied->getOriginalEventId());
479
        $eventJsonLD = $originalDocument->getBody();
480
481
        // Set the created and modified date.
482
        $created = $this->getCreated($domainMessage);
483
        $eventJsonLD->created = $created;
484
        $eventJsonLD->modified = $created;
485
486
        // Set the creator.
487
        $eventJsonLD->creator = $this->getAuthorFromMetadata($domainMessage->getMetadata())->toNative();
488
489
        // Set the id.
490
        $eventJsonLD->{'@id'} = $this->iriGenerator->iri($eventCopied->getItemId());
491
492
        // Set the new calendar.
493
        $eventJsonLD = (object) array_merge(
494
            (array) $eventJsonLD,
495
            $eventCopied->getCalendar()->toJsonLd()
496
        );
497
498
        // Set workflow status.
499
        $eventJsonLD->workflowStatus = WorkflowStatus::DRAFT()->getName();
500
501
        // Remove labels.
502
        unset($eventJsonLD->labels);
503
        unset($eventJsonLD->hiddenLabels);
504
505
        // Set available to and from.
506
        $availableTo = AvailableTo::createFromCalendar($eventCopied->getCalendar());
507
        $eventJsonLD->availableTo = (string) $availableTo;
508
        unset($eventJsonLD->availableFrom);
509
510
        $newDocument = new JsonDocument($eventCopied->getItemId());
511
        $newDocument = $newDocument->withBody($eventJsonLD);
512
        $this->repository->save($newDocument);
513
    }
514
515
    /**
516
     * @param EventDeleted $eventDeleted
517
     */
518
    protected function applyEventDeleted(EventDeleted $eventDeleted)
519
    {
520
        $this->repository->remove($eventDeleted->getItemId());
521
    }
522
523
    /**
524
     * Apply the major info updated command to the projector.
525
     */
526
    protected function applyMajorInfoUpdated(MajorInfoUpdated $majorInfoUpdated)
527
    {
528
        $document = $this
529
            ->loadDocumentFromRepository($majorInfoUpdated)
530
            ->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...
531
532
        $jsonLD = $document->getBody();
533
534
        $jsonLD->name->nl = $majorInfoUpdated->getTitle();
535
        $jsonLD->location = array(
536
          '@type' => 'Place',
537
        ) + (array)$this->placeJSONLD($majorInfoUpdated->getLocation()->getCdbid());
538
539
        $availableTo = AvailableTo::createFromCalendar($majorInfoUpdated->getCalendar());
540
        $jsonLD->availableTo = (string)$availableTo;
541
542
        // Remove old theme and event type.
543
        $jsonLD->terms = array_filter($jsonLD->terms, function ($term) {
544
            return $term->domain !== EventType::DOMAIN &&  $term->domain !== Theme::DOMAIN;
545
        });
546
        $jsonLD->terms = array_values($jsonLD->terms);
547
548
        $eventType = $majorInfoUpdated->getEventType();
549
        $jsonLD->terms[] = $eventType->toJsonLd();
550
551
        $theme = $majorInfoUpdated->getTheme();
552
        if (!empty($theme)) {
553
            $jsonLD->terms[] = $theme->toJsonLd();
554
        }
555
556
        $this->repository->save($document->withBody($jsonLD));
557
    }
558
559
    /**
560
     * @param AudienceUpdated $audienceUpdated
561
     */
562
    protected function applyAudienceUpdated(AudienceUpdated $audienceUpdated)
563
    {
564
        $document = $this->loadDocumentFromRepository($audienceUpdated);
565
        $jsonLD = $document->getBody();
566
567
        $jsonLD->audience = $audienceUpdated->getAudience()->serialize();
568
569
        $this->repository->save($document->withBody($jsonLD));
570
    }
571
572
    /**
573
     * @inheritdoc
574
     */
575
    public function placeJSONLD($placeId)
576
    {
577
        if (empty($placeId)) {
578
            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...
579
        }
580
581
        try {
582
            $placeJSONLD = $this->placeService->getEntity(
583
                $placeId
584
            );
585
586
            return json_decode($placeJSONLD);
587
        } catch (EntityNotFoundException $e) {
588
            // In case the place can not be found at the moment, just add its ID
589
            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...
590
                '@id' => $this->placeService->iri($placeId)
591
            );
592
        } catch (DocumentGoneException $e) {
593
            // In case the place can not be found at the moment, just add its ID
594
            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...
595
                '@id' => $this->placeService->iri($placeId)
596
            );
597
        }
598
    }
599
600
    private function generateSameAs($eventId, $name)
601
    {
602
        $eventSlug = $this->slugger->slug($name);
603
        return array(
604
            'http://www.uitinvlaanderen.be/agenda/e/' . $eventSlug . '/' . $eventId,
605
        );
606
    }
607
608 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...
609
    {
610
        $properties = $metadata->serialize();
611
612
        if (isset($properties['user_nick'])) {
613
            return new StringLiteral($properties['user_nick']);
614
        }
615
    }
616
617 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...
618
    {
619
        $properties = $metadata->serialize();
620
621
        if (isset($properties['consumer']['name'])) {
622
            return new StringLiteral($properties['consumer']['name']);
623
        }
624
    }
625
626
    /**
627
     * @param DomainMessage $domainMessage
628
     * @return string
629
     */
630
    private function getCreated(DomainMessage $domainMessage)
631
    {
632
        $recordedOn = $domainMessage->getRecordedOn()->toString();
633
634
        return \DateTime::createFromFormat(
635
            DateTime::FORMAT_STRING,
636
            $recordedOn
637
        )->format('c');
638
    }
639
640
    /**
641
     * @return string
642
     */
643
    protected function getLabelAddedClassName()
644
    {
645
        return LabelAdded::class;
646
    }
647
648
    /**
649
     * @return string
650
     */
651
    protected function getLabelRemovedClassName()
652
    {
653
        return LabelRemoved::class;
654
    }
655
656
    /**
657
     * @return string
658
     */
659
    protected function getImageAddedClassName()
660
    {
661
        return ImageAdded::class;
662
    }
663
664
    /**
665
     * @return string
666
     */
667
    protected function getImageRemovedClassName()
668
    {
669
        return ImageRemoved::class;
670
    }
671
672
    /**
673
     * @return string
674
     */
675
    protected function getImageUpdatedClassName()
676
    {
677
        return ImageUpdated::class;
678
    }
679
680
    protected function getMainImageSelectedClassName()
681
    {
682
        return MainImageSelected::class;
683
    }
684
685
    /**
686
     * @return string
687
     */
688
    protected function getTitleTranslatedClassName()
689
    {
690
        return TitleTranslated::class;
691
    }
692
693
    /**
694
     * @return string
695
     */
696
    protected function getDescriptionTranslatedClassName()
697
    {
698
        return DescriptionTranslated::class;
699
    }
700
701
    /**
702
     * @return string
703
     */
704
    protected function getOrganizerUpdatedClassName()
705
    {
706
        return OrganizerUpdated::class;
707
    }
708
709
    /**
710
     * @return string
711
     */
712
    protected function getOrganizerDeletedClassName()
713
    {
714
        return OrganizerDeleted::class;
715
    }
716
717
    protected function getBookingInfoUpdatedClassName()
718
    {
719
        return BookingInfoUpdated::class;
720
    }
721
722
    /**
723
     * @return string
724
     */
725
    protected function getPriceInfoUpdatedClassName()
726
    {
727
        return PriceInfoUpdated::class;
728
    }
729
730
    protected function getContactPointUpdatedClassName()
731
    {
732
        return ContactPointUpdated::class;
733
    }
734
735
    protected function getDescriptionUpdatedClassName()
736
    {
737
        return DescriptionUpdated::class;
738
    }
739
740
    protected function getTypicalAgeRangeUpdatedClassName()
741
    {
742
        return TypicalAgeRangeUpdated::class;
743
    }
744
745
    protected function getTypicalAgeRangeDeletedClassName()
746
    {
747
        return TypicalAgeRangeDeleted::class;
748
    }
749
750
    protected function getPublishedClassName()
751
    {
752
        return Published::class;
753
    }
754
755
    protected function getApprovedClassName()
756
    {
757
        return Approved::class;
758
    }
759
760
    protected function getRejectedClassName()
761
    {
762
        return Rejected::class;
763
    }
764
765
    protected function getFlaggedAsDuplicateClassName()
766
    {
767
        return FlaggedAsDuplicate::class;
768
    }
769
770
    protected function getFlaggedAsInappropriateClassName()
771
    {
772
        return FlaggedAsInappropriate::class;
773
    }
774
775
    protected function getImagesImportedFromUdb2ClassName()
776
    {
777
        return ImagesImportedFromUDB2::class;
778
    }
779
780
    protected function getImagesUpdatedFromUdb2ClassName()
781
    {
782
        return ImagesUpdatedFromUDB2::class;
783
    }
784
}
785