Completed
Pull Request — master (#261)
by Luc
04:43
created

EventLDProjector::applyAudienceUpdated()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 9
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 9
rs 9.6666
c 0
b 0
f 0
cc 1
eloc 5
nc 1
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\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\EventCreated;
17
use CultuurNet\UDB3\Event\Events\EventDeleted;
18
use CultuurNet\UDB3\Event\Events\EventImportedFromUDB2;
19
use CultuurNet\UDB3\Event\Events\EventUpdatedFromUDB2;
20
use CultuurNet\UDB3\Event\Events\ImageAdded;
21
use CultuurNet\UDB3\Event\Events\ImageRemoved;
22
use CultuurNet\UDB3\Event\Events\Image\ImagesImportedFromUDB2;
23
use CultuurNet\UDB3\Event\Events\Image\ImagesUpdatedFromUDB2;
24
use CultuurNet\UDB3\Event\Events\ImageUpdated;
25
use CultuurNet\UDB3\Event\Events\LabelAdded;
26
use CultuurNet\UDB3\Event\Events\LabelRemoved;
27
use CultuurNet\UDB3\Event\Events\MainImageSelected;
28
use CultuurNet\UDB3\Event\Events\MajorInfoUpdated;
29
use CultuurNet\UDB3\Event\Events\Moderation\Approved;
30
use CultuurNet\UDB3\Event\Events\Moderation\FlaggedAsDuplicate;
31
use CultuurNet\UDB3\Event\Events\Moderation\FlaggedAsInappropriate;
32
use CultuurNet\UDB3\Event\Events\Moderation\Published;
33
use CultuurNet\UDB3\Event\Events\Moderation\Rejected;
34
use CultuurNet\UDB3\Event\Events\OrganizerDeleted;
35
use CultuurNet\UDB3\Event\Events\OrganizerUpdated;
36
use CultuurNet\UDB3\Event\Events\PriceInfoUpdated;
37
use CultuurNet\UDB3\Event\Events\TitleTranslated;
38
use CultuurNet\UDB3\Event\Events\TypicalAgeRangeDeleted;
39
use CultuurNet\UDB3\Event\Events\TypicalAgeRangeUpdated;
40
use CultuurNet\UDB3\Event\EventType;
41
use CultuurNet\UDB3\Event\ReadModel\DocumentGoneException;
42
use CultuurNet\UDB3\Event\ReadModel\DocumentRepositoryInterface;
43
use CultuurNet\UDB3\Event\EventServiceInterface;
44
use CultuurNet\UDB3\Event\ValueObjects\Audience;
45
use CultuurNet\UDB3\Event\ValueObjects\AudienceType;
46
use CultuurNet\UDB3\Iri\IriGeneratorInterface;
47
use CultuurNet\UDB3\Offer\AvailableTo;
48
use CultuurNet\UDB3\Offer\IriOfferIdentifierFactoryInterface;
49
use CultuurNet\UDB3\Offer\ReadModel\JSONLD\OfferLDProjector;
50
use CultuurNet\UDB3\Offer\ReadModel\JSONLD\OfferUpdate;
51
use CultuurNet\UDB3\Offer\WorkflowStatus;
52
use CultuurNet\UDB3\Organizer\OrganizerProjectedToJSONLD;
53
use CultuurNet\UDB3\OrganizerService;
54
use CultuurNet\UDB3\Place\Events\PlaceProjectedToJSONLD;
55
use CultuurNet\UDB3\PlaceService;
56
use CultuurNet\UDB3\ReadModel\JsonDocument;
57
use CultuurNet\UDB3\Theme;
58
use Symfony\Component\Serializer\SerializerInterface;
59
use ValueObjects\String\String;
60
use ValueObjects\Web\Url;
61
62
/**
63
 * Projects state changes on Event entities to a JSON-LD read model in a
64
 * document repository.
65
 *
66
 * Implements PlaceServiceInterface and OrganizerServiceInterface to do a double
67
 * dispatch with CdbXMLImporter.
68
 */
69
class EventLDProjector extends OfferLDProjector implements
70
    EventListenerInterface,
71
    PlaceServiceInterface,
72
    OrganizerServiceInterface
73
{
74
    /**
75
     * @var PlaceService
76
     */
77
    protected $placeService;
78
79
    /**
80
     * @var EventServiceInterface
81
     */
82
    protected $eventService;
83
84
    /**
85
     * @var IriOfferIdentifierFactoryInterface
86
     */
87
    protected $iriOfferIdentifierFactory;
88
89
    /**
90
     * @var CdbXMLImporter
91
     */
92
    protected $cdbXmlImporter;
93
94
    /**
95
     * @param DocumentRepositoryInterface $repository
96
     * @param IriGeneratorInterface $iriGenerator
97
     * @param EventServiceInterface $eventService
98
     * @param PlaceService $placeService
99
     * @param OrganizerService $organizerService
100
     * @param SerializerInterface $mediaObjectSerializer
101
     * @param IriOfferIdentifierFactoryInterface $iriOfferIdentifierFactory
102
     * @param CdbXMLImporter $cdbXMLImporter
103
     */
104
    public function __construct(
105
        DocumentRepositoryInterface $repository,
106
        IriGeneratorInterface $iriGenerator,
107
        EventServiceInterface $eventService,
108
        PlaceService $placeService,
109
        OrganizerService $organizerService,
110
        SerializerInterface $mediaObjectSerializer,
111
        IriOfferIdentifierFactoryInterface $iriOfferIdentifierFactory,
112
        CdbXMLImporter $cdbXMLImporter
113
    ) {
114
        parent::__construct(
115
            $repository,
116
            $iriGenerator,
117
            $organizerService,
118
            $mediaObjectSerializer
119
        );
120
121
        $this->placeService = $placeService;
122
        $this->eventService = $eventService;
123
        $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...
124
125
        $this->iriOfferIdentifierFactory = $iriOfferIdentifierFactory;
126
    }
127
128
    protected function applyOrganizerProjectedToJSONLD(OrganizerProjectedToJSONLD $organizerProjectedToJSONLD)
129
    {
130
        $eventIds = $this->eventsOrganizedByOrganizer(
131
            $organizerProjectedToJSONLD->getId()
132
        );
133
134
        $organizer = $this->organizerService->getEntity(
135
            $organizerProjectedToJSONLD->getId()
136
        );
137
138
        foreach ($eventIds as $eventId) {
139
            $document = $this->loadDocumentFromRepositoryByItemId(
140
                $eventId
141
            );
142
            $eventLD = $document->getBody();
143
144
            $newEventLD = clone $eventLD;
145
            $newEventLD->organizer = json_decode($organizer);
146
147
            if ($newEventLD != $eventLD) {
148
                $this->repository->save($document->withBody($newEventLD));
149
            }
150
        }
151
    }
152
153
    protected function applyPlaceProjectedToJSONLD(
154
        PlaceProjectedToJSONLD $placeProjectedToJSONLD
155
    ) {
156
        $identifier = $this->iriOfferIdentifierFactory->fromIri(
157
            Url::fromNative($placeProjectedToJSONLD->getIri())
158
        );
159
160
        $eventsLocatedAtPlace = $this->eventsLocatedAtPlace(
161
            $identifier->getId()
162
        );
163
164
        $placeJSONLD = $this->placeService->getEntity(
165
            $identifier->getId()
166
        );
167
168
        foreach ($eventsLocatedAtPlace as $eventId) {
169
            $document = $this->loadDocumentFromRepositoryByItemId(
170
                $eventId
171
            );
172
            $eventLD = $document->getBody();
173
174
            $newEventLD = clone $eventLD;
175
            $newEventLD->location = json_decode($placeJSONLD);
176
177
            if ($newEventLD != $eventLD) {
178
                $this->repository->save($document->withBody($newEventLD));
179
            }
180
        }
181
    }
182
183
    /**
184
     * @param string $organizerId
185
     * @return string[]
186
     */
187
    protected function eventsOrganizedByOrganizer($organizerId)
188
    {
189
        return $this->eventService->eventsOrganizedByOrganizer(
190
            $organizerId
191
        );
192
    }
193
194
    /**
195
     * @param string $placeId
196
     * @return string[]
197
     */
198
    protected function eventsLocatedAtPlace($placeId)
199
    {
200
        return $this->eventService->eventsLocatedAtPlace(
201
            $placeId
202
        );
203
    }
204
205
    /**
206
     * @param EventImportedFromUDB2 $eventImportedFromUDB2
207
     */
208
    protected function applyEventImportedFromUDB2(
209
        EventImportedFromUDB2 $eventImportedFromUDB2
210
    ) {
211
        $this->applyEventCdbXmlFromUDB2(
212
            $eventImportedFromUDB2->getEventId(),
213
            $eventImportedFromUDB2->getCdbXmlNamespaceUri(),
214
            $eventImportedFromUDB2->getCdbXml()
215
        );
216
    }
217
218
    /**
219
     * @param EventUpdatedFromUDB2 $eventUpdatedFromUDB2
220
     */
221
    protected function applyEventUpdatedFromUDB2(
222
        EventUpdatedFromUDB2 $eventUpdatedFromUDB2
223
    ) {
224
        $this->applyEventCdbXmlFromUDB2(
225
            $eventUpdatedFromUDB2->getEventId(),
226
            $eventUpdatedFromUDB2->getCdbXmlNamespaceUri(),
227
            $eventUpdatedFromUDB2->getCdbXml()
228
        );
229
    }
230
231
    /**
232
     * Helper function to save JSONLD document from entryapi cdbxml.
233
     *
234
     * @param string $eventId
235
     * @param string $cdbXmlNamespaceUri
236
     * @param string $cdbXml
237
     * @param DomainMessage $domainMessage
238
     */
239
    protected function applyEventFromCdbXml(
240
        $eventId,
241
        $cdbXmlNamespaceUri,
242
        $cdbXml,
243
        $domainMessage
244
    ) {
245
        $this->saveNewDocument(
246
            $eventId,
247
            function (\stdClass $eventLd) use ($eventId, $cdbXmlNamespaceUri, $cdbXml, $domainMessage) {
248
                $eventLd = $this->projectEventCdbXmlToObject(
249
                    $eventLd,
250
                    $eventId,
251
                    $cdbXmlNamespaceUri,
252
                    $cdbXml
253
                );
254
255
                // Add creation date and update date from metadata.
256
                $eventCreationDate = $domainMessage->getRecordedOn();
257
258
                $eventCreationString = $eventCreationDate->toString();
259
                $eventCreationDateTime = \DateTime::createFromFormat(
260
                    DateTime::FORMAT_STRING,
261
                    $eventCreationString
262
                );
263
                $eventLd->created = $eventCreationDateTime->format('c');
264
                $eventLd->modified = $eventCreationDateTime->format('c');
265
266
                // Add creator.
267
                $eventLd->creator = $this->getAuthorFromMetadata($domainMessage->getMetadata())->toNative();
268
269
                // Add publisher, which is the consumer name.
270
                $eventLd->publisher = $this->getConsumerFromMetadata($domainMessage->getMetadata())->toNative();
271
272
                return $eventLd;
273
            }
274
        );
275
    }
276
277
    /**
278
     * @param string $eventId
279
     * @param callable $fn
280
     */
281
    protected function saveNewDocument($eventId, callable $fn)
282
    {
283
        $document = $this
284
            ->newDocument($eventId)
285
            ->apply($fn);
286
287
        $this->repository->save($document);
288
    }
289
290
    /**
291
     * Helper function to save a JSON-LD document from cdbxml coming from UDB2.
292
     *
293
     * @param string $eventId
294
     * @param string $cdbXmlNamespaceUri
295
     * @param string $cdbXml
296
     */
297
    protected function applyEventCdbXmlFromUDB2(
298
        $eventId,
299
        $cdbXmlNamespaceUri,
300
        $cdbXml
301
    ) {
302
        $this->saveNewDocument(
303
            $eventId,
304
            function (\stdClass $eventLd) use ($cdbXmlNamespaceUri, $eventId, $cdbXml) {
305
                return $this->projectEventCdbXmlToObject(
306
                    $eventLd,
307
                    $eventId,
308
                    $cdbXmlNamespaceUri,
309
                    $cdbXml
310
                ) ;
311
            }
312
        );
313
    }
314
315
    /**
316
     * @param \stdClass $jsonLd
317
     * @param string $eventId
318
     * @param string $cdbXmlNamespaceUri
319
     * @param string $cdbXml
320
     *
321
     * @return \stdClass
322
     */
323
    protected function projectEventCdbXmlToObject(
324
        \stdClass $jsonLd,
325
        $eventId,
326
        $cdbXmlNamespaceUri,
327
        $cdbXml
328
    ) {
329
        $udb2Event = EventItemFactory::createEventFromCdbXml(
330
            $cdbXmlNamespaceUri,
331
            $cdbXml
332
        );
333
334
        $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...
335
            $jsonLd,
336
            $udb2Event,
337
            $this,
338
            $this,
339
            $this->slugger
340
        );
341
342
        // Because we can not properly track media coming from UDB2 we simply
343
        // ignore it and give priority to content added through UDB3.
344
        // It's possible that an event has been deleted in udb3, but never
345
        // in udb2. If an update comes for that event from udb2, it should
346
        // be imported again. This is intended by design.
347
        // @see https://jira.uitdatabank.be/browse/III-1092
348
        try {
349
            $document = $this->loadDocumentFromRepositoryByItemId($eventId);
350
        } catch (DocumentGoneException $documentGoneException) {
351
            $document = $this->newDocument($eventId);
352
        }
353
354
        $media = $this->UDB3Media($document);
355
        if (!empty($media)) {
356
            $jsonLd->mediaObject = $media;
357
        }
358
359
        // Because UDB2 cannot keep track of UDB3 places as a location
360
        // ignore it and give priority to content added through UDB3.
361
        $location = $this->UDB3Location($document);
362
        if (!empty($location)) {
363
            $jsonLd->location = $location;
364
        }
365
366
        return $jsonLd;
367
    }
368
369
    /**
370
     * Return the media of an event if it already exists.
371
     *
372
     * @param JsonDocument $document The JsonDocument.
373
     *
374
     * @return array
375
     *  A list of media objects.
376
     */
377
    private function UDB3Media($document)
378
    {
379
        $media = [];
380
381
        if ($document) {
382
            $item = $document->getBody();
383
            // At the moment we do not include any media coming from UDB2.
384
            // If the mediaObject property contains data it's coming from UDB3.
385
            $item->mediaObject = isset($item->mediaObject) ? $item->mediaObject : [];
386
        }
387
388
        return $media;
389
    }
390
391
    /**
392
     * Return the location of an event if it already exists.
393
     *
394
     * @param JsonDocument $document The JsonDocument.
395
     *
396
     * @return array|null
397
     *  The location
398
     */
399
    private function UDB3Location($document)
400
    {
401
        $location = null;
402
403
        if ($document) {
404
            $item = $document->getBody();
405
            $location = isset($item->location) ? $item->location : null;
406
        }
407
408
        return $location;
409
    }
410
411
    /**
412
     * @param EventCreated $eventCreated
413
     * @param DomainMessage $domainMessage
414
     */
415
    protected function applyEventCreated(
416
        EventCreated $eventCreated,
417
        DomainMessage $domainMessage
418
    ) {
419
        $this->saveNewDocument(
420
            $eventCreated->getEventId(),
421
            function (\stdClass $jsonLD) use ($eventCreated, $domainMessage) {
422
                $jsonLD->{'@id'} = $this->iriGenerator->iri(
423
                    $eventCreated->getEventId()
424
                );
425
                $jsonLD->name['nl'] = $eventCreated->getTitle();
426
                $jsonLD->location = array(
427
                        '@type' => 'Place',
428
                    ) + (array)$this->placeJSONLD(
429
                        $eventCreated->getLocation()->getCdbid()
430
                    );
431
432
                $calendarJsonLD = $eventCreated->getCalendar()->toJsonLd();
433
                $jsonLD = (object)array_merge((array)$jsonLD, $calendarJsonLD);
434
435
                $availableTo = AvailableTo::createFromCalendar($eventCreated->getCalendar());
436
                $jsonLD->availableTo = (string)$availableTo;
437
438
                // Same as.
439
                $jsonLD->sameAs = $this->generateSameAs(
440
                    $eventCreated->getEventId(),
441
                    reset($jsonLD->name)
442
                );
443
444
                $eventType = $eventCreated->getEventType();
445
                $jsonLD->terms = [
446
                    $eventType->toJsonLd()
447
                ];
448
449
                $theme = $eventCreated->getTheme();
450
                if (!empty($theme)) {
451
                    $jsonLD->terms[] = $theme->toJsonLd();
452
                }
453
454
                $recordedOn = $domainMessage->getRecordedOn()->toString();
455
                $jsonLD->created = \DateTime::createFromFormat(
456
                    DateTime::FORMAT_STRING,
457
                    $recordedOn
458
                )->format('c');
459
                $jsonLD->modified = $jsonLD->created;
460
461
                $metaData = $domainMessage->getMetadata()->serialize();
462 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...
463
                    $jsonLD->creator = $metaData['user_email'];
464
                } elseif (isset($metaData['user_nick'])) {
465
                    $jsonLD->creator = $metaData['user_nick'];
466
                }
467
468
                $jsonLD->workflowStatus = WorkflowStatus::DRAFT()->getName();
469
470
                $defaultAudience = new Audience(AudienceType::EVERYONE());
471
                $jsonLD->audience = $defaultAudience->serialize();
472
473
                return $jsonLD;
474
            }
475
        );
476
    }
