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) |
|
|
|
|
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) { |
|
|
|
|
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), |
|
|
|
|
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
|
|
|
|
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.