Completed
Pull Request — master (#270)
by Luc
06:03
created

Projector::getUserId()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 5
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 5
rs 9.4285
c 0
b 0
f 0
cc 2
eloc 3
nc 2
nop 1
1
<?php
2
3
namespace CultuurNet\UDB3\ReadModel\Index;
4
5
use Broadway\Domain\DomainMessage;
6
use Broadway\EventHandling\EventListenerInterface;
7
use CultureFeed_Cdb_Item_Event;
8
use CultuurNet\UDB3\Cdb\ActorItemFactory;
9
use CultuurNet\UDB3\Cdb\CreatedByToUserIdResolverInterface;
10
use CultuurNet\UDB3\Cdb\EventItemFactory;
11
use CultuurNet\UDB3\Event\Events\EventCopied;
12
use CultuurNet\UDB3\Event\Events\EventImportedFromUDB2;
13
use CultuurNet\UDB3\Event\Events\EventCreated;
14
use CultuurNet\UDB3\Event\Events\EventDeleted;
15
use CultuurNet\UDB3\Event\Events\EventProjectedToJSONLD;
16
use CultuurNet\UDB3\EventHandling\DelegateEventHandlingToSpecificMethodTrait;
17
use CultuurNet\UDB3\Offer\Events\AbstractEventWithIri;
18
use CultuurNet\UDB3\Offer\IriOfferIdentifierFactoryInterface;
19
use CultuurNet\UDB3\Place\Events\PlaceCreated;
20
use CultuurNet\UDB3\Place\Events\PlaceDeleted;
21
use CultuurNet\UDB3\Place\Events\PlaceImportedFromUDB2;
22
use CultuurNet\UDB3\Place\Events\PlaceProjectedToJSONLD;
23
use DateTime;
24
use DateTimeInterface;
25
use DateTimeZone;
26
use ValueObjects\StringLiteral\StringLiteral;
27
use ValueObjects\Web\Domain;
28
use ValueObjects\Web\Url;
29
30
/**
31
 * Logs new events / updates to an index for querying.
32
 */
33
class Projector implements EventListenerInterface
34
{
35
    use DelegateEventHandlingToSpecificMethodTrait {
36
        DelegateEventHandlingToSpecificMethodTrait::handle as handleMethodSpecificEvents;
37
    }
38
39
    /**
40
     * @var RepositoryInterface
41
     */
42
    protected $repository;
43
44
    /**
45
     * @var CreatedByToUserIdResolverInterface
46
     */
47
    protected $userIdResolver;
48
49
    /**
50
     * A list of events that should trigger an index item update.
51
     *  The key is the namespaced class name.
52
     *  The value is the method the method to call to get the id of the index item.
53
     *
54
     * @var string[]
55
     */
56
    protected static $indexUpdateEvents = [
57
        EventProjectedToJSONLD::class,
58
        PlaceProjectedToJSONLD::class,
59
    ];
60
61
    /**
62
     * @var Domain
63
     */
64
    protected $localDomain;
65
66
    /**
67
     * @var Domain
68
     */
69
    protected $UDB2Domain;
70
71
    /**
72
     * @var IriOfferIdentifierFactoryInterface
73
     */
74
    protected $identifierFactory;
75
76
    /**
77
     * @param RepositoryInterface $repository
78
     * @param CreatedByToUserIdResolverInterface $createdByToUserIdResolver
79
     * @param Domain $localDomain
80
     * @param Domain $UDB2Domain
81
     * @param IriOfferIdentifierFactoryInterface $identifierFactory
82
     */
83
    public function __construct(
84
        RepositoryInterface $repository,
85
        CreatedByToUserIdResolverInterface $createdByToUserIdResolver,
86
        Domain $localDomain,
87
        Domain $UDB2Domain,
88
        IriOfferIdentifierFactoryInterface $identifierFactory
89
    ) {
90
        $this->repository = $repository;
91
        $this->userIdResolver = $createdByToUserIdResolver;
92
        $this->localDomain = $localDomain;
93
        $this->UDB2Domain = $UDB2Domain;
94
        $this->identifierFactory = $identifierFactory;
95
    }
96
97
    /**
98
     * {@inheritdoc}
99
     */
100
    public function handle(DomainMessage $domainMessage)
101
    {
102
        $this->handleIndexUpdateEvents($domainMessage);
103
        $this->handleMethodSpecificEvents($domainMessage);
104
    }
105
106
    protected function handleIndexUpdateEvents(DomainMessage $domainMessage)
107
    {
108
        /** @var AbstractEventWithIri $event */
109
        $event = $domainMessage->getPayload();
110
        $eventName = get_class($event);
111
112
        if (in_array($eventName, self::$indexUpdateEvents)) {
113
            $identifier = $this->identifierFactory->fromIri(
114
                Url::fromNative($event->getIri())
115
            );
116
            $dateUpdated = new DateTime($domainMessage->getRecordedOn()->toString());
117
118
            $this->setItemUpdateDate($identifier->getId(), $dateUpdated);
119
        }
120
    }
121
122
    /**
123
     * @param string $itemId
124
     * @param DateTimeInterface $dateUpdated
125
     */
126
    protected function setItemUpdateDate($itemId, DateTimeInterface $dateUpdated)
127
    {
128
        $this->repository->setUpdateDate($itemId, $dateUpdated);
129
    }
130
131
    /**
132
     * @param \CultureFeed_Cdb_Item_Base $udb2Item
133
     *
134
     * @return null|string
135
     */
136
    protected function resolveUserId(\CultureFeed_Cdb_Item_Base $udb2Item)
137
    {
138
        $createdByIdentifier = $udb2Item->getCreatedBy();
139
        if ($createdByIdentifier) {
140
            $userId = $this->userIdResolver->resolveCreatedByToUserId(
141
                new StringLiteral($createdByIdentifier)
142
            );
143
        }
144
145
        return isset($userId) ? (string) $userId : '';
146
    }
147
148
    /**
149
     *
150
     * @param EventImportedFromUDB2 $eventImportedFromUDB2
151
     */
152
    protected function applyEventImportedFromUDB2(
153
        EventImportedFromUDB2 $eventImportedFromUDB2
154
    ) {
155
        $eventId = $eventImportedFromUDB2->getEventId();
156
        $udb2Event = EventItemFactory::createEventFromCdbXml(
157
            $eventImportedFromUDB2->getCdbXmlNamespaceUri(),
158
            $eventImportedFromUDB2->getCdbXml()
159
        );
160
        $userId = $this->resolveUserId($udb2Event);
161
162
        $this->updateIndexWithUDB2Event($eventId, EntityType::EVENT(), $userId, $udb2Event);
163
    }
164
165
    /**
166
     * @param string $itemId
167
     * @param EntityType $itemType
168
     * @param string $userId
169
     * @param CultureFeed_Cdb_Item_Event $udb2Event
170
     */
171
    protected function updateIndexWithUDB2Event(
172
        $itemId,
173
        EntityType $itemType,
174
        $userId,
175
        CultureFeed_Cdb_Item_Event $udb2Event
176
    ) {
177
        /** @var \CultureFeed_Cdb_Data_EventDetail $detail */
178
        $detail = null;
179
        $postalCode = '';
180
181
        $details = $udb2Event->getDetails();
182
        foreach ($details as $languageDetail) {
183
            // The first language detail found will be used.
184
            $detail = $languageDetail;
185
            break;
186
        }
187
188
        $name = trim($detail->getTitle());
189
190
        // Ignore items without a name. They might occur in UDB2 although this
191
        // is not considered normal.
192
        if (empty($name)) {
193
            return;
194
        }
195
196
        $contact_cdb = $udb2Event->getContactInfo();
197
        if ($contact_cdb) {
198
            $addresses = $contact_cdb->getAddresses();
199
200
            /** @var \CultureFeed_Cdb_Data_Address $address */
201
            foreach ($addresses as $address) {
202
                /** @var \CultureFeed_Cdb_Data_Address_PhysicalAddress $physicalAddress */
203
                $physicalAddress = $address->getPhysicalAddress();
204
                if ($physicalAddress) {
205
                    $postalCode = $physicalAddress->getZip();
206
                }
207
            }
208
        }
209
210
        $creationDate = $this->dateTimeFromUDB2DateString(
211
            $udb2Event->getCreationDate()
212
        );
213
214
        $this->updateIndex(
215
            $itemId,
216
            $itemType,
217
            (string) $userId,
218
            $name,
219
            $postalCode,
220
            $this->UDB2Domain,
221
            $creationDate
0 ignored issues
show
Security Bug introduced by
It seems like $creationDate defined by $this->dateTimeFromUDB2D...ent->getCreationDate()) on line 210 can also be of type false; however, CultuurNet\UDB3\ReadMode...rojector::updateIndex() does only seem to accept null|object<DateTimeInterface>, did you maybe forget to handle an error condition?

This check looks for type mismatches where the missing type is false. This is usually indicative of an error condtion.

Consider the follow example

<?php

function getDate($date)
{
    if ($date !== null) {
        return new DateTime($date);
    }

    return false;
}

This function either returns a new DateTime object or false, if there was an error. This is a typical pattern in PHP programming to show that an error has occurred without raising an exception. The calling code should check for this returned false before passing on the value to another function or method that may not be able to handle a false.

Loading history...
222
        );
223
    }
224
225
    /**
226
     *
227
     * @param PlaceImportedFromUDB2 $placeImportedFromUDB2
228
     */
229
    protected function applyPlaceImportedFromUDB2(PlaceImportedFromUDB2 $placeImportedFromUDB2)
230
    {
231
232
        $placeId = $placeImportedFromUDB2->getActorId();
233
        /** @var \CultureFeed_Cdb_Data_ActorDetail $detail */
234
        $detail = null;
235
        $postalCode = '';
236
237
        $udb2Actor = ActorItemFactory::createActorFromCdbXml(
238
            $placeImportedFromUDB2->getCdbXmlNamespaceUri(),
239
            $placeImportedFromUDB2->getCdbXml()
240
        );
241
242
        $userId = $this->resolveUserId($udb2Actor);
243
244
        $details = $udb2Actor->getDetails();
245
        foreach ($details as $languageDetail) {
246
            // The first language detail found will be used.
247
            $detail = $languageDetail;
248
            break;
249
        }
250
251
        $name = trim($detail->getTitle());
252
253
        // Ignore items without a name. They might occur in UDB2 although this
254
        // is not considered normal.
255
        if (empty($name)) {
256
            return;
257
        }
258
259
        $contact_cdb = $udb2Actor->getContactInfo();
260
        if ($contact_cdb) {
261
            $addresses = $contact_cdb->getAddresses();
262
263
            /** @var \CultureFeed_Cdb_Data_Address $address */
264
            foreach ($addresses as $address) {
265
                $physicalAddress = $address->getPhysicalAddress();
266
                if ($physicalAddress) {
267
                    $postalCode = $physicalAddress->getZip();
268
                }
269
            }
270
        }
271
272
        $creationDate = $this->dateTimeFromUDB2DateString(
273
            $udb2Actor->getCreationDate()
274
        );
275
276
        $this->updateIndex(
277
            $placeId,
278
            EntityType::PLACE(),
279
            $userId,
280
            $name,
281
            $postalCode,
282
            $this->UDB2Domain,
283
            $creationDate
0 ignored issues
show
Security Bug introduced by
It seems like $creationDate defined by $this->dateTimeFromUDB2D...tor->getCreationDate()) on line 272 can also be of type false; however, CultuurNet\UDB3\ReadMode...rojector::updateIndex() does only seem to accept null|object<DateTimeInterface>, did you maybe forget to handle an error condition?

This check looks for type mismatches where the missing type is false. This is usually indicative of an error condtion.

Consider the follow example

<?php

function getDate($date)
{
    if ($date !== null) {
        return new DateTime($date);
    }

    return false;
}

This function either returns a new DateTime object or false, if there was an error. This is a typical pattern in PHP programming to show that an error has occurred without raising an exception. The calling code should check for this returned false before passing on the value to another function or method that may not be able to handle a false.

Loading history...
284
        );
285
    }
