Completed
Pull Request — master (#272)
by Kristof
12:29
created

Projector::applyPlaceImportedFromUDB2Event()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 12
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
c 0
b 0
f 0
dl 0
loc 12
rs 9.4285
cc 1
eloc 8
nc 1
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\EventCreated;
12
use CultuurNet\UDB3\Event\Events\EventDeleted;
13
use CultuurNet\UDB3\Event\Events\EventImportedFromUDB2;
14
use CultuurNet\UDB3\Event\Events\EventProjectedToJSONLD;
15
use CultuurNet\UDB3\EventHandling\DelegateEventHandlingToSpecificMethodTrait;
16
use CultuurNet\UDB3\Offer\Events\AbstractEventWithIri;
17
use CultuurNet\UDB3\Offer\IriOfferIdentifierFactoryInterface;
18
use CultuurNet\UDB3\Place\Events\PlaceCreated;
19
use CultuurNet\UDB3\Place\Events\PlaceDeleted;
20
use CultuurNet\UDB3\Place\Events\PlaceImportedFromUDB2;
21
use CultuurNet\UDB3\Place\Events\PlaceProjectedToJSONLD;
22
use DateTime;
23
use DateTimeInterface;
24
use DateTimeZone;
25
use ValueObjects\String\String as StringLiteral;
26
use ValueObjects\Web\Domain;
27
use ValueObjects\Web\Url;
28
29
/**
30
 * Logs new events / updates to an index for querying.
31
 */
32
class Projector implements EventListenerInterface
33
{
34
    use DelegateEventHandlingToSpecificMethodTrait {
35
        DelegateEventHandlingToSpecificMethodTrait::handle as handleMethodSpecificEvents;
36
    }
37
38
    /**
39
     * @var RepositoryInterface
40
     */
41
    protected $repository;
42
43
    /**
44
     * @var CreatedByToUserIdResolverInterface
45
     */
46
    protected $userIdResolver;
47
48
    /**
49
     * A list of events that should trigger an index item update.
50
     *  The key is the namespaced class name.
51
     *  The value is the method the method to call to get the id of the index item.
52
     *
53
     * @var string[]
54
     */
55
    protected static $indexUpdateEvents = [
56
        EventProjectedToJSONLD::class,
57
        PlaceProjectedToJSONLD::class,
58
    ];
59
60
    /**
61
     * @var Domain
62
     */
63
    protected $localDomain;
64
65
    /**
66
     * @var Domain
67
     */
68
    protected $UDB2Domain;
69
70
    /**
71
     * @var IriOfferIdentifierFactoryInterface
72
     */
73
    protected $identifierFactory;
74
75
    /**
76
     * @param RepositoryInterface $repository
77
     * @param CreatedByToUserIdResolverInterface $createdByToUserIdResolver
78
     * @param Domain $localDomain
79
     * @param Domain $UDB2Domain
80
     * @param IriOfferIdentifierFactoryInterface $identifierFactory
81
     */
82
    public function __construct(
83
        RepositoryInterface $repository,
84
        CreatedByToUserIdResolverInterface $createdByToUserIdResolver,
85
        Domain $localDomain,
86
        Domain $UDB2Domain,
87
        IriOfferIdentifierFactoryInterface $identifierFactory
88
    ) {
89
        $this->repository = $repository;
90
        $this->userIdResolver = $createdByToUserIdResolver;
91
        $this->localDomain = $localDomain;
92
        $this->UDB2Domain = $UDB2Domain;
93
        $this->identifierFactory = $identifierFactory;
94
    }
95
96
    /**
97
     * {@inheritdoc}
98
     */
99
    public function handle(DomainMessage $domainMessage)
100
    {
101
        $this->handleIndexUpdateEvents($domainMessage);
102
        $this->handleMethodSpecificEvents($domainMessage);
103
    }
104
105
    protected function handleIndexUpdateEvents(DomainMessage $domainMessage)
106
    {
107
        /** @var AbstractEventWithIri $event */
108
        $event = $domainMessage->getPayload();
109
        $eventName = get_class($event);
110
111
        if (in_array($eventName, self::$indexUpdateEvents)) {
112
            $identifier = $this->identifierFactory->fromIri(
113
                Url::fromNative($event->getIri())
114
            );
115
            $dateUpdated = new DateTime($domainMessage->getRecordedOn()->toString());
116
117
            $this->setItemUpdateDate($identifier->getId(), $dateUpdated);
118
        }
119
    }
120
121
    /**
122
     * @param string $itemId
123
     * @param DateTimeInterface $dateUpdated
124
     */
125
    protected function setItemUpdateDate($itemId, DateTimeInterface $dateUpdated)
126
    {
127
        $this->repository->setUpdateDate($itemId, $dateUpdated);
128
    }
129
130
    /**
131
     * @param \CultureFeed_Cdb_Item_Base $udb2Item
132
     *
133
     * @return null|string
134
     */
135
    protected function resolveUserId(\CultureFeed_Cdb_Item_Base $udb2Item)
136
    {
137
        $createdByIdentifier = $udb2Item->getCreatedBy();
138
        if ($createdByIdentifier) {
139
            $userId = $this->userIdResolver->resolveCreatedByToUserId(
140
                new StringLiteral($createdByIdentifier)
141
            );
142
        }
143
144
        return isset($userId) ? (string) $userId : '';
145
    }
146
147
    /**
148
     *
149
     * @param EventImportedFromUDB2 $eventImportedFromUDB2
150
     */
151
    protected function applyEventImportedFromUDB2(
152
        EventImportedFromUDB2 $eventImportedFromUDB2
153
    ) {
154
        $eventId = $eventImportedFromUDB2->getEventId();
155
        $udb2Event = EventItemFactory::createEventFromCdbXml(
156
            $eventImportedFromUDB2->getCdbXmlNamespaceUri(),
157
            $eventImportedFromUDB2->getCdbXml()
158
        );
159
        $userId = $this->resolveUserId($udb2Event);
160
161
        $this->updateIndexWithUDB2Event($eventId, EntityType::EVENT(), $userId, $udb2Event);
162
    }
163
164
    /**
165
     * @param string $itemId
166
     * @param EntityType $itemType
167
     * @param string $userId
168
     * @param CultureFeed_Cdb_Item_Event $udb2Event
169
     */
170
    protected function updateIndexWithUDB2Event(
171
        $itemId,
172
        EntityType $itemType,
173
        $userId,
174
        CultureFeed_Cdb_Item_Event $udb2Event
175
    ) {
176
        /** @var \CultureFeed_Cdb_Data_EventDetail $detail */
177
        $detail = null;
178
        $postalCode = '';
179
180
        $details = $udb2Event->getDetails();
181
        foreach ($details as $languageDetail) {
182
            // The first language detail found will be used.
183
            $detail = $languageDetail;
184
            break;
185
        }
186
187
        $name = trim($detail->getTitle());
188
189
        // Ignore items without a name. They might occur in UDB2 although this
190
        // is not considered normal.
191
        if (empty($name)) {
192
            return;
193
        }
194
195
        $contact_cdb = $udb2Event->getContactInfo();
196
        if ($contact_cdb) {
197
            $addresses = $contact_cdb->getAddresses();
198
199
            /** @var \CultureFeed_Cdb_Data_Address $address */
200
            foreach ($addresses as $address) {
201
                /** @var \CultureFeed_Cdb_Data_Address_PhysicalAddress $physicalAddress */
202
                $physicalAddress = $address->getPhysicalAddress();
203
                if ($physicalAddress) {
204
                    $postalCode = $physicalAddress->getZip();
205
                }
206
            }
207
        }
208
209
        $creationDate = $this->dateTimeFromUDB2DateString(
210
            $udb2Event->getCreationDate()
211
        );
212
213
        $this->updateIndex(
214
            $itemId,
215
            $itemType,
216
            (string) $userId,
217
            $name,
218
            $postalCode,
219
            $this->UDB2Domain,
220
            $creationDate
0 ignored issues
show
Security Bug introduced by
It seems like $creationDate defined by $this->dateTimeFromUDB2D...ent->getCreationDate()) on line 209 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...
221
        );
222
    }
223
224
    /**
225
     *
226
     * @param PlaceImportedFromUDB2 $placeImportedFromUDB2
227
     */
228
    protected function applyPlaceImportedFromUDB2(PlaceImportedFromUDB2 $placeImportedFromUDB2)
229
    {
230
231
        $placeId = $placeImportedFromUDB2->getActorId();
232
        /** @var \CultureFeed_Cdb_Data_ActorDetail $detail */
233
        $detail = null;
234
        $postalCode = '';
235
236
        $udb2Actor = ActorItemFactory::createActorFromCdbXml(
237
            $placeImportedFromUDB2->getCdbXmlNamespaceUri(),
238
            $placeImportedFromUDB2->getCdbXml()
239
        );
240
241
        $userId = $this->resolveUserId($udb2Actor);
242
243
        $details = $udb2Actor->getDetails();
244
        foreach ($details as $languageDetail) {
245
            // The first language detail found will be used.
246
            $detail = $languageDetail;
247
            break;
248
        }
249
250
        $name = trim($detail->getTitle());
251
252
        // Ignore items without a name. They might occur in UDB2 although this
253
        // is not considered normal.
254
        if (empty($name)) {
255
            return;
256
        }
257
258
        $contact_cdb = $udb2Actor->getContactInfo();
259
        if ($contact_cdb) {
260
            $addresses = $contact_cdb->getAddresses();
261
262
            /** @var \CultureFeed_Cdb_Data_Address $address */
263
            foreach ($addresses as $address) {
264
                $physicalAddress = $address->getPhysicalAddress();
265
                if ($physicalAddress) {
266
                    $postalCode = $physicalAddress->getZip();
267
                }
268
            }
269
        }
270
271
        $creationDate = $this->dateTimeFromUDB2DateString(
272
            $udb2Actor->getCreationDate()
273
        );
274
275
        $this->updateIndex(
276
            $placeId,
277
            EntityType::PLACE(),
278
            $userId,
279
            $name,
280
            $postalCode,
281
            $this->UDB2Domain,
282
            $creationDate
0 ignored issues
show
Security Bug introduced by
It seems like $creationDate defined by $this->dateTimeFromUDB2D...tor->getCreationDate()) on line 271 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...
283
        );
284
    }
