MySQLData   F
last analyzed

Complexity

Total Complexity 81

Size/Duplication

Total Lines 672
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 7

Test Coverage

Coverage 100%

Importance

Changes 0
Metric Value
wmc 81
lcom 1
cbo 7
dl 0
loc 672
ccs 325
cts 325
cp 1
rs 1.928
c 0
b 0
f 0

23 Methods

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