286
287
    /**
288
     * Listener for event created commands.
289
     * @param EventCreated $eventCreated
290
     * @param DomainMessage $domainMessage
291
     */
292 View Code Duplication
    protected function applyEventCreated(EventCreated $eventCreated, DomainMessage $domainMessage)
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...
293
    {
294
        $eventId = $eventCreated->getEventId();
295
296
        $userId = $this->getUserId($domainMessage);
297
298
        $location = $eventCreated->getLocation();
299
300
        $creationDate = new DateTime('now', new DateTimeZone('Europe/Brussels'));
301
302
        $this->updateIndex(
303
            $eventId,
304
            EntityType::EVENT(),
305
            $userId,
306
            $eventCreated->getTitle(),
307
            $location->getAddress()->getPostalCode(),
308
            $this->localDomain,
309
            $creationDate
310
        );
311
    }
312
313
    /**
314
     * @param EventCopied $eventCopied
315
     * @param DomainMessage $domainMessage
316
     */
317
    public function applyEventCopied(EventCopied $eventCopied, DomainMessage $domainMessage)
318
    {
319
        $eventId = $eventCopied->getItemId();
320
321
        $userId = $this->getUserId($domainMessage);
322
323
        $creationDate = new DateTime('now', new DateTimeZone('Europe/Brussels'));
324
325
        $this->updateIndex(
326
            $eventId,
327
            EntityType::EVENT(),
328
            $userId,
329
            null,
330
            null,
331
            $this->localDomain,
332
            $creationDate
333
        );
334
    }
