Completed
Push — master ( 670c74...7830c9 )
by Kristof
05:10
created

EventLDProjector::applyEventFromCdbXml()   B

Complexity

Conditions 1
Paths 1

Size

Total Lines 37
Code Lines 23

Duplication

Lines 0
Ratio 0 %

Importance

Changes 2
Bugs 0 Features 0
Metric Value
c 2
b 0
f 0
dl 0
loc 37
rs 8.8571
cc 1
eloc 23
nc 1
nop 4
1
<?php
2
/**
3
 * @file
4
 */
5
6
namespace CultuurNet\UDB3\Event;
7
8
use Broadway\Domain\DateTime;
9
use Broadway\Domain\DomainMessage;
10
use Broadway\Domain\Metadata;
11
use Broadway\EventHandling\EventListenerInterface;
12
use CultuurNet\UDB3\Cdb\EventItemFactory;
13
use CultuurNet\UDB3\CulturefeedSlugger;
14
use CultuurNet\UDB3\EntityNotFoundException;
15
use CultuurNet\UDB3\Event\Events\BookingInfoUpdated;
16
use CultuurNet\UDB3\Event\Events\ContactPointUpdated;
17
use CultuurNet\UDB3\Event\Events\DescriptionUpdated;
18
use CultuurNet\UDB3\Event\Events\EventCreated;
19
use CultuurNet\UDB3\Event\Events\EventCreatedFromCdbXml;
20
use CultuurNet\UDB3\Event\Events\EventDeleted;
21
use CultuurNet\UDB3\Event\Events\EventImportedFromUDB2;
22
use CultuurNet\UDB3\Event\Events\EventUpdatedFromCdbXml;
23
use CultuurNet\UDB3\Event\Events\EventUpdatedFromUDB2;
24
use CultuurNet\UDB3\Event\Events\EventWasLabelled;
25
use CultuurNet\UDB3\Event\Events\ImageAdded;
26
use CultuurNet\UDB3\Event\Events\ImageRemoved;
27
use CultuurNet\UDB3\Event\Events\ImageUpdated;
28
use CultuurNet\UDB3\Event\Events\LabelsMerged;
29
use CultuurNet\UDB3\Event\Events\MajorInfoUpdated;
30
use CultuurNet\UDB3\Event\Events\OrganizerDeleted;
31
use CultuurNet\UDB3\Event\Events\OrganizerUpdated;
32
use CultuurNet\UDB3\Event\Events\TranslationApplied;
33
use CultuurNet\UDB3\Event\Events\TranslationDeleted;
34
use CultuurNet\UDB3\Event\Events\TypicalAgeRangeDeleted;
35
use CultuurNet\UDB3\Event\Events\TypicalAgeRangeUpdated;
36
use CultuurNet\UDB3\Event\Events\Unlabelled;
37
use CultuurNet\UDB3\Event\ReadModel\DocumentRepositoryInterface;
38
use CultuurNet\UDB3\Event\ReadModel\JSONLD\CdbXMLImporter;
39
use CultuurNet\UDB3\Event\ReadModel\JSONLD\OrganizerServiceInterface;
40
use CultuurNet\UDB3\Event\ReadModel\JSONLD\PlaceServiceInterface;
41
use CultuurNet\UDB3\EventHandling\DelegateEventHandlingToSpecificMethodTrait;
42
use CultuurNet\UDB3\EventServiceInterface;
43
use CultuurNet\UDB3\Iri\IriGeneratorInterface;
44
use CultuurNet\UDB3\Label;
45
use CultuurNet\UDB3\LabelCollection;
46
use CultuurNet\UDB3\Offer\ReadModel\JSONLD\CdbXMLItemBaseImporter;
47
use CultuurNet\UDB3\Organizer\OrganizerProjectedToJSONLD;
48
use CultuurNet\UDB3\OrganizerService;
49
use CultuurNet\UDB3\Place\PlaceProjectedToJSONLD;
50
use CultuurNet\UDB3\PlaceService;
51
use CultuurNet\UDB3\ReadModel\JsonDocument;
52
use CultuurNet\UDB3\SluggerInterface;
53
use CultuurNet\UDB3\StringFilter\StringFilterInterface;
54
use CultuurNet\UDB3\Theme;
55
use Symfony\Component\Serializer\SerializerInterface;
56
use ValueObjects\String\String;
57
58
/**
59
 * Projects state changes on Event entities to a JSON-LD read model in a
60
 * document repository.
61
 *
62
 * Implements PlaceServiceInterface and OrganizerServiceInterface to do a double
63
 * dispatch with CdbXMLImporter.
64
 */
