Completed
Push — master ( 854424...6fa762 )
by
unknown
11s
created

DBALRepository::getPagedDashboardItems()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 49

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 49
rs 9.1127
c 0
b 0
f 0
cc 2
nc 2
nop 5
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
    {
276
        $q = $this->connection->createQueryBuilder();
277
        $expr = $q->expr();
278
279
        $q->select('entity_id')
280
            ->from($this->tableName->toNative())
281
            ->where(
282
                $expr->andX(
283
                    $expr->eq('entity_type', ':entity_type'),
284
                    $expr->eq('city', ':city'),
285
                    $expr->eq('country', ':country')
286
                )
287
            );
288
289
        $q->setParameter('entity_type', EntityType::PLACE()->toNative());
290
        $q->setParameter('city', $city);
291
        $q->setParameter('country', $country);
292
293
        $results = $q->execute();
294
295
        return $results->fetchAll(\PDO::FETCH_COLUMN);
296
    }
297
298
    /**
299
     * @inheritdoc
300
     */
301
    public function findByUser($userId, Natural $limit, Natural $start)
302
    {
303
        $expr = $this->connection->getExpressionBuilder();
304
        $itemIsOwnedByUser = $expr->andX(
305
            $expr->eq('uid', ':user_id'),
306
            $expr->orX(
307
                $expr->eq('entity_type', '"event"'),
308
                $expr->eq('entity_type', '"place"')
309
            )
310
        );
311
312
        return $this->getPagedDashboardItems(
313
            $userId,
314
            $limit,
315
            $start,
316
            $itemIsOwnedByUser
317
        );
318
    }
319
320
    /**
321
     * @inheritdoc
322
     */
323
    public function findByUserForDomain(
324
        $userId,
325
        Natural $limit,
326
        Natural $start,
327
        Domain $owningDomain
328
    ) {
329
        $expr = $this->connection->getExpressionBuilder();
330
        $ownedByUserForDomain = $expr->andX(
331
            $expr->eq('uid', ':user_id'),
332
            $expr->eq('owning_domain', ':owning_domain'),
333
            $expr->orX(
334
                $expr->eq('entity_type', '"event"'),
335
                $expr->eq('entity_type', '"place"')
336
            )
337
        );
338
        $parameters = ['owning_domain' => $owningDomain->toNative()];
339
340
        return $this->getPagedDashboardItems(
341
            $userId,
342
            $limit,
343
            $start,
344
            $ownedByUserForDomain,
345
            $parameters
346
        );
347
    }
348
349
    /**
350
     * @param string $userId
351
     * @param Natural $limit
352
     * @param Natural $start
353
     * @param CompositeExpression $filterExpression
354
     * @param array $parameters
355
     * @return Results
356
     */
357
    private function getPagedDashboardItems(
358
        $userId,
359
        Natural $limit,
360
        Natural $start,
361
        CompositeExpression $filterExpression,
362
        $parameters = []
363
    ) {
364
        $queryBuilder = $this->connection->createQueryBuilder();
365
        $queryBuilder->select('entity_id', 'entity_iri', 'entity_type')
366
            ->from($this->tableName->toNative())
367
            ->where($filterExpression)
368
            ->orderBy('updated', 'DESC')
369
            ->setMaxResults($limit->toNative())
370
            ->setFirstResult($start->toNative());
371
372
        $queryBuilder->setParameter('user_id', $userId);
373
        foreach ($parameters as $param => $value) {
374
            $queryBuilder->setParameter($param, $value);
375
        }
376
377
        $parameters = $queryBuilder->getParameters();
378
379
        $results = $queryBuilder->execute();
380
        $offerIdentifierArray = array_map(
381
            function ($resultRow) {
382
                $offerIdentifier = new IriOfferIdentifier(
383
                    Url::fromNative($resultRow['entity_iri']),
384
                    $resultRow['entity_id'],
385
                    OfferType::fromNative(ucfirst($resultRow['entity_type']))
386
                );
387
388
                return $offerIdentifier;
389
            },
390
            $results->fetchAll(\PDO::FETCH_ASSOC)
391
        );
392
393
        $q = $this->connection->createQueryBuilder();
394
        $totalItems = $q->resetQueryParts()->select('COUNT(*) AS total')
395
            ->from($this->tableName->toNative())
396
            ->where($filterExpression)
397
            ->setParameters($parameters)
398
            ->execute()
399
            ->fetchColumn(0);
400
401
        return new Results(
402
            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...
403
            new Integer($totalItems)
404
        );
405
    }
406
407
    /**
408
     * @inheritdoc
409
     */
410
    public function setUpdateDate($id, DateTimeInterface $updated)
411
    {
412
        $queryBuilder = $this->connection->createQueryBuilder();
413
        $expr = $this->connection->getExpressionBuilder();
414
415
        $queryBuilder
416
            ->update($this->tableName->toNative())
417
            ->where(
418
                $expr->andX(
419
                    $expr->eq('entity_id', ':entity_id')
420
                )
421
            )
422
            ->set('updated', $updated->getTimestamp())
423
            ->setParameter('entity_id', $id)
424
            ->execute();
425
    }
426
}
427