285
286
    /**
287
     * Listener for event created commands.
288
     * @param EventCreated $eventCreated
289
     * @param DomainMessage $domainMessage
290
     */
291 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...
292
    {
293
294
        $eventId = $eventCreated->getEventId();
295
296
        $metaData = $domainMessage->getMetadata()->serialize();
297
        $userId = isset($metaData['user_id']) ? $metaData['user_id'] : '';
298
299
        $location = $eventCreated->getLocation();
300
301
        $creationDate = new DateTime('now', new DateTimeZone('Europe/Brussels'));
302
303
        $this->updateIndex(
304
            $eventId,
305
            EntityType::EVENT(),
306
            $userId,
307
            $eventCreated->getTitle(),
308
            $location->getAddress()->getPostalCode(),
309
            $this->localDomain,
310
            $creationDate
311
        );
312
    }
313
314
    /**
315
     * Listener for place created commands.
316
     * @param PlaceCreated $placeCreated
317
     * @param DomainMessage $domainMessage
318
     */
319 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...
320
    {
321
322
        $placeId = $placeCreated->getPlaceId();
323
324
        $metaData = $domainMessage->getMetadata()->serialize();
325
        $userId = isset($metaData['user_id']) ? $metaData['user_id'] : '';
326
327
        $address = $placeCreated->getAddress();
328
329
        $creationDate = new DateTime('now', new DateTimeZone('Europe/Brussels'));
330
        $this->updateIndex(
331
            $placeId,
332
            EntityType::PLACE(),
333
            $userId,
334
            $placeCreated->getTitle(),
335
            $address->getPostalCode(),
336
            $this->localDomain,
337
            $creationDate
338
        );
339
    }
340
341
    /**
342
     * @param $dateString
343
     *  A UDB2 formatted date string
344
     *
345
     * @return DateTimeInterface
346
     */
347
    protected function dateTimeFromUDB2DateString($dateString)
348
    {
349
        return DateTime::createFromFormat(
350
            'Y-m-d?H:i:s',
351
            $dateString,
352
            new DateTimeZone('Europe/Brussels')
353
        );
354
    }
355
356
    /**
357
     * Update the index
358
     * @param $id
359
     * @param EntityType $type
360
     * @param $userId
361
     * @param $name
362
     * @param $postalCode
363
     * @param Domain $owningDomain
364
     * @param DateTimeInterface $creationDate
365
     */
366
    protected function updateIndex(
367
        $id,
368
        EntityType $type,
369
        $userId,
370
        $name,
371
        $postalCode,
372
        Domain $owningDomain,
373
        DateTimeInterface $creationDate = null
374
    ) {
375
        $this->repository->updateIndex(
376
            $id,
377
            $type,
378
            $userId,
379
            $name,
380
            $postalCode,
381
            $owningDomain,
382
            $creationDate
383
        );
384
    }
385
386
    /**
387
     * Remove the index for events
388
     * @param EventDeleted $eventDeleted
389
     * @param DomainMessage $domainMessage
390
     */
391
    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...
392
    {
393
        $this->repository->deleteIndex($eventDeleted->getItemId(), EntityType::EVENT());
394
    }
395
396
    /**
397
     * Remove the index for places
398
     * @param PlaceDeleted $placeDeleted
399
     * @param DomainMessage $domainMessage
400
     */
401
    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...
402
    {
403
        $this->repository->deleteIndex($placeDeleted->getItemId(), EntityType::PLACE());
404
    }
405
}
406