65
class EventLDProjector implements EventListenerInterface, PlaceServiceInterface, OrganizerServiceInterface
66
{
67
    use DelegateEventHandlingToSpecificMethodTrait;
68
69
    /**
70
     * @var DocumentRepositoryInterface
71
     */
72
    protected $repository;
73
74
    /**
75
     * @var IriGeneratorInterface
76
     */
77
    protected $iriGenerator;
78
79
    /**
80
     * @var OrganizerService
81
     */
82
    protected $organizerService;
83
84
    /**
85
     * @var PlaceService
86
     */
87
    protected $placeService;
88
89
    /**
90
     * @var EventServiceInterface
91
     */
92
    protected $eventService;
93
94
    /**
95
     * @var SluggerInterface
96
     */
97
    protected $slugger;
98
99
    /**
100
     * @var CdbXMLImporter
101
     */
102
    protected $cdbXMLImporter;
103
104
    /**
105
     * @var SerializerInterface
106
     */
107
    protected $mediaObjectSerializer;
108
109
    /**
110
     * @param DocumentRepositoryInterface $repository
111
     * @param IriGeneratorInterface $iriGenerator
112
     * @param EventServiceInterface $eventService
113
     * @param PlaceService $placeService
114
     * @param OrganizerService $organizerService
115
     * @param SerializerInterface $mediaObjectSerializer
116
     */
117
    public function __construct(
118
        DocumentRepositoryInterface $repository,
119
        IriGeneratorInterface $iriGenerator,
120
        EventServiceInterface $eventService,
121
        PlaceService $placeService,
122
        OrganizerService $organizerService,
123
        SerializerInterface $mediaObjectSerializer
124
    ) {
125
        $this->repository = $repository;
126
        $this->iriGenerator = $iriGenerator;
127
        $this->organizerService = $organizerService;
128
        $this->placeService = $placeService;
129
        $this->eventService = $eventService;
130
        $this->mediaObjectSerializer = $mediaObjectSerializer;
131
132
        $this->slugger = new CulturefeedSlugger();
133
        $this->cdbXMLImporter = new CdbXMLImporter(new CdbXMLItemBaseImporter());
134
    }
135
136 View Code Duplication
    protected function applyOrganizerProjectedToJSONLD(OrganizerProjectedToJSONLD $organizerProjectedToJSONLD)
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...
137
    {
138
        $eventIds = $this->eventsOrganizedByOrganizer(
139
            $organizerProjectedToJSONLD->getId()
140
        );
141
142
        $organizer = $this->organizerService->getEntity(
143
            $organizerProjectedToJSONLD->getId()
144
        );
145
146
        foreach ($eventIds as $eventId) {
147
            $document = $this->loadDocumentFromRepositoryByEventId(
148
                $eventId
149
            );
150
            $eventLD = $document->getBody();
151
152
            $newEventLD = clone $eventLD;
153
            $newEventLD->organizer = json_decode($organizer);
154
155
            if ($newEventLD != $eventLD) {
156
                $this->repository->save($document->withBody($newEventLD));
157
            }
158
        }
159
    }
160
161 View Code Duplication
    protected function applyPlaceProjectedToJSONLD(
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...
162
        PlaceProjectedToJSONLD $placeProjectedToJSONLD
163
    ) {
164
        $eventsLocatedAtPlace = $this->eventsLocatedAtPlace(
165
            $placeProjectedToJSONLD->getId()
166
        );
167
168
        $placeJSONLD = $this->placeService->getEntity(
169
            $placeProjectedToJSONLD->getId()
170
        );
171
172
        foreach ($eventsLocatedAtPlace as $eventId) {
173
            $document = $this->loadDocumentFromRepositoryByEventId(
174
                $eventId
175
            );
176
            $eventLD = $document->getBody();
177
178
            $newEventLD = clone $eventLD;
179
            $newEventLD->location = json_decode($placeJSONLD);
180
181
            if ($newEventLD != $eventLD) {
182
                $this->repository->save($document->withBody($newEventLD));
183
            }
184
        }
185
    }
186
187
    /**
188
     * @param string $organizerId
189
     * @return string[]
190
     */
191
    protected function eventsOrganizedByOrganizer($organizerId)
192
    {
193
        return $this->eventService->eventsOrganizedByOrganizer(
194
            $organizerId
195
        );
196
    }
197
198
    /**
199
     * @param string $placeId
200
     * @return string[]
201
     */
202
    protected function eventsLocatedAtPlace($placeId)
203
    {
204
        return $this->eventService->eventsLocatedAtPlace(
205
            $placeId
206
        );
207
    }
208
209
    /**
210
     * @param EventImportedFromUDB2 $eventImportedFromUDB2
211
     */
212
    protected function applyEventImportedFromUDB2(
213
        EventImportedFromUDB2 $eventImportedFromUDB2
214
    ) {
215
        $this->applyEventCdbXmlFromUDB2(
216
            $eventImportedFromUDB2->getEventId(),
217
            $eventImportedFromUDB2->getCdbXmlNamespaceUri(),
218
            $eventImportedFromUDB2->getCdbXml()
219
        );
220
    }
221
222
    /**
223
     * @param EventUpdatedFromUDB2 $eventUpdatedFromUDB2
224
     */
225
    protected function applyEventUpdatedFromUDB2(
226
        EventUpdatedFromUDB2 $eventUpdatedFromUDB2
227
    ) {
228
        $this->applyEventCdbXmlFromUDB2(
229
            $eventUpdatedFromUDB2->getEventId(),
230
            $eventUpdatedFromUDB2->getCdbXmlNamespaceUri(),
231
            $eventUpdatedFromUDB2->getCdbXml()
232
        );
233
    }
234
235
    /**
236
     * @param EventCreatedFromCdbXml $eventCreatedFromCdbXml
237
     * @param DomainMessage $domainMessage
238
     */
239 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...
240
        EventCreatedFromCdbXml $eventCreatedFromCdbXml,
241
        DomainMessage $domainMessage
242
    ) {
243
        $cdbXmlNamespaceUri = $eventCreatedFromCdbXml->getCdbXmlNamespaceUri()->toNative();
244
        $cdbXml = $eventCreatedFromCdbXml->getEventXmlString()->toEventXmlString();
245
        $eventId = $eventCreatedFromCdbXml->getEventId()->toNative();
246
247
        $this->applyEventFromCdbXml(
248
            $eventId,
249
            $cdbXmlNamespaceUri,
250
            $cdbXml,
251
            $domainMessage
252
        );
253
    }
