Completed
Push — develop ( db9bd8...90d90f )
by Neomerx
04:13 queued 02:35
created

ModelQueryBuilder::innerJoinTwoSequentialTables()   B

Complexity

Conditions 1
Paths 1

Size

Total Lines 36
Code Lines 32

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 19
CRAP Score 1

Importance

Changes 0
Metric Value
dl 0
loc 36
ccs 19
cts 19
cp 1
rs 8.8571
c 0
b 0
f 0
cc 1
eloc 32
nc 1
nop 12
crap 1

How to fix   Many Parameters   

Many Parameters

Methods with many parameters are not only hard to understand, but their parameters also often become inconsistent when you need more, or different data.

There are several approaches to avoid long parameter lists:

1
<?php namespace Limoncello\Flute\Adapters;
2
3
/**
4
 * Copyright 2015-2017 [email protected]
5
 *
6
 * Licensed under the Apache License, Version 2.0 (the "License");
7
 * you may not use this file except in compliance with the License.
8
 * You may obtain a copy of the License at
9
 *
10
 * http://www.apache.org/licenses/LICENSE-2.0
11
 *
12
 * Unless required by applicable law or agreed to in writing, software
13
 * distributed under the License is distributed on an "AS IS" BASIS,
14
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15
 * See the License for the specific language governing permissions and
16
 * limitations under the License.
17
 */
18
19
use Closure;
20
use Doctrine\DBAL\Connection;
21
use Doctrine\DBAL\Query\Expression\CompositeExpression;
22
use Doctrine\DBAL\Query\QueryBuilder;
23
use Doctrine\DBAL\Types\Type;
24
use Generator;
25
use Limoncello\Contracts\Data\ModelSchemeInfoInterface;
26
use Limoncello\Contracts\Data\RelationshipTypes;
27
use Limoncello\Flute\Contracts\Http\Query\FilterParameterInterface;
28
use Limoncello\Flute\Exceptions\InvalidArgumentException;
29
use PDO;
30
31
/**
32
 * @package Limoncello\Flute
33
 *
34
 * @SuppressWarnings(PHPMD.TooManyMethods)
35
 * @SuppressWarnings(PHPMD.TooManyPublicMethods)
36
 * @SuppressWarnings(PHPMD.ExcessiveClassComplexity)
37
 */
