Completed
Push — master ( e70114...4f027b )
by Philip
07:33
created

MySQLData   C

Complexity

Total Complexity 78

Size/Duplication

Total Lines 637
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 5

Importance

Changes 0
Metric Value
wmc 78
lcom 1
cbo 5
dl 0
loc 637
rs 5.2975
c 0
b 0
f 0

24 Methods

Rating   Name   Duplication   Size   Complexity  
A getManyFields() 0 6 1
A getFormFields() 0 10 3
A setValuesAndParameters() 0 13 4
A deleteChildren() 0 9 3
A hasChildren() 0 17 3
B doDelete() 0 23 4
A getManyIds() 0 20 2
B addFilter() 0 26 6
A addPagination() 0 9 3
A addSort() 0 12 4
B fetchReferencesForField() 0 37 6
A generateUUID() 0 9 2
A enrichWithManyField() 0 23 3
A enrichWithMany() 0 14 4
A saveMany() 0 16 4
A __construct() 0 6 1
A get() 0 7 2
A listEntries() 0 23 2
B create() 0 38 4
B update() 0 25 2
A getIdToNameMap() 0 20 3
B countBy() 0 40 5
A fetchReferences() 0 11 4
B hasManySet() 0 27 3

How to fix   Complexity   

Complex Class

Complex classes like MySQLData often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use MySQLData, and based on these observations, apply Extract Interface, too.

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

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
641
            $queryBuilder
642
                ->andWhere('t1.`'.$thisField.'` != ?')
643
                ->setParameter(0, $excludeId)
644
            ;
645
        }
646
        $queryResult  = $queryBuilder->execute();
647
        $existingMany = $queryResult->fetchAll(\PDO::FETCH_ASSOC);
648
        $existingMap  = [];
649
        foreach ($existingMany as $existing) {
650
            $existingMap[$existing['this']][] = $existing['that'];
651
        }
652
        sort($thatIds);
653
        return in_array($thatIds, array_values($existingMap));
654
    }
655
656
}
657