254
255
    /**
256
     * @param EventUpdatedFromCdbXml $eventUpdatedFromCdbXml
257
     * @param DomainMessage $domainMessage
258
     */
259 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...
260
        EventUpdatedFromCdbXml $eventUpdatedFromCdbXml,
261
        DomainMessage $domainMessage
262
    ) {
263
        $cdbXmlNamespaceUri = $eventUpdatedFromCdbXml->getCdbXmlNamespaceUri()->toNative();
264
        $cdbXml = $eventUpdatedFromCdbXml->getEventXmlString()->toEventXmlString();
265
        $eventId = $eventUpdatedFromCdbXml->getEventId()->toNative();
266
267
        $this->applyEventFromCdbXml(
268
            $eventId,
269
            $cdbXmlNamespaceUri,
270
            $cdbXml,
271
            $domainMessage
272
        );
273
    }
274
275
    /**
276
     * Helper function to save JSONLD document from entryapi cdbxml.
277
     *
278
     * @param string $eventId
279
     * @param string $cdbXmlNamespaceUri
280
     * @param string $cdbXml
281
     * @param DomainMessage $domainMessage
282
     */
283
    protected function applyEventFromCdbXml(
284
        $eventId,
285
        $cdbXmlNamespaceUri,
286
        $cdbXml,
287
        $domainMessage
288
    ) {
289
        $this->saveNewDocument(
290
            $eventId,
291
            function (\stdClass $eventLd) use ($eventId, $cdbXmlNamespaceUri, $cdbXml, $domainMessage) {
292
                $eventLd = $this->projectEventCdbXmlToObject(
293
                    $eventLd,
294
                    $eventId,
295
                    $cdbXmlNamespaceUri,
296
                    $cdbXml
297
                );
298
299
                // Add creation date and update date from metadata.
300
                $eventCreationDate = $domainMessage->getRecordedOn();
301
302
                $eventCreationString = $eventCreationDate->toString();
303
                $eventCreationDateTime = \DateTime::createFromFormat(
304
                    DateTime::FORMAT_STRING,
305
                    $eventCreationString
306
                );
307
                $eventLd->created = $eventCreationDateTime->format('c');
308
                $eventLd->modified = $eventCreationDateTime->format('c');
309
310
                // Add creator.
311
                $eventLd->creator = $this->getAuthorFromMetadata($domainMessage->getMetadata())->toNative();
312
313
                // Add publisher, which is the consumer name.
314
                $eventLd->publisher = $this->getConsumerFromMetadata($domainMessage->getMetadata())->toNative();
315
316
                return $eventLd;
317
            }
318
        );
319
    }
320
321
    /**
322
     * @param string $eventId
323
     * @param callable $fn
324
     */