335
336
    /**
337
     * Listener for place created commands.
338
     * @param PlaceCreated $placeCreated
339
     * @param DomainMessage $domainMessage
340
     */
341 View Code Duplication
    protected function applyPlaceCreated(PlaceCreated $placeCreated, DomainMessage $domainMessage)
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...
342
    {
343
        $placeId = $placeCreated->getPlaceId();
344
345
        $userId = $this->getUserId($domainMessage);
346
347
        $address = $placeCreated->getAddress();
348
349
        $creationDate = new DateTime('now', new DateTimeZone('Europe/Brussels'));
350
        $this->updateIndex(
351
            $placeId,
352
            EntityType::PLACE(),
353
            $userId,
354
            $placeCreated->getTitle(),
355
            $address->getPostalCode(),
356
            $this->localDomain,
357
            $creationDate
358
        );
359
    }
360
361
    /**
362
     * @param $dateString
363
     *  A UDB2 formatted date string
364
     *
365
     * @return DateTimeInterface
366
     */
367
    protected function dateTimeFromUDB2DateString($dateString)
368
    {
369
        return DateTime::createFromFormat(
370
            'Y-m-d?H:i:s',
371
            $dateString,
372
            new DateTimeZone('Europe/Brussels')
373
        );
374
    }
375
376
    /**
377
     * Update the index
378
     * @param $id
379
     * @param EntityType $type
380
     * @param $userId
381
     * @param $name
382
     * @param $postalCode
383
     * @param Domain $owningDomain
384
     * @param DateTimeInterface $creationDate
385
     */
