Completed
Pull Request — master (#385)
by
unknown
03:43
created

DBALRepository::setUpdateDate()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 16

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 16
rs 9.7333
c 0
b 0
f 0
cc 1
nc 1
nop 2
1
<?php
2
namespace CultuurNet\UDB3\ReadModel\Index\Doctrine;
3
4
use CultuurNet\UDB3\Dashboard\DashboardItemLookupServiceInterface;
5
use CultuurNet\UDB3\Offer\IriOfferIdentifier;
6
use CultuurNet\UDB3\Offer\OfferIdentifierCollection;
7
use CultuurNet\UDB3\Offer\OfferType;
8
use CultuurNet\UDB3\Place\ReadModel\Lookup\PlaceLookupServiceInterface;
9
use CultuurNet\UDB3\ReadModel\Index\EntityIriGeneratorFactoryInterface;
10
use CultuurNet\UDB3\ReadModel\Index\EntityType;
11
use CultuurNet\UDB3\ReadModel\Index\RepositoryInterface;
12
use CultuurNet\UDB3\Search\Results;
13
use DateTimeInterface;
14
use Doctrine\DBAL\Connection;
15
use Doctrine\DBAL\Query\Expression\CompositeExpression;
16
use Doctrine\DBAL\Query\QueryBuilder;
17
use ValueObjects\Number\Integer;
18
use ValueObjects\Number\Natural;
19
use ValueObjects\StringLiteral\StringLiteral;
20
use ValueObjects\Web\Domain;
21
use ValueObjects\Web\Url;
22
23
class DBALRepository implements RepositoryInterface, PlaceLookupServiceInterface, DashboardItemLookupServiceInterface
24
{
25
    /**
26
     * @var Connection
27
     */
28
    protected $connection;
29
30
    /**
31
     * @var StringLiteral
32
     */
33
    protected $tableName;
34
35
    /**
36
     * @var EntityIriGeneratorFactoryInterface
37
     */
38
    protected $iriGeneratorFactory;
39
40
    /**
41
     * @param Connection $connection
42
     * @param StringLiteral $tableName
43
     * @param EntityIriGeneratorFactoryInterface $iriGeneratorFactory
44
     */
45
    public function __construct(
46
        Connection $connection,
47
        StringLiteral $tableName,
48
        EntityIriGeneratorFactoryInterface $iriGeneratorFactory
49
    ) {
50
        $this->connection = $connection;
51
        $this->tableName = $tableName;
52
        $this->iriGeneratorFactory = $iriGeneratorFactory;
53
    }
54
55
    /**
56
     * @inheritdoc
57
     */
58
    public function updateIndex(
59
        $id,
60
        EntityType $entityType,
61
        $userId,
62
        $name,
63
        $postalCode,
64
        $city,
65
        $country,
66
        Domain $owningDomain,
67
        DateTimeInterface $created = null
68
    ) {
69
        $this->connection->beginTransaction();
70
71
        try {
72
            $iriGenerator = $this->iriGeneratorFactory->forEntityType($entityType);
73
            $iri = $iriGenerator->iri($id);
74
75
            if ($this->itemExists($id, $entityType)) {
76
                $q = $this->connection->createQueryBuilder();
77
                $q->update($this->tableName->toNative())
78
                    ->where($this->matchesIdAndEntityType())
79
                    ->set('uid', ':uid')
80
                    ->set('title', ':title')
81
                    ->set('zip', ':zip')
82
                    ->set('city', ':city')
83
                    ->set('country', ':country')
84
                    ->set('owning_domain', ':owning_domain')
85
                    ->set('entity_iri', ':entity_iri');
86
87
                if ($created instanceof DateTimeInterface) {
88
                    $q->set('created', ':created');
89
                }
90
91
                $this->setIdAndEntityType($q, $id, $entityType);
92
                $this->setValues(
93
                    $q,
94
                    $userId,
95
                    $name,
96
                    $postalCode,
97
                    $city,
98
                    $country,
99
                    $owningDomain,
100
                    $created
101
                );
102
                $q->setParameter('entity_iri', $iri);
103
104
                $q->execute();
105
            } else {
106
                if (!$created instanceof DateTimeInterface) {
107
                    $created = new \DateTimeImmutable('now');
108
                }
109
110
                $q = $this->connection->createQueryBuilder();
111
                $q->insert($this->tableName->toNative())
112
                    ->values(
113
                        [
114
                            'entity_id' => ':entity_id',
115
                            'entity_type' => ':entity_type',
116
                            'entity_iri' => ':entity_iri',
117
                            'uid' => ':uid',
118
                            'title' => ':title',
119
                            'zip' => ':zip',
120
                            'city' => ':city',
121
                            'country' => ':country',
122
                            'created' => ':created',
123
                            'updated' => ':created',
124
                            'owning_domain' => ':owning_domain',
125
                        ]
126
                    );
127
128
                $this->setIdAndEntityType($q, $id, $entityType);
129
                $this->setValues(
130
                    $q,
131
                    $userId,
132
                    $name,
133
                    $postalCode,
134
                    $city,
135
                    $country,
136
                    $owningDomain,
137
                    $created
138
                );
139
                $q->setParameter('entity_iri', $iri);
140
141
                $q->execute();
142
            }
143
        } catch (\Exception $e) {
144
            $this->connection->rollBack();
145
146
            throw $e;
147
        }
148
149
        $this->connection->commit();
150
    }
151
152
    /**
153
     * @param QueryBuilder $q
154
     * @param string $userId
155
     * @param string $name
156
     * @param string $postalCode
157
     * @param string $country
158
     * @param Domain $owningDomain
159
     * @param DateTimeInterface $created
160
     */
161
    private function setValues(
162
        QueryBuilder $q,
163
        $userId,
164
        $name,
165
        $postalCode,
166
        $city,
167
        $country,
168
        Domain $owningDomain,
169
        DateTimeInterface $created = null
170
    ) {
171
        $q->setParameter('uid', $userId);
172
        $q->setParameter('title', $name);
173
        $q->setParameter('zip', $postalCode);
174
        $q->setParameter('city', $city);
175
        $q->setParameter('country', $country);
176
        $q->setParameter('owning_domain', $owningDomain->toNative());
177
        if ($created instanceof DateTimeInterface) {
178
            $q->setParameter('created', $created->getTimestamp());
179
        }
180
    }
181
182
    /**
183
     * Returns the WHERE predicates for matching the id and entity_type columns.
184
     *
185
     * @return \Doctrine\DBAL\Query\Expression\CompositeExpression
186
     */
187
    private function matchesIdAndEntityType()
188
    {
189
        $expr = $this->connection->getExpressionBuilder();
190
191
        return $expr->andX(
192
            $expr->eq('entity_id', ':entity_id'),
193
            $expr->eq('entity_type', ':entity_type')
194
        );
195
    }
196
197
    /**
198
     * @param QueryBuilder $q
199
     * @param string $id
200
     * @param EntityType $entityType
201
     */
202
    private function setIdAndEntityType(
203
        QueryBuilder $q,
204
        $id,
205
        EntityType $entityType
206
    ) {
207
        $q->setParameter('entity_id', $id);
208
        $q->setParameter('entity_type', $entityType->toNative());
209
    }
210
211
    /**
212
     * @param $id
213
     * @param EntityType $entityType
214
     * @return bool
215
     */
216
    private function itemExists($id, EntityType $entityType)
217
    {
218
        $q = $this->connection->createQueryBuilder();
219
220
        $q->select('1')->from($this->tableName->toNative())->where(
221
            $this->matchesIdAndEntityType()
222
        );
223
224
        $this->setIdAndEntityType($q, $id, $entityType);
225
226
        $result = $q->execute();
227
        $items = $result->fetchAll();
228
229
        return count($items) > 0;
230
    }
231
232
    /**
233
     * @inheritdoc
234
     */
235
    public function deleteIndex($id, EntityType $entityType)
236
    {
237
        $q = $this->connection->createQueryBuilder();
238
239
        $q->delete($this->tableName->toNative())
240
            ->where($this->matchesIdAndEntityType());
241
242
        $this->setIdAndEntityType($q, $id, $entityType);
243
244
        $q->execute();
245
    }
246
247
    /**
248
     * @inheritdoc
249
     */
250 View Code Duplication
    public function findPlacesByPostalCode($postalCode, $country)
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...
251
    {
252
        $q = $this->connection->createQueryBuilder();
253
        $expr = $q->expr();
254
255
        $q->select('entity_id')
256
            ->from($this->tableName->toNative())
257
            ->where(
258
                $expr->andX(
259
                    $expr->eq('entity_type', ':entity_type'),
260
                    $expr->eq('zip', ':zip'),
261
                    $expr->eq('country', ':country')
262
                )
263
            );
264
265
        $q->setParameter('entity_type', EntityType::PLACE()->toNative());
266
        $q->setParameter('zip', $postalCode);
267
        $q->setParameter('country', $country);
268
269
        $results = $q->execute();
270
271
        return $results->fetchAll(\PDO::FETCH_COLUMN);
272
    }
273
274 View Code Duplication
    public function findPlacesByCity($city, $country) {
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...
275
        $q = $this->connection->createQueryBuilder();
276
        $expr = $q->expr();
277
278
        $q->select('entity_id')
279
            ->from($this->tableName->toNative())
280
            ->where(
281
                $expr->andX(
282
                    $expr->eq('entity_type', ':entity_type'),
283
                    $expr->eq('city', ':zip'),
284
                    $expr->eq('country', ':country')
285
                )
286
            );
287
288
        $q->setParameter('entity_type', EntityType::PLACE()->toNative());
289
        $q->setParameter('city', $city);
290
        $q->setParameter('country', $country);
291
292
        $results = $q->execute();
293
294
        return $results->fetchAll(\PDO::FETCH_COLUMN);
295
    }
296
297
    /**
298
     * @inheritdoc
299
     */
300
    public function findByUser($userId, Natural $limit, Natural $start)
301
    {
302
        $expr = $this->connection->getExpressionBuilder();
303
        $itemIsOwnedByUser = $expr->andX(
304
            $expr->eq('uid', ':user_id'),
305
            $expr->orX(
306
                $expr->eq('entity_type', '"event"'),
307
                $expr->eq('entity_type', '"place"')
308
            )
309
        );
310
311
        return $this->getPagedDashboardItems(
312
            $userId,
313
            $limit,
314
            $start,
315
            $itemIsOwnedByUser
316
        );
317
    }
318
319
    /**
320
     * @inheritdoc
321
     */
322
    public function findByUserForDomain(
323
        $userId,
324
        Natural $limit,
325
        Natural $start,
326
        Domain $owningDomain
327
    ) {
328
        $expr = $this->connection->getExpressionBuilder();
329
        $ownedByUserForDomain = $expr->andX(
330
            $expr->eq('uid', ':user_id'),
331
            $expr->eq('owning_domain', ':owning_domain'),
332
            $expr->orX(
333
                $expr->eq('entity_type', '"event"'),
334
                $expr->eq('entity_type', '"place"')
335
            )
336
        );
337
        $parameters = ['owning_domain' => $owningDomain->toNative()];
338
339
        return $this->getPagedDashboardItems(
340
            $userId,
341
            $limit,
342
            $start,
343
            $ownedByUserForDomain,
344
            $parameters
345
        );
346
    }
347
348
    /**
349
     * @param string $userId
350
     * @param Natural $limit
351
     * @param Natural $start
352
     * @param CompositeExpression $filterExpression
353
     * @param array $parameters
354
     * @return Results
355
     */
356
    private function getPagedDashboardItems(
357
        $userId,
358
        Natural $limit,
359
        Natural $start,
360
        CompositeExpression $filterExpression,
361
        $parameters = []
362
    ) {
363
        $queryBuilder = $this->connection->createQueryBuilder();
364
        $queryBuilder->select('entity_id', 'entity_iri', 'entity_type')
365
            ->from($this->tableName->toNative())
366
            ->where($filterExpression)
367
            ->orderBy('updated', 'DESC')
368
            ->setMaxResults($limit->toNative())
369
            ->setFirstResult($start->toNative());
370
371
        $queryBuilder->setParameter('user_id', $userId);
372
        foreach ($parameters as $param => $value) {
373
            $queryBuilder->setParameter($param, $value);
374
        }
375
376
        $parameters = $queryBuilder->getParameters();
377
378
        $results = $queryBuilder->execute();
379
        $offerIdentifierArray = array_map(
380
            function ($resultRow) {
381
                $offerIdentifier = new IriOfferIdentifier(
382
                    Url::fromNative($resultRow['entity_iri']),
383
                    $resultRow['entity_id'],
384
                    OfferType::fromNative(ucfirst($resultRow['entity_type']))
385
                );
386
387
                return $offerIdentifier;
388
            },
389
            $results->fetchAll(\PDO::FETCH_ASSOC)
390
        );
391
392
        $q = $this->connection->createQueryBuilder();
393
        $totalItems = $q->resetQueryParts()->select('COUNT(*) AS total')
394
            ->from($this->tableName->toNative())
395
            ->where($filterExpression)
396
            ->setParameters($parameters)
397
            ->execute()
398
            ->fetchColumn(0);
399
400
        return new Results(
401
            OfferIdentifierCollection::fromArray($offerIdentifierArray),
0 ignored issues
show
Compatibility introduced by
\CultuurNet\UDB3\Offer\O...($offerIdentifierArray) of type object<TwoDotsTwice\Coll...ion\AbstractCollection> is not a sub-type of object<CultuurNet\UDB3\O...erIdentifierCollection>. It seems like you assume a child class of the class TwoDotsTwice\Collection\AbstractCollection to be always present.

This check looks for parameters that are defined as one type in their type hint or doc comment but seem to be used as a narrower type, i.e an implementation of an interface or a subclass.

Consider changing the type of the parameter or doing an instanceof check before assuming your parameter is of the expected type.

Loading history...
402
            new Integer($totalItems)
403
        );
404
    }
405
406
    /**
407
     * @inheritdoc
408
     */
409
    public function setUpdateDate($id, DateTimeInterface $updated)
410
    {
411
        $queryBuilder = $this->connection->createQueryBuilder();
412
        $expr = $this->connection->getExpressionBuilder();
413
414
        $queryBuilder
415
            ->update($this->tableName->toNative())
416
            ->where(
417
                $expr->andX(
418
                    $expr->eq('entity_id', ':entity_id')
419
                )
420
            )
421
            ->set('updated', $updated->getTimestamp())
422
            ->setParameter('entity_id', $id)
423
            ->execute();
424
    }
425
}
426