325
    protected function saveNewDocument($eventId, callable $fn)
326
    {
327
        $document = $this
328
            ->newDocument($eventId)
329
            ->apply($fn);
330
331
        $this->repository->save($document);
332
    }
333
334
    /**
335
     * Helper function to save a JSON-LD document from cdbxml coming from UDB2.
336
     *
337
     * @param string $eventId
338
     * @param string $cdbXmlNamespaceUri
339
     * @param string $cdbXml
340
     */
341
    protected function applyEventCdbXmlFromUDB2(
342
        $eventId,
343
        $cdbXmlNamespaceUri,
344
        $cdbXml
345
    ) {
346
        $this->saveNewDocument(
347
            $eventId,
348
            function (\stdClass $eventLd) use ($cdbXmlNamespaceUri, $eventId, $cdbXml) {
349
                return $this->projectEventCdbXmlToObject(
350
                    $eventLd,
351
                    $eventId,
352
                    $cdbXmlNamespaceUri,
353
                    $cdbXml
354
                ) ;
355
            }
356
        );
357
    }
358
359
    /**
360
     * @param \stdClass $jsonLd
361
     * @param string $eventId
362
     * @param string $cdbXmlNamespaceUri
363
     * @param string $cdbXml
364
     *
365
     * @return \stdClass
366
     */
367
    protected function projectEventCdbXmlToObject(
368
        \stdClass $jsonLd,
369
        $eventId,
370
        $cdbXmlNamespaceUri,
371
        $cdbXml
372
    ) {
373
        $udb2Event = EventItemFactory::createEventFromCdbXml(
374
            $cdbXmlNamespaceUri,
375
            $cdbXml
376
        );
377
378
        $jsonLd = $this->cdbXMLImporter->documentWithCdbXML(
379
            $jsonLd,
380
            $udb2Event,
381
            $this,
382
            $this,
383
            $this->slugger
384
        );
385
386
        // Because we can not properly track media coming from UDB2 we simply
387
        // ignore it and give priority to content added through UDB3.
388
        $media = $this->UDB3Media($eventId);
389
        if (!empty($media)) {
390
            $jsonLd->mediaObject = $media;
391
        }
392
393
        return $jsonLd;
394
    }
395
396
    /**
397
     * Return the media of an event if it already exists.
398
     *
399
     * @param $eventId
400
     *  The id of the event.
401
     *
402
     * @return array
403
     *  A list of media objects.
404
     */
405
    private function UDB3Media($eventId)
406
    {
407
        $document = $this->loadDocumentFromRepositoryByEventId($eventId);
408
        $media = [];
409
410
        if ($document) {
411
            $item = $document->getBody();
412
            // At the moment we do not include any media coming from UDB2.
413
            // If the mediaObject property contains data it's coming from UDB3.
414
            $item->mediaObject = isset($item->mediaObject) ? $item->mediaObject : [];
415
        }
416
417
        return $media;
418
    }
419
420
    /**
421
     * @param EventCreated $eventCreated
422
     * @param DomainMessage $domainMessage
423
     */
424
    protected function applyEventCreated(
425
        EventCreated $eventCreated,
426
        DomainMessage $domainMessage
427
    ) {
428
        $this->saveNewDocument(
429
            $eventCreated->getEventId(),
430
            function (\stdClass $jsonLD) use ($eventCreated, $domainMessage) {
431
                $jsonLD->{'@id'} = $this->iriGenerator->iri(
432
                    $eventCreated->getEventId()
433
                );
434
                $jsonLD->name['nl'] = $eventCreated->getTitle();
435
                $jsonLD->location = array(
436
                        '@type' => 'Place',
437
                    ) + (array)$this->placeJSONLD(
438
                        $eventCreated->getLocation()->getCdbid()
439
                    );
440
441
                $calendarJsonLD = $eventCreated->getCalendar()->toJsonLd();
442
                $jsonLD = (object)array_merge((array)$jsonLD, $calendarJsonLD);
443
444
                // Same as.
445
                $jsonLD->sameAs = $this->generateSameAs(
446
                    $eventCreated->getEventId(),
447
                    reset($jsonLD->name)
448
                );
449
450
                $eventType = $eventCreated->getEventType();
451
                $jsonLD->terms = [
452
                    $eventType->toJsonLd()
453
                ];
454
455
                $theme = $eventCreated->getTheme();
456
                if (!empty($theme)) {
457
                    $jsonLD->terms[] = $theme->toJsonLd();
458
                }
459
460
                $recordedOn = $domainMessage->getRecordedOn()->toString();
461
                $jsonLD->created = \DateTime::createFromFormat(
462
                    DateTime::FORMAT_STRING,
463
                    $recordedOn
464
                )->format('c');
465
                $jsonLD->modified = $jsonLD->created;
466
467
                $metaData = $domainMessage->getMetadata()->serialize();
468 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...
469
                    $jsonLD->creator = $metaData['user_email'];
470
                } elseif (isset($metaData['user_nick'])) {
471
                    $jsonLD->creator = $metaData['user_nick'];
472
                }
473
474
                return $jsonLD;
475
            }
476
        );
