Completed
Push — master ( d107a0...92ed96 )
by Neomerx
02:21
created

ModelQueryBuilder::innerJoinTwoSequentialTables()   B

Complexity

Conditions 1
Paths 1

Size

Total Lines 36
Code Lines 32

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 19
CRAP Score 1

Importance

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

How to fix   Many Parameters   

Many Parameters

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

There are several approaches to avoid long parameter lists:

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

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

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

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

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

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

function acceptsInteger($int) { }

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

// Instead of
acceptsInteger($x);

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

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

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

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

function acceptsInteger($int) { }

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

// Instead of
acceptsInteger($x);

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

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

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

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

function acceptsInteger($int) { }

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

// Instead of
acceptsInteger($x);

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

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

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

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

function acceptsInteger($int) { }

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

// Instead of
acceptsInteger($x);

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