Completed
Push — master ( 5eda5d...617b3a )
by Axel
05:41
created

AbstractRouteRepository::selectSearch()   A

Complexity

Conditions 3
Paths 4

Size

Total Lines 18
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 3
eloc 6
nc 4
nop 6
dl 0
loc 18
rs 10
c 0
b 0
f 0
1
<?php
2
3
/**
4
 * Routes.
5
 *
6
 * @copyright Zikula contributors (Zikula)
7
 * @license http://www.gnu.org/licenses/lgpl.html GNU Lesser General Public License
8
 * @author Zikula contributors <[email protected]>.
9
 * @see https://ziku.la
10
 * @version Generated by ModuleStudio 1.4.0 (https://modulestudio.de).
11
 */
12
13
declare(strict_types=1);
14
15
namespace Zikula\RoutesModule\Entity\Repository\Base;
16
17
use Doctrine\Common\Collections\ArrayCollection;
18
use Doctrine\ORM\Query;
19
use Doctrine\ORM\QueryBuilder;
20
use Gedmo\Sortable\Entity\Repository\SortableRepository;
21
use InvalidArgumentException;
22
use Psr\Log\LoggerInterface;
23
use Symfony\Contracts\Translation\TranslatorInterface;
24
use Zikula\Bundle\CoreBundle\Doctrine\Paginator;
25
use Zikula\UsersModule\Api\ApiInterface\CurrentUserApiInterface;
26
use Zikula\RoutesModule\Entity\RouteEntity;
27
use Zikula\RoutesModule\Helper\CollectionFilterHelper;
28
29
/**
30
 * Repository class used to implement own convenience methods for performing certain DQL queries.
31
 *
32
 * This is the base repository class for route entities.
33
 */
34
abstract class AbstractRouteRepository extends SortableRepository
35
{
36
    /**
37
     * @var string The main entity class
38
     */
39
    protected $mainEntityClass = RouteEntity::class;
40
41
    /**
42
     * @var string The default sorting field/expression
43
     */
44
    protected $defaultSortingField = 'sort';
45
46
    /**
47
     * @var CollectionFilterHelper
48
     */
49
    protected $collectionFilterHelper;
50
51
    /**
52
     * Retrieves an array with all fields which can be used for sorting instances.
53
     *
54
     * @return string[] List of sorting field names
55
     */
56
    public function getAllowedSortingFields(): array
57
    {
58
        return [
59
            'bundle',
60
            'controller',
61
            'action',
62
            'path',
63
            'host',
64
            'schemes',
65
            'methods',
66
            'prependBundlePrefix',
67
            'translatable',
68
            'translationPrefix',
69
            'condition',
70
            'description',
71
            'sort',
72
            'createdBy',
73
            'createdDate',
74
            'updatedBy',
75
            'updatedDate',
76
        ];
77
    }
78
    
79
    public function getDefaultSortingField(): ?string
80
    {
81
        return $this->defaultSortingField;
82
    }
83
    
84
    public function setDefaultSortingField(string $defaultSortingField = null): void
85
    {
86
        if ($this->defaultSortingField !== $defaultSortingField) {
87
            $this->defaultSortingField = $defaultSortingField;
88
        }
89
    }
90
    
91
    public function getCollectionFilterHelper(): ?CollectionFilterHelper
92
    {
93
        return $this->collectionFilterHelper;
94
    }
95
    
96
    public function setCollectionFilterHelper(CollectionFilterHelper $collectionFilterHelper = null): void
97
    {
98
        if ($this->collectionFilterHelper !== $collectionFilterHelper) {
99
            $this->collectionFilterHelper = $collectionFilterHelper;
100
        }
101
    }
102
    
103
    /**
104
     * Updates the creator of all objects created by a certain user.
105
     *
106
     * @throws InvalidArgumentException Thrown if invalid parameters are received
107
     */
108
    public function updateCreator(
109
        int $userId,
110
        int $newUserId,
111
        TranslatorInterface $translator,
112
        LoggerInterface $logger,
113
        CurrentUserApiInterface $currentUserApi
114
    ): void {
115
        if (0 === $userId || 0 === $newUserId) {
116
            throw new InvalidArgumentException($translator->trans('Invalid user identifier received.'));
117
        }
118
    
119
        $qb = $this->getEntityManager()->createQueryBuilder();
120
        $qb->update($this->mainEntityClass, 'tbl')
121
           ->set('tbl.createdBy', $newUserId)
122
           ->where('tbl.createdBy = :creator')
123
           ->setParameter('creator', $userId);
124
        $query = $qb->getQuery();
125
        $query->execute();
126
    
127
        $logArgs = [
128
            'app' => 'ZikulaRoutesModule',
129
            'user' => $currentUserApi->get('uname'),
130
            'entities' => 'routes',
131
            'userid' => $userId
132
        ];
133
        $logger->debug('{app}: User {user} updated {entities} created by user id {userid}.', $logArgs);
134
    }
135
    
136
    /**
137
     * Updates the last editor of all objects updated by a certain user.
138
     *
139
     * @throws InvalidArgumentException Thrown if invalid parameters are received
140
     */
141
    public function updateLastEditor(
142
        int $userId,
143
        int $newUserId,
144
        TranslatorInterface $translator,
145
        LoggerInterface $logger,
146
        CurrentUserApiInterface $currentUserApi
147
    ): void {
148
        if (0 === $userId || 0 === $newUserId) {
149
            throw new InvalidArgumentException($translator->trans('Invalid user identifier received.'));
150
        }
151
    
152
        $qb = $this->getEntityManager()->createQueryBuilder();
153
        $qb->update($this->mainEntityClass, 'tbl')
154
           ->set('tbl.updatedBy', $newUserId)
155
           ->where('tbl.updatedBy = :editor')
156
           ->setParameter('editor', $userId);
157
        $query = $qb->getQuery();
158
        $query->execute();
159
    
160
        $logArgs = [
161
            'app' => 'ZikulaRoutesModule',
162
            'user' => $currentUserApi->get('uname'),
163
            'entities' => 'routes',
164
            'userid' => $userId
165
        ];
166
        $logger->debug('{app}: User {user} updated {entities} edited by user id {userid}.', $logArgs);
167
    }
168
    
169
    /**
170
     * Deletes all objects created by a certain user.
171
     *
172
     * @throws InvalidArgumentException Thrown if invalid parameters are received
173
     */
174
    public function deleteByCreator(
175
        int $userId,
176
        TranslatorInterface $translator,
177
        LoggerInterface $logger,
178
        CurrentUserApiInterface $currentUserApi
179
    ): void {
180
        if (0 === $userId) {
181
            throw new InvalidArgumentException($translator->trans('Invalid user identifier received.'));
182
        }
183
    
184
        $qb = $this->getEntityManager()->createQueryBuilder();
185
        $qb->delete($this->mainEntityClass, 'tbl')
186
           ->where('tbl.createdBy = :creator')
187
           ->setParameter('creator', $userId);
188
        $query = $qb->getQuery();
189
        $query->execute();
190
    
191
        $logArgs = [
192
            'app' => 'ZikulaRoutesModule',
193
            'user' => $currentUserApi->get('uname'),
194
            'entities' => 'routes',
195
            'userid' => $userId
196
        ];
197
        $logger->debug('{app}: User {user} deleted {entities} created by user id {userid}.', $logArgs);
198
    }
199
    
200
    /**
201
     * Deletes all objects updated by a certain user.
202
     *
203
     * @throws InvalidArgumentException Thrown if invalid parameters are received
204
     */
205
    public function deleteByLastEditor(
206
        int $userId,
207
        TranslatorInterface $translator,
208
        LoggerInterface $logger,
209
        CurrentUserApiInterface $currentUserApi
210
    ): void {
211
        if (0 === $userId) {
212
            throw new InvalidArgumentException($translator->trans('Invalid user identifier received.'));
213
        }
214
    
215
        $qb = $this->getEntityManager()->createQueryBuilder();
216
        $qb->delete($this->mainEntityClass, 'tbl')
217
           ->where('tbl.updatedBy = :editor')
218
           ->setParameter('editor', $userId);
219
        $query = $qb->getQuery();
220
        $query->execute();
221
    
222
        $logArgs = [
223
            'app' => 'ZikulaRoutesModule',
224
            'user' => $currentUserApi->get('uname'),
225
            'entities' => 'routes',
226
            'userid' => $userId
227
        ];
228
        $logger->debug('{app}: User {user} deleted {entities} edited by user id {userid}.', $logArgs);
229
    }
230
231
    /**
232
     * Adds an array of id filters to given query instance.
233
     *
234
     * @throws InvalidArgumentException Thrown if invalid parameters are received
235
     */
236
    protected function addIdListFilter(array $idList, QueryBuilder $qb): QueryBuilder
237
    {
238
        $orX = $qb->expr()->orX();
239
    
240
        foreach ($idList as $key => $id) {
241
            if (0 === $id) {
242
                throw new InvalidArgumentException('Invalid identifier received.');
243
            }
244
    
245
            $orX->add($qb->expr()->eq('tbl.id', ':idListFilter_' . $key));
246
            $qb->setParameter('idListFilter_' . $key, $id);
247
        }
248
    
249
        $qb->andWhere($orX);
250
    
251
        return $qb;
252
    }
253
    
254
    /**
255
     * Selects an object from the database.
256
     *
257
     * @param mixed $id The id (or array of ids) to use to retrieve the object (optional) (default=0)
258
     * @param bool $useJoins Whether to include joining related objects (optional) (default=true)
259
     * @param bool $slimMode If activated only some basic fields are selected without using any joins
260
     *                       (optional) (default=false)
261
     *
262
     * @return array|RouteEntity Retrieved data array or routeEntity instance
263
     */
264
    public function selectById(
265
        $id = 0,
266
        bool $useJoins = true,
267
        bool $slimMode = false
268
    ) {
269
        $results = $this->selectByIdList(is_array($id) ? $id : [$id], $useJoins, $slimMode);
270
    
271
        return null !== $results && 0 < count($results) ? $results[0] : null;
272
    }
273
    
274
    /**
275
     * Selects a list of objects with an array of ids
276
     *
277
     * @param array $idList The array of ids to use to retrieve the objects (optional) (default=0)
278
     * @param bool $useJoins Whether to include joining related objects (optional) (default=true)
279
     * @param bool $slimMode If activated only some basic fields are selected without using any joins
280
     *                       (optional) (default=false)
281
     *
282
     * @return array Retrieved RouteEntity instances
283
     */
284
    public function selectByIdList(
285
        array $idList = [0],
286
        bool $useJoins = true,
287
        bool $slimMode = false
288
    ): ?array {
289
        $qb = $this->genericBaseQuery('', '', $useJoins, $slimMode);
290
        $qb = $this->addIdListFilter($idList, $qb);
291
    
292
        if (!$slimMode && null !== $this->collectionFilterHelper) {
293
            $qb = $this->collectionFilterHelper->applyDefaultFilters('route', $qb);
294
        }
295
    
296
        $query = $this->getQueryFromBuilder($qb);
297
    
298
        $results = $query->getResult();
299
    
300
        return 0 < count($results) ? $results : null;
301
    }
302
303
    /**
304
     * Adds where clauses excluding desired identifiers from selection.
305
     */
306
    protected function addExclusion(QueryBuilder $qb, array $exclusions = []): QueryBuilder
307
    {
308
        if (0 < count($exclusions)) {
309
            $qb->andWhere('tbl.id NOT IN (:excludedIdentifiers)')
310
               ->setParameter('excludedIdentifiers', $exclusions);
311
        }
312
    
313
        return $qb;
314
    }
315
316
    /**
317
     * Returns query builder for selecting a list of objects with a given where clause.
318
     */
319
    public function getListQueryBuilder(
320
        string $where = '',
321
        string $orderBy = '',
322
        bool $useJoins = true,
323
        bool $slimMode = false
324
    ): QueryBuilder {
325
        $qb = $this->genericBaseQuery($where, $orderBy, $useJoins, $slimMode);
326
        if (!$slimMode && null !== $this->collectionFilterHelper) {
327
            $qb = $this->collectionFilterHelper->addCommonViewFilters('route', $qb);
328
        }
329
    
330
        return $qb;
331
    }
332
    
333
    /**
334
     * Selects a list of objects with a given where clause.
335
     */
336
    public function selectWhere(
337
        string $where = '',
338
        string $orderBy = '',
339
        bool $useJoins = true,
340
        bool $slimMode = false
341
    ): array {
342
        $qb = $this->getListQueryBuilder($where, $orderBy, $useJoins, $slimMode);
343
    
344
        return $this->retrieveCollectionResult($qb);
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->retrieveCollectionResult($qb) could return the type Zikula\Bundle\CoreBundle\Doctrine\Paginator which is incompatible with the type-hinted return array. Consider adding an additional type-check to rule them out.
Loading history...
345
    }
346
347
    /**
348
     * Selects a list of objects with a given where clause and pagination parameters.
349
     *
350
     * @return Paginator
351
     */
352
    public function selectWherePaginated(
353
        string $where = '',
354
        string $orderBy = '',
355
        int $currentPage = 1,
356
        int $resultsPerPage = 25,
357
        bool $useJoins = true,
358
        bool $slimMode = false
359
    ): Paginator {
360
        $qb = $this->getListQueryBuilder($where, $orderBy, $useJoins, $slimMode);
361
    
362
        return $this->retrieveCollectionResult($qb, true, $currentPage, $resultsPerPage);
363
    }
364
365
    /**
366
     * Selects entities by a given search fragment.
367
     *
368
     * @return array Retrieved collection and (for paginated queries) the amount of total records affected
369
     */
370
    public function selectSearch(
371
        string $fragment = '',
372
        array $exclude = [],
373
        string $orderBy = '',
374
        int $currentPage = 1,
375
        int $resultsPerPage = 25,
376
        bool $useJoins = true
377
    ): array {
378
        $qb = $this->getListQueryBuilder('', $orderBy, $useJoins);
379
        if (0 < count($exclude)) {
380
            $qb = $this->addExclusion($qb, $exclude);
381
        }
382
    
383
        if (null !== $this->collectionFilterHelper) {
384
            $qb = $this->collectionFilterHelper->addSearchFilter('route', $qb, $fragment);
385
        }
386
    
387
        return $this->retrieveCollectionResult($qb, true, $currentPage, $resultsPerPage);
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->retrieveCo...tPage, $resultsPerPage) returns the type Zikula\Bundle\CoreBundle\Doctrine\Paginator which is incompatible with the type-hinted return array.
Loading history...
388
    }
389
390
    /**
391
     * Performs a given database selection and post-processed the results.
392
     *
393
     * @return Paginator|array Paginator (for paginated queries) or retrieved collection
394
     */
395
    public function retrieveCollectionResult(
396
        QueryBuilder $qb,
397
        bool $isPaginated = false,
398
        int $currentPage = 1,
399
        int $resultsPerPage = 25
400
    ) {
401
        if (!$isPaginated) {
402
            $query = $this->getQueryFromBuilder($qb);
403
    
404
            return $query->getResult();
405
        }
406
    
407
        return (new Paginator($qb, $resultsPerPage))->paginate($currentPage);
408
    }
409
410
    /**
411
     * Returns query builder instance for a count query.
412
     */
413
    public function getCountQuery(string $where = '', bool $useJoins = false): QueryBuilder
414
    {
415
        $selection = 'COUNT(tbl.id) AS numRoutes';
416
    
417
        $qb = $this->getEntityManager()->createQueryBuilder();
418
        $qb->select($selection)
419
           ->from($this->mainEntityClass, 'tbl');
420
    
421
        if (true === $useJoins) {
422
            $this->addJoinsToFrom($qb);
423
        }
424
    
425
        if (!empty($where)) {
426
            $qb->andWhere($where);
427
        }
428
    
429
        return $qb;
430
    }
431
432
    /**
433
     * Selects entity count with a given where clause.
434
     */
435
    public function selectCount(string $where = '', bool $useJoins = false, array $parameters = []): int
436
    {
437
        $qb = $this->getCountQuery($where, $useJoins);
438
    
439
        if (null !== $this->collectionFilterHelper) {
440
            $qb = $this->collectionFilterHelper->applyDefaultFilters('route', $qb, $parameters);
441
        }
442
    
443
        $query = $qb->getQuery();
444
    
445
        return (int)$query->getSingleScalarResult();
446
    }
447
448
    /**
449
     * Checks for unique values.
450
     */
451
    public function detectUniqueState(string $fieldName, string $fieldValue, int $excludeId = 0): bool
452
    {
453
        $qb = $this->getCountQuery();
454
        $qb->andWhere('tbl.' . $fieldName . ' = :' . $fieldName)
455
           ->setParameter($fieldName, $fieldValue);
456
    
457
        if ($excludeId > 0) {
458
            $qb = $this->addExclusion($qb, [$excludeId]);
459
        }
460
    
461
        $query = $qb->getQuery();
462
    
463
        $count = (int)$query->getSingleScalarResult();
464
    
465
        return 1 > $count;
466
    }
467
468
    /**
469
     * Builds a generic Doctrine query supporting WHERE and ORDER BY.
470
     */
471
    public function genericBaseQuery(
472
        string $where = '',
473
        string $orderBy = '',
474
        bool $useJoins = true,
475
        bool $slimMode = false
476
    ): QueryBuilder {
477
        // normally we select the whole table
478
        $selection = 'tbl';
479
    
480
        if (true === $slimMode) {
481
            // but for the slim version we select only the basic fields, and no joins
482
    
483
            $selection = 'tbl.id';
484
            $selection .= ', tbl.path';
485
            $selection .= ', tbl.sort';
486
            $useJoins = false;
487
        }
488
    
489
        if (true === $useJoins) {
490
            $selection .= $this->addJoinsToSelection();
491
        }
492
    
493
        $qb = $this->getEntityManager()->createQueryBuilder();
494
        $qb->select($selection)
495
           ->from($this->mainEntityClass, 'tbl');
496
    
497
        if (true === $useJoins) {
498
            $this->addJoinsToFrom($qb);
499
        }
500
    
501
        if (!empty($where)) {
502
            $qb->andWhere($where);
503
        }
504
    
505
        $this->genericBaseQueryAddOrderBy($qb, $orderBy);
506
    
507
        return $qb;
508
    }
509
510
    /**
511
     * Adds ORDER BY clause to given query builder.
512
     */
513
    protected function genericBaseQueryAddOrderBy(QueryBuilder $qb, string $orderBy = ''): QueryBuilder
514
    {
515
        if ('RAND()' === $orderBy) {
516
            // random selection
517
            $qb->addSelect('MOD(tbl.id, ' . random_int(2, 15) . ') AS HIDDEN randomIdentifiers')
518
               ->orderBy('randomIdentifiers');
519
    
520
            return $qb;
521
        }
522
    
523
        if (empty($orderBy)) {
524
            $orderBy = $this->defaultSortingField;
525
        }
526
    
527
        if (empty($orderBy)) {
528
            return $qb;
529
        }
530
    
531
        // add order by clause
532
        if (false === strpos($orderBy, '.')) {
533
            $orderBy = 'tbl.' . $orderBy;
534
        }
535
        if (false !== strpos($orderBy, 'tbl.createdBy')) {
536
            $qb->addSelect('tblCreator')
537
               ->leftJoin('tbl.createdBy', 'tblCreator');
538
            $orderBy = str_replace('tbl.createdBy', 'tblCreator.uname', $orderBy);
539
        }
540
        if (false !== strpos($orderBy, 'tbl.updatedBy')) {
541
            $qb->addSelect('tblUpdater')
542
               ->leftJoin('tbl.updatedBy', 'tblUpdater');
543
            $orderBy = str_replace('tbl.updatedBy', 'tblUpdater.uname', $orderBy);
544
        }
545
        $qb->add('orderBy', $orderBy);
0 ignored issues
show
Bug introduced by
$orderBy of type string is incompatible with the type array|object expected by parameter $dqlPart of Doctrine\ORM\QueryBuilder::add(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

545
        $qb->add('orderBy', /** @scrutinizer ignore-type */ $orderBy);
Loading history...
546
    
547
        return $qb;
548
    }
549
550
    /**
551
     * Retrieves Doctrine query from query builder.
552
     */
553
    public function getQueryFromBuilder(QueryBuilder $qb): Query
554
    {
555
        $query = $qb->getQuery();
556
    
557
        return $query;
558
    }
559
560
    /**
561
     * Helper method to add join selections.
562
     */
563
    protected function addJoinsToSelection(): string
564
    {
565
        $selection = '';
566
    
567
        return $selection;
568
    }
569
    
570
    /**
571
     * Helper method to add joins to from clause.
572
     */
573
    protected function addJoinsToFrom(QueryBuilder $qb): QueryBuilder
574
    {
575
    
576
        return $qb;
577
    }
578
}
579