477
    }
478
479
    /**
480
     * @param EventDeleted $eventDeleted
481
     */
482
    protected function applyEventDeleted(EventDeleted $eventDeleted)
483
    {
484
        $this->repository->remove($eventDeleted->getEventId());
485
    }
486
487
    /**
488
     * Apply the major info updated command to the projector.
489
     */
490
    protected function applyMajorInfoUpdated(MajorInfoUpdated $majorInfoUpdated)
491
    {
492
493
        $document = $this->loadDocumentFromRepository($majorInfoUpdated);
494
        $jsonLD = $document->getBody();
495
496
        $jsonLD->name->nl = $majorInfoUpdated->getTitle();
497
        $jsonLD->location = array(
498
          '@type' => 'Place',
499
        ) + (array)$this->placeJSONLD($majorInfoUpdated->getLocation()->getCdbid());
500
501
        $calendarJsonLD = $majorInfoUpdated->getCalendar()->toJsonLd();
502
        $jsonLD = (object) array_merge((array) $jsonLD, $calendarJsonLD);
503
504
        // Remove old theme and event type.
505
        $jsonLD->terms = array_filter($jsonLD->terms, function ($term) {
506
            return $term->domain !== EventType::DOMAIN &&  $term->domain !== Theme::DOMAIN;
507
        });
508
509
        $eventType = $majorInfoUpdated->getEventType();
510
        $jsonLD->terms = [
511
            $eventType->toJsonLd()
512
        ];
513
514
        $theme = $majorInfoUpdated->getTheme();
515
        if (!empty($theme)) {
516
            $jsonLD->terms[] = $theme->toJsonLd();
517
        }
518
519
        $this->repository->save($document->withBody($jsonLD));
520
521
    }
522
523
    /**
524
     * @inheritdoc
525
     */
526
    public function placeJSONLD($placeId)
527
    {
528
        try {
529
            $placeJSONLD = $this->placeService->getEntity(
530
                $placeId
531
            );
532
533
            return json_decode($placeJSONLD);
534
        } catch (EntityNotFoundException $e) {
535
            // In case the place can not be found at the moment, just add its ID
536
            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...
537
                '@id' => $this->placeService->iri($placeId)
538
            );
539
        }
540
    }
541
542
    /**
543
     * @inheritdoc
544
     */