386
    protected function updateIndex(
387
        $id,
388
        EntityType $type,
389
        $userId,
390
        $name,
391
        $postalCode,
392
        Domain $owningDomain,
393
        DateTimeInterface $creationDate = null
394
    ) {
395
        $this->repository->updateIndex(
396
            $id,
397
            $type,
398
            $userId,
399
            $name,
400
            $postalCode,
401
            $owningDomain,
402
            $creationDate
403
        );
404
    }
405
406
    /**
407
     * Remove the index for events
408
     * @param EventDeleted $eventDeleted
409
     * @param DomainMessage $domainMessage
410
     */
411
    public function applyEventDeleted(EventDeleted $eventDeleted, DomainMessage $domainMessage)
0 ignored issues
show
Unused Code introduced by
The parameter $domainMessage 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...
412
    {
413
        $this->repository->deleteIndex($eventDeleted->getItemId(), EntityType::EVENT());
414
    }
415
416
    /**
417
     * Remove the index for places
418
     * @param PlaceDeleted $placeDeleted
419
     * @param DomainMessage $domainMessage
420
     */
421
    public function applyPlaceDeleted(PlaceDeleted $placeDeleted, DomainMessage $domainMessage)
0 ignored issues
show
Unused Code introduced by
The parameter $domainMessage 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...
422
    {
423
        $this->repository->deleteIndex($placeDeleted->getItemId(), EntityType::PLACE());
424
    }
425
426
    /**
427
     * @param DomainMessage $domainMessage
428
     * @return string
429
     */
430
    private function getUserId(DomainMessage $domainMessage)
431
    {
432
        $metaData = $domainMessage->getMetadata()->serialize();
433
        return isset($metaData['user_id']) ? $metaData['user_id'] : '';
434
    }
435
}
436