Completed
Push — develop ( db9bd8...90d90f )
by Neomerx
04:13 queued 02:35
created

ModelQueryBuilder::getPdoType()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 18
Code Lines 13

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 11
CRAP Score 3

Importance

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

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

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

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

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

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

function acceptsInteger($int) { }

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

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
260
261 2
        return $this;
262
    }
263
264
    /**
265
     * @param iterable $filters
266
     *
267
     * @return self
268
     */
269 9
    public function addFiltersWithAndToTable(iterable $filters): self
270
    {
271 9
        return $this->addFilters($this->getMainTableName(), $this->expr()->andX(), $filters);
272
    }
273
274
    /**
275
     * @param iterable $filters
276
     *
277
     * @return self
278
     */
279 1
    public function addFiltersWithOrToTable(iterable $filters): self
280
    {
281 1
        return $this->addFilters($this->getMainTableName(), $this->expr()->orX(), $filters);
282
    }
283
284
    /**
285
     * @param iterable $filters
286
     *
287
     * @return self
288
     */
289 38
    public function addFiltersWithAndToAlias(iterable $filters): self
290
    {
291 38
        return $this->addFilters($this->getMainAlias(), $this->expr()->andX(), $filters);
292
    }
293
294
    /**
295
     * @param iterable $filters
296
     *
297
     * @return self
298
     */
299 2
    public function addFiltersWithOrToAlias(iterable $filters): self
300
    {
301 2
        return $this->addFilters($this->getMainAlias(), $this->expr()->orX(), $filters);
302
    }
303
304
    /**
305
     * @param string        $relationshipName
306
     * @param iterable      $relationshipFilters
307
     * @param iterable|null $relationshipSorts
308
     *
309
     * @return self
310
     */
311 24
    public function addRelationshipFiltersAndSortsWithAnd(
312
        string $relationshipName,
313
        iterable $relationshipFilters,
314
        ?iterable $relationshipSorts
315
    ): self {
316 24
        $joinWith = $this->expr()->andX();
317
318 24
        return $this->addRelationshipFiltersAndSorts(
319 24
            $relationshipName,
320 24
            $joinWith,
321 24
            $relationshipFilters,
322 24
            $relationshipSorts
323
        );
324
    }
325
326
    /**
327
     * @return self
328
     */
329 14
    public function distinct(): self
330
    {
331
        // emulate SELECT DISTINCT with grouping by primary key
332 14
        $primaryColumn = $this->getModelSchemes()->getPrimaryKey($this->getModelClass());
333 14
        $this->addGroupBy($this->getQuotedMainAliasColumn($primaryColumn));
334
335 14
        return $this;
336
    }
337
338
    /**
339
     * @param string        $relationshipName
340
     * @param iterable      $relationshipFilters
341
     * @param iterable|null $relationshipSorts
342
     *
343
     * @return self
344
     */
345 2
    public function addRelationshipFiltersAndSortsWithOr(
346
        string $relationshipName,
347
        iterable $relationshipFilters,
348
        ?iterable $relationshipSorts
349
    ): self {
350 2
        $joinWith = $this->expr()->orX();
351
352 2
        return $this->addRelationshipFiltersAndSorts(
353 2
            $relationshipName,
354 2
            $joinWith,
355 2
            $relationshipFilters,
356 2
            $relationshipSorts
357
        );
358
    }
359
360
    /**
361
     * @param iterable $sortParameters
362
     *
363
     * @return self
364
     */
365 8
    public function addSorts(iterable $sortParameters): self
366
    {
367 8
        foreach ($sortParameters as $columnName => $isAsc) {
368 8
            assert(is_string($columnName) === true && is_bool($isAsc) === true);
369 8
            $fullColumnName = $this->getQuotedMainAliasColumn($columnName);
370 8
            assert($this->getModelSchemes()->hasAttributeType($this->getModelClass(), $columnName));
371 8
            $this->addOrderBy($fullColumnName, $isAsc === true ? 'ASC' : 'DESC');
372
        }
373
374 8
        return $this;
375
    }
376
377
    /**
378
     * @param string              $tableOrAlias
379
     * @param CompositeExpression $filterLink
380
     * @param iterable            $filters
381
     *
382
     * @return self
383
     */
