1
|
|
|
<?php |
2
|
|
|
|
3
|
|
|
/* |
4
|
|
|
* This file is part of the CRUDlex package. |
5
|
|
|
* |
6
|
|
|
* (c) Philip Lehmann-Böhm <[email protected]> |
7
|
|
|
* |
8
|
|
|
* For the full copyright and license information, please view the LICENSE |
9
|
|
|
* file that was distributed with this source code. |
10
|
|
|
*/ |
11
|
|
|
|
12
|
|
|
namespace CRUDlex; |
13
|
|
|
|
14
|
|
|
use Doctrine\DBAL\Connection; |
15
|
|
|
use Doctrine\DBAL\Query\QueryBuilder; |
16
|
|
|
|
17
|
|
|
/** |
18
|
|
|
* MySQL Data implementation using a given Doctrine DBAL instance. |
19
|
|
|
*/ |
20
|
|
|
class MySQLData extends AbstractData { |
21
|
|
|
|
22
|
|
|
/** |
23
|
|
|
* Holds the Doctrine DBAL instance. |
24
|
|
|
*/ |
25
|
|
|
protected $database; |
26
|
|
|
|
27
|
|
|
/** |
28
|
|
|
* Flag whether to use UUIDs as primary key. |
29
|
|
|
*/ |
30
|
|
|
protected $useUUIDs; |
31
|
|
|
|
32
|
|
|
/** |
33
|
|
|
* Gets the many-to-many fields. |
34
|
|
|
* |
35
|
|
|
* @return array|\string[] |
36
|
|
|
* the many-to-many fields |
37
|
|
|
*/ |
38
|
|
|
protected function getManyFields() { |
39
|
|
|
$fields = $this->definition->getFieldNames(true); |
40
|
|
|
return array_filter($fields, function($field) { |
41
|
|
|
return $this->definition->getType($field) === 'many'; |
42
|
|
|
}); |
43
|
|
|
} |
44
|
|
|
|
45
|
|
|
/** |
46
|
|
|
* Gets all form fields including the many-to-many-ones. |
47
|
|
|
* |
48
|
|
|
* @return array |
49
|
|
|
* all form fields |
50
|
|
|
*/ |
51
|
|
|
protected function getFormFields() { |
52
|
|
|
$manyFields = $this->getManyFields(); |
53
|
|
|
$formFields = []; |
54
|
|
|
foreach ($this->definition->getEditableFieldNames() as $field) { |
55
|
|
|
if (!in_array($field, $manyFields)) { |
56
|
|
|
$formFields[] = $field; |
57
|
|
|
} |
58
|
|
|
} |
59
|
|
|
return $formFields; |
60
|
|
|
} |
61
|
|
|
|
62
|
|
|
/** |
63
|
|
|
* Sets the values and parameters of the upcoming given query according |
64
|
|
|
* to the entity. |
65
|
|
|
* |
66
|
|
|
* @param Entity $entity |
67
|
|
|
* the entity with its fields and values |
68
|
|
|
* @param QueryBuilder $queryBuilder |
69
|
|
|
* the upcoming query |
70
|
|
|
* @param string $setMethod |
71
|
|
|
* what method to use on the QueryBuilder: 'setValue' or 'set' |
72
|
|
|
*/ |
73
|
|
|
protected function setValuesAndParameters(Entity $entity, QueryBuilder $queryBuilder, $setMethod) { |
74
|
|
|
$formFields = $this->getFormFields(); |
75
|
|
|
$count = count($formFields); |
76
|
|
|
for ($i = 0; $i < $count; ++$i) { |
77
|
|
|
$type = $this->definition->getType($formFields[$i]); |
78
|
|
|
$value = $entity->get($formFields[$i]); |
79
|
|
|
if ($type == 'boolean') { |
80
|
|
|
$value = $value ? 1 : 0; |
81
|
|
|
} |
82
|
|
|
if ($type == 'reference' && is_array($value)) { |
83
|
|
|
$value = $value['id']; |
84
|
|
|
} |
85
|
|
|
$queryBuilder->$setMethod('`'.$formFields[$i].'`', '?'); |
86
|
|
|
$queryBuilder->setParameter($i, $value); |
87
|
|
|
} |
88
|
|
|
} |
89
|
|
|
|
90
|
|
|
/** |
91
|
|
|
* Performs the cascading children deletion. |
92
|
|
|
* |
93
|
|
|
* @param integer $id |
94
|
|
|
* the current entities id |
95
|
|
|
* @param boolean $deleteCascade |
96
|
|
|
* whether to delete children and sub children |
97
|
|
|
*/ |
98
|
|
|
protected function deleteChildren($id, $deleteCascade) { |
99
|
|
|
foreach ($this->definition->getChildren() as $childArray) { |
100
|
|
|
$childData = $this->definition->getServiceProvider()->getData($childArray[2]); |
101
|
|
|
$children = $childData->listEntries([$childArray[1] => $id]); |
102
|
|
|
foreach ($children as $child) { |
103
|
|
|
$childData->doDelete($child, $deleteCascade); |
104
|
|
|
} |
105
|
|
|
} |
106
|
|
|
} |
107
|
|
|
|
108
|
|
|
/** |
109
|
|
|
* Checks whether the by id given entity still has children referencing it. |
110
|
|
|
* |
111
|
|
|
* @param integer $id |
112
|
|
|
* the current entities id |
113
|
|
|
* |
114
|
|
|
* @return boolean |
115
|
|
|
* true if the entity still has children |
116
|
|
|
*/ |
117
|
|
|
protected function hasChildren($id) { |
118
|
|
|
foreach ($this->definition->getChildren() as $child) { |
119
|
|
|
$queryBuilder = $this->database->createQueryBuilder(); |
120
|
|
|
$queryBuilder |
121
|
|
|
->select('COUNT(id)') |
122
|
|
|
->from('`'.$child[0].'`', '`'.$child[0].'`') |
123
|
|
|
->where('`'.$child[1].'` = ?') |
124
|
|
|
->andWhere('deleted_at IS NULL') |
125
|
|
|
->setParameter(0, $id); |
126
|
|
|
$queryResult = $queryBuilder->execute(); |
127
|
|
|
$result = $queryResult->fetch(\PDO::FETCH_NUM); |
128
|
|
|
if ($result[0] > 0) { |
129
|
|
|
return true; |
130
|
|
|
} |
131
|
|
|
} |
132
|
|
|
return false; |
133
|
|
|
} |
134
|
|
|
|
135
|
|
|
/** |
136
|
|
|
* {@inheritdoc} |
137
|
|
|
*/ |
138
|
|
|
protected function doDelete(Entity $entity, $deleteCascade) { |
139
|
|
|
$result = $this->shouldExecuteEvents($entity, 'before', 'delete'); |
140
|
|
|
if (!$result) { |
141
|
|
|
return static::DELETION_FAILED_EVENT; |
142
|
|
|
} |
143
|
|
|
$id = $entity->get('id'); |
144
|
|
|
if ($deleteCascade) { |
145
|
|
|
$this->deleteChildren($id, $deleteCascade); |
146
|
|
|
} elseif ($this->hasChildren($id)) { |
147
|
|
|
return static::DELETION_FAILED_STILL_REFERENCED; |
148
|
|
|
} |
149
|
|
|
|
150
|
|
|
$query = $this->database->createQueryBuilder(); |
151
|
|
|
$query |
152
|
|
|
->update('`'.$this->definition->getTable().'`') |
153
|
|
|
->set('deleted_at', 'UTC_TIMESTAMP()') |
154
|
|
|
->where('id = ?') |
155
|
|
|
->setParameter(0, $id); |
156
|
|
|
|
157
|
|
|
$query->execute(); |
158
|
|
|
$this->shouldExecuteEvents($entity, 'after', 'delete'); |
159
|
|
|
return static::DELETION_SUCCESS; |
160
|
|
|
} |
161
|
|
|
|
162
|
|
|
/** |
163
|
|
|
* Gets all possible many-to-many ids existing for this definition. |
164
|
|
|
* |
165
|
|
|
* @param array $fields |
166
|
|
|
* the many field names to fetch for |
167
|
|
|
* @param $params |
168
|
|
|
* the parameters the possible many field values to fetch for |
169
|
|
|
* @return array |
170
|
|
|
* an array of this many-to-many ids |
171
|
|
|
*/ |
172
|
|
|
protected function getManyIds(array $fields, array $params) { |
173
|
|
|
$manyIds = []; |
174
|
|
|
foreach ($fields as $field) { |
175
|
|
|
$thisField = $this->definition->getSubTypeField($field, 'many', 'thisField'); |
176
|
|
|
$thatField = $this->definition->getSubTypeField($field, 'many', 'thatField'); |
177
|
|
|
$queryBuilder = $this->database->createQueryBuilder(); |
178
|
|
|
$queryBuilder |
179
|
|
|
->select('`'.$thisField.'`') |
180
|
|
|
->from($field) |
181
|
|
|
->where('`'.$thatField.'` IN (?)') |
182
|
|
|
->setParameter(0, array_column($params[$field], 'id'), Connection::PARAM_STR_ARRAY) |
183
|
|
|
->groupBy('`'.$thisField.'`') |
184
|
|
|
; |
185
|
|
|
$queryResult = $queryBuilder->execute(); |
186
|
|
|
$manyResults = $queryResult->fetchAll(\PDO::FETCH_ASSOC); |
187
|
|
|
$manyIds = array_merge($manyIds, array_column($manyResults, $thisField)); |
188
|
|
|
|
189
|
|
|
} |
190
|
|
|
return $manyIds; |
191
|
|
|
} |
192
|
|
|
|
193
|
|
|
/** |
194
|
|
|
* Adds sorting parameters to the query. |
195
|
|
|
* |
196
|
|
|
* @param QueryBuilder $queryBuilder |
197
|
|
|
* the query |
198
|
|
|
* @param $filter |
199
|
|
|
* the filter all resulting entities must fulfill, the keys as field names |
200
|
|
|
* @param $filterOperators |
201
|
|
|
* the operators of the filter like "=" defining the full condition of the field |
202
|
|
|
*/ |
203
|
|
|
protected function addFilter(QueryBuilder $queryBuilder, array $filter, array $filterOperators) { |
204
|
|
|
$i = 0; |
205
|
|
|
$manyFields = []; |
206
|
|
|
foreach ($filter as $field => $value) { |
207
|
|
|
if ($this->definition->getType($field) === 'many') { |
208
|
|
|
$manyFields[] = $field; |
209
|
|
|
continue; |
210
|
|
|
} |
211
|
|
|
if ($value === null) { |
212
|
|
|
$queryBuilder->andWhere('`'.$field.'` IS NULL'); |
213
|
|
|
} else { |
214
|
|
|
$operator = array_key_exists($field, $filterOperators) ? $filterOperators[$field] : '='; |
215
|
|
|
$queryBuilder |
216
|
|
|
->andWhere('`'.$field.'` '.$operator.' ?') |
217
|
|
|
->setParameter($i, $value, \PDO::PARAM_STR); |
218
|
|
|
} |
219
|
|
|
$i++; |
220
|
|
|
} |
221
|
|
|
$idsToInclude = $this->getManyIds($manyFields, $filter); |
222
|
|
|
if (!empty($idsToInclude)) { |
223
|
|
|
$queryBuilder |
224
|
|
|
->andWhere('id IN (?)') |
225
|
|
|
->setParameter($i, $idsToInclude, Connection::PARAM_STR_ARRAY) |
226
|
|
|
; |
227
|
|
|
} |
228
|
|
|
} |
229
|
|
|
|
230
|
|
|
/** |
231
|
|
|
* Adds pagination parameters to the query. |
232
|
|
|
* |
233
|
|
|
* @param QueryBuilder $queryBuilder |
234
|
|
|
* the query |
235
|
|
|
* @param integer|null $skip |
236
|
|
|
* the rows to skip |
237
|
|
|
* @param integer|null $amount |
238
|
|
|
* the maximum amount of rows |
239
|
|
|
*/ |
240
|
|
|
protected function addPagination(QueryBuilder $queryBuilder, $skip, $amount) { |
241
|
|
|
$queryBuilder->setMaxResults(9999999999); |
242
|
|
|
if ($amount !== null) { |
243
|
|
|
$queryBuilder->setMaxResults(abs(intval($amount))); |
244
|
|
|
} |
245
|
|
|
if ($skip !== null) { |
246
|
|
|
$queryBuilder->setFirstResult(abs(intval($skip))); |
247
|
|
|
} |
248
|
|
|
} |
249
|
|
|
|
250
|
|
|
/** |
251
|
|
|
* Adds sorting parameters to the query. |
252
|
|
|
* |
253
|
|
|
* @param QueryBuilder $queryBuilder |
254
|
|
|
* the query |
255
|
|
|
* @param string|null $sortField |
256
|
|
|
* the sort field |
257
|
|
|
* @param boolean|null $sortAscending |
258
|
|
|
* true if sort ascending, false if descending |
259
|
|
|
*/ |
260
|
|
|
protected function addSort(QueryBuilder $queryBuilder, $sortField, $sortAscending) { |
261
|
|
|
if ($sortField !== null) { |
262
|
|
|
|
263
|
|
|
$type = $this->definition->getType($sortField); |
264
|
|
|
if ($type === 'many') { |
265
|
|
|
$sortField = $this->definition->getInitialSortField(); |
266
|
|
|
} |
267
|
|
|
|
268
|
|
|
$order = $sortAscending === true ? 'ASC' : 'DESC'; |
269
|
|
|
$queryBuilder->orderBy('`'.$sortField.'`', $order); |
270
|
|
|
} |
271
|
|
|
} |
272
|
|
|
|
273
|
|
|
/** |
274
|
|
|
* Adds the id and name of referenced entities to the given entities. The |
275
|
|
|
* reference field is before the raw id of the referenced entity and after |
276
|
|
|
* the fetch, it's an array with the keys id and name. |
277
|
|
|
* |
278
|
|
|
* @param Entity[] &$entities |
279
|
|
|
* the entities to fetch the references for |
280
|
|
|
* @param string $field |
281
|
|
|
* the reference field |
282
|
|
|
*/ |
283
|
|
|
protected function fetchReferencesForField(array &$entities, $field) { |
284
|
|
|
$nameField = $this->definition->getSubTypeField($field, 'reference', 'nameField'); |
285
|
|
|
$queryBuilder = $this->database->createQueryBuilder(); |
286
|
|
|
|
287
|
|
|
$ids = array_map(function(Entity $entity) use ($field) { |
288
|
|
|
$id = $entity->get($field); |
289
|
|
|
return is_array($id) ? $id['id'] : $id; |
290
|
|
|
}, $entities); |
291
|
|
|
|
292
|
|
|
$referenceEntity = $this->definition->getSubTypeField($field, 'reference', 'entity'); |
293
|
|
|
$table = $this->definition->getServiceProvider()->getData($referenceEntity)->getDefinition()->getTable(); |
294
|
|
|
$queryBuilder |
295
|
|
|
->from('`'.$table.'`', '`'.$table.'`') |
296
|
|
|
->where('id IN (?)') |
297
|
|
|
->andWhere('deleted_at IS NULL'); |
298
|
|
|
if ($nameField) { |
299
|
|
|
$queryBuilder->select('id', $nameField); |
300
|
|
|
} else { |
301
|
|
|
$queryBuilder->select('id'); |
302
|
|
|
} |
303
|
|
|
|
304
|
|
|
$queryBuilder->setParameter(0, $ids, Connection::PARAM_STR_ARRAY); |
305
|
|
|
|
306
|
|
|
$queryResult = $queryBuilder->execute(); |
307
|
|
|
$rows = $queryResult->fetchAll(\PDO::FETCH_ASSOC); |
308
|
|
|
$amount = count($entities); |
309
|
|
|
foreach ($rows as $row) { |
310
|
|
|
for ($i = 0; $i < $amount; ++$i) { |
311
|
|
|
if ($entities[$i]->get($field) == $row['id']) { |
312
|
|
|
$value = ['id' => $entities[$i]->get($field)]; |
313
|
|
|
if ($nameField) { |
314
|
|
|
$value['name'] = $row[$nameField]; |
315
|
|
|
} |
316
|
|
|
$entities[$i]->set($field, $value); |
317
|
|
|
} |
318
|
|
|
} |
319
|
|
|
} |
320
|
|
|
} |
321
|
|
|
|
322
|
|
|
/** |
323
|
|
|
* Generates a new UUID. |
324
|
|
|
* |
325
|
|
|
* @return string|null |
326
|
|
|
* the new UUID or null if this instance isn't configured to do so |
327
|
|
|
*/ |
328
|
|
|
protected function generateUUID() { |
329
|
|
|
$uuid = null; |
330
|
|
|
if ($this->useUUIDs) { |
331
|
|
|
$sql = 'SELECT UUID() as id'; |
332
|
|
|
$result = $this->database->fetchAssoc($sql); |
333
|
|
|
$uuid = $result['id']; |
334
|
|
|
} |
335
|
|
|
return $uuid; |
336
|
|
|
} |
337
|
|
|
|
338
|
|
|
/** |
339
|
|
|
* Enriches the given mapping of entity id to raw entity data with some many-to-many data. |
340
|
|
|
* |
341
|
|
|
* @param array $idToData |
342
|
|
|
* a reference to the map entity id to raw entity data |
343
|
|
|
* @param $manyField |
344
|
|
|
* the many field to enrich data with |
345
|
|
|
*/ |
346
|
|
|
protected function enrichWithManyField(&$idToData, $manyField) { |
347
|
|
|
$queryBuilder = $this->database->createQueryBuilder(); |
348
|
|
|
$nameField = $this->definition->getSubTypeField($manyField, 'many', 'nameField'); |
349
|
|
|
$thisField = $this->definition->getSubTypeField($manyField, 'many', 'thisField'); |
350
|
|
|
$thatField = $this->definition->getSubTypeField($manyField, 'many', 'thatField'); |
351
|
|
|
$entity = $this->definition->getSubTypeField($manyField, 'many', 'entity'); |
352
|
|
|
$entityTable = $this->definition->getServiceProvider()->getData($entity)->getDefinition()->getTable(); |
353
|
|
|
$nameSelect = $nameField !== null ? ', t2.`'.$nameField.'` AS name' : ''; |
354
|
|
|
$queryBuilder |
355
|
|
|
->select('t1.`'.$thisField.'` AS this, t1.`'.$thatField.'` AS id'.$nameSelect) |
356
|
|
|
->from('`'.$manyField.'`', 't1') |
357
|
|
|
->leftJoin('t1', '`'.$entityTable.'`', 't2', 't2.id = t1.`'.$thatField.'`') |
358
|
|
|
->where('t1.`'.$thisField.'` IN (?)') |
359
|
|
|
->andWhere('t2.deleted_at IS NULL'); |
360
|
|
|
$queryBuilder->setParameter(0, array_keys($idToData), Connection::PARAM_STR_ARRAY); |
361
|
|
|
$queryResult = $queryBuilder->execute(); |
362
|
|
|
$manyReferences = $queryResult->fetchAll(\PDO::FETCH_ASSOC); |
363
|
|
|
foreach ($manyReferences as $manyReference) { |
364
|
|
|
$entityId = $manyReference['this']; |
365
|
|
|
unset($manyReference['this']); |
366
|
|
|
$idToData[$entityId][$manyField][] = $manyReference; |
367
|
|
|
} |
368
|
|
|
} |
369
|
|
|
|
370
|
|
|
/** |
371
|
|
|
* Fetches to the rows belonging many-to-many entries and adds them to the rows. |
372
|
|
|
* |
373
|
|
|
* @param array $rows |
374
|
|
|
* the rows to enrich |
375
|
|
|
* @return array |
376
|
|
|
* the enriched rows |
377
|
|
|
*/ |
378
|
|
|
protected function enrichWithMany(array $rows) { |
379
|
|
|
$manyFields = $this->getManyFields(); |
380
|
|
|
$idToData = []; |
381
|
|
|
foreach ($rows as $row) { |
382
|
|
|
foreach ($manyFields as $manyField) { |
383
|
|
|
$row[$manyField] = []; |
384
|
|
|
} |
385
|
|
|
$idToData[$row['id']] = $row; |
386
|
|
|
} |
387
|
|
|
foreach ($manyFields as $manyField) { |
388
|
|
|
$this->enrichWithManyField($idToData, $manyField); |
389
|
|
|
} |
390
|
|
|
return array_values($idToData); |
391
|
|
|
} |
392
|
|
|
|
393
|
|
|
/** |
394
|
|
|
* First, deletes all to the given entity related many-to-many entries from the DB |
395
|
|
|
* and then writes them again. |
396
|
|
|
* |
397
|
|
|
* @param Entity $entity |
398
|
|
|
* the entity to save the many-to-many entries of |
399
|
|
|
*/ |
400
|
|
|
protected function saveMany(Entity $entity) { |
401
|
|
|
$manyFields = $this->getManyFields(); |
402
|
|
|
$id = $entity->get('id'); |
403
|
|
|
foreach ($manyFields as $manyField) { |
404
|
|
|
$thisField = $this->definition->getSubTypeField($manyField, 'many', 'thisField'); |
405
|
|
|
$thatField = $this->definition->getSubTypeField($manyField, 'many', 'thatField'); |
406
|
|
|
$this->database->delete($manyField, [$thisField => $id]); |
407
|
|
|
$manyValues = $entity->get($manyField) ?: []; |
408
|
|
|
foreach ($manyValues as $thatId) { |
409
|
|
|
$this->database->insert($manyField, [ |
410
|
|
|
$thisField => $id, |
411
|
|
|
$thatField => $thatId['id'] |
412
|
|
|
]); |
413
|
|
|
} |
414
|
|
|
} |
415
|
|
|
} |
416
|
|
|
|
417
|
|
|
/** |
418
|
|
|
* Adds the id and name of referenced entities to the given entities. Each |
419
|
|
|
* reference field is before the raw id of the referenced entity and after |
420
|
|
|
* the fetch, it's an array with the keys id and name. |
421
|
|
|
* |
422
|
|
|
* @param Entity[] &$entities |
423
|
|
|
* the entities to fetch the references for |
424
|
|
|
* |
425
|
|
|
* @return void |
426
|
|
|
*/ |
427
|
|
|
protected function enrichWithReference(array &$entities) { |
428
|
|
|
if (!$entities) { |
|
|
|
|
429
|
|
|
return; |
430
|
|
|
} |
431
|
|
|
foreach ($this->definition->getFieldNames() as $field) { |
432
|
|
|
if ($this->definition->getType($field) !== 'reference') { |
433
|
|
|
continue; |
434
|
|
|
} |
435
|
|
|
$this->fetchReferencesForField($entities, $field); |
436
|
|
|
} |
437
|
|
|
} |
438
|
|
|
|
439
|
|
|
/** |
440
|
|
|
* Constructor. |
441
|
|
|
* |
442
|
|
|
* @param EntityDefinition $definition |
443
|
|
|
* the entity definition |
444
|
|
|
* @param FileProcessorInterface $fileProcessor |
445
|
|
|
* the file processor to use |
446
|
|
|
* @param $database |
447
|
|
|
* the Doctrine DBAL instance to use |
448
|
|
|
* @param boolean $useUUIDs |
449
|
|
|
* flag whether to use UUIDs as primary key |
450
|
|
|
*/ |
451
|
|
|
public function __construct(EntityDefinition $definition, FileProcessorInterface $fileProcessor, $database, $useUUIDs) { |
452
|
|
|
$this->definition = $definition; |
453
|
|
|
$this->fileProcessor = $fileProcessor; |
454
|
|
|
$this->database = $database; |
455
|
|
|
$this->useUUIDs = $useUUIDs; |
456
|
|
|
} |
457
|
|
|
|
458
|
|
|
/** |
459
|
|
|
* {@inheritdoc} |
460
|
|
|
*/ |
461
|
|
|
public function get($id) { |
462
|
|
|
$entities = $this->listEntries(['id' => $id]); |
463
|
|
|
if (count($entities) == 0) { |
464
|
|
|
return null; |
465
|
|
|
} |
466
|
|
|
return $entities[0]; |
467
|
|
|
} |
468
|
|
|
|
469
|
|
|
/** |
470
|
|
|
* {@inheritdoc} |
471
|
|
|
*/ |
472
|
|
|
public function listEntries(array $filter = [], array $filterOperators = [], $skip = null, $amount = null, $sortField = null, $sortAscending = null) { |
473
|
|
|
$fieldNames = $this->definition->getFieldNames(); |
474
|
|
|
|
475
|
|
|
$queryBuilder = $this->database->createQueryBuilder(); |
476
|
|
|
$table = $this->definition->getTable(); |
477
|
|
|
$queryBuilder |
478
|
|
|
->select('`'.implode('`,`', $fieldNames).'`') |
479
|
|
|
->from('`'.$table.'`', '`'.$table.'`') |
480
|
|
|
->where('deleted_at IS NULL'); |
481
|
|
|
|
482
|
|
|
$this->addFilter($queryBuilder, $filter, $filterOperators); |
483
|
|
|
$this->addPagination($queryBuilder, $skip, $amount); |
484
|
|
|
$this->addSort($queryBuilder, $sortField, $sortAscending); |
485
|
|
|
|
486
|
|
|
$queryResult = $queryBuilder->execute(); |
487
|
|
|
$rows = $queryResult->fetchAll(\PDO::FETCH_ASSOC); |
488
|
|
|
$rows = $this->enrichWithMany($rows); |
489
|
|
|
$entities = []; |
490
|
|
|
foreach ($rows as $row) { |
491
|
|
|
$entities[] = $this->hydrate($row); |
492
|
|
|
} |
493
|
|
|
$this->enrichWithReference($entities); |
494
|
|
|
return $entities; |
495
|
|
|
} |
496
|
|
|
|
497
|
|
|
/** |
498
|
|
|
* {@inheritdoc} |
499
|
|
|
*/ |
500
|
|
|
public function create(Entity $entity) { |
501
|
|
|
|
502
|
|
|
$result = $this->shouldExecuteEvents($entity, 'before', 'create'); |
503
|
|
|
if (!$result) { |
504
|
|
|
return false; |
505
|
|
|
} |
506
|
|
|
|
507
|
|
|
$queryBuilder = $this->database->createQueryBuilder(); |
508
|
|
|
$queryBuilder |
509
|
|
|
->insert('`'.$this->definition->getTable().'`') |
510
|
|
|
->setValue('created_at', 'UTC_TIMESTAMP()') |
511
|
|
|
->setValue('updated_at', 'UTC_TIMESTAMP()') |
512
|
|
|
->setValue('version', 0); |
513
|
|
|
|
514
|
|
|
|
515
|
|
|
$this->setValuesAndParameters($entity, $queryBuilder, 'setValue'); |
516
|
|
|
|
517
|
|
|
$id = $this->generateUUID(); |
518
|
|
|
if ($this->useUUIDs) { |
519
|
|
|
$queryBuilder->setValue('`id`', '?'); |
520
|
|
|
$uuidI = count($this->getFormFields()); |
521
|
|
|
$queryBuilder->setParameter($uuidI, $id); |
522
|
|
|
} |
523
|
|
|
|
524
|
|
|
$queryBuilder->execute(); |
525
|
|
|
|
526
|
|
|
if (!$this->useUUIDs) { |
527
|
|
|
$id = $this->database->lastInsertId(); |
528
|
|
|
} |
529
|
|
|
|
530
|
|
|
$this->enrichEntityWithMetaData($id, $entity); |
531
|
|
|
$this->saveMany($entity); |
532
|
|
|
$entities = [$entity]; |
533
|
|
|
$this->enrichWithReference($entities); |
534
|
|
|
|
535
|
|
|
$this->shouldExecuteEvents($entity, 'after', 'create'); |
536
|
|
|
|
537
|
|
|
return true; |
538
|
|
|
} |
539
|
|
|
|
540
|
|
|
/** |
541
|
|
|
* {@inheritdoc} |
542
|
|
|
*/ |
543
|
|
|
public function update(Entity $entity) { |
544
|
|
|
|
545
|
|
|
if (!$this->shouldExecuteEvents($entity, 'before', 'update')) { |
546
|
|
|
return false; |
547
|
|
|
} |
548
|
|
|
|
549
|
|
|
$queryBuilder = $this->database->createQueryBuilder(); |
550
|
|
|
$queryBuilder->update('`'.$this->definition->getTable().'`') |
551
|
|
|
->set('updated_at', 'UTC_TIMESTAMP()') |
552
|
|
|
->set('version', 'version + 1') |
553
|
|
|
->where('id = ?') |
554
|
|
|
->setParameter(count($this->getFormFields()), $entity->get('id')); |
555
|
|
|
|
556
|
|
|
$this->setValuesAndParameters($entity, $queryBuilder, 'set'); |
557
|
|
|
$affected = $queryBuilder->execute(); |
558
|
|
|
|
559
|
|
|
$this->saveMany($entity); |
560
|
|
|
$entities = [$entity]; |
561
|
|
|
$this->enrichWithReference($entities); |
562
|
|
|
|
563
|
|
|
$this->shouldExecuteEvents($entity, 'after', 'update'); |
564
|
|
|
|
565
|
|
|
return $affected; |
566
|
|
|
} |
567
|
|
|
|
568
|
|
|
/** |
569
|
|
|
* {@inheritdoc} |
570
|
|
|
*/ |
571
|
|
|
public function getIdToNameMap($entity, $nameField) { |
572
|
|
|
$nameSelect = $nameField !== null ? ',`'.$nameField.'`' : ''; |
573
|
|
|
$drivingField = $nameField ?: 'id'; |
574
|
|
|
|
575
|
|
|
$table = $this->definition->getServiceProvider()->getData($entity)->getDefinition()->getTable(); |
576
|
|
|
$queryBuilder = $this->database->createQueryBuilder(); |
577
|
|
|
$queryBuilder |
578
|
|
|
->select('id'.$nameSelect) |
579
|
|
|
->from('`'.$table.'`', 't1') |
580
|
|
|
->where('deleted_at IS NULL') |
581
|
|
|
->orderBy($drivingField) |
582
|
|
|
; |
583
|
|
|
$queryResult = $queryBuilder->execute(); |
584
|
|
|
$manyReferences = $queryResult->fetchAll(\PDO::FETCH_ASSOC); |
585
|
|
|
$result = array_reduce($manyReferences, function(&$carry, $manyReference) use ($drivingField) { |
586
|
|
|
$carry[$manyReference['id']] = $manyReference[$drivingField]; |
587
|
|
|
return $carry; |
588
|
|
|
}, []); |
589
|
|
|
return $result; |
590
|
|
|
} |
591
|
|
|
|
592
|
|
|
/** |
593
|
|
|
* {@inheritdoc} |
594
|
|
|
*/ |
595
|
|
|
public function countBy($table, array $params, array $paramsOperators, $excludeDeleted) { |
596
|
|
|
$queryBuilder = $this->database->createQueryBuilder(); |
597
|
|
|
$queryBuilder |
598
|
|
|
->select('COUNT(id)') |
599
|
|
|
->from('`'.$table.'`', '`'.$table.'`') |
600
|
|
|
; |
601
|
|
|
|
602
|
|
|
$deletedExcluder = 'where'; |
603
|
|
|
$i = 0; |
604
|
|
|
$manyFields = []; |
605
|
|
|
foreach ($params as $name => $value) { |
606
|
|
|
if ($this->definition->getType($name) === 'many') { |
607
|
|
|
$manyFields[] = $name; |
608
|
|
|
continue; |
609
|
|
|
} |
610
|
|
|
$queryBuilder |
611
|
|
|
->andWhere('`'.$name.'` '.$paramsOperators[$name].' ?') |
612
|
|
|
->setParameter($i, $value, \PDO::PARAM_STR) |
613
|
|
|
; |
614
|
|
|
$i++; |
615
|
|
|
$deletedExcluder = 'andWhere'; |
616
|
|
|
} |
617
|
|
|
|
618
|
|
|
$idsToInclude = $this->getManyIds($manyFields, $params); |
619
|
|
|
if (!empty($idsToInclude)) { |
620
|
|
|
$queryBuilder |
621
|
|
|
->andWhere('id IN (?)') |
622
|
|
|
->setParameter($i, $idsToInclude, Connection::PARAM_STR_ARRAY) |
623
|
|
|
; |
624
|
|
|
$deletedExcluder = 'andWhere'; |
625
|
|
|
} |
626
|
|
|
|
627
|
|
|
if ($excludeDeleted) { |
628
|
|
|
$queryBuilder->$deletedExcluder('deleted_at IS NULL'); |
629
|
|
|
} |
630
|
|
|
|
631
|
|
|
$queryResult = $queryBuilder->execute(); |
632
|
|
|
$result = $queryResult->fetch(\PDO::FETCH_NUM); |
633
|
|
|
return intval($result[0]); |
634
|
|
|
} |
635
|
|
|
|
636
|
|
|
/** |
637
|
|
|
* {@inheritdoc} |
638
|
|
|
*/ |
639
|
|
|
public function hasManySet($field, array $thatIds, $excludeId = null) { |
640
|
|
|
$thisField = $this->definition->getSubTypeField($field, 'many', 'thisField'); |
641
|
|
|
$thatField = $this->definition->getSubTypeField($field, 'many', 'thatField'); |
642
|
|
|
$thatEntity = $this->definition->getSubTypeField($field, 'many', 'entity'); |
643
|
|
|
$entityTable = $this->definition->getServiceProvider()->getData($thatEntity)->getDefinition()->getTable(); |
644
|
|
|
$queryBuilder = $this->database->createQueryBuilder(); |
645
|
|
|
$queryBuilder->select('t1.`'.$thisField.'` AS this, t1.`'.$thatField.'` AS that') |
646
|
|
|
->from('`'.$field.'`', 't1') |
647
|
|
|
->leftJoin('t1', '`'.$entityTable.'`', 't2', 't2.id = t1.`'.$thatField.'`') |
648
|
|
|
->where('t2.deleted_at IS NULL') |
649
|
|
|
->orderBy('this, that'); |
650
|
|
|
if ($excludeId !== null) { |
651
|
|
|
$queryBuilder->andWhere('t1.`'.$thisField.'` != ?')->setParameter(0, $excludeId); |
652
|
|
|
} |
653
|
|
|
$existingMany = $queryBuilder->execute()->fetchAll(\PDO::FETCH_ASSOC); |
654
|
|
|
$existingMap = array_reduce($existingMany, function(&$carry, $existing) { |
655
|
|
|
$carry[$existing['this']][] = $existing['that']; |
656
|
|
|
return $carry; |
657
|
|
|
}, []); |
658
|
|
|
sort($thatIds); |
659
|
|
|
return in_array($thatIds, array_values($existingMap)); |
660
|
|
|
} |
661
|
|
|
|
662
|
|
|
} |
663
|
|
|
|
This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.
Consider making the comparison explicit by using
empty(..)
or! empty(...)
instead.