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