384 43
    private function addFilters(string $tableOrAlias, CompositeExpression $filterLink, iterable $filters): self
385
    {
386 43
        $added = false;
387 43
        foreach ($filters as $columnName => $operationsWithArgs) {
388 43
            $fullColumnName = $this->buildColumnName($tableOrAlias, $columnName);
389 43
            $this->applyFilter($filterLink, $fullColumnName, $operationsWithArgs);
390 42
            $added = true;
391
        }
392 42
        if ($added === true) {
393 42
            $this->andWhere($filterLink);
394
        }
395
396 42
        return $this;
397
    }
398
399
    /**
400
     * @param string              $relationshipName
401
     * @param CompositeExpression $filterLink
402
     * @param iterable            $relationshipFilters
403
     * @param iterable|null       $relationshipSorts
404
     *
405
     * @return self
406
     */
407 26
    private function addRelationshipFiltersAndSorts(
408
        string $relationshipName,
409
        CompositeExpression $filterLink,
410
        iterable $relationshipFilters,
411
        ?iterable $relationshipSorts
412
    ): self {
413 26
        $relationshipType = $this->getModelSchemes()->getRelationshipType($this->getModelClass(), $relationshipName);
414
        switch ($relationshipType) {
415 26
            case RelationshipTypes::BELONGS_TO:
416 15
                $builder = $this->addBelongsToFiltersAndSorts(
417 15
                    $relationshipName,
418 15
                    $filterLink,
419 15
                    $relationshipFilters,
420 15
                    $relationshipSorts
421
                );
422 15
                break;
423
424 16
            case RelationshipTypes::HAS_MANY:
425 12
                $builder = $this->addHasManyFiltersAndSorts(
426 12
                    $relationshipName,
427 12
                    $filterLink,
428 12
                    $relationshipFilters,
429 12
                    $relationshipSorts
430
                );
431 12
                break;
432
433 9
            case RelationshipTypes::BELONGS_TO_MANY:
434
            default:
435 9
                assert($relationshipType === RelationshipTypes::BELONGS_TO_MANY);
436 9
                $builder = $this->addBelongsToManyFiltersAndSorts(
437 9
                    $relationshipName,
438 9
                    $filterLink,
439 9
                    $relationshipFilters,
440 9
                    $relationshipSorts
441
                );
442 9
                break;
443
        }
444
445 26
        return $builder;
446
    }
447
448
    /**
449
     * @return string
450
     */
451 56
    private function getMainTableName(): string
452
    {
453 56
        return $this->mainTableName;
454
    }
455
456
    /**
457
     * @return ModelSchemeInfoInterface
458
     */
459 56
    private function getModelSchemes(): ModelSchemeInfoInterface
460
    {
461 56
        return $this->modelSchemes;
462
    }
463
464
    /**
465
     * @return string
466
     */
467 52
    private function getMainAlias(): string
468
    {
469 52
        return $this->mainAlias;
470
    }
471
472
    /**
473
     * @param string              $relationshipName
474
     * @param CompositeExpression $filterLink
475
     * @param iterable            $relationshipFilters
476
     * @param iterable|null       $relationshipSorts
477
     *
478
     * @return self
479
     */
480 15
    private function addBelongsToFiltersAndSorts(
481
        string $relationshipName,
482
        CompositeExpression $filterLink,
483
        iterable $relationshipFilters,
484
        ?iterable $relationshipSorts
485
    ): self {
486 15
        $foreignKey = $this->getModelSchemes()->getForeignKey($this->getModelClass(), $relationshipName);
487
        list($onePrimaryKey, $oneTable) =
488 15
            $this->getModelSchemes()->getReversePrimaryKey($this->getModelClass(), $relationshipName);
489
490 15
        $this->innerJoinOneTable(
491 15
            $this->getMainAlias(),
492 15
            $foreignKey,
493 15
            $oneTable,
494 15
            $onePrimaryKey,
495 15
            $filterLink,
496 15
            $relationshipFilters,
497 15
            $relationshipSorts
498
        );
499 15
        $this->andWhere($filterLink);
500
501 15
        return $this;
502
    }
503
504
    /**
505
     * @param string              $relationshipName
506
     * @param CompositeExpression $filterLink
507
     * @param iterable            $relationshipFilters
508
     * @param iterable|null       $relationshipSorts
509
     *
510
     * @return self
511
     */
