Completed
Push — develop ( 0fd0fb...9d9666 )
by Neomerx
02:16
created

ModelQueryBuilder::__construct()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 12
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 7
CRAP Score 1

Importance

Changes 0
Metric Value
dl 0
loc 12
ccs 7
cts 7
cp 1
rs 9.4285
c 0
b 0
f 0
cc 1
eloc 7
nc 1
nop 3
crap 1
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