Completed
Push — develop ( c77a0c...216818 )
by Neomerx
07:38 queued 05:29
created

ModelQueryBuilder::getDbalType()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 7
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 4
CRAP Score 1

Importance

Changes 0
Metric Value
dl 0
loc 7
ccs 4
cts 4
cp 1
rs 9.4285
c 0
b 0
f 0
cc 1
eloc 4
nc 1
nop 1
crap 1
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