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