545 View Code Duplication
    public function organizerJSONLD($organizerId)
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...
546
    {
547
548
        try {
549
            $organizerJSONLD = $this->organizerService->getEntity(
550
                $organizerId
551
            );
552
553
            return json_decode($organizerJSONLD);
554
        } catch (EntityNotFoundException $e) {
555
            // In case the place can not be found at the moment, just add its ID
556
            return array(
0 ignored issues
show
Bug Best Practice introduced by
The return type of return array('@id' => $t...ce->iri($organizerId)); (array<string,string>) is incompatible with the return type declared by the interface CultuurNet\UDB3\Event\Re...erface::organizerJSONLD of type stdClass.

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

Let’s take a look at an example:

class Author {
    private $name;

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

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

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

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

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

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

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

Loading history...
557
                '@id' => $this->organizerService->iri($organizerId)
558
            );
559
        }
560
    }
561
562
    /**
563
     * @param EventWasLabelled $eventWasLabelled
564
     */
565
    protected function applyEventWasLabelled(EventWasLabelled $eventWasLabelled)
566
    {
567
        $document = $this->loadDocumentFromRepository($eventWasLabelled);
568
569
        $eventLd = $document->getBody();
570
571
        $labels = isset($eventLd->labels) ? $eventLd->labels : [];
572
        $label = (string) $eventWasLabelled->getLabel();
573
574
        $labels[] = $label;
575
        $eventLd->labels = array_unique($labels);
576
577
        $this->repository->save($document->withBody($eventLd));
578
    }
579
580
    protected function applyUnlabelled(Unlabelled $unlabelled)
581
    {
582
        $document = $this->loadDocumentFromRepository($unlabelled);
583
584
        $eventLd = $document->getBody();
585
586
        if (is_array($eventLd->labels)) {
587
            $eventLd->labels = array_filter(
588
                $eventLd->labels,
589
                function ($label) use ($unlabelled) {
590
                    return !$unlabelled->getLabel()->equals(
591
                        new Label($label)
592
                    );
593
                }
594
            );
595
            // Ensure array keys start with 0 so json_encode() does encode it
596
            // as an array and not as an object.
597
            $eventLd->labels = array_values($eventLd->labels);
598
        }
599
600
        $this->repository->save($document->withBody($eventLd));
601
    }
602
603
    /**
604
     * @param LabelsMerged $labelsMerged
605
     */
606
    protected function applyLabelsMerged(LabelsMerged $labelsMerged)
607
    {
608
        $document = $this->loadDocumentFromRepositoryByEventId($labelsMerged->getEventId()->toNative());
609
610
        $eventLd = $document->getBody();
611
612
        $labels = isset($eventLd->labels) ? $eventLd->labels : [];
613
614
        $currentCollection = LabelCollection::fromStrings($labels);
615
616
        $newLabels = $labelsMerged->getLabels();
617
618
        $eventLd->labels = $currentCollection->merge($newLabels)->toStrings();
619
620
        $this->repository->save($document->withBody($eventLd));
621
    }
622
623
    protected function applyTitleTranslated(TitleTranslated $titleTranslated)
624
    {
625
        $document = $this->loadDocumentFromRepository($titleTranslated);
626
627
        $eventLd = $document->getBody();
628
        $eventLd->name->{$titleTranslated->getLanguage()->getCode(
629
        )} = $titleTranslated->getTitle();
630
631
        $this->repository->save($document->withBody($eventLd));
632
    }
633
634 View Code Duplication
    protected function applyDescriptionTranslated(
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...
635
        DescriptionTranslated $descriptionTranslated
636
    ) {
637
        $document = $this->loadDocumentFromRepository($descriptionTranslated);
638
639
        $eventLd = $document->getBody();
640
        $eventLd->description->{$descriptionTranslated->getLanguage()->getCode(
641
        )} = $descriptionTranslated->getDescription();
642
643
        $this->repository->save($document->withBody($eventLd));
644
    }
645
646
    protected function applyTranslationApplied(
647
        TranslationApplied $translationApplied
648
    ) {
649
        $document = $this->loadDocumentFromRepositoryByEventId($translationApplied->getEventId()->toNative());
650
651
        $eventLd = $document->getBody();
652
653 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...
654
            $eventLd->name->{$translationApplied->getLanguage()->getCode(
655
            )} = $translationApplied->getTitle()->toNative();
656
        }
657
658 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...
659
            $eventLd->description->{$translationApplied->getLanguage()->getCode(
660
            )} = $translationApplied->getLongDescription()->toNative();
661
        }
662
663
        $this->repository->save($document->withBody($eventLd));
664
    }
665
666
    /**
667
     * Apply the translation deleted event to the event repository.
668
     * @param TranslationDeleted $translationDeleted
669
     */
670
    protected function applyTranslationDeleted(
671
        TranslationDeleted $translationDeleted
672
    ) {
673
        $document = $this->loadDocumentFromRepositoryByEventId($translationDeleted->getEventId()->toNative());
674
675
        $eventLd = $document->getBody();
676
677
        unset($eventLd->name->{$translationDeleted->getLanguage()->getCode()});
678
679
        unset($eventLd->description->{$translationDeleted->getLanguage()->getCode()});
680
681
        $this->repository->save($document->withBody($eventLd));
682
    }
683
684
    /**
685
     * Apply the description updated event to the event repository.
686
     * @param DescriptionUpdated $descriptionUpdated
687
     */
688 View Code Duplication
    protected function applyDescriptionUpdated(
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...
689
        DescriptionUpdated $descriptionUpdated
690
    ) {
691
        $document = $this->loadDocumentFromRepository($descriptionUpdated);
692
693
        $eventLd = $document->getBody();
694
        if (empty($eventLd->description)) {
695
            $eventLd->description = new \stdClass();
696
        }
697
        $eventLd->description->{'nl'} = $descriptionUpdated->getDescription();
698
699
        $this->repository->save($document->withBody($eventLd));
700
    }
701
702
    /**
703
     * Apply the typical age range updated event to the event repository.
704
     * @param TypicalAgeRangeUpdated $typicalAgeRangeUpdated
705
     */
