Completed
Push — develop ( 0fd0fb...9d9666 )
by Neomerx
02:16
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 Doctrine\DBAL\Connection;
20
use Doctrine\DBAL\Query\Expression\CompositeExpression;
21
use Doctrine\DBAL\Query\QueryBuilder;
22
use Doctrine\DBAL\Types\Type;
23
use Generator;
24
use Limoncello\Contracts\Data\ModelSchemeInfoInterface;
25
use Limoncello\Contracts\Data\RelationshipTypes;
26
use Limoncello\Flute\Contracts\Http\Query\FilterParameterInterface;
27
use Limoncello\Flute\Exceptions\InvalidArgumentException;
28
use PDO;
29
30
/**
31
 * @package Limoncello\Flute
32
 *
33
 * @SuppressWarnings(PHPMD.TooManyMethods)
34
 * @SuppressWarnings(PHPMD.TooManyPublicMethods)
35
 * @SuppressWarnings(PHPMD.ExcessiveClassComplexity)
36
 */
37
class ModelQueryBuilder extends QueryBuilder
38
{
39
    /**
40
     * @var string
41
     */
42
    private $modelClass;
43
44
    /**
45
     * @var string
46
     */
47
    private $mainTableName;
48
49
    /**
50
     * @var string
51
     */
52
    private $mainAlias;
53
54
    /**
55
     * @var ModelSchemeInfoInterface
56
     */
57
    private $modelSchemes;
58
59
    /**
60
     * @var int
61
     */
62
    private $aliasIdCounter = 0;
63
64
    /**
65
     * @var array
66
     */
67
    private $knownAliases = [];
68
69
    /**
70
     * @param Connection               $connection
71
     * @param string                   $modelClass
72
     * @param ModelSchemeInfoInterface $modelSchemes
73
     */
74 48
    public function __construct(Connection $connection, string $modelClass, ModelSchemeInfoInterface $modelSchemes)
75
    {
76 48
        assert(!empty($modelClass));
77
78 48
        parent::__construct($connection);
79
80 48
        $this->modelSchemes = $modelSchemes;
81 48
        $this->modelClass   = $modelClass;
82
83 48
        $this->mainTableName = $this->getModelSchemes()->getTable($this->getModelClass());
84 48
        $this->mainAlias     = $this->createAlias($this->getMainTableName());
85
    }
86
87
    /**
88
     * @return string
89
     */
90 48
    public function getModelClass(): string
91
    {
92 48
        return $this->modelClass;
93
    }
94
95
    /**
96
     * Select all fields associated with model.
97
     *
98
     * @param iterable|null $columns
99
     *
100
     * @return self
101
     */
102 45
    public function selectModelColumns(iterable $columns = null): self
103
    {
104
        $selectedColumns =
105 45
            $columns === null ? $this->getModelSchemes()->getAttributes($this->getModelClass()) : $columns;
106
107 45
        $quotedColumns = [];
108 45
        foreach ($selectedColumns as $column) {
109 45
            $quotedColumns[] = $this->getQuotedMainAliasColumn($column);
110
        }
111
112 45
        $this->select($quotedColumns);
113
114 45
        return $this;
115
    }
116
117
    /**
118
     * @return self
119
     */
120 46
    public function fromModelTable(): self
121
    {
122 46
        $this->from(
123 46
            $this->quoteTableName($this->getMainTableName()),
124 46
            $this->quoteTableName($this->getMainAlias())
125
        );
126
127 46
        return $this;
128
    }
129
130
    /**
131
     * @param iterable $attributes
132
     *
133
     * @return self
134
     *
135
     * @SuppressWarnings(PHPMD.StaticAccess)
136
     */
137 4
    public function createModel(iterable $attributes): self
138
    {
139 4
        $this->insert($this->quoteTableName($this->getMainTableName()));
140
141 4
        $valuesAsParams = [];
142 4
        foreach ($this->bindAttributes($this->getModelClass(), $attributes) as $quotedColumn => $parameterName) {
143 4
            $valuesAsParams[$quotedColumn] = $parameterName;
144
        }
145 4
        $this->values($valuesAsParams);
146
147 4
        return $this;
148
    }
149
150
    /**
151
     * @param iterable $attributes
152
     *
153
     * @return self
154
     *
155
     * @SuppressWarnings(PHPMD.StaticAccess)
156
     */
157 3
    public function updateModels(iterable $attributes): self
158
    {
159 3
        $this->update($this->quoteTableName($this->getMainTableName()));
160
161 3
        foreach ($this->bindAttributes($this->getModelClass(), $attributes) as $quotedColumn => $parameterName) {
162 3
            $this->set($quotedColumn, $parameterName);
163
        }
164
165 3
        return $this;
166
    }
167
168
    /**
169
     * @param string   $modelClass
170
     * @param iterable $attributes
171
     *
172
     * @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...
173
     *
174
     * @SuppressWarnings(PHPMD.StaticAccess)
175
     */
176 7
    public function bindAttributes(string $modelClass, iterable $attributes): iterable
177
    {
178 7
        $dbPlatform = $this->getConnection()->getDatabasePlatform();
179 7
        $types      = $this->getModelSchemes()->getAttributeTypes($modelClass);
180
181 7
        foreach ($attributes as $column => $value) {
182 7
            assert(is_string($column) && $this->getModelSchemes()->hasAttributeType($this->getModelClass(), $column));
183
184 7
            $quotedColumn  = $this->quoteColumnName($column);
185
186 7
            $type          = Type::getType($types[$column]);
187 7
            $pdoValue      = $type->convertToDatabaseValue($value, $dbPlatform);
188 7
            $parameterName = $this->createNamedParameter($pdoValue, $type->getBindingType());
189
190 7
            yield $quotedColumn => $parameterName;
191
        }
192
    }
193
194
    /**
195
     * @return self
196
     */
197 5
    public function deleteModels(): self
198
    {
199 5
        $this->delete($this->quoteTableName($this->getMainTableName()));
200
201 5
        return $this;
202
    }
203
204
    /**
205
     * @inheritdoc
206
     */
207 4
    public function prepareCreateInToManyRelationship(
208
        string $relationshipName,
209
        string $identity,
210
        string $secondaryIdBindName
211
    ): self {
212
        list ($intermediateTable, $primaryKey, $secondaryKey) =
213 4
            $this->getModelSchemes()->getBelongsToManyRelationship($this->getModelClass(), $relationshipName);
214
215
        $this
216 4
            ->insert($this->quoteTableName($intermediateTable))
217 4
            ->values([
218 4
                $this->quoteColumnName($primaryKey)   => $this->createNamedParameter($identity),
219 4
                $this->quoteColumnName($secondaryKey) => $secondaryIdBindName,
220
            ]);
221
222 4
        return $this;
223
    }
224
225
    /**
226
     * @inheritdoc
227
     */
228 2
    public function clearToManyRelationship(string $relationshipName, string $identity): self
229
    {
230
        list ($intermediateTable, $primaryKey) =
231 2
            $this->getModelSchemes()->getBelongsToManyRelationship($this->getModelClass(), $relationshipName);
232
233 2
        $filters = [$primaryKey => [FilterParameterInterface::OPERATION_EQUALS => [$identity]]];
234
        $this
235 2
            ->delete($this->quoteTableName($intermediateTable))
236 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...
237
238 2
        return $this;
239
    }
240
241
    /**
242
     * @param iterable $filters
243
     *
244
     * @return self
245
     */
246 8
    public function addFiltersWithAndToTable(iterable $filters): self
247
    {
248 8
        return $this->addFilters($this->getMainTableName(), $this->expr()->andX(), $filters);
249
    }
250
251
    /**
252
     * @param iterable $filters
253
     *
254
     * @return self
255
     */
256
    public function addFiltersWithOrToTable(iterable $filters): self
257
    {
258
        return $this->addFilters($this->getMainTableName(), $this->expr()->orX(), $filters);
259
    }
260
261
    /**
262
     * @param iterable $filters
263
     *
264
     * @return self
265
     */
266 33
    public function addFiltersWithAndToAlias(iterable $filters): self
267
    {
268 33
        return $this->addFilters($this->getMainAlias(), $this->expr()->andX(), $filters);
269
    }
270
271
    /**
272
     * @param iterable $filters
273
     *
274
     * @return self
275
     */
276 2
    public function addFiltersWithOrToAlias(iterable $filters): self
277
    {
278 2
        return $this->addFilters($this->getMainAlias(), $this->expr()->orX(), $filters);
279
    }
280
281
    /**
282
     * @param string        $relationshipName
283
     * @param iterable      $relationshipFilters
284
     * @param iterable|null $relationshipSorts
285
     *
286
     * @return self
287
     */
288 23
    public function addRelationshipFiltersAndSortsWithAnd(
289
        string $relationshipName,
290
        iterable $relationshipFilters,
291
        ?iterable $relationshipSorts
292
    ): self {
293 23
        $joinWith = $this->expr()->andX();
294
295 23
        return $this->addRelationshipFiltersAndSorts(
296 23
            $relationshipName,
297 23
            $joinWith,
298 23
            $relationshipFilters,
299 23
            $relationshipSorts
300
        );
301
    }
302
303
    /**
304
     * @return self
305
     */
306 13
    public function distinct(): self
307
    {
308
        // emulate SELECT DISTINCT with grouping by primary key
309 13
        $primaryColumn = $this->getModelSchemes()->getPrimaryKey($this->getModelClass());
310 13
        $this->addGroupBy($this->getQuotedMainAliasColumn($primaryColumn));
311
312 13
        return $this;
313
    }
314
315
    /**
316
     * @param string        $relationshipName
317
     * @param iterable      $relationshipFilters
318
     * @param iterable|null $relationshipSorts
319
     *
320
     * @return self
321
     */
322 1
    public function addRelationshipFiltersAndSortsWithOr(
323
        string $relationshipName,
324
        iterable $relationshipFilters,
325
        ?iterable $relationshipSorts
326
    ): self {
327 1
        $joinWith = $this->expr()->orX();
328
329 1
        return $this->addRelationshipFiltersAndSorts(
330 1
            $relationshipName,
331 1
            $joinWith,
332 1
            $relationshipFilters,
333 1
            $relationshipSorts
334
        );
335
    }
336
337
    /**
338
     * @param iterable $sortParameters
339
     *
340
     * @return self
341
     */
342 6
    public function addSorts(iterable $sortParameters): self
343
    {
344 6
        foreach ($sortParameters as $columnName => $isAsc) {
345 6
            assert(is_string($columnName) === true && is_bool($isAsc) === true);
346 6
            $fullColumnName = $this->getQuotedMainAliasColumn($columnName);
347 6
            assert($this->getModelSchemes()->hasAttributeType($this->getModelClass(), $columnName));
348 6
            $this->addOrderBy($fullColumnName, $isAsc === true ? 'ASC' : 'DESC');
349
        }
350
351 6
        return $this;
352
    }
353
354
    /**
355
     * @param string              $tableOrAlias
356
     * @param CompositeExpression $filterLink
357
     * @param iterable            $filters
358
     *
359
     * @return self
360
     */
361 36
    private function addFilters(string $tableOrAlias, CompositeExpression $filterLink, iterable $filters): self
362
    {
363 36
        $added = false;
364 36
        foreach ($filters as $columnName => $operationsWithArgs) {
365 36
            $fullColumnName = $this->buildColumnName($tableOrAlias, $columnName);
366 36
            $this->applyFilter($filterLink, $fullColumnName, $operationsWithArgs);
367 35
            $added = true;
368
        }
369 35
        if ($added === true) {
370 35
            $this->andWhere($filterLink);
371
        }
372
373 35
        return $this;
374
    }
375
376
    /**
377
     * @param string              $relationshipName
378
     * @param CompositeExpression $filterLink
379
     * @param iterable            $relationshipFilters
380
     * @param iterable|null       $relationshipSorts
381
     *
382
     * @return self
383
     */
384 24
    private function addRelationshipFiltersAndSorts(
385
        string $relationshipName,
386
        CompositeExpression $filterLink,
387
        iterable $relationshipFilters,
388
        ?iterable $relationshipSorts
389
    ): self {
390 24
        $relationshipType = $this->getModelSchemes()->getRelationshipType($this->getModelClass(), $relationshipName);
391
        switch ($relationshipType) {
392 24
            case RelationshipTypes::BELONGS_TO:
393 13
                $builder = $this->addBelongsToFiltersAndSorts(
394 13
                    $relationshipName,
395 13
                    $filterLink,
396 13
                    $relationshipFilters,
397 13
                    $relationshipSorts
398
                );
399 13
                break;
400
401 15
            case RelationshipTypes::HAS_MANY:
402 11
                $builder = $this->addHasManyFiltersAndSorts(
403 11
                    $relationshipName,
404 11
                    $filterLink,
405 11
                    $relationshipFilters,
406 11
                    $relationshipSorts
407
                );
408 11
                break;
409
410 8
            case RelationshipTypes::BELONGS_TO_MANY:
411
            default:
412 8
                assert($relationshipType === RelationshipTypes::BELONGS_TO_MANY);
413 8
                $builder = $this->addBelongsToManyFiltersAndSorts(
414 8
                    $relationshipName,
415 8
                    $filterLink,
416 8
                    $relationshipFilters,
417 8
                    $relationshipSorts
418
                );
419 8
                break;
420
        }
421
422 24
        return $builder;
423
    }
424
425
    /**
426
     * @return string
427
     */
428 48
    private function getMainTableName(): string
429
    {
430 48
        return $this->mainTableName;
431
    }
432
433
    /**
434
     * @return ModelSchemeInfoInterface
435
     */
436 48
    private function getModelSchemes(): ModelSchemeInfoInterface
437
    {
438 48
        return $this->modelSchemes;
439
    }
440
441
    /**
442
     * @return string
443
     */
444 46
    private function getMainAlias(): string
445
    {
446 46
        return $this->mainAlias;
447
    }
448
449
    /**
450
     * @param string              $relationshipName
451
     * @param CompositeExpression $filterLink
452
     * @param iterable            $relationshipFilters
453
     * @param iterable|null       $relationshipSorts
454
     *
455
     * @return self
456
     */
457 13
    private function addBelongsToFiltersAndSorts(
458
        string $relationshipName,
459
        CompositeExpression $filterLink,
460
        iterable $relationshipFilters,
461
        ?iterable $relationshipSorts
462
    ): self {
463 13
        $foreignKey = $this->getModelSchemes()->getForeignKey($this->getModelClass(), $relationshipName);
464
        list($onePrimaryKey, $oneTable) =
465 13
            $this->getModelSchemes()->getReversePrimaryKey($this->getModelClass(), $relationshipName);
466
467 13
        $this->innerJoinOneTable(
468 13
            $this->getMainAlias(),
469 13
            $foreignKey,
470 13
            $oneTable,
471 13
            $onePrimaryKey,
472 13
            $filterLink,
473 13
            $relationshipFilters,
474 13
            $relationshipSorts
475
        );
476 13
        $this->andWhere($filterLink);
477
478 13
        return $this;
479
    }
480
481
    /**
482
     * @param string              $relationshipName
483
     * @param CompositeExpression $filterLink
484
     * @param iterable            $relationshipFilters
485
     * @param iterable|null       $relationshipSorts
486
     *
487
     * @return self
488
     */
489 11
    private function addHasManyFiltersAndSorts(
490
        string $relationshipName,
491
        CompositeExpression $filterLink,
492
        iterable $relationshipFilters,
493
        ?iterable $relationshipSorts
494
    ): self {
495 11
        $primaryKey = $this->getModelSchemes()->getPrimaryKey($this->getModelClass());
496
        list($manyForeignKey, $manyTable) =
497 11
            $this->getModelSchemes()->getReverseForeignKey($this->getModelClass(), $relationshipName);
498
499 11
        $this->innerJoinOneTable(
500 11
            $this->getMainAlias(),
501 11
            $primaryKey,
502 11
            $manyTable,
503 11
            $manyForeignKey,
504 11
            $filterLink,
505 11
            $relationshipFilters,
506 11
            $relationshipSorts
507
        );
508 11
        $this->andWhere($filterLink);
509
510 11
        return $this;
511
    }
512
513
    /**
514
     * @param string              $relationshipName
515
     * @param CompositeExpression $targetFilterLink
516
     * @param iterable            $relationshipFilters
517
     * @param iterable|null       $relationshipSorts
518
     *
519
     * @return self
520
     */
521 8
    private function addBelongsToManyFiltersAndSorts(
522
        string $relationshipName,
523
        CompositeExpression $targetFilterLink,
524
        iterable $relationshipFilters,
525
        ?iterable $relationshipSorts
526
    ): self {
527 8
        $primaryKey = $this->getModelSchemes()->getPrimaryKey($this->getModelClass());
528
        list ($intermediateTable, $intermediatePk, $intermediateFk) =
529 8
            $this->getModelSchemes()->getBelongsToManyRelationship($this->getModelClass(), $relationshipName);
530
        list($targetPrimaryKey, $targetTable) =
531 8
            $this->getModelSchemes()->getReversePrimaryKey($this->getModelClass(), $relationshipName);
532
533
        // no filters for intermediate table
534 8
        $intFilterLink = null;
535 8
        $intFilters    = null;
536 8
        $this->innerJoinTwoSequentialTables(
537 8
            $this->getMainAlias(),
538 8
            $primaryKey,
539 8
            $intermediateTable,
540 8
            $intermediatePk,
541 8
            $intermediateFk,
542 8
            $targetTable,
543 8
            $targetPrimaryKey,
544 8
            $intFilterLink,
545 8
            $intFilters,
546 8
            $targetFilterLink,
547 8
            $relationshipFilters,
548 8
            $relationshipSorts
549
        );
550 8
        $this->andWhere($targetFilterLink);
551
552 8
        return $this;
553
    }
554
555
    /**
556
     * @param string                   $fromAlias
557
     * @param string                   $fromColumn
558
     * @param string                   $targetTable
559
     * @param string                   $targetColumn
560
     * @param CompositeExpression|null $targetFilterLink
561
     * @param iterable|null            $targetFilterParams
562
     * @param iterable|null            $relationshipSorts
563
     *
564
     * @return string
565
     */
566 24
    private function innerJoinOneTable(
567
        string $fromAlias,
568
        string $fromColumn,
569
        string $targetTable,
570
        string $targetColumn,
571
        ?CompositeExpression $targetFilterLink,
572
        ?iterable $targetFilterParams,
573
        ?iterable $relationshipSorts
574
    ): string {
575 24
        $targetAlias   = $this->createAlias($targetTable);
576 24
        $joinCondition = $this->buildColumnName($fromAlias, $fromColumn) . '=' .
577 24
            $this->buildColumnName($targetAlias, $targetColumn);
578
579 24
        $this->innerJoin(
580 24
            $this->quoteTableName($fromAlias),
581 24
            $this->quoteTableName($targetTable),
582 24
            $this->quoteTableName($targetAlias),
583 24
            $joinCondition
584
        );
585
586 24
        if ($targetFilterLink !== null && $targetFilterParams !== null) {
587 24
            foreach ($targetFilterParams as $columnName => $operationsWithArgs) {
588 24
                assert(is_string($columnName) === true);
589 24
                $fullColumnName = $this->buildColumnName($targetAlias, $columnName);
590 24
                $this->applyFilter($targetFilterLink, $fullColumnName, $operationsWithArgs);
591
            }
592
        }
593 24
        if ($relationshipSorts !== null) {
594 18
            foreach ($relationshipSorts as $columnName => $isAsc) {
595 5
                assert(is_string($columnName) === true && is_bool($isAsc) === true);
596 5
                $fullColumnName = $this->buildColumnName($targetAlias, $columnName);
597 5
                $this->addOrderBy($fullColumnName, $isAsc === true ? 'ASC' : 'DESC');
598
            }
599
        }
600
601 24
        return $targetAlias;
602
    }
603
604
    /** @noinspection PhpTooManyParametersInspection
605
     * @param string                   $fromAlias
606
     * @param string                   $fromColumn
607
     * @param string                   $intTable
608
     * @param string                   $intToFromColumn
609
     * @param string                   $intToTargetColumn
610
     * @param string                   $targetTable
611
     * @param string                   $targetColumn
612
     * @param CompositeExpression|null $intFilterLink
613
     * @param iterable|null            $intFilterParams
614
     * @param CompositeExpression|null $targetFilterLink
615
     * @param iterable|null            $targetFilterParams
616
     * @param iterable|null            $targetSortParams
617
     *
618
     * @return string
619
     *
620
     * @SuppressWarnings(PHPMD.ExcessiveParameterList)
621
     */
622 8
    private function innerJoinTwoSequentialTables(
623
        string $fromAlias,
624
        string $fromColumn,
625
        string $intTable,
626
        string $intToFromColumn,
627
        string $intToTargetColumn,
628
        string $targetTable,
629
        string $targetColumn,
630
        ?CompositeExpression $intFilterLink,
631
        ?iterable $intFilterParams,
632
        ?CompositeExpression $targetFilterLink,
633
        ?iterable $targetFilterParams,
634
        ?iterable $targetSortParams
635
    ): string {
636 8
        $intNoSorting = null;
637 8
        $intAlias     = $this->innerJoinOneTable(
638 8
            $fromAlias,
639 8
            $fromColumn,
640 8
            $intTable,
641 8
            $intToFromColumn,
642 8
            $intFilterLink,
643 8
            $intFilterParams,
644 8
            $intNoSorting
645
        );
646 8
        $targetAlias  = $this->innerJoinOneTable(
647 8
            $intAlias,
648 8
            $intToTargetColumn,
649 8
            $targetTable,
650 8
            $targetColumn,
651 8
            $targetFilterLink,
652 8
            $targetFilterParams,
653 8
            $targetSortParams
654
        );
655
656 8
        return $targetAlias;
657
    }
658
659
    /**
660
     * @param string $tableName
661
     *
662
     * @return string
663
     */
664 48
    private function createAlias(string $tableName): string
665
    {
666 48
        $alias                          = $tableName . (++$this->aliasIdCounter);
667 48
        $this->knownAliases[$tableName] = $alias;
668
669 48
        return $alias;
670
    }
671
672
    /**
673
     * @inheritdoc
674
     */
675 48
    private function quoteTableName(string $tableName): string
676
    {
677 48
        return "`$tableName`";
678
    }
679
680
    /**
681
     * @inheritdoc
682
     */
683 7
    private function quoteColumnName(string $columnName): string
684
    {
685 7
        return "`$columnName`";
686
    }
687
688
    /**
689
     * @inheritdoc
690
     */
691 48
    private function buildColumnName(string $table, string $column): string
692
    {
693 48
        return "`$table`.`$column`";
694
    }
695
696
    /**
697
     * @param string $column
698
     *
699
     * @return string
700
     */
701
    public function getQuotedMainTableColumn(string $column): string
702
    {
703
        return $this->buildColumnName($this->getMainTableName(), $column);
704
    }
705
706
    /**
707
     * @param string $column
708
     *
709
     * @return string
710
     */
711 45
    public function getQuotedMainAliasColumn(string $column): string
712
    {
713 45
        return $this->buildColumnName($this->getMainAlias(), $column);
714
    }
715
716
    /**
717
     * @param CompositeExpression $filterLink
718
     * @param string              $fullColumnName
719
     * @param iterable            $operationsWithArgs
720
     *
721
     * @return void
722
     *
723
     * @SuppressWarnings(PHPMD.CyclomaticComplexity)
724
     */
725 46
    private function applyFilter(
726
        CompositeExpression $filterLink,
727
        string $fullColumnName,
728
        iterable $operationsWithArgs
729
    ): void {
730 46
        foreach ($operationsWithArgs as $operation => $arguments) {
731 46
            assert(is_int($operation));
732 46
            assert(
733 46
                is_array($arguments) || $arguments instanceof Generator,
734 46
                "Filter argument(s) for $fullColumnName must be iterable (an array or Generator)."
735
            );
736
            switch ($operation) {
737 46
                case FilterParameterInterface::OPERATION_EQUALS:
738 39
                    $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...
739 38
                    break;
740 16
                case FilterParameterInterface::OPERATION_NOT_EQUALS:
741 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...
742 1
                    break;
743 16
                case FilterParameterInterface::OPERATION_LESS_THAN:
744 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...
745 6
                    break;
746 16
                case FilterParameterInterface::OPERATION_LESS_OR_EQUALS:
747 5
                    $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...
748 5
                    break;
749 15
                case FilterParameterInterface::OPERATION_GREATER_THAN:
750 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...
751 2
                    break;
752 14
                case FilterParameterInterface::OPERATION_GREATER_OR_EQUALS:
753 4
                    $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...
754 4
                    break;
755 11
                case FilterParameterInterface::OPERATION_LIKE:
756 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...
757 9
                    break;
758 5
                case FilterParameterInterface::OPERATION_NOT_LIKE:
759 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...
760 1
                    $expression = $this->expr()->notLike($fullColumnName, $parameter);
761 1
                    break;
762 5
                case FilterParameterInterface::OPERATION_IN:
763 5
                    $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...
764 5
                    break;
765 1
                case FilterParameterInterface::OPERATION_NOT_IN:
766 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...
767 1
                    break;
768 1
                case FilterParameterInterface::OPERATION_IS_NULL:
769 1
                    $expression = $this->expr()->isNull($fullColumnName);
770 1
                    break;
771 1
                case FilterParameterInterface::OPERATION_IS_NOT_NULL:
772
                default:
773 1
                    $expression = $this->expr()->isNotNull($fullColumnName);
774 1
                    break;
775
            }
776
777 45
            $filterLink->add($expression);
778
        }
779
    }
780
781
    /**
782
     * @param iterable $arguments
783
     *
784
     * @return string
785
     */
786 44
    private function createSingleNamedParameter(iterable $arguments): string
787
    {
788 44
        foreach ($arguments as $argument) {
789 43
            $paramName = $this->createNamedParameter($argument, $this->getPdoType($argument));
790
791 43
            return $paramName;
792
        }
793
794
        // arguments are empty
795 1
        throw new InvalidArgumentException();
796
    }
797
798
    /**
799
     * @param iterable $arguments
800
     *
801
     * @return string[]
802
     */
803 5
    private function createNamedParameterArray(iterable $arguments): array
804
    {
805 5
        $names = [];
806
807 5
        foreach ($arguments as $argument) {
808 5
            $names[] = $this->createNamedParameter($argument, $this->getPdoType($argument));
809
        }
810
811 5
        return $names;
812
    }
813
814
    /**
815
     * @param mixed $value
816
     *
817
     * @return int
818
     *
819
     * @SuppressWarnings(PHPMD.ElseExpression)
820
     */
821 45
    private function getPdoType($value): int
822
    {
823 45
        if (is_int($value) === true) {
824 33
            $type = PDO::PARAM_INT;
825 27
        } elseif (is_bool($value)) {
826 1
            $type = PDO::PARAM_BOOL;
827
        } else {
828 26
            assert(
829 26
                $value !== null,
830
                'It seems you are trying to use `null` with =, >, <, or etc operator. ' .
831 26
                'Use `is null` or `not null` instead.'
832
            );
833 26
            assert(is_string($value), "Only strings, booleans and integers are supported.");
834 26
            $type = PDO::PARAM_STR;
835
        }
836
837 45
        return $type;
838
    }
839
}
840