Completed
Pull Request — master (#287)
by Luc
04:52
created

DBALRepository::findPlacesByPostalCode()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 21
Code Lines 13

Duplication

Lines 0
Ratio 0 %

Importance

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