706 View Code Duplication
    protected function applyTypicalAgeRangeUpdated(
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...
707
        TypicalAgeRangeUpdated $typicalAgeRangeUpdated
708
    ) {
709
        $document = $this->loadDocumentFromRepository($typicalAgeRangeUpdated);
710
711
        $eventLd = $document->getBody();
712
        $eventLd->typicalAgeRange = $typicalAgeRangeUpdated->getTypicalAgeRange();
713
714
        $this->repository->save($document->withBody($eventLd));
715
    }
716
717
    /**
718
     * Apply the typical age range deleted event to the event repository.
719
     * @param TypicalAgeRangeDeleted $typicalAgeRangeDeleted
720
     */
721
    protected function applyTypicalAgeRangeDeleted(
722
        TypicalAgeRangeDeleted $typicalAgeRangeDeleted
723
    ) {
724
        $document = $this->loadDocumentFromRepository($typicalAgeRangeDeleted);
725
726
        $eventLd = $document->getBody();
727
728
        unset($eventLd->typicalAgeRange);
729
730
        $this->repository->save($document->withBody($eventLd));
731
    }
732
733
    /**
734
     * Apply the organizer updated event to the event repository.
735
     * @param OrganizerUpdated $organizerUpdated
736
     */
737 View Code Duplication
    protected function applyOrganizerUpdated(OrganizerUpdated $organizerUpdated)
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...
738
    {
739
740
        $document = $this->loadDocumentFromRepository($organizerUpdated);
741
742
        $eventLd = $document->getBody();
743
744
        $eventLd->organizer = array(
745
          '@type' => 'Organizer',
746
        ) + (array)$this->organizerJSONLD($organizerUpdated->getOrganizerId());
747
748
        $this->repository->save($document->withBody($eventLd));
749
    }
750
751
    /**
752
     * Apply the organizer delete event to the event repository.
753
     * @param OrganizerDeleted $organizerDeleted
754
     */
755
    protected function applyOrganizerDeleted(OrganizerDeleted $organizerDeleted)
756
    {
757
758
        $document = $this->loadDocumentFromRepository($organizerDeleted);
759
760
        $eventLd = $document->getBody();
761
762
        unset($eventLd->organizer);
763
764
        $this->repository->save($document->withBody($eventLd));
765
    }
766
767
    /**
768
     * Apply the contact info updated event to the event repository.
769
     * @param ContactPointUpdated $contactPointUpdated
770
     */
771
    protected function applyContactPointUpdated(ContactPointUpdated $contactPointUpdated)
772
    {
773
774
        $document = $this->loadDocumentFromRepository($contactPointUpdated);
775
776
        $eventLd = $document->getBody();
777
        $eventLd->contactPoint = $contactPointUpdated->getContactPoint()->toJsonLd();
778
779
        $this->repository->save($document->withBody($eventLd));
780
    }
781
782
    /**
783
     * Apply the booking info updated event to the event repository.
784
     * @param BookingInfoUpdated $bookingInfoUpdated
785
     */
786 View Code Duplication
    protected function applyBookingInfoUpdated(BookingInfoUpdated $bookingInfoUpdated)
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...
787
    {
788
789
        $document = $this->loadDocumentFromRepository($bookingInfoUpdated);
790
791
        $eventLd = $document->getBody();
792
        $eventLd->bookingInfo = $bookingInfoUpdated->getBookingInfo()->toJsonLd();
793
794
        $this->repository->save($document->withBody($eventLd));
795
796
    }
797
798
    /**
799
     * Apply the imageAdded event to the event repository.
800
     *
801
     * @param ImageAdded $imageAdded
802
     */
803 View Code Duplication
    protected function applyImageAdded(ImageAdded $imageAdded)
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...
804
    {
805
        $document = $this->loadDocumentFromRepository($imageAdded);
806
807
        $eventLd = $document->getBody();
808
        $eventLd->mediaObject = isset($eventLd->mediaObject) ? $eventLd->mediaObject : [];
809
810
        $imageData = $this->mediaObjectSerializer
811
            ->serialize($imageAdded->getImage(), 'json-ld');
812
        $eventLd->mediaObject[] = $imageData;
813
814
        $this->repository->save($document->withBody($eventLd));
815
    }
816
817
    /**
818
     * Apply the ImageUpdated event to the event repository.
819
     *
820
     * @param ImageUpdated $imageUpdated
821
     */
822
    protected function applyImageUpdated(ImageUpdated $imageUpdated)
