Completed
Pull Request — master (#349)
by Luc
05:22
created

Organizer::importLabels()   B

Complexity

Conditions 5
Paths 9

Size

Total Lines 39
Code Lines 17

Duplication

Lines 10
Ratio 25.64 %

Importance

Changes 0
Metric Value
c 0
b 0
f 0
dl 10
loc 39
rs 8.439
cc 5
eloc 17
nc 9
nop 1
1
<?php
2
3
namespace CultuurNet\UDB3\Organizer;
4
5
use Broadway\EventSourcing\EventSourcedAggregateRoot;
6
use CultuurNet\UDB3\Address\Address;
7
use CultuurNet\UDB3\Cdb\ActorItemFactory;
8
use CultuurNet\UDB3\Cdb\UpdateableWithCdbXmlInterface;
9
use CultuurNet\UDB3\ContactPoint;
10
use CultuurNet\UDB3\Label;
11
use CultuurNet\UDB3\LabelAwareAggregateRoot;
12
use CultuurNet\UDB3\LabelCollection;
13
use CultuurNet\UDB3\Language;
14
use CultuurNet\UDB3\Organizer\Commands\ImportLabels;
15
use CultuurNet\UDB3\Organizer\Events\AddressUpdated;
16
use CultuurNet\UDB3\Organizer\Events\ContactPointUpdated;
17
use CultuurNet\UDB3\Organizer\Events\LabelAdded;
18
use CultuurNet\UDB3\Organizer\Events\LabelRemoved;
19
use CultuurNet\UDB3\Organizer\Events\LabelsImported;
20
use CultuurNet\UDB3\Organizer\Events\OrganizerCreated;
21
use CultuurNet\UDB3\Organizer\Events\OrganizerCreatedWithUniqueWebsite;
22
use CultuurNet\UDB3\Organizer\Events\OrganizerDeleted;
23
use CultuurNet\UDB3\Organizer\Events\OrganizerImportedFromUDB2;
24
use CultuurNet\UDB3\Organizer\Events\OrganizerUpdatedFromUDB2;
25
use CultuurNet\UDB3\Organizer\Events\TitleTranslated;
26
use CultuurNet\UDB3\Organizer\Events\TitleUpdated;
27
use CultuurNet\UDB3\Organizer\Events\WebsiteUpdated;
28
use CultuurNet\UDB3\Title;
29
use ValueObjects\Web\Url;
30
31
class Organizer extends EventSourcedAggregateRoot implements UpdateableWithCdbXmlInterface, LabelAwareAggregateRoot
32
{
33
    /**
34
     * The actor id.
35
     *
36
     * @var string
37
     */
38
    protected $actorId;
39
40
    /**
41
     * @var Language
42
     */
43
    private $mainLanguage;
44
45
    /**
46
     * @var Url
47
     */
48
    private $website;
49
50
    /**
51
     * @var Title[]
52
     */
53
    private $titles;
54
55
    /**
56
     * @var Address|null
57
     */
58
    private $address;
59
60
    /**
61
     * @var ContactPoint
62
     */
63
    private $contactPoint;
64
65
    /**
66
     * @var LabelCollection|Label[]
67
     */
68
    private $labels;
69
70
    /**
71
     * {@inheritdoc}
72
     */
73
    public function getAggregateRootId()
74
    {
75
        return $this->actorId;
76
    }
77
78
    public function __construct()
79
    {
80
        // Contact points can be empty, but we only want to start recording
81
        // ContactPointUpdated events as soon as the organizer is updated
82
        // with a non-empty contact point. To enforce this we initialize the
83
        // aggregate state with an empty contact point.
84
        $this->contactPoint = new ContactPoint();
85
        $this->labels = new LabelCollection();
86
    }
87
88
    /**
89
     * Import from UDB2.
90
     *
91
     * @param string $actorId
92
     *   The actor id.
93
     * @param string $cdbXml
94
     *   The cdb xml.
95
     * @param string $cdbXmlNamespaceUri
96
     *   The cdb xml namespace uri.
97
     *
98
     * @return Organizer
99
     *   The actor.
100
     */
101
    public static function importFromUDB2(
102
        $actorId,
103
        $cdbXml,
104
        $cdbXmlNamespaceUri
105
    ) {
106
        $organizer = new static();
107
        $organizer->apply(
108
            new OrganizerImportedFromUDB2(
109
                $actorId,
110
                $cdbXml,
111
                $cdbXmlNamespaceUri
112
            )
113
        );
114
115
        return $organizer;
116
    }
117
118
    /**
119
     * Factory method to create a new Organizer.
120
     *
121
     * @param string $id
122
     * @param Language $mainLanguage
123
     * @param Url $website
124
     * @param Title $title
125
     * @return Organizer
126
     */
127
    public static function create(
128
        $id,
129
        Language $mainLanguage,
130
        Url $website,
131
        Title $title
132
    ) {
133
        $organizer = new self();
134
135
        $organizer->apply(
136
            new OrganizerCreatedWithUniqueWebsite($id, $mainLanguage, $website, $title)
137
        );
138
139
        return $organizer;
140
    }
141
142
    /**
143
     * @inheritdoc
144
     */
145
    public function updateWithCdbXml($cdbXml, $cdbXmlNamespaceUri)
146
    {
147
        $this->apply(
148
            new OrganizerUpdatedFromUDB2(
149
                $this->actorId,
150
                $cdbXml,
151
                $cdbXmlNamespaceUri
152
            )
153
        );
154
    }
155
156
    /**
157
     * @param Url $website
158
     */
159
    public function updateWebsite(Url $website)
160
    {
161
        if (is_null($this->website) || !$this->website->sameValueAs($website)) {
162
            $this->apply(
163
                new WebsiteUpdated(
164
                    $this->actorId,
165
                    $website
166
                )
167
            );
168
        }
169
    }
170
171
    /**
172
     * @param Title $title
173
     * @param Language $language
174
     */
175
    public function updateTitle(
176
        Title $title,
177
        Language $language
178
    ) {
179
        if ($this->isTitleChanged($title, $language)) {
180
            if ($language->getCode() !== $this->mainLanguage->getCode()) {
181
                $event = new TitleTranslated(
182
                    $this->actorId,
183
                    $title,
184
                    $language
185
                );
186
            } else {
187
                $event = new TitleUpdated(
188
                    $this->actorId,
189
                    $title
190
                );
191
            }
192
193
            $this->apply($event);
194
        }
195
    }
196
197
    /**
198
     * @param Address $address
199
     */
200
    public function updateAddress(Address $address)
201
    {
202
        if (is_null($this->address) || !$this->address->sameAs($address)) {
203
            $this->apply(
204
                new AddressUpdated($this->actorId, $address)
205
            );
206
        }
207
    }
208
209
    /**
210
     * @param ContactPoint $contactPoint
211
     */
212
    public function updateContactPoint(ContactPoint $contactPoint)
213
    {
214
        if (!$this->contactPoint->sameAs($contactPoint)) {
215
            $this->apply(
216
                new ContactPointUpdated($this->actorId, $contactPoint)
217
            );
218
        }
219
    }
220
221
    /**
222
     * @inheritdoc
223
     */
224
    public function addLabel(Label $label)
225
    {
226
        if (!$this->labels->contains($label)) {
227
            $this->apply(new LabelAdded($this->actorId, $label));
228
        }
229
    }
230
231
    /**
232
     * @inheritdoc
233
     */
234
    public function removeLabel(Label $label)
235
    {
236
        if ($this->labels->contains($label)) {
237
            $this->apply(new LabelRemoved($this->actorId, $label));
238
        }
239
    }
240
241
    /**
242
     * @param ImportLabels $importLabels
243
     */
244
    public function importLabels(ImportLabels $importLabels)
245
    {
246
        // Fire a LabelsImported event for everything.
247
        $this->apply(new LabelsImported(
248
            $this->actorId,
249
            $importLabels->getLabels()
250
        ));
251
252
        // Convert to imported labels to label collection.
253
        $importLabelsCollection = new LabelCollection(
254
            array_map(
255
                function (\CultuurNet\UDB3\Model\ValueObject\Taxonomy\Label\Label $label) {
256
                    return new Label(
257
                        $label->getName()->toString(),
258
                        $label->isVisible()
259
                    );
260
                },
261
                $importLabels->getLabels()->toArray()
262
            )
263
        );
264
265
        // What are the deleted labels?
266
        // Labels which are inside the internal state but not inside imported labels.
267
        // For each label fire a LabelDeleted event.
268 View Code Duplication
        foreach ($this->labels->asArray() as $label) {
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...
269
            if (!$importLabelsCollection->contains($label)) {
270
                $this->apply(new LabelRemoved($this->actorId, $label));
271
            }
272
        }
273
274
        // What are the added labels?
275
        // Labels which are not inside the internal state but inside the imported labels
276
        // For each label fire a LabelAdded event.
277 View Code Duplication
        foreach ($importLabelsCollection->asArray() as $label) {
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...
278
            if (!$this->labels->contains($label)) {
279
                $this->apply(new LabelAdded($this->actorId, $label));
280
            }
281
        }
282
    }
283
284
    public function delete()
285
    {
286
        $this->apply(
287
            new OrganizerDeleted($this->getAggregateRootId())
288
        );
289
    }
290
291
    /**
292
     * Apply the organizer created event.
293
     * @param OrganizerCreated $organizerCreated
294
     */
295
    protected function applyOrganizerCreated(OrganizerCreated $organizerCreated)
296
    {
297
        $this->actorId = $organizerCreated->getOrganizerId();
298
299
        $this->mainLanguage = new Language('nl');
300
301
        $this->setTitle($organizerCreated->getTitle(), $this->mainLanguage);
302
    }
303
304
    /**
305
     * Apply the organizer created event.
306
     * @param OrganizerCreatedWithUniqueWebsite $organizerCreated
307
     */
308
    protected function applyOrganizerCreatedWithUniqueWebsite(OrganizerCreatedWithUniqueWebsite $organizerCreated)
309
    {
310
        $this->actorId = $organizerCreated->getOrganizerId();
311
312
        $this->mainLanguage = $organizerCreated->getMainLanguage();
313
314
        $this->website = $organizerCreated->getWebsite();
315
316
        $this->setTitle($organizerCreated->getTitle(), $this->mainLanguage);
317
    }
318
319
    /**
320
     * @param OrganizerImportedFromUDB2 $organizerImported
321
     */
322
    protected function applyOrganizerImportedFromUDB2(
323
        OrganizerImportedFromUDB2 $organizerImported
324
    ) {
325
        $this->actorId = (string) $organizerImported->getActorId();
326
327
        // On import from UDB2 the default main language is 'nl'.
328
        $this->mainLanguage = new Language('nl');
329
330
        $actor = ActorItemFactory::createActorFromCdbXml(
331
            $organizerImported->getCdbXmlNamespaceUri(),
332
            $organizerImported->getCdbXml()
333
        );
334
335
        $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...
336
337
        $this->labels = LabelCollection::fromKeywords($actor->getKeywords(true));
338
    }
339
340
    /**
341
     * @param OrganizerUpdatedFromUDB2 $organizerUpdatedFromUDB2
342
     */
343
    protected function applyOrganizerUpdatedFromUDB2(
344
        OrganizerUpdatedFromUDB2 $organizerUpdatedFromUDB2
345
    ) {
346
        // Note: never change the main language on update from UDB2.
347
348
        $actor = ActorItemFactory::createActorFromCdbXml(
349
            $organizerUpdatedFromUDB2->getCdbXmlNamespaceUri(),
350
            $organizerUpdatedFromUDB2->getCdbXml()
351
        );
352
353
        $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...
354
355
        $this->labels = LabelCollection::fromKeywords($actor->getKeywords(true));
356
    }
357
358
    /**
359
     * @param WebsiteUpdated $websiteUpdated
360
     */
361
    protected function applyWebsiteUpdated(WebsiteUpdated $websiteUpdated)
362
    {
363
        $this->website = $websiteUpdated->getWebsite();
364
    }
365
366
    /**
367
     * @param TitleUpdated $titleUpdated
368
     */
369
    protected function applyTitleUpdated(TitleUpdated $titleUpdated)
370
    {
371
        $this->setTitle($titleUpdated->getTitle(), $this->mainLanguage);
372
    }
373
374
    /**
375
     * @param TitleTranslated $titleTranslated
376
     */
377
    protected function applyTitleTranslated(TitleTranslated $titleTranslated)
378
    {
379
        $this->setTitle($titleTranslated->getTitle(), $titleTranslated->getLanguage());
380
    }
381
382
    /**
383
     * @param AddressUpdated $addressUpdated
384
     */
385
    protected function applyAddressUpdated(AddressUpdated $addressUpdated)
386
    {
387
        $this->address = $addressUpdated->getAddress();
388
    }
389
390
    /**
391
     * @param ContactPointUpdated $contactPointUpdated
392
     */
393
    protected function applyContactPointUpdated(ContactPointUpdated $contactPointUpdated)
394
    {
395
        $this->contactPoint = $contactPointUpdated->getContactPoint();
396
    }
397
398
    /**
399
     * @param LabelAdded $labelAdded
400
     */
401
    protected function applyLabelAdded(LabelAdded $labelAdded)
402
    {
403
        $this->labels = $this->labels->with($labelAdded->getLabel());
404
    }
405
406
    /**
407
     * @param LabelRemoved $labelRemoved
408
     */
409
    protected function applyLabelRemoved(LabelRemoved $labelRemoved)
410
    {
411
        $this->labels = $this->labels->without($labelRemoved->getLabel());
412
    }
413
414
    /**
415
     * @param \CultureFeed_Cdb_Item_Actor $actor
416
     * @return null|Title
417
     */
418
    private function getTitle(\CultureFeed_Cdb_Item_Actor $actor)
419
    {
420
        $details = $actor->getDetails();
421
        $details->rewind();
422
423
        // The first language detail found will be used to retrieve
424
        // properties from which in UDB3 are not any longer considered
425
        // to be language specific.
426
        if ($details->valid()) {
427
            return new Title($details->current()->getTitle());
428
        } else {
429
            return null;
430
        }
431
    }
432
433
    /**
434
     * @param Title $title
435
     * @param Language $language
436
     */
437
    private function setTitle(Title $title, Language $language)
438
    {
439
        $this->titles[$language->getCode()] = $title;
440
    }
441
442
    /**
443
     * @param Title $title
444
     * @param Language $language
445
     * @return bool
446
     */
447
    private function isTitleChanged(Title $title, Language $language)
448
    {
449
        return !isset($this->titles[$language->getCode()]) ||
450
            !$title->sameValueAs($this->titles[$language->getCode()]);
451
    }
452
}
453