Completed
Push — master ( b6f561...db9bd8 )
by Neomerx
03:16
created

ModelQueryBuilder::innerJoinOneTable()   C

Complexity

Conditions 8
Paths 4

Size

Total Lines 37
Code Lines 28

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 20
CRAP Score 8

Importance

Changes 0
Metric Value
dl 0
loc 37
ccs 20
cts 20
cp 1
rs 5.3846
c 0
b 0
f 0
cc 8
eloc 28
nc 4
nop 7
crap 8
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 52
    public function __construct(Connection $connection, string $modelClass, ModelSchemeInfoInterface $modelSchemes)
81
    {
82 52
        assert(!empty($modelClass));
83
84 52
        parent::__construct($connection);
85
86 52
        $this->modelSchemes = $modelSchemes;
87 52
        $this->modelClass   = $modelClass;
88
89 52
        $this->mainTableName = $this->getModelSchemes()->getTable($this->getModelClass());
90 52
        $this->mainAlias     = $this->createAlias($this->getMainTableName());
91
92 52
        $this->setColumnToDatabaseMapper(Closure::fromCallable([$this, 'getQuotedMainAliasColumn']));
93
    }
94
95
    /**
96
     * @return string
97
     */
98 52
    public function getModelClass(): string
99
    {
100 52
        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 49
    public function selectModelColumns(iterable $columns = null): self
111
    {
112
        $selectedColumns =
113 49
            $columns === null ? $this->getModelSchemes()->getAttributes($this->getModelClass()) : $columns;
114
115 49
        $quotedColumns = [];
116 49
        $columnMapper  = $this->getColumnToDatabaseMapper();
117 49
        foreach ($selectedColumns as $column) {
118 49
            $quotedColumns[] = call_user_func($columnMapper, $column, $this);
119
        }
120
121 49
        $this->select($quotedColumns);
122
123 49
        return $this;
124
    }
125
126
    /**
127
     * @param Closure $columnMapper
128
     *
129
     * @return self
130
     */
131 52
    public function setColumnToDatabaseMapper(Closure $columnMapper): self
132
    {
133 52
        $this->columnMapper = $columnMapper;
134
135 52
        return $this;
136
    }
137
138
    /**
139
     * @return self
140
     */
141 50
    public function fromModelTable(): self
142
    {
143 50
        $this->from(
144 50
            $this->quoteTableName($this->getMainTableName()),
145 50
            $this->quoteTableName($this->getMainAlias())
146
        );
147
148 50
        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 37
    public function addFiltersWithAndToAlias(iterable $filters): self
288
    {
289 37
        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 40
    private function addFilters(string $tableOrAlias, CompositeExpression $filterLink, iterable $filters): self
383
    {
384 40
        $added = false;
385 40
        foreach ($filters as $columnName => $operationsWithArgs) {
386 40
            $fullColumnName = $this->buildColumnName($tableOrAlias, $columnName);
387 40
            $this->applyFilter($filterLink, $fullColumnName, $operationsWithArgs);
388 39
            $added = true;
389
        }
390 39
        if ($added === true) {
391 39
            $this->andWhere($filterLink);
392
        }
393
394 39
        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 52
    private function getMainTableName(): string
450
    {
451 52
        return $this->mainTableName;
452
    }
453
454
    /**
455
     * @return ModelSchemeInfoInterface
456
     */
457 52
    private function getModelSchemes(): ModelSchemeInfoInterface
458
    {
459 52
        return $this->modelSchemes;
460
    }
461
462
    /**
463
     * @return string
464
     */
465 50
    private function getMainAlias(): string
466
    {
467 50
        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 52
    private function createAlias(string $tableName): string
686
    {
687 52
        $alias                          = $tableName . (++$this->aliasIdCounter);
688 52
        $this->knownAliases[$tableName] = $alias;
689
690 52
        return $alias;
691
    }
692
693
    /**
694
     * @inheritdoc
695
     */
696 52
    private function quoteTableName(string $tableName): string
697
    {
698 52
        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 52
    private function buildColumnName(string $table, string $column): string
713
    {
714 52
        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 49
    public function getQuotedMainAliasColumn(string $column): string
733
    {
734 49
        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 50
    private function applyFilter(
747
        CompositeExpression $filterLink,
748
        string $fullColumnName,
749
        iterable $operationsWithArgs
750
    ): void {
751 50
        foreach ($operationsWithArgs as $operation => $arguments) {
752 50
            assert(is_int($operation));
753 50
            assert(
754 50
                is_array($arguments) || $arguments instanceof Generator,
755 50
                "Filter argument(s) for $fullColumnName must be iterable (an array or Generator)."
756
            );
757
            switch ($operation) {
758 50
                case FilterParameterInterface::OPERATION_EQUALS:
759 42
                    $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 41
                    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 49
            $filterLink->add($expression);
799
        }
800
    }
801
802
    /**
803
     * @param iterable $arguments
804
     *
805
     * @return string
806
     */
807 48
    private function createSingleNamedParameter(iterable $arguments): string
808
    {
809 48
        foreach ($arguments as $argument) {
810 47
            $paramName = $this->createNamedParameter($argument, $this->getPdoType($argument));
811
812 47
            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 49
    private function getColumnToDatabaseMapper(): Closure
839
    {
840 49
        return $this->columnMapper;
841
    }
842
843
    /**
844
     * @param mixed $value
845
     *
846
     * @return int
847
     *
848
     * @SuppressWarnings(PHPMD.ElseExpression)
849
     */
850 49
    private function getPdoType($value): int
851
    {
852 49
        if (is_int($value) === true) {
853 37
            $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 49
        return $type;
867
    }
868
}
869