477
478
    /**
479
     * @param EventDeleted $eventDeleted
480
     */
481
    protected function applyEventDeleted(EventDeleted $eventDeleted)
482
    {
483
        $this->repository->remove($eventDeleted->getItemId());
484
    }
485
486
    /**
487
     * Apply the major info updated command to the projector.
488
     */
489
    protected function applyMajorInfoUpdated(MajorInfoUpdated $majorInfoUpdated)
490
    {
491
        $document = $this
492
            ->loadDocumentFromRepository($majorInfoUpdated)
493
            ->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...
494
495
        $jsonLD = $document->getBody();
496
497
        $jsonLD->name->nl = $majorInfoUpdated->getTitle();
498
        $jsonLD->location = array(
499
          '@type' => 'Place',
500
        ) + (array)$this->placeJSONLD($majorInfoUpdated->getLocation()->getCdbid());
501
502
        $availableTo = AvailableTo::createFromCalendar($majorInfoUpdated->getCalendar());
503
        $jsonLD->availableTo = (string)$availableTo;
504
505
        // Remove old theme and event type.
506
        $jsonLD->terms = array_filter($jsonLD->terms, function ($term) {
507
            return $term->domain !== EventType::DOMAIN &&  $term->domain !== Theme::DOMAIN;
508
        });
509
510
        $eventType = $majorInfoUpdated->getEventType();
511
        $jsonLD->terms = [
512
            $eventType->toJsonLd()
513
        ];
514
515
        $theme = $majorInfoUpdated->getTheme();
516
        if (!empty($theme)) {
517
            $jsonLD->terms[] = $theme->toJsonLd();
518
        }
519
520
        $this->repository->save($document->withBody($jsonLD));
521
    }