823
    {
824
        $document = $this->loadDocumentFromRepositoryByEventId($imageUpdated->getItemId());
825
826
        $eventLd = $document->getBody();
827
828
        if (!isset($eventLd->mediaObject)) {
829
            throw new \Exception('The image to update could not be found.');
830
        }
831
832
        $updatedMediaObjects = [];
833
834 View Code Duplication
        foreach ($eventLd->mediaObject as $mediaObject) {
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...
835
            $mediaObjectMatches = (
836
                strpos(
837
                    $mediaObject->{'@id'},
838
                    (string)$imageUpdated->getMediaObjectId()
839
                ) > 0
840
            );
841
842
            if ($mediaObjectMatches) {
843
                $mediaObject->description = (string)$imageUpdated->getDescription();
844
                $mediaObject->copyrightHolder = (string)$imageUpdated->getCopyrightHolder();
845
846
                $updatedMediaObjects[] = $mediaObject;
847
            }
848
        };
849
850
        if (empty($updatedMediaObjects)) {
851
            throw new \Exception('The image to update could not be found.');
852
        }
853
854
        $this->repository->save($document->withBody($eventLd));
855
    }
856
857
    /**
858
     * @param ImageRemoved $imageRemoved
859
     */
860
    protected function applyImageRemoved(ImageRemoved $imageRemoved)
861
    {
862
        $document = $this->loadDocumentFromRepositoryByEventId(
863
            $imageRemoved->getItemId()
864
        );
865
866
        $eventLd = $document->getBody();
867
868
        // Nothing to remove if there are no media objects!
869
        if (!isset($eventLd->mediaObject)) {
870
            return;
871
        }
872
873
        $imageId = (string) $imageRemoved->getImage()->getMediaObjectId();
874
875
        /**
876
         * Matches any object that is not the removed image.
877
         *
878
         * @param Object $mediaObject
879
         *  An existing projection of a media object.
880
         *
881
         * @return bool
882
         *  Returns true when the media object does not match the image to remove.
883
         */
884
        $shouldNotBeRemoved = function ($mediaObject) use ($imageId) {
885
            $containsId = !!strpos($mediaObject->{'@id'}, $imageId);
886
            return !$containsId;
887
        };
888
889
        // Remove any media objects that match the image.
890
        $filteredMediaObjects = array_filter(
891
            $eventLd->mediaObject,
892
            $shouldNotBeRemoved
893
        );
894
895
        // Unset the main image if it matches the removed image
896
        if (isset($eventLd->image) && strpos($eventLd->{'image'}, $imageId)) {
897
            unset($eventLd->{"image"});
898
        }
899
900
        // If no media objects are left remove the attribute.
901
        if (empty($filteredMediaObjects)) {
902
            unset($eventLd->{"mediaObject"});
903
        } else {
904
            $eventLd->mediaObject = array_values($filteredMediaObjects);
905
        }
906
907
        $this->repository->save($document->withBody($eventLd));
908
    }
909
910
    /**
911
     * @param string $id
912
     * @return JsonDocument
913
     */
914 View Code Duplication
    protected function newDocument($id)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
915
    {
916
        $document = new JsonDocument($id);
917
918
        $eventLd = $document->getBody();
919
        $eventLd->{'@id'} = $this->iriGenerator->iri($id);
920
921
        // @todo provide Event-LD context here relative to the base URI
922
        $eventLd->{'@context'} = '/api/1.0/event.jsonld';
923
924
        return $document->withBody($eventLd);
925
    }
926
927
    /**
928
     * @param EventEvent $event
929
     * @return JsonDocument
930
     */
931
    protected function loadDocumentFromRepository(EventEvent $event)
932
    {
933
        return $this->loadDocumentFromRepositoryByEventId($event->getEventId());
934
    }
935
936
    /**
937
     * @param string $eventId
938
     * @return JsonDocument
939
     */
940
    protected function loadDocumentFromRepositoryByEventId($eventId)
941
    {
942
        $document = $this->repository->get($eventId);
943
944
        if (!$document) {
945
            return $this->newDocument($eventId);
946
        }
947
948
        return $document;
949
    }
950
951
    private function generateSameAs($eventId, $name)
952
    {
953
        $eventSlug = $this->slugger->slug($name);
954
        return array(
955
            'http://www.uitinvlaanderen.be/agenda/e/' . $eventSlug . '/' . $eventId,
956
        );
957
    }
958
959
    public function addDescriptionFilter(StringFilterInterface $filter)
960
    {
961
        $this->cdbXMLImporter->addDescriptionFilter($filter);
962
    }
963
964 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...
965
    {
966
        $properties = $metadata->serialize();
967
968
        if (isset($properties['user_nick'])) {
969
            return new String($properties['user_nick']);
970
        }
971
    }
972
973 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...
974
    {
975
        $properties = $metadata->serialize();
976
977
        if (isset($properties['consumer']['name'])) {
978
            return new String($properties['consumer']['name']);
979
        }
980
    }
981
}
982