512 12
    private function addHasManyFiltersAndSorts(
513
        string $relationshipName,
514
        CompositeExpression $filterLink,
515
        iterable $relationshipFilters,
516
        ?iterable $relationshipSorts
517
    ): self {
518 12
        $primaryKey = $this->getModelSchemes()->getPrimaryKey($this->getModelClass());
519
        list($manyForeignKey, $manyTable) =
520 12
            $this->getModelSchemes()->getReverseForeignKey($this->getModelClass(), $relationshipName);
521
522 12
        $this->innerJoinOneTable(
523 12
            $this->getMainAlias(),
524 12
            $primaryKey,
525 12
            $manyTable,
526 12
            $manyForeignKey,
527 12
            $filterLink,
528 12
            $relationshipFilters,
529 12
            $relationshipSorts
530
        );
531 12
        $this->andWhere($filterLink);
532
533 12
        return $this;
534
    }
535
536
    /**
537
     * @param string              $relationshipName
538
     * @param CompositeExpression $targetFilterLink
539
     * @param iterable            $relationshipFilters
540
     * @param iterable|null       $relationshipSorts
541
     *
542
     * @return self
543
     */
544 9
    private function addBelongsToManyFiltersAndSorts(
545
        string $relationshipName,
546
        CompositeExpression $targetFilterLink,
547
        iterable $relationshipFilters,
548
        ?iterable $relationshipSorts
549
    ): self {
550 9
        $primaryKey = $this->getModelSchemes()->getPrimaryKey($this->getModelClass());
551
        list ($intermediateTable, $intermediatePk, $intermediateFk) =
552 9
            $this->getModelSchemes()->getBelongsToManyRelationship($this->getModelClass(), $relationshipName);
553
        list($targetPrimaryKey, $targetTable) =
554 9
            $this->getModelSchemes()->getReversePrimaryKey($this->getModelClass(), $relationshipName);
555
556
        // no filters for intermediate table
557 9
        $intFilterLink = null;
558 9
        $intFilters    = null;
559 9
        $this->innerJoinTwoSequentialTables(
560 9
            $this->getMainAlias(),
561 9
            $primaryKey,
562 9
            $intermediateTable,
563 9
            $intermediatePk,
564 9
            $intermediateFk,
565 9
            $targetTable,
566 9
            $targetPrimaryKey,
567 9
            $intFilterLink,
568 9
            $intFilters,
569 9
            $targetFilterLink,
570 9
            $relationshipFilters,
571 9
            $relationshipSorts
572
        );
573 9
        $this->andWhere($targetFilterLink);
574
575 9
        return $this;
576
    }
577
578
    /**
579
     * @param string                   $fromAlias
580
     * @param string                   $fromColumn
581
     * @param string                   $targetTable
582
     * @param string                   $targetColumn
583
     * @param CompositeExpression|null $targetFilterLink
584
     * @param iterable|null            $targetFilterParams
585
     * @param iterable|null            $relationshipSorts
586
     *
587
     * @return string
588
     */
589 26
    private function innerJoinOneTable(
590
        string $fromAlias,
591
        string $fromColumn,
592
        string $targetTable,
593
        string $targetColumn,
594
        ?CompositeExpression $targetFilterLink,
595
        ?iterable $targetFilterParams,
596
        ?iterable $relationshipSorts
597
    ): string {
598 26
        $targetAlias   = $this->createAlias($targetTable);
599 26
        $joinCondition = $this->buildColumnName($fromAlias, $fromColumn) . '=' .
600 26
            $this->buildColumnName($targetAlias, $targetColumn);
601
602 26
        $this->innerJoin(
603 26
            $this->quoteTableName($fromAlias),
604 26
            $this->quoteTableName($targetTable),
605 26
            $this->quoteTableName($targetAlias),
606 26
            $joinCondition
607
        );
608
609 26
        if ($targetFilterLink !== null && $targetFilterParams !== null) {
610 26
            foreach ($targetFilterParams as $columnName => $operationsWithArgs) {
611 26
                assert(is_string($columnName) === true);
612 26
                $fullColumnName = $this->buildColumnName($targetAlias, $columnName);
613 26
                $this->applyFilter($targetFilterLink, $fullColumnName, $operationsWithArgs);
614
            }
615
        }
616 26
        if ($relationshipSorts !== null) {
617 19
            foreach ($relationshipSorts as $columnName => $isAsc) {
618 5
                assert(is_string($columnName) === true && is_bool($isAsc) === true);
619 5
                $fullColumnName = $this->buildColumnName($targetAlias, $columnName);
620 5
                $this->addOrderBy($fullColumnName, $isAsc === true ? 'ASC' : 'DESC');
621
            }
622
        }
623
624 26
        return $targetAlias;
625
    }