522
523
    /**
524
     * @param AudienceUpdated $audienceUpdated
525
     */
526
    protected function applyAudienceUpdated(AudienceUpdated $audienceUpdated)
527
    {
528
        $document = $this->loadDocumentFromRepository($audienceUpdated);
529
        $jsonLD = $document->getBody();
530
531
        $jsonLD->audience = $audienceUpdated->getAudience()->serialize();
532
533
        $this->repository->save($document->withBody($jsonLD));
534
    }
535
536
    /**
537
     * @inheritdoc
538
     */
539
    public function placeJSONLD($placeId)
540
    {
541
        if (empty($placeId)) {
542
            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...
543
        }
544
545
        try {
546
            $placeJSONLD = $this->placeService->getEntity(
547
                $placeId
548
            );
549
550
            return json_decode($placeJSONLD);
551
        } catch (EntityNotFoundException $e) {
552
            // In case the place can not be found at the moment, just add its ID
553
            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...
554
                '@id' => $this->placeService->iri($placeId)
555
            );
556
        } catch (DocumentGoneException $e) {
557
            // In case the place can not be found at the moment, just add its ID
558
            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...
559
                '@id' => $this->placeService->iri($placeId)
560
            );
561
        }
562
    }
563
564
    private function generateSameAs($eventId, $name)
