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