626
627
    /** @noinspection PhpTooManyParametersInspection
628
     * @param string                   $fromAlias
629
     * @param string                   $fromColumn
630
     * @param string                   $intTable
631
     * @param string                   $intToFromColumn
632
     * @param string                   $intToTargetColumn
633
     * @param string                   $targetTable
634
     * @param string                   $targetColumn
635
     * @param CompositeExpression|null $intFilterLink
636
     * @param iterable|null            $intFilterParams
637
     * @param CompositeExpression|null $targetFilterLink
638
     * @param iterable|null            $targetFilterParams
639
     * @param iterable|null            $targetSortParams
640
     *
641
     * @return string
642
     *
643
     * @SuppressWarnings(PHPMD.ExcessiveParameterList)
644
     */
645 9
    private function innerJoinTwoSequentialTables(
646
        string $fromAlias,
647
        string $fromColumn,
648
        string $intTable,
649
        string $intToFromColumn,
650
        string $intToTargetColumn,
651
        string $targetTable,
652
        string $targetColumn,
653
        ?CompositeExpression $intFilterLink,
654
        ?iterable $intFilterParams,
655
        ?CompositeExpression $targetFilterLink,
656
        ?iterable $targetFilterParams,
657
        ?iterable $targetSortParams
658
    ): string {
659 9
        $intNoSorting = null;
660 9
        $intAlias     = $this->innerJoinOneTable(
661 9
            $fromAlias,
662 9
            $fromColumn,
663 9
            $intTable,
664 9
            $intToFromColumn,
665 9
            $intFilterLink,
666 9
            $intFilterParams,
667 9
            $intNoSorting
668
        );
669 9
        $targetAlias  = $this->innerJoinOneTable(
670 9
            $intAlias,
671 9
            $intToTargetColumn,
672 9
            $targetTable,
673 9
            $targetColumn,
674 9
            $targetFilterLink,
675 9
            $targetFilterParams,
676 9
            $targetSortParams
677
        );
678
679 9
        return $targetAlias;
680
    }
681
682
    /**
683
     * @param string $tableName
684
     *
685
     * @return string
686
     */
687 56
    private function createAlias(string $tableName): string
688
    {
689 56
        $alias                          = $tableName . (++$this->aliasIdCounter);
690 56
        $this->knownAliases[$tableName] = $alias;
691
692 56
        return $alias;
693
    }
694
695
    /**
696
     * @inheritdoc
697
     */
698 55
    private function quoteTableName(string $tableName): string
699
    {
700 55
        return "`$tableName`";
701
    }
702
703
    /**
704
     * @inheritdoc
705
     */
706 9
    private function quoteColumnName(string $columnName): string
707
    {
708 9
        return "`$columnName`";
709
    }
710
711
    /**
712
     * @inheritdoc
713
     */
714 56
    private function buildColumnName(string $table, string $column): string
715
    {
716 56
        return "`$table`.`$column`";
717
    }
718
719
    /**
720
     * @param string $column
721
     *
722
     * @return string
723
     */
724 1
    public function getQuotedMainTableColumn(string $column): string
725
    {
726 1
        return $this->buildColumnName($this->getMainTableName(), $column);
727
    }
728
729
    /**
730
     * @param string $column
731
     *
732
     * @return string
733
     */
734 51
    public function getQuotedMainAliasColumn(string $column): string
735
    {
736 51
        return $this->buildColumnName($this->getMainAlias(), $column);
737
    }
738
739
    /**
740
     * @param CompositeExpression $filterLink
741
     * @param string              $fullColumnName
742
     * @param iterable            $operationsWithArgs
743
     *
744
     * @return void
745
     *
746
     * @SuppressWarnings(PHPMD.CyclomaticComplexity)
747
     */
