Completed
Pull Request — master (#453)
by
unknown
03:17 queued 10s
created

Organizer::create()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 14

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 14
rs 9.7998
c 0
b 0
f 0
cc 1
nc 1
nop 4
1
<?php
2
3
namespace CultuurNet\UDB3\Organizer;
4
5
use Broadway\EventSourcing\EventSourcedAggregateRoot;
6
use CultuurNet\Geocoding\Coordinate\Coordinates;
7
use CultuurNet\UDB3\Address\Address;
8
use CultuurNet\UDB3\Cdb\ActorItemFactory;
9
use CultuurNet\UDB3\Cdb\UpdateableWithCdbXmlInterface;
10
use CultuurNet\UDB3\ContactPoint;
11
use CultuurNet\UDB3\Label;
12
use CultuurNet\UDB3\LabelAwareAggregateRoot;
13
use CultuurNet\UDB3\LabelCollection;
14
use CultuurNet\UDB3\Language;
15
use CultuurNet\UDB3\Model\ValueObject\Taxonomy\Label\LabelName;
16
use CultuurNet\UDB3\Model\ValueObject\Taxonomy\Label\Labels;
17
use CultuurNet\UDB3\Organizer\Events\AddressRemoved;
18
use CultuurNet\UDB3\Organizer\Events\AddressTranslated;
19
use CultuurNet\UDB3\Organizer\Events\AddressUpdated;
20
use CultuurNet\UDB3\Organizer\Events\ContactPointUpdated;
21
use CultuurNet\UDB3\Organizer\Events\GeoCoordinatesUpdated;
22
use CultuurNet\UDB3\Organizer\Events\LabelAdded;
23
use CultuurNet\UDB3\Organizer\Events\LabelRemoved;
24
use CultuurNet\UDB3\Organizer\Events\LabelsImported;
25
use CultuurNet\UDB3\Organizer\Events\OrganizerCreated;
26
use CultuurNet\UDB3\Organizer\Events\OrganizerCreatedWithUniqueWebsite;
27
use CultuurNet\UDB3\Organizer\Events\OrganizerDeleted;
28
use CultuurNet\UDB3\Organizer\Events\OrganizerImportedFromUDB2;
29
use CultuurNet\UDB3\Organizer\Events\OrganizerUpdatedFromUDB2;
30
use CultuurNet\UDB3\Organizer\Events\TitleTranslated;
31
use CultuurNet\UDB3\Organizer\Events\TitleUpdated;
32
use CultuurNet\UDB3\Organizer\Events\WebsiteUpdated;
33
use CultuurNet\UDB3\Title;
34
use ValueObjects\Web\Url;
35
36
class Organizer extends EventSourcedAggregateRoot implements UpdateableWithCdbXmlInterface, LabelAwareAggregateRoot
37
{
38
    /**
39
     * The actor id.
40
     *
41
     * @var string
42
     */
43
    protected $actorId;
44
45
    /**
46
     * @var Language
47
     */
48
    private $mainLanguage;
49
50
    /**
51
     * @var Url
52
     */
53
    private $website;
54
55
    /**
56
     * @var Title[]
57
     */
58
    private $titles;
59
60
    /**
61
     * @var Address[]|null
62
     */
63
    private $addresses;
64
65
    /**
66
     * @var ContactPoint
67
     */
68
    private $contactPoint;
69
70
    /**
71
     * @var LabelCollection|Label[]
72
     */
73
    private $labels;
74
75
    /**
76
     * {@inheritdoc}
77
     */
78
    public function getAggregateRootId()
79
    {
80
        return $this->actorId;
81
    }
82
83
    public function __construct()
84
    {
85
        // Contact points can be empty, but we only want to start recording
86
        // ContactPointUpdated events as soon as the organizer is updated
87
        // with a non-empty contact point. To enforce this we initialize the
88
        // aggregate state with an empty contact point.
89
        $this->contactPoint = new ContactPoint();
90
        $this->labels = new LabelCollection();
91
    }
92
93
    /**
94
     * Import from UDB2.
95
     *
96
     * @param string $actorId
97
     *   The actor id.
98
     * @param string $cdbXml
99
     *   The cdb xml.
100
     * @param string $cdbXmlNamespaceUri
101
     *   The cdb xml namespace uri.
102
     *
103
     * @return Organizer
104
     *   The actor.
105
     */
106
    public static function importFromUDB2(
107
        $actorId,
108
        $cdbXml,
109
        $cdbXmlNamespaceUri
110
    ) {
111
        $organizer = new static();
112
        $organizer->apply(
113
            new OrganizerImportedFromUDB2(
114
                $actorId,
115
                $cdbXml,
116
                $cdbXmlNamespaceUri
117
            )
118
        );
119
120
        return $organizer;
121
    }
122
123
    /**
124
     * Factory method to create a new Organizer.
125
     *
126
     * @param string $id
127
     * @param Language $mainLanguage
128
     * @param Url $website
129
     * @param Title $title
130
     * @return Organizer
131
     */
132
    public static function create(
133
        $id,
134
        Language $mainLanguage,
135
        Url $website,
136
        Title $title
137
    ) {
138
        $organizer = new self();
139
140
        $organizer->apply(
141
            new OrganizerCreatedWithUniqueWebsite($id, $mainLanguage, $website, $title)
142
        );
143
144
        return $organizer;
145
    }
146
147
    /**
148
     * @inheritdoc
149
     */
150
    public function updateWithCdbXml($cdbXml, $cdbXmlNamespaceUri)
151
    {
152
        $this->apply(
153
            new OrganizerUpdatedFromUDB2(
154
                $this->actorId,
155
                $cdbXml,
156
                $cdbXmlNamespaceUri
157
            )
158
        );
159
    }
160
161
    /**
162
     * @param Url $website
163
     */
164
    public function updateWebsite(Url $website)
165
    {
166
        if (is_null($this->website) || !$this->website->sameValueAs($website)) {
167
            $this->apply(
168
                new WebsiteUpdated(
169
                    $this->actorId,
170
                    $website
171
                )
172
            );
173
        }
174
    }
175
176
    /**
177
     * @param Title $title
178
     * @param Language $language
179
     */
180 View Code Duplication
    public function updateTitle(
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...
181
        Title $title,
182
        Language $language
183
    ) {
184
        if ($this->isTitleChanged($title, $language)) {
185
            if ($language->getCode() !== $this->mainLanguage->getCode()) {
186
                $event = new TitleTranslated(
187
                    $this->actorId,
188
                    $title,
189
                    $language
190
                );
191
            } else {
192
                $event = new TitleUpdated(
193
                    $this->actorId,
194
                    $title
195
                );
196
            }
197
198
            $this->apply($event);
199
        }
200
    }
201
202
    /**
203
     * @param Address $address
204
     * @param Language $language
205
     */
206 View Code Duplication
    public function updateAddress(
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...
207
        Address $address,
208
        Language $language
209
    ) {
210
        if ($this->isAddressChanged($address, $language)) {
211
            if ($language->getCode() !== $this->mainLanguage->getCode()) {
212
                $event = new AddressTranslated(
213
                    $this->actorId,
214
                    $address,
215
                    $language
216
                );
217
            } else {
218
                $event = new AddressUpdated(
219
                    $this->actorId,
220
                    $address
221
                );
222
            }
223
224
            $this->apply($event);
225
        }
226
    }
227
228
    public function removeAddress()
229
    {
230
        if (!$this->hasAddress()) {
231
            return;
232
        }
233
234
        $this->apply(
235
            new AddressRemoved($this->actorId)
236
        );
237
    }
238
239
    /**
240
     * @param ContactPoint $contactPoint
241
     */
242
    public function updateContactPoint(ContactPoint $contactPoint)
243
    {
244
        if (!$this->contactPoint->sameAs($contactPoint)) {
245
            $this->apply(
246
                new ContactPointUpdated($this->actorId, $contactPoint)
247
            );
248
        }
249
    }
250
251
    public function updateGeoCoordinates(Coordinates $coordinate)
252
    {
253
        $this->apply(
254
            new GeoCoordinatesUpdated(
255
                $this->actorId,
256
                $coordinate
257
            )
258
        );
259
    }
260
261
    /**
262
     * @inheritdoc
263
     */
264
    public function addLabel(Label $label)
265
    {
266
        if (!$this->labels->contains($label)) {
267
            $this->apply(new LabelAdded($this->actorId, $label));
268
        }
269
    }
270
271
    /**
272
     * @inheritdoc
273
     */
274
    public function removeLabel(Label $label)
275
    {
276
        if ($this->labels->contains($label)) {
277
            $this->apply(new LabelRemoved($this->actorId, $label));
278
        }
279
    }
280
281
    /**
282
     * @param Labels $labels
283
     */
284
    public function importLabels(Labels $labels, Labels $labelsToKeepIfAlreadyOnOrganizer)
285
    {
286
        $convertLabelClass = function (\CultuurNet\UDB3\Model\ValueObject\Taxonomy\Label\Label $label) {
287
            return new Label(
288
                $label->getName()->toString(),
289
                $label->isVisible()
290
            );
291
        };
292
293
        // Convert the imported labels to label collection.
294
        $importLabelsCollection = new LabelCollection(
295
            array_map($convertLabelClass, $labels->toArray())
296
        );
297
298
        // Convert the labels to keep if already applied.
299
        $keepLabelsCollection = new LabelCollection(
300
            array_map($convertLabelClass, $labelsToKeepIfAlreadyOnOrganizer->toArray())
301
        );
302
303
        // What are the added labels?
304
        // Labels which are not inside the internal state but inside the imported labels
305
        $addedLabels = new LabelCollection();
306
        foreach ($importLabelsCollection->asArray() as $label) {
307
            if (!$this->labels->contains($label)) {
308
                $addedLabels = $addedLabels->with($label);
309
            }
310
        }
311
312
        // Fire a LabelsImported for all new labels.
313
        $importLabels = new Labels();
314 View Code Duplication
        foreach ($addedLabels->asArray() as $addedLabel) {
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...
315
            $importLabels = $importLabels->with(
316
                new \CultuurNet\UDB3\Model\ValueObject\Taxonomy\Label\Label(
317
                    new LabelName((string) $addedLabel),
318
                    $addedLabel->isVisible()
319
                )
320
            );
321
        }
322
        if ($importLabels->count() > 0) {
323
            $this->apply(new LabelsImported(
324
                $this->actorId,
325
                $importLabels
326
            ));
327
        }
328
329
        // For each added label fire a LabelAdded event.
330
        foreach ($addedLabels->asArray() as $label) {
331
            $this->apply(new LabelAdded($this->actorId, $label));
332
        }
333
334
        // What are the deleted labels?
335
        // Labels which are inside the internal state but not inside imported labels.
336
        // For each deleted label fire a LabelDeleted event.
337
        foreach ($this->labels->asArray() as $label) {
338
            if (!$importLabelsCollection->contains($label) && !$keepLabelsCollection->contains($label)) {
339
                $this->apply(new LabelRemoved($this->actorId, $label));
340
            }
341
        }
342
    }
343
344
    public function delete()
345
    {
346
        $this->apply(
347
            new OrganizerDeleted($this->getAggregateRootId())
348
        );
349
    }
350
351
    /**
352
     * Apply the organizer created event.
353
     * @param OrganizerCreated $organizerCreated
354
     */
355
    protected function applyOrganizerCreated(OrganizerCreated $organizerCreated)
356
    {
357
        $this->actorId = $organizerCreated->getOrganizerId();
358
359
        $this->mainLanguage = new Language('nl');
360
361
        $this->setTitle($organizerCreated->getTitle(), $this->mainLanguage);
362
    }
363
364
    /**
365
     * Apply the organizer created event.
366
     * @param OrganizerCreatedWithUniqueWebsite $organizerCreated
367
     */
368
    protected function applyOrganizerCreatedWithUniqueWebsite(OrganizerCreatedWithUniqueWebsite $organizerCreated)
369
    {
370
        $this->actorId = $organizerCreated->getOrganizerId();
371
372
        $this->mainLanguage = $organizerCreated->getMainLanguage();
373
374
        $this->website = $organizerCreated->getWebsite();
375
376
        $this->setTitle($organizerCreated->getTitle(), $this->mainLanguage);
377
    }
378
379
    /**
380
     * @param OrganizerImportedFromUDB2 $organizerImported
381
     * @throws \CultureFeed_Cdb_ParseException
382
     */
383
    protected function applyOrganizerImportedFromUDB2(
384
        OrganizerImportedFromUDB2 $organizerImported
385
    ) {
386
        $this->actorId = (string) $organizerImported->getActorId();
387
388
        // On import from UDB2 the default main language is 'nl'.
389
        $this->mainLanguage = new Language('nl');
390
391
        $actor = ActorItemFactory::createActorFromCdbXml(
392
            $organizerImported->getCdbXmlNamespaceUri(),
393
            $organizerImported->getCdbXml()
394
        );
395
396
        $this->setTitle($this->getTitle($actor), $this->mainLanguage);
0 ignored issues
show
Bug introduced by
It seems like $this->getTitle($actor) can be null; however, setTitle() does not accept null, maybe add an additional type check?

Unless you are absolutely sure that the expression can never be null because of other conditions, we strongly recommend to add an additional type check to your code:

/** @return stdClass|null */
function mayReturnNull() { }

function doesNotAcceptNull(stdClass $x) { }

// With potential error.
function withoutCheck() {
    $x = mayReturnNull();
    doesNotAcceptNull($x); // Potential error here.
}

// Safe - Alternative 1
function withCheck1() {
    $x = mayReturnNull();
    if ( ! $x instanceof stdClass) {
        throw new \LogicException('$x must be defined.');
    }
    doesNotAcceptNull($x);
}

// Safe - Alternative 2
function withCheck2() {
    $x = mayReturnNull();
    if ($x instanceof stdClass) {
        doesNotAcceptNull($x);
    }
}
Loading history...
397
398
        $this->labels = LabelCollection::fromKeywords($actor->getKeywords(true));
399
    }
400
401
    /**
402
     * @param OrganizerUpdatedFromUDB2 $organizerUpdatedFromUDB2
403
     * @throws \CultureFeed_Cdb_ParseException
404
     */
405
    protected function applyOrganizerUpdatedFromUDB2(
406
        OrganizerUpdatedFromUDB2 $organizerUpdatedFromUDB2
407
    ) {
408
        // Note: never change the main language on update from UDB2.
409
410
        $actor = ActorItemFactory::createActorFromCdbXml(
411
            $organizerUpdatedFromUDB2->getCdbXmlNamespaceUri(),
412
            $organizerUpdatedFromUDB2->getCdbXml()
413
        );
414
415
        $this->setTitle($this->getTitle($actor), $this->mainLanguage);
0 ignored issues
show
Bug introduced by
It seems like $this->getTitle($actor) can be null; however, setTitle() does not accept null, maybe add an additional type check?

Unless you are absolutely sure that the expression can never be null because of other conditions, we strongly recommend to add an additional type check to your code:

/** @return stdClass|null */
function mayReturnNull() { }

function doesNotAcceptNull(stdClass $x) { }

// With potential error.
function withoutCheck() {
    $x = mayReturnNull();
    doesNotAcceptNull($x); // Potential error here.
}

// Safe - Alternative 1
function withCheck1() {
    $x = mayReturnNull();
    if ( ! $x instanceof stdClass) {
        throw new \LogicException('$x must be defined.');
    }
    doesNotAcceptNull($x);
}

// Safe - Alternative 2
function withCheck2() {
    $x = mayReturnNull();
    if ($x instanceof stdClass) {
        doesNotAcceptNull($x);
    }
}
Loading history...
416
417
        $this->labels = LabelCollection::fromKeywords($actor->getKeywords(true));
418
    }
419
420
    /**
421
     * @param WebsiteUpdated $websiteUpdated
422
     */
423
    protected function applyWebsiteUpdated(WebsiteUpdated $websiteUpdated)
424
    {
425
        $this->website = $websiteUpdated->getWebsite();
426
    }
427
428
    /**
429
     * @param TitleUpdated $titleUpdated
430
     */
431
    protected function applyTitleUpdated(TitleUpdated $titleUpdated)
432
    {
433
        $this->setTitle($titleUpdated->getTitle(), $this->mainLanguage);
434
    }
435
436
    /**
437
     * @param TitleTranslated $titleTranslated
438
     */
439
    protected function applyTitleTranslated(TitleTranslated $titleTranslated)
440
    {
441
        $this->setTitle($titleTranslated->getTitle(), $titleTranslated->getLanguage());
442
    }
443
444
    /**
445
     * @param AddressUpdated $addressUpdated
446
     */
447
    protected function applyAddressUpdated(AddressUpdated $addressUpdated)
448
    {
449
        $this->setAddress($addressUpdated->getAddress(), $this->mainLanguage);
450
    }
451
452
    /**
453
     * @param AddressRemoved $addressRemoved
454
     */
455
    protected function applyAddressRemoved(AddressRemoved $addressRemoved)
0 ignored issues
show
Unused Code introduced by
The parameter $addressRemoved is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
456
    {
457
        $this->addresses = null;
458
    }
459
460
    /**
461
     * @param AddressTranslated $addressTranslated
462
     */
463
    protected function applyAddressTranslated(AddressTranslated $addressTranslated)
464
    {
465
        $this->setAddress($addressTranslated->getAddress(), $addressTranslated->getLanguage());
466
    }
467
468
    /**
469
     * @param ContactPointUpdated $contactPointUpdated
470
     */
471
    protected function applyContactPointUpdated(ContactPointUpdated $contactPointUpdated)
472
    {
473
        $this->contactPoint = $contactPointUpdated->getContactPoint();
474
    }
475
476
    /**
477
     * @param LabelAdded $labelAdded
478
     */
479
    protected function applyLabelAdded(LabelAdded $labelAdded)
480
    {
481
        $this->labels = $this->labels->with($labelAdded->getLabel());
482
    }
483
484
    /**
485
     * @param LabelRemoved $labelRemoved
486
     */
487
    protected function applyLabelRemoved(LabelRemoved $labelRemoved)
488
    {
489
        $this->labels = $this->labels->without($labelRemoved->getLabel());
490
    }
491
492
    /**
493
     * @param \CultureFeed_Cdb_Item_Actor $actor
494
     * @return null|Title
495
     */
496
    private function getTitle(\CultureFeed_Cdb_Item_Actor $actor)
497
    {
498
        $details = $actor->getDetails();
499
        $details->rewind();
500
501
        // The first language detail found will be used to retrieve
502
        // properties from which in UDB3 are not any longer considered
503
        // to be language specific.
504
        if ($details->valid()) {
505
            return new Title($details->current()->getTitle());
506
        } else {
507
            return null;
508
        }
509
    }
510
511
    /**
512
     * @param Title $title
513
     * @param Language $language
514
     */
515
    private function setTitle(Title $title, Language $language)
516
    {
517
        $this->titles[$language->getCode()] = $title;
518
    }
519
520
    /**
521
     * @param Title $title
522
     * @param Language $language
523
     * @return bool
524
     */
525
    private function isTitleChanged(Title $title, Language $language)
526
    {
527
        return !isset($this->titles[$language->getCode()]) ||
528
            !$title->sameValueAs($this->titles[$language->getCode()]);
529
    }
530
531
    /**
532
     * @param Address $address
533
     * @param Language $language
534
     */
535
    private function setAddress(Address $address, Language $language)
536
    {
537
        $this->addresses[$language->getCode()] = $address;
538
    }
539
540
    /**
541
     * @param Address $address
542
     * @param Language $language
543
     * @return bool
544
     */
545
    private function isAddressChanged(Address $address, Language $language)
546
    {
547
        return !isset($this->addresses[$language->getCode()]) ||
548
            !$address->sameAs($this->addresses[$language->getCode()]);
549
    }
550
551
    private function hasAddress(): bool
552
    {
553
        return $this->addresses !== null;
554
    }
555
}
556