565
    {
566
        $eventSlug = $this->slugger->slug($name);
567
        return array(
568
            'http://www.uitinvlaanderen.be/agenda/e/' . $eventSlug . '/' . $eventId,
569
        );
570
    }
571
572 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...
573
    {
574
        $properties = $metadata->serialize();
575
576
        if (isset($properties['user_nick'])) {
577
            return new String($properties['user_nick']);
578
        }
579
    }
580
581 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...
582
    {
583
        $properties = $metadata->serialize();
584
585
        if (isset($properties['consumer']['name'])) {
586
            return new String($properties['consumer']['name']);
587
        }
588
    }
589
590
    /**
591
     * @return string
592
     */
593
    protected function getLabelAddedClassName()
594
    {
595
        return LabelAdded::class;
596
    }
597
598
    /**
599
     * @return string
600
     */
601
    protected function getLabelRemovedClassName()
602
    {
603
        return LabelRemoved::class;
604
    }
605
606
    /**
607
     * @return string
608
     */
609
    protected function getImageAddedClassName()
610
    {
611
        return ImageAdded::class;
612
    }
613
614
    /**
615
     * @return string
616
     */
617
    protected function getImageRemovedClassName()
618
    {
619
        return ImageRemoved::class;
620
    }
621
622
    /**
623
     * @return string
624
     */
625
    protected function getImageUpdatedClassName()
626
    {
627
        return ImageUpdated::class;
628
    }
629
630
    protected function getMainImageSelectedClassName()
631
    {
632
        return MainImageSelected::class;
633
    }
634
635
    /**
636
     * @return string
637
     */
638
    protected function getTitleTranslatedClassName()
639
    {
640
        return TitleTranslated::class;
641
    }
642
643
    /**
644
     * @return string
645
     */
646
    protected function getDescriptionTranslatedClassName()
647
    {
648
        return DescriptionTranslated::class;
649
    }
650
651
    /**
652
     * @return string
653
     */
654
    protected function getOrganizerUpdatedClassName()
655
    {
656
        return OrganizerUpdated::class;
657
    }
658
659
    /**
660
     * @return string
661
     */
662
    protected function getOrganizerDeletedClassName()
663
    {
664
        return OrganizerDeleted::class;
665
    }
666
667
    protected function getBookingInfoUpdatedClassName()
668
    {
669
        return BookingInfoUpdated::class;
670
    }
671
672
    /**
673
     * @return string
674
     */
675
    protected function getPriceInfoUpdatedClassName()
676
    {
677
        return PriceInfoUpdated::class;
678
    }
679
680
    protected function getContactPointUpdatedClassName()
681
    {
682
        return ContactPointUpdated::class;
683
    }
684
685
    protected function getDescriptionUpdatedClassName()
686
    {
687
        return DescriptionUpdated::class;
688
    }
689
690
    protected function getTypicalAgeRangeUpdatedClassName()
691
    {
692
        return TypicalAgeRangeUpdated::class;
693
    }
694
695
    protected function getTypicalAgeRangeDeletedClassName()
696
    {
697
        return TypicalAgeRangeDeleted::class;
698
    }
699
700
    protected function getPublishedClassName()
701
    {
702
        return Published::class;
703
    }
704
705
    protected function getApprovedClassName()
706
    {
707
        return Approved::class;
708
    }
709
710
    protected function getRejectedClassName()
711
    {
712
        return Rejected::class;
713
    }
714
715
    protected function getFlaggedAsDuplicateClassName()
716
    {
717
        return FlaggedAsDuplicate::class;
718
    }
719
720
    protected function getFlaggedAsInappropriateClassName()
721
    {
722
        return FlaggedAsInappropriate::class;
723
    }
724
725
    protected function getImagesImportedFromUdb2ClassName()
726
    {
727
        return ImagesImportedFromUDB2::class;
728
    }
729
730
    protected function getImagesUpdatedFromUdb2ClassName()
731
    {
732
        return ImagesUpdatedFromUDB2::class;
733
    }
734
}
735