748 53
    private function applyFilter(
749
        CompositeExpression $filterLink,
750
        string $fullColumnName,
751
        iterable $operationsWithArgs
752
    ): void {
753 53
        foreach ($operationsWithArgs as $operation => $arguments) {
754 53
            assert(is_int($operation));
755 53
            assert(
756 53
                is_array($arguments) || $arguments instanceof Generator,
757 53
                "Filter argument(s) for $fullColumnName must be iterable (an array or Generator)."
758
            );
759
            switch ($operation) {
760 53
                case FilterParameterInterface::OPERATION_EQUALS:
761 44
                    $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...
762 43
                    break;
763 19
                case FilterParameterInterface::OPERATION_NOT_EQUALS:
764 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...
765 1
                    break;
766 19
                case FilterParameterInterface::OPERATION_LESS_THAN:
767 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...
768 6
                    break;
769 19
                case FilterParameterInterface::OPERATION_LESS_OR_EQUALS:
770 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...
771 7
                    break;
772 18
                case FilterParameterInterface::OPERATION_GREATER_THAN:
773 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...
774 2
                    break;
775 17
                case FilterParameterInterface::OPERATION_GREATER_OR_EQUALS:
776 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...
777 6
                    break;
778 12
                case FilterParameterInterface::OPERATION_LIKE:
779 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...
780 9
                    break;
781 6
                case FilterParameterInterface::OPERATION_NOT_LIKE:
782 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...
783 1
                    $expression = $this->expr()->notLike($fullColumnName, $parameter);
784 1
                    break;
785 6
                case FilterParameterInterface::OPERATION_IN:
786 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...
787 6
                    break;
788 1
                case FilterParameterInterface::OPERATION_NOT_IN:
789 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...
790 1
                    break;
791 1
                case FilterParameterInterface::OPERATION_IS_NULL:
792 1
                    $expression = $this->expr()->isNull($fullColumnName);
793 1
                    break;
794 1
                case FilterParameterInterface::OPERATION_IS_NOT_NULL:
795
                default:
796 1
                    $expression = $this->expr()->isNotNull($fullColumnName);
797 1
                    break;
798
            }
799
800 52
            $filterLink->add($expression);
801
        }
802
    }
803
804
    /**
805
     * @param iterable $arguments
806
     *
807
     * @return string
808
     */
809 51
    private function createSingleNamedParameter(iterable $arguments): string
810
    {
811 51
        foreach ($arguments as $argument) {
812 50
            $paramName = $this->createNamedParameter($argument, $this->getPdoType($argument));
813
814 50
            return $paramName;
815
        }
816
817
        // arguments are empty
818 1
        throw new InvalidArgumentException();
819
    }
820
821
    /**
822
     * @param iterable $arguments
823
     *
824
     * @return string[]
825
     */
826 6
    private function createNamedParameterArray(iterable $arguments): array
827
    {
828 6
        $names = [];
829
830 6
        foreach ($arguments as $argument) {
831 6
            $names[] = $this->createNamedParameter($argument, $this->getPdoType($argument));
832
        }
833
834 6
        return $names;
835
    }
836
837
    /**
838
     * @return Closure
839
     */
840 50
    private function getColumnToDatabaseMapper(): Closure
841
    {
842 50
        return $this->columnMapper;
843
    }
844
845
    /**
846
     * @param mixed $value
847
     *
848
     * @return int
849
     *
850
     * @SuppressWarnings(PHPMD.ElseExpression)
851
     */
852 52
    private function getPdoType($value): int
853
    {
854 52
        if (is_int($value) === true) {
855 40
            $type = PDO::PARAM_INT;
856 28
        } elseif (is_bool($value)) {
857 1
            $type = PDO::PARAM_BOOL;
858
        } else {
859 27
            assert(
860 27
                $value !== null,
861
                'It seems you are trying to use `null` with =, >, <, or etc operator. ' .
862 27
                'Use `is null` or `not null` instead.'
863
            );
864 27
            assert(is_string($value), "Only strings, booleans and integers are supported.");
865 27
            $type = PDO::PARAM_STR;
866
        }
867
868 52
        return $type;
869
    }
870
}
871