Completed
Pull Request — master (#384)
by Kristof
03:20
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
        $country,
65
        Domain $owningDomain,
66
        DateTimeInterface $created = null
67
    ) {
68
        $this->connection->beginTransaction();
69
70
        try {
71
            $iriGenerator = $this->iriGeneratorFactory->forEntityType($entityType);
72
            $iri = $iriGenerator->iri($id);
73
74
            if ($this->itemExists($id, $entityType)) {
75
                $q = $this->connection->createQueryBuilder();
76
                $q->update($this->tableName->toNative())
77
                    ->where($this->matchesIdAndEntityType())
78
                    ->set('uid', ':uid')
79
                    ->set('title', ':title')
80
                    ->set('zip', ':zip')
81
                    ->set('country', ':country')
82
                    ->set('owning_domain', ':owning_domain')
83
                    ->set('entity_iri', ':entity_iri');
84
85
                if ($created instanceof DateTimeInterface) {
86
                    $q->set('created', ':created');
87
                }
88
89
                $this->setIdAndEntityType($q, $id, $entityType);
90
                $this->setValues(
91
                    $q,
92
                    $userId,
93
                    $name,
94
                    $postalCode,
95
                    $country,
96
                    $owningDomain,
97
                    $created
98
                );
99
                $q->setParameter('entity_iri', $iri);
100
101
                $q->execute();
102
            } else {
103
                if (!$created instanceof DateTimeInterface) {
104
                    $created = new \DateTimeImmutable('now');
105
                }
106
107
                $q = $this->connection->createQueryBuilder();
108
                $q->insert($this->tableName->toNative())
109
                    ->values(
110
                        [
111
                            'entity_id' => ':entity_id',
112
                            'entity_type' => ':entity_type',
113
                            'entity_iri' => ':entity_iri',
114
                            'uid' => ':uid',
115
                            'title' => ':title',
116
                            'zip' => ':zip',
117
                            'country' => ':country',
118
                            'created' => ':created',
119
                            'updated' => ':created',
120
                            'owning_domain' => ':owning_domain',
121
                        ]
122
                    );
123
124
                $this->setIdAndEntityType($q, $id, $entityType);
125
                $this->setValues(
126
                    $q,
127
                    $userId,
128
                    $name,
129
                    $postalCode,
130
                    $country,
131
                    $owningDomain,
132
                    $created
133
                );
134
                $q->setParameter('entity_iri', $iri);
135
136
                $q->execute();
137
            }
138
        } catch (\Exception $e) {
139
            $this->connection->rollBack();
140
141
            throw $e;
142
        }
143
144
        $this->connection->commit();
145
    }
146
147
    /**
148
     * @param QueryBuilder $q
149
     * @param string $userId
150
     * @param string $name
151
     * @param string $postalCode
152
     * @param string $country
153
     * @param Domain $owningDomain
154
     * @param DateTimeInterface $created
155
     */
156
    private function setValues(
157
        QueryBuilder $q,
158
        $userId,
159
        $name,
160
        $postalCode,
161
        $country,
162
        Domain $owningDomain,
163
        DateTimeInterface $created = null
164
    ) {
165
        $q->setParameter('uid', $userId);
166
        $q->setParameter('title', $name);
167
        $q->setParameter('zip', $postalCode);
168
        $q->setParameter('country', $country);
169
        $q->setParameter('owning_domain', $owningDomain->toNative());
170
        if ($created instanceof DateTimeInterface) {
171
            $q->setParameter('created', $created->getTimestamp());
172
        }
173
    }
174
175
    /**
176
     * Returns the WHERE predicates for matching the id and entity_type columns.
177
     *
178
     * @return \Doctrine\DBAL\Query\Expression\CompositeExpression
179
     */
180
    private function matchesIdAndEntityType()
181
    {
182
        $expr = $this->connection->getExpressionBuilder();
183
184
        return $expr->andX(
185
            $expr->eq('entity_id', ':entity_id'),
186
            $expr->eq('entity_type', ':entity_type')
187
        );
188
    }
189
190
    /**
191
     * @param QueryBuilder $q
192
     * @param string $id
193
     * @param EntityType $entityType
194
     */
195
    private function setIdAndEntityType(
196
        QueryBuilder $q,
197
        $id,
198
        EntityType $entityType
199
    ) {
200
        $q->setParameter('entity_id', $id);
201
        $q->setParameter('entity_type', $entityType->toNative());
202
    }
203
204
    /**
205
     * @param $id
206
     * @param EntityType $entityType
207
     * @return bool
208
     */
209
    private function itemExists($id, EntityType $entityType)
210
    {
211
        $q = $this->connection->createQueryBuilder();
212
213
        $q->select('1')->from($this->tableName->toNative())->where(
214
            $this->matchesIdAndEntityType()
215
        );
216
217
        $this->setIdAndEntityType($q, $id, $entityType);
218
219
        $result = $q->execute();
220
        $items = $result->fetchAll();
221
222
        return count($items) > 0;
223
    }
224
225
    /**
226
     * @inheritdoc
227
     */
228
    public function deleteIndex($id, EntityType $entityType)
229
    {
230
        $q = $this->connection->createQueryBuilder();
231
232
        $q->delete($this->tableName->toNative())
233
            ->where($this->matchesIdAndEntityType());
234
235
        $this->setIdAndEntityType($q, $id, $entityType);
236
237
        $q->execute();
238
    }
239
240
    /**
241
     * @inheritdoc
242
     */
243
    public function findPlacesByPostalCode($postalCode, $country)
244
    {
245
        $q = $this->connection->createQueryBuilder();
246
        $expr = $q->expr();
247
248
        $q->select('entity_id')
249
            ->from($this->tableName->toNative())
250
            ->where(
251
                $expr->andX(
252
                    $expr->eq('entity_type', ':entity_type'),
253
                    $expr->eq('zip', ':zip'),
254
                    $expr->eq('country', ':country')
255
                )
256
            );
257
258
        $q->setParameter('entity_type', EntityType::PLACE()->toNative());
259
        $q->setParameter('zip', $postalCode);
260
        $q->setParameter('country', $country);
261
262
        $results = $q->execute();
263
264
        return $results->fetchAll(\PDO::FETCH_COLUMN);
265
    }
266
267
    /**
268
     * @inheritdoc
269
     */
270
    public function findByUser($userId, Natural $limit, Natural $start)
271
    {
272
        $expr = $this->connection->getExpressionBuilder();
273
        $itemIsOwnedByUser = $expr->andX(
274
            $expr->eq('uid', ':user_id'),
275
            $expr->orX(
276
                $expr->eq('entity_type', '"event"'),
277
                $expr->eq('entity_type', '"place"')
278
            )
279
        );
280
281
        return $this->getPagedDashboardItems(
282
            $userId,
283
            $limit,
284
            $start,
285
            $itemIsOwnedByUser
286
        );
287
    }
288
289
    /**
290
     * @inheritdoc
291
     */
292
    public function findByUserForDomain(
293
        $userId,
294
        Natural $limit,
295
        Natural $start,
296
        Domain $owningDomain
297
    ) {
298
        $expr = $this->connection->getExpressionBuilder();
299
        $ownedByUserForDomain = $expr->andX(
300
            $expr->eq('uid', ':user_id'),
301
            $expr->eq('owning_domain', ':owning_domain'),
302
            $expr->orX(
303
                $expr->eq('entity_type', '"event"'),
304
                $expr->eq('entity_type', '"place"')
305
            )
306
        );
307
        $parameters = ['owning_domain' => $owningDomain->toNative()];
308
309
        return $this->getPagedDashboardItems(
310
            $userId,
311
            $limit,
312
            $start,
313
            $ownedByUserForDomain,
314
            $parameters
315
        );
316
    }
317
318
    /**
319
     * @param string $userId
320
     * @param Natural $limit
321
     * @param Natural $start
322
     * @param CompositeExpression $filterExpression
323
     * @param array $parameters
324
     * @return Results
325
     */
326
    private function getPagedDashboardItems(
327
        $userId,
328
        Natural $limit,
329
        Natural $start,
330
        CompositeExpression $filterExpression,
331
        $parameters = []
332
    ) {
333
        $queryBuilder = $this->connection->createQueryBuilder();
334
        $queryBuilder->select('entity_id', 'entity_iri', 'entity_type')
335
            ->from($this->tableName->toNative())
336
            ->where($filterExpression)
337
            ->orderBy('updated', 'DESC')
338
            ->setMaxResults($limit->toNative())
339
            ->setFirstResult($start->toNative());
340
341
        $queryBuilder->setParameter('user_id', $userId);
342
        foreach ($parameters as $param => $value) {
343
            $queryBuilder->setParameter($param, $value);
344
        }
345
346
        $parameters = $queryBuilder->getParameters();
347
348
        $results = $queryBuilder->execute();
349
        $offerIdentifierArray = array_map(
350
            function ($resultRow) {
351
                $offerIdentifier = new IriOfferIdentifier(
352
                    Url::fromNative($resultRow['entity_iri']),
353
                    $resultRow['entity_id'],
354
                    OfferType::fromNative(ucfirst($resultRow['entity_type']))
355
                );
356
357
                return $offerIdentifier;
358
            },
359
            $results->fetchAll(\PDO::FETCH_ASSOC)
360
        );
361
362
        $q = $this->connection->createQueryBuilder();
363
        $totalItems = $q->resetQueryParts()->select('COUNT(*) AS total')
364
            ->from($this->tableName->toNative())
365
            ->where($filterExpression)
366
            ->setParameters($parameters)
367
            ->execute()
368
            ->fetchColumn(0);
369
370
        return new Results(
371
            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...
372
            new Integer($totalItems)
373
        );
374
    }
375
376
    /**
377
     * @inheritdoc
378
     */
379
    public function setUpdateDate($id, DateTimeInterface $updated)
380
    {
381
        $queryBuilder = $this->connection->createQueryBuilder();
382
        $expr = $this->connection->getExpressionBuilder();
383
384
        $queryBuilder
385
            ->update($this->tableName->toNative())
386
            ->where(
387
                $expr->andX(
388
                    $expr->eq('entity_id', ':entity_id')
389
                )
390
            )
391
            ->set('updated', $updated->getTimestamp())
392
            ->setParameter('entity_id', $id)
393
            ->execute();
394
    }
395
}
396