38
class ModelQueryBuilder extends QueryBuilder
39
{
40
    /**
41
     * @var string
42
     */
43
    private $modelClass;
44
45
    /**
46
     * @var string
47
     */
48
    private $mainTableName;
49
50
    /**
51
     * @var string
52
     */
53
    private $mainAlias;
54
55
    /**
56
     * @var Closure
57
     */
58
    private $columnMapper;
59
60
    /**
61
     * @var ModelSchemeInfoInterface
62
     */
63
    private $modelSchemes;
64
65
    /**
66
     * @var int
67
     */
68
    private $aliasIdCounter = 0;
69
70
    /**
71
     * @var array
72
     */
73
    private $knownAliases = [];
74
75
    /**
76
     * @param Connection               $connection
77
     * @param string                   $modelClass
78
     * @param ModelSchemeInfoInterface $modelSchemes
79
     *
80
     * @SuppressWarnings(PHPMD.StaticAccess)
81
     */
82 56
    public function __construct(Connection $connection, string $modelClass, ModelSchemeInfoInterface $modelSchemes)
83
    {
84 56
        assert(!empty($modelClass));
85
86 56
        parent::__construct($connection);
87
88 56
        $this->modelSchemes = $modelSchemes;
89 56
        $this->modelClass   = $modelClass;
90
91 56
        $this->mainTableName = $this->getModelSchemes()->getTable($this->getModelClass());
92 56
        $this->mainAlias     = $this->createAlias($this->getMainTableName());
93
94 56
        $this->setColumnToDatabaseMapper(Closure::fromCallable([$this, 'getQuotedMainAliasColumn']));
95
    }
96
97
    /**
98
     * @return string
99
     */
100 56
    public function getModelClass(): string
101
    {
102 56
        return $this->modelClass;
103
    }
104
105
    /**
106
     * Select all fields associated with model.
107
     *
108
     * @param iterable|null $columns
109
     *
110
     * @return self
111
     */
112 50
    public function selectModelColumns(iterable $columns = null): self
113
    {
114
        $selectedColumns =
115 50
            $columns === null ? $this->getModelSchemes()->getAttributes($this->getModelClass()) : $columns;
116
117 50
        $quotedColumns = [];
118 50
        $columnMapper  = $this->getColumnToDatabaseMapper();
119 50
        foreach ($selectedColumns as $column) {
120 50
            $quotedColumns[] = call_user_func($columnMapper, $column, $this);
121
        }
122
123 50
        $this->select($quotedColumns);
124
125 50
        return $this;
126
    }
127
128
    /**
129
     * @param Closure $columnMapper
130
     *
131
     * @return self
132
     */
133 56
    public function setColumnToDatabaseMapper(Closure $columnMapper): self
134
    {
135 56
        $this->columnMapper = $columnMapper;
136
137 56
        return $this;
138
    }
139
140
    /**
141
     * @return self
142
     */
143 51
    public function fromModelTable(): self
144
    {
145 51
        $this->from(
146 51
            $this->quoteTableName($this->getMainTableName()),
147 51
            $this->quoteTableName($this->getMainAlias())
148
        );
149
150 51
        return $this;
151
    }
152
153
    /**
154
     * @param iterable $attributes
155
     *
156
     * @return self
157
     *
158
     * @SuppressWarnings(PHPMD.StaticAccess)
159
     */
160 4
    public function createModel(iterable $attributes): self
161
    {
162 4
        $this->insert($this->quoteTableName($this->getMainTableName()));
163
164 4
        $valuesAsParams = [];
165 4
        foreach ($this->bindAttributes($this->getModelClass(), $attributes) as $quotedColumn => $parameterName) {
166 4
            $valuesAsParams[$quotedColumn] = $parameterName;
167
        }
168 4
        $this->values($valuesAsParams);
169
170 4
        return $this;
171
    }
172
173
    /**
174
     * @param iterable $attributes
175
     *
176
     * @return self
177
     *
178
     * @SuppressWarnings(PHPMD.StaticAccess)
179
     */
180 5
    public function updateModels(iterable $attributes): self
181
    {
182 5
        $this->update($this->quoteTableName($this->getMainTableName()));
183
184 5
        foreach ($this->bindAttributes($this->getModelClass(), $attributes) as $quotedColumn => $parameterName) {
185 5
            $this->set($quotedColumn, $parameterName);
186
        }
187
188 5
        return $this;
189
    }
190
191
    /**
192
     * @param string   $modelClass
193
     * @param iterable $attributes
194
     *
195
     * @return iterable
0 ignored issues
show
Documentation introduced by
Should the return type not be Generator?

This check compares the return type specified in the @return annotation of a function or method doc comment with the types returned by the function and raises an issue if they mismatch.

Loading history...
196
     *
197
     * @SuppressWarnings(PHPMD.StaticAccess)
198
     */
199 9
    public function bindAttributes(string $modelClass, iterable $attributes): iterable
200
    {
201 9
        $dbPlatform = $this->getConnection()->getDatabasePlatform();
202 9
        $types      = $this->getModelSchemes()->getAttributeTypes($modelClass);
203
204 9
        foreach ($attributes as $column => $value) {
205 9
            assert(is_string($column) && $this->getModelSchemes()->hasAttributeType($this->getModelClass(), $column));
206
207 9
            $quotedColumn  = $this->quoteColumnName($column);
208
209 9
            $type          = Type::getType($types[$column]);
210 9
            $pdoValue      = $type->convertToDatabaseValue($value, $dbPlatform);
211 9
            $parameterName = $this->createNamedParameter($pdoValue, $type->getBindingType());
212
213 9
            yield $quotedColumn => $parameterName;
214
        }
215
    }
216
217
    /**
218
     * @return self
219
     */
220 5
    public function deleteModels(): self
221
    {
222 5
        $this->delete($this->quoteTableName($this->getMainTableName()));
223
224 5
        return $this;
225
    }
226
227
    /**
228
     * @inheritdoc
229
     */
230 4
    public function prepareCreateInToManyRelationship(
231
        string $relationshipName,
232
        string $identity,
233
        string $secondaryIdBindName
234
    ): self {
235
        list ($intermediateTable, $primaryKey, $secondaryKey) =
236 4
            $this->getModelSchemes()->getBelongsToManyRelationship($this->getModelClass(), $relationshipName);
237
238
        $this
239 4
            ->insert($this->quoteTableName($intermediateTable))
240 4
            ->values([
241 4
                $this->quoteColumnName($primaryKey)   => $this->createNamedParameter($identity),
242 4
                $this->quoteColumnName($secondaryKey) => $secondaryIdBindName,
243
            ]);
244
245 4
        return $this;
246
    }
247
248
    /**
249
     * @inheritdoc
250
     */
251 2
    public function clearToManyRelationship(string $relationshipName, string $identity): self
252
    {
253
        list ($intermediateTable, $primaryKey) =
254 2
            $this->getModelSchemes()->getBelongsToManyRelationship($this->getModelClass(), $relationshipName);
255
256 2
        $filters = [$primaryKey => [FilterParameterInterface::OPERATION_EQUALS => [$identity]]];
257
        $this
258 2
            ->delete($this->quoteTableName($intermediateTable))
259 2
            ->addFilters($intermediateTable, $this->expr()->andX(), $filters);
0 ignored issues
show
Documentation introduced by
$filters is of type array<?,array<string|int...tring,{"0":"string"}>>>, but the function expects a object<Limoncello\Flute\Adapters\iterable>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
260
261 2
        return $this;
262
    }
263
264
    /**
265
     * @param iterable $filters
266
     *
267
     * @return self
268
     */
269 9
    public function addFiltersWithAndToTable(iterable $filters): self
270
    {
271 9
        return $this->addFilters($this->getMainTableName(), $this->expr()->andX(), $filters);
272
    }
273
274
    /**
275
     * @param iterable $filters
276
     *
277
     * @return self
278
     */
279 1
    public function addFiltersWithOrToTable(iterable $filters): self
280
    {
281 1
        return $this->addFilters($this->getMainTableName(), $this->expr()->orX(), $filters);
282
    }
283
284
    /**
285
     * @param iterable $filters
286
     *
287
     * @return self
288
     */
289 38
    public function addFiltersWithAndToAlias(iterable $filters): self
290
    {
291 38
        return $this->addFilters($this->getMainAlias(), $this->expr()->andX(), $filters);
292
    }
293
294
    /**
295
     * @param iterable $filters
296
     *
297
     * @return self
298
     */
299 2
    public function addFiltersWithOrToAlias(iterable $filters): self
300
    {
301 2
        return $this->addFilters($this->getMainAlias(), $this->expr()->orX(), $filters);
302
    }
303
304
    /**
305
     * @param string        $relationshipName
306
     * @param iterable      $relationshipFilters
307
     * @param iterable|null $relationshipSorts
308
     *
309
     * @return self
310
     */
311 24
    public function addRelationshipFiltersAndSortsWithAnd(
312
        string $relationshipName,
313
        iterable $relationshipFilters,
314
        ?iterable $relationshipSorts
315
    ): self {
316 24
        $joinWith = $this->expr()->andX();
317
318 24
        return $this->addRelationshipFiltersAndSorts(
319 24
            $relationshipName,
320 24
            $joinWith,
321 24
            $relationshipFilters,
322 24
            $relationshipSorts
323
        );
324
    }
325
326
    /**
327
     * @return self
328
     */
329 14
    public function distinct(): self
330
    {
331
        // emulate SELECT DISTINCT with grouping by primary key
332 14
        $primaryColumn = $this->getModelSchemes()->getPrimaryKey($this->getModelClass());
333 14
        $this->addGroupBy($this->getQuotedMainAliasColumn($primaryColumn));
334
335 14
        return $this;
336
    }
337
338
    /**
339
     * @param string        $relationshipName
340
     * @param iterable      $relationshipFilters
341
     * @param iterable|null $relationshipSorts
342
     *
343
     * @return self
344
     */
345 2
    public function addRelationshipFiltersAndSortsWithOr(
346
        string $relationshipName,
347
        iterable $relationshipFilters,
348
        ?iterable $relationshipSorts
349
    ): self {
350 2
        $joinWith = $this->expr()->orX();
351
352 2
        return $this->addRelationshipFiltersAndSorts(
353 2
            $relationshipName,
354 2
            $joinWith,
355 2
            $relationshipFilters,
356 2
            $relationshipSorts
357
        );
358
    }
359
360
    /**
361
     * @param iterable $sortParameters
362
     *
363
     * @return self
364
     */
365 8
    public function addSorts(iterable $sortParameters): self
366
    {
367 8
        foreach ($sortParameters as $columnName => $isAsc) {
368 8
            assert(is_string($columnName) === true && is_bool($isAsc) === true);
369 8
            $fullColumnName = $this->getQuotedMainAliasColumn($columnName);
370 8
            assert($this->getModelSchemes()->hasAttributeType($this->getModelClass(), $columnName));
371 8
            $this->addOrderBy($fullColumnName, $isAsc === true ? 'ASC' : 'DESC');
372
        }
373
374 8
        return $this;
375
    }
376
377
    /**
378
     * @param string              $tableOrAlias
379
     * @param CompositeExpression $filterLink
380
     * @param iterable            $filters
381
     *
382
     * @return self
383
     */
384 43
    private function addFilters(string $tableOrAlias, CompositeExpression $filterLink, iterable $filters): self
385
    {
386 43
        $added = false;
387 43
        foreach ($filters as $columnName => $operationsWithArgs) {
388 43
            $fullColumnName = $this->buildColumnName($tableOrAlias, $columnName);
389 43
            $this->applyFilter($filterLink, $fullColumnName, $operationsWithArgs);
390 42
            $added = true;
391
        }
392 42
        if ($added === true) {
393 42
            $this->andWhere($filterLink);
394
        }
395
396 42
        return $this;
397
    }
398
399
    /**
400
     * @param string              $relationshipName
401
     * @param CompositeExpression $filterLink
402
     * @param iterable            $relationshipFilters
403
     * @param iterable|null       $relationshipSorts
404
     *
405
     * @return self
406
     */
407 26
    private function addRelationshipFiltersAndSorts(
408
        string $relationshipName,
409
        CompositeExpression $filterLink,
410
        iterable $relationshipFilters,
411
        ?iterable $relationshipSorts
412
    ): self {
413 26
        $relationshipType = $this->getModelSchemes()->getRelationshipType($this->getModelClass(), $relationshipName);
414
        switch ($relationshipType) {
415 26
            case RelationshipTypes::BELONGS_TO:
416 15
                $builder = $this->addBelongsToFiltersAndSorts(
417 15
                    $relationshipName,
418 15
                    $filterLink,
419 15
                    $relationshipFilters,
420 15
                    $relationshipSorts
421
                );
422 15
                break;
423
424 16
            case RelationshipTypes::HAS_MANY:
425 12
                $builder = $this->addHasManyFiltersAndSorts(
426 12
                    $relationshipName,
427 12
                    $filterLink,
428 12
                    $relationshipFilters,
429 12
                    $relationshipSorts
430
                );
431 12
                break;
432
433 9
            case RelationshipTypes::BELONGS_TO_MANY:
434
            default:
435 9
                assert($relationshipType === RelationshipTypes::BELONGS_TO_MANY);
436 9
                $builder = $this->addBelongsToManyFiltersAndSorts(
437 9
                    $relationshipName,
438 9
                    $filterLink,
439 9
                    $relationshipFilters,
440 9
                    $relationshipSorts
441
                );
442 9
                break;
443
        }
444
445 26
        return $builder;
446
    }
447
448
    /**
449
     * @return string
450
     */
451 56
    private function getMainTableName(): string
452
    {
453 56
        return $this->mainTableName;
454
    }
455
456
    /**
457
     * @return ModelSchemeInfoInterface
458
     */
459 56
    private function getModelSchemes(): ModelSchemeInfoInterface
460
    {
461 56
        return $this->modelSchemes;
462
    }
463
464
    /**
465
     * @return string
466
     */
467 52
    private function getMainAlias(): string
468
    {
469 52
        return $this->mainAlias;
470
    }
471
472
    /**
473
     * @param string              $relationshipName
474
     * @param CompositeExpression $filterLink
475
     * @param iterable            $relationshipFilters
476
     * @param iterable|null       $relationshipSorts
477
     *
478
     * @return self
479
     */
480 15
    private function addBelongsToFiltersAndSorts(
481
        string $relationshipName,
482
        CompositeExpression $filterLink,
483
        iterable $relationshipFilters,
484
        ?iterable $relationshipSorts
485
    ): self {
486 15
        $foreignKey = $this->getModelSchemes()->getForeignKey($this->getModelClass(), $relationshipName);
487
        list($onePrimaryKey, $oneTable) =
488 15
            $this->getModelSchemes()->getReversePrimaryKey($this->getModelClass(), $relationshipName);
489
490 15
        $this->innerJoinOneTable(
491 15
            $this->getMainAlias(),
492 15
            $foreignKey,
493 15
            $oneTable,
494 15
            $onePrimaryKey,
495 15
            $filterLink,
496 15
            $relationshipFilters,
497 15
            $relationshipSorts
498
        );
499 15
        $this->andWhere($filterLink);
500
501 15
        return $this;
502
    }
503
504
    /**
505
     * @param string              $relationshipName
506
     * @param CompositeExpression $filterLink
507
     * @param iterable            $relationshipFilters
508
     * @param iterable|null       $relationshipSorts
509
     *
510
     * @return self
511
     */
512 12
    private function addHasManyFiltersAndSorts(
513
        string $relationshipName,
514
        CompositeExpression $filterLink,
515
        iterable $relationshipFilters,
516
        ?iterable $relationshipSorts
517
    ): self {
518 12
        $primaryKey = $this->getModelSchemes()->getPrimaryKey($this->getModelClass());
519
        list($manyForeignKey, $manyTable) =
520 12
            $this->getModelSchemes()->getReverseForeignKey($this->getModelClass(), $relationshipName);
521
522 12
        $this->innerJoinOneTable(
523 12
            $this->getMainAlias(),
524 12
            $primaryKey,
525 12
            $manyTable,
526 12
            $manyForeignKey,
527 12
            $filterLink,
528 12
            $relationshipFilters,
529 12
            $relationshipSorts
530
        );
531 12
        $this->andWhere($filterLink);
532
533 12
        return $this;
534
    }
535
536
    /**
537
     * @param string              $relationshipName
538
     * @param CompositeExpression $targetFilterLink
539
     * @param iterable            $relationshipFilters
540
     * @param iterable|null       $relationshipSorts
541
     *
542
     * @return self
543
     */
544 9
    private function addBelongsToManyFiltersAndSorts(
545
        string $relationshipName,
546
        CompositeExpression $targetFilterLink,
547
        iterable $relationshipFilters,
548
        ?iterable $relationshipSorts
549
    ): self {
550 9
        $primaryKey = $this->getModelSchemes()->getPrimaryKey($this->getModelClass());
551
        list ($intermediateTable, $intermediatePk, $intermediateFk) =
552 9
            $this->getModelSchemes()->getBelongsToManyRelationship($this->getModelClass(), $relationshipName);
553
        list($targetPrimaryKey, $targetTable) =
554 9
            $this->getModelSchemes()->getReversePrimaryKey($this->getModelClass(), $relationshipName);
555
556
        // no filters for intermediate table
557 9
        $intFilterLink = null;
558 9
        $intFilters    = null;
559 9
        $this->innerJoinTwoSequentialTables(
560 9
            $this->getMainAlias(),
561 9
            $primaryKey,
562 9
            $intermediateTable,
563 9
            $intermediatePk,
564 9
            $intermediateFk,
565 9
            $targetTable,
566 9
            $targetPrimaryKey,
567 9
            $intFilterLink,
568 9
            $intFilters,
569 9
            $targetFilterLink,
570 9
            $relationshipFilters,
571 9
            $relationshipSorts
572
        );
573 9
        $this->andWhere($targetFilterLink);
574
575 9
        return $this;
576
    }
577
578
    /**
579
     * @param string                   $fromAlias
580
     * @param string                   $fromColumn
581
     * @param string                   $targetTable
582
     * @param string                   $targetColumn
583
     * @param CompositeExpression|null $targetFilterLink
584
     * @param iterable|null            $targetFilterParams
585
     * @param iterable|null            $relationshipSorts
586
     *
587
     * @return string
588
     */
589 26
    private function innerJoinOneTable(
590
        string $fromAlias,
591
        string $fromColumn,
592
        string $targetTable,
593
        string $targetColumn,
594
        ?CompositeExpression $targetFilterLink,
595
        ?iterable $targetFilterParams,
596
        ?iterable $relationshipSorts
597
    ): string {
598 26
        $targetAlias   = $this->createAlias($targetTable);
599 26
        $joinCondition = $this->buildColumnName($fromAlias, $fromColumn) . '=' .
600 26
            $this->buildColumnName($targetAlias, $targetColumn);
601
602 26
        $this->innerJoin(
603 26
            $this->quoteTableName($fromAlias),
604 26
            $this->quoteTableName($targetTable),
605 26
            $this->quoteTableName($targetAlias),
606 26
            $joinCondition
607
        );
608
609 26
        if ($targetFilterLink !== null && $targetFilterParams !== null) {
610 26
            foreach ($targetFilterParams as $columnName => $operationsWithArgs) {
611 26
                assert(is_string($columnName) === true);
612 26
                $fullColumnName = $this->buildColumnName($targetAlias, $columnName);
613 26
                $this->applyFilter($targetFilterLink, $fullColumnName, $operationsWithArgs);
614
            }
615
        }
616 26
        if ($relationshipSorts !== null) {
617 19
            foreach ($relationshipSorts as $columnName => $isAsc) {
618 5
                assert(is_string($columnName) === true && is_bool($isAsc) === true);
619 5
                $fullColumnName = $this->buildColumnName($targetAlias, $columnName);
620 5
                $this->addOrderBy($fullColumnName, $isAsc === true ? 'ASC' : 'DESC');
621
            }
622
        }
623
624 26
        return $targetAlias;
625
    }
626
627
    /** @noinspection PhpTooManyParametersInspection
628
     * @param string                   $fromAlias
629
     * @param string                   $fromColumn
630
     * @param string                   $intTable
631
     * @param string                   $intToFromColumn
632
     * @param string                   $intToTargetColumn
633
     * @param string                   $targetTable
634
     * @param string                   $targetColumn
635
     * @param CompositeExpression|null $intFilterLink
636
     * @param iterable|null            $intFilterParams
637
     * @param CompositeExpression|null $targetFilterLink
638
     * @param iterable|null            $targetFilterParams
639
     * @param iterable|null            $targetSortParams
640
     *
641
     * @return string
642
     *
643
     * @SuppressWarnings(PHPMD.ExcessiveParameterList)
644
     */
645 9
    private function innerJoinTwoSequentialTables(
646
        string $fromAlias,
647
        string $fromColumn,
648
        string $intTable,
649
        string $intToFromColumn,
650
        string $intToTargetColumn,
651
        string $targetTable,
652
        string $targetColumn,
653
        ?CompositeExpression $intFilterLink,
654
        ?iterable $intFilterParams,
655
        ?CompositeExpression $targetFilterLink,
656
        ?iterable $targetFilterParams,
657
        ?iterable $targetSortParams
658
    ): string {
659 9
        $intNoSorting = null;
660 9
        $intAlias     = $this->innerJoinOneTable(
661 9
            $fromAlias,
662 9
            $fromColumn,
663 9
            $intTable,
664 9
            $intToFromColumn,
665 9
            $intFilterLink,
666 9
            $intFilterParams,
667 9
            $intNoSorting
668
        );
669 9
        $targetAlias  = $this->innerJoinOneTable(
670 9
            $intAlias,
671 9
            $intToTargetColumn,
672 9
            $targetTable,
673 9
            $targetColumn,
674 9
            $targetFilterLink,
675 9
            $targetFilterParams,
676 9
            $targetSortParams
677
        );
678
679 9
        return $targetAlias;
680
    }
681
682
    /**
683
     * @param string $tableName
684
     *
685
     * @return string
686
     */
687 56
    private function createAlias(string $tableName): string
688
    {
689 56
        $alias                          = $tableName . (++$this->aliasIdCounter);
690 56
        $this->knownAliases[$tableName] = $alias;
691
692 56
        return $alias;
693
    }
694
695
    /**
696
     * @inheritdoc
697
     */
698 55
    private function quoteTableName(string $tableName): string
699
    {
700 55
        return "`$tableName`";
701
    }
702
703
    /**
704
     * @inheritdoc
705
     */
706 9
    private function quoteColumnName(string $columnName): string
707
    {
708 9
        return "`$columnName`";
709
    }
710
711
    /**
712
     * @inheritdoc
713
     */
714 56
    private function buildColumnName(string $table, string $column): string
715
    {
716 56
        return "`$table`.`$column`";
717
    }
718
719
    /**
720
     * @param string $column
721
     *
722
     * @return string
723
     */
724 1
    public function getQuotedMainTableColumn(string $column): string
725
    {
726 1
        return $this->buildColumnName($this->getMainTableName(), $column);
727
    }
728
729
    /**
730
     * @param string $column
731
     *
732
     * @return string
733
     */
734 51
    public function getQuotedMainAliasColumn(string $column): string
735
    {
736 51
        return $this->buildColumnName($this->getMainAlias(), $column);
737
    }
738
739
    /**
740
     * @param CompositeExpression $filterLink
741
     * @param string              $fullColumnName
742
     * @param iterable            $operationsWithArgs
743
     *
744
     * @return void
745
     *
746
     * @SuppressWarnings(PHPMD.CyclomaticComplexity)
747
     */
748 53
    private function applyFilter(
749
        CompositeExpression $filterLink,
750
        string $fullColumnName,
751
        iterable $operationsWithArgs
752
    ): void {
753 53
        foreach ($operationsWithArgs as $operation => $arguments) {
754 53
            assert(is_int($operation));
755 53
            assert(
756 53
                is_array($arguments) || $arguments instanceof Generator,
757 53
                "Filter argument(s) for $fullColumnName must be iterable (an array or Generator)."
758
            );
759
            switch ($operation) {
760 53
                case FilterParameterInterface::OPERATION_EQUALS:
761 44
                    $expression = $this->expr()->eq($fullColumnName, $this->createSingleNamedParameter($arguments));
0 ignored issues
show
Documentation introduced by
$arguments is of type array|object<Generator>, but the function expects a object<Limoncello\Flute\Adapters\iterable>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
762 43
                    break;
763 19
                case FilterParameterInterface::OPERATION_NOT_EQUALS:
764 1
                    $expression = $this->expr()->neq($fullColumnName, $this->createSingleNamedParameter($arguments));
0 ignored issues
show
Documentation introduced by
$arguments is of type array|object<Generator>, but the function expects a object<Limoncello\Flute\Adapters\iterable>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
765 1
                    break;
766 19
                case FilterParameterInterface::OPERATION_LESS_THAN:
767 6
                    $expression = $this->expr()->lt($fullColumnName, $this->createSingleNamedParameter($arguments));
0 ignored issues
show
Documentation introduced by
$arguments is of type array|object<Generator>, but the function expects a object<Limoncello\Flute\Adapters\iterable>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
768 6
                    break;
769 19
                case FilterParameterInterface::OPERATION_LESS_OR_EQUALS:
770 7
                    $expression = $this->expr()->lte($fullColumnName, $this->createSingleNamedParameter($arguments));
0 ignored issues
show
Documentation introduced by
$arguments is of type array|object<Generator>, but the function expects a object<Limoncello\Flute\Adapters\iterable>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
771 7
                    break;
772 18
                case FilterParameterInterface::OPERATION_GREATER_THAN:
773 2
                    $expression = $this->expr()->gt($fullColumnName, $this->createSingleNamedParameter($arguments));
0 ignored issues
show
Documentation introduced by
$arguments is of type array|object<Generator>, but the function expects a object<Limoncello\Flute\Adapters\iterable>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
774 2
                    break;
775 17
                case FilterParameterInterface::OPERATION_GREATER_OR_EQUALS:
776 6
                    $expression = $this->expr()->gte($fullColumnName, $this->createSingleNamedParameter($arguments));
0 ignored issues
show
Documentation introduced by
$arguments is of type array|object<Generator>, but the function expects a object<Limoncello\Flute\Adapters\iterable>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
777 6
                    break;
778 12
                case FilterParameterInterface::OPERATION_LIKE:
779 9
                    $expression = $this->expr()->like($fullColumnName, $this->createSingleNamedParameter($arguments));
0 ignored issues
show
Documentation introduced by
$arguments is of type array|object<Generator>, but the function expects a object<Limoncello\Flute\Adapters\iterable>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
780 9
                    break;
781 6
                case FilterParameterInterface::OPERATION_NOT_LIKE:
782 1
                    $parameter  = $this->createSingleNamedParameter($arguments);
0 ignored issues
show
Documentation introduced by
$arguments is of type array|object<Generator>, but the function expects a object<Limoncello\Flute\Adapters\iterable>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
783 1
                    $expression = $this->expr()->notLike($fullColumnName, $parameter);
784 1
                    break;
785 6
                case FilterParameterInterface::OPERATION_IN:
786 6
                    $expression = $this->expr()->in($fullColumnName, $this->createNamedParameterArray($arguments));
0 ignored issues
show
Documentation introduced by
$arguments is of type array|object<Generator>, but the function expects a object<Limoncello\Flute\Adapters\iterable>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
787 6
                    break;
788 1
                case FilterParameterInterface::OPERATION_NOT_IN:
789 1
                    $expression = $this->expr()->notIn($fullColumnName, $this->createNamedParameterArray($arguments));
0 ignored issues
show
Documentation introduced by
$arguments is of type array|object<Generator>, but the function expects a object<Limoncello\Flute\Adapters\iterable>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
790 1
                    break;
791 1
                case FilterParameterInterface::OPERATION_IS_NULL:
792 1
                    $expression = $this->expr()->isNull($fullColumnName);
793 1
                    break;
794 1
                case FilterParameterInterface::OPERATION_IS_NOT_NULL:
795
                default:
796 1
                    $expression = $this->expr()->isNotNull($fullColumnName);
797 1
                    break;
798
            }
799
800 52
            $filterLink->add($expression);
801
        }
802
    }
803
804
    /**
805
     * @param iterable $arguments
806
     *
807
     * @return string
808
     */
809 51
    private function createSingleNamedParameter(iterable $arguments): string
810
    {
811 51
        foreach ($arguments as $argument) {
812 50
            $paramName = $this->createNamedParameter($argument, $this->getPdoType($argument));
813
814 50
            return $paramName;
815
        }
816
817
        // arguments are empty
818 1
        throw new InvalidArgumentException();
819
    }
820
821
    /**
822
     * @param iterable $arguments
823
     *
824
     * @return string[]
825
     */
826 6
    private function createNamedParameterArray(iterable $arguments): array
827
    {
828 6
        $names = [];
829
830 6
        foreach ($arguments as $argument) {
831 6
            $names[] = $this->createNamedParameter($argument, $this->getPdoType($argument));
832
        }
833
834 6
        return $names;
835
    }
836
837
    /**
838
     * @return Closure
839
     */
840 50
    private function getColumnToDatabaseMapper(): Closure
841
    {
842 50
        return $this->columnMapper;
843
    }
844
845
    /**
846
     * @param mixed $value
847
     *
848
     * @return int
849
     *
850
     * @SuppressWarnings(PHPMD.ElseExpression)
851
     */
852 52
    private function getPdoType($value): int
853
    {
854 52
        if (is_int($value) === true) {
855 40
            $type = PDO::PARAM_INT;
856 28
        } elseif (is_bool($value)) {
857 1
            $type = PDO::PARAM_BOOL;
858
        } else {
859 27
            assert(
860 27
                $value !== null,
861
                'It seems you are trying to use `null` with =, >, <, or etc operator. ' .
862 27
                'Use `is null` or `not null` instead.'
863
            );
864 27
            assert(is_string($value), "Only strings, booleans and integers are supported.");
865 27
            $type = PDO::PARAM_STR;
866
        }
867
868 52
        return $type;
869
    }
870
}
871