Completed
Push — develop ( 10c295...73136c )
by Neomerx
03:55
created

convertDataTimeToDatabaseFormat()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 12
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 7
CRAP Score 2

Importance

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

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

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

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

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

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

function acceptsInteger($int) { }

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

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
268
269 2
        return $this;
270
    }
271
272
    /**
273
     * @param iterable $filters
274
     *
275
     * @return self
276
     */
277 9
    public function addFiltersWithAndToTable(iterable $filters): self
278
    {
279 9
        return $this->addFilters($this->getMainTableName(), $this->expr()->andX(), $filters);
280
    }
281
282
    /**
283
     * @param iterable $filters
284
     *
285
     * @return self
286
     */
287 1
    public function addFiltersWithOrToTable(iterable $filters): self
288
    {
289 1
        return $this->addFilters($this->getMainTableName(), $this->expr()->orX(), $filters);
290
    }
291
292
    /**
293
     * @param iterable $filters
294
     *
295
     * @return self
296
     */
297 38
    public function addFiltersWithAndToAlias(iterable $filters): self
298
    {
299 38
        return $this->addFilters($this->getMainAlias(), $this->expr()->andX(), $filters);
300
    }
301
302
    /**
303
     * @param iterable $filters
304
     *
305
     * @return self
306
     */
307 2
    public function addFiltersWithOrToAlias(iterable $filters): self
308
    {
309 2
        return $this->addFilters($this->getMainAlias(), $this->expr()->orX(), $filters);
310
    }
311
312
    /**
313
     * @param string        $relationshipName
314
     * @param iterable      $relationshipFilters
315
     * @param iterable|null $relationshipSorts
316
     *
317
     * @return self
318
     */
319 26
    public function addRelationshipFiltersAndSortsWithAnd(
320
        string $relationshipName,
321
        iterable $relationshipFilters,
322
        ?iterable $relationshipSorts
323
    ): self {
324 26
        $joinWith = $this->expr()->andX();
325
326 26
        return $this->addRelationshipFiltersAndSorts(
327 26
            $relationshipName,
328 26
            $joinWith,
329 26
            $relationshipFilters,
330 26
            $relationshipSorts
331
        );
332
    }
333
334
    /**
335
     * @return self
336
     */
337 14
    public function distinct(): self
338
    {
339
        // emulate SELECT DISTINCT with grouping by primary key
340 14
        $primaryColumn = $this->getModelSchemes()->getPrimaryKey($this->getModelClass());
341 14
        $this->addGroupBy($this->getQuotedMainAliasColumn($primaryColumn));
342
343 14
        return $this;
344
    }
345
346
    /**
347
     * @param string        $relationshipName
348
     * @param iterable      $relationshipFilters
349
     * @param iterable|null $relationshipSorts
350
     *
351
     * @return self
352
     */
353 2
    public function addRelationshipFiltersAndSortsWithOr(
354
        string $relationshipName,
355
        iterable $relationshipFilters,
356
        ?iterable $relationshipSorts
357
    ): self {
358 2
        $joinWith = $this->expr()->orX();
359
360 2
        return $this->addRelationshipFiltersAndSorts(
361 2
            $relationshipName,
362 2
            $joinWith,
363 2
            $relationshipFilters,
364 2
            $relationshipSorts
365
        );
366
    }
367
368
    /**
369
     * @param iterable $sortParameters
370
     *
371
     * @return self
372
     */
373 8
    public function addSorts(iterable $sortParameters): self
374
    {
375 8
        foreach ($sortParameters as $columnName => $isAsc) {
376 8
            assert(is_string($columnName) === true && is_bool($isAsc) === true);
377 8
            $fullColumnName = $this->getQuotedMainAliasColumn($columnName);
378 8
            assert($this->getModelSchemes()->hasAttributeType($this->getModelClass(), $columnName));
379 8
            $this->addOrderBy($fullColumnName, $isAsc === true ? 'ASC' : 'DESC');
380
        }
381
382 8
        return $this;
383
    }
384
385
    /**
386
     * @param string              $tableOrAlias
387
     * @param CompositeExpression $filterLink
388
     * @param iterable            $filters
389
     *
390
     * @return self
391
     */
392 43
    private function addFilters(string $tableOrAlias, CompositeExpression $filterLink, iterable $filters): self
393
    {
394 43
        foreach ($filters as $columnName => $operationsWithArgs) {
395 43
            $fullColumnName = $this->buildColumnName($tableOrAlias, $columnName);
396 43
            $this->applyFilter($filterLink, $fullColumnName, $operationsWithArgs);
397
        }
398 42
        if ($filterLink->count() > 0) {
399 42
            $this->andWhere($filterLink);
400
        }
401
402 42
        return $this;
403
    }
404
405
    /**
406
     * @param string              $relationshipName
407
     * @param CompositeExpression $filterLink
408
     * @param iterable            $relationshipFilters
409
     * @param iterable|null       $relationshipSorts
410
     *
411
     * @return self
412
     */
413 28
    private function addRelationshipFiltersAndSorts(
414
        string $relationshipName,
415
        CompositeExpression $filterLink,
416
        iterable $relationshipFilters,
417
        ?iterable $relationshipSorts
418
    ): self {
419 28
        $relationshipType = $this->getModelSchemes()->getRelationshipType($this->getModelClass(), $relationshipName);
420
        switch ($relationshipType) {
421 28
            case RelationshipTypes::BELONGS_TO:
422 17
                $builder = $this->addBelongsToFiltersAndSorts(
423 17
                    $relationshipName,
424 17
                    $filterLink,
425 17
                    $relationshipFilters,
426 17
                    $relationshipSorts
427
                );
428 17
                break;
429
430 16
            case RelationshipTypes::HAS_MANY:
431 12
                $builder = $this->addHasManyFiltersAndSorts(
432 12
                    $relationshipName,
433 12
                    $filterLink,
434 12
                    $relationshipFilters,
435 12
                    $relationshipSorts
436
                );
437 12
                break;
438
439 9
            case RelationshipTypes::BELONGS_TO_MANY:
440
            default:
441 9
                assert($relationshipType === RelationshipTypes::BELONGS_TO_MANY);
442 9
                $builder = $this->addBelongsToManyFiltersAndSorts(
443 9
                    $relationshipName,
444 9
                    $filterLink,
445 9
                    $relationshipFilters,
446 9
                    $relationshipSorts
447
                );
448 9
                break;
449
        }
450
451 28
        return $builder;
452
    }
453
454
    /**
455
     * @return string
456
     */
457 58
    private function getMainTableName(): string
458
    {
459 58
        return $this->mainTableName;
460
    }
461
462
    /**
463
     * @return ModelSchemeInfoInterface
464
     */
465 58
    private function getModelSchemes(): ModelSchemeInfoInterface
466
    {
467 58
        return $this->modelSchemes;
468
    }
469
470
    /**
471
     * @return string
472
     */
473 54
    private function getMainAlias(): string
474
    {
475 54
        return $this->mainAlias;
476
    }
477
478
    /**
479
     * @param string              $relationshipName
480
     * @param CompositeExpression $filterLink
481
     * @param iterable            $relationshipFilters
482
     * @param iterable|null       $relationshipSorts
483
     *
484
     * @return self
485
     */
486 17
    private function addBelongsToFiltersAndSorts(
487
        string $relationshipName,
488
        CompositeExpression $filterLink,
489
        iterable $relationshipFilters,
490
        ?iterable $relationshipSorts
491
    ): self {
492 17
        $foreignKey = $this->getModelSchemes()->getForeignKey($this->getModelClass(), $relationshipName);
493
        list($onePrimaryKey, $oneTable) =
494 17
            $this->getModelSchemes()->getReversePrimaryKey($this->getModelClass(), $relationshipName);
495
496 17
        $this->innerJoinOneTable(
497 17
            $this->getMainAlias(),
498 17
            $foreignKey,
499 17
            $oneTable,
500 17
            $onePrimaryKey,
501 17
            $filterLink,
502 17
            $relationshipFilters,
503 17
            $relationshipSorts
504
        );
505 17
        if ($filterLink->count() > 0) {
506 16
            $this->andWhere($filterLink);
507
        }
508
509 17
        return $this;
510
    }
511
512
    /**
513
     * @param string              $relationshipName
514
     * @param CompositeExpression $filterLink
515
     * @param iterable            $relationshipFilters
516
     * @param iterable|null       $relationshipSorts
517
     *
518
     * @return self
519
     */
520 12
    private function addHasManyFiltersAndSorts(
521
        string $relationshipName,
522
        CompositeExpression $filterLink,
523
        iterable $relationshipFilters,
524
        ?iterable $relationshipSorts
525
    ): self {
526 12
        $primaryKey = $this->getModelSchemes()->getPrimaryKey($this->getModelClass());
527
        list($manyForeignKey, $manyTable) =
528 12
            $this->getModelSchemes()->getReverseForeignKey($this->getModelClass(), $relationshipName);
529
530 12
        $this->innerJoinOneTable(
531 12
            $this->getMainAlias(),
532 12
            $primaryKey,
533 12
            $manyTable,
534 12
            $manyForeignKey,
535 12
            $filterLink,
536 12
            $relationshipFilters,
537 12
            $relationshipSorts
538
        );
539 12
        if ($filterLink->count() > 0) {
540 12
            $this->andWhere($filterLink);
541
        }
542
543 12
        return $this;
544
    }
545
546
    /**
547
     * @param string              $relationshipName
548
     * @param CompositeExpression $targetFilterLink
549
     * @param iterable            $relationshipFilters
550
     * @param iterable|null       $relationshipSorts
551
     *
552
     * @return self
553
     */
554 9
    private function addBelongsToManyFiltersAndSorts(
555
        string $relationshipName,
556
        CompositeExpression $targetFilterLink,
557
        iterable $relationshipFilters,
558
        ?iterable $relationshipSorts
559
    ): self {
560 9
        $primaryKey = $this->getModelSchemes()->getPrimaryKey($this->getModelClass());
561
        list ($intermediateTable, $intermediatePk, $intermediateFk) =
562 9
            $this->getModelSchemes()->getBelongsToManyRelationship($this->getModelClass(), $relationshipName);
563
        list($targetPrimaryKey, $targetTable) =
564 9
            $this->getModelSchemes()->getReversePrimaryKey($this->getModelClass(), $relationshipName);
565
566
        // no filters for intermediate table
567 9
        $intFilterLink = null;
568 9
        $intFilters    = null;
569 9
        $this->innerJoinTwoSequentialTables(
570 9
            $this->getMainAlias(),
571 9
            $primaryKey,
572 9
            $intermediateTable,
573 9
            $intermediatePk,
574 9
            $intermediateFk,
575 9
            $targetTable,
576 9
            $targetPrimaryKey,
577 9
            $intFilterLink,
578 9
            $intFilters,
579 9
            $targetFilterLink,
580 9
            $relationshipFilters,
581 9
            $relationshipSorts
582
        );
583 9
        if ($targetFilterLink->count() > 0) {
584 9
            $this->andWhere($targetFilterLink);
585
        }
586
587 9
        return $this;
588
    }
589
590
    /**
591
     * @param string                   $fromAlias
592
     * @param string                   $fromColumn
593
     * @param string                   $targetTable
594
     * @param string                   $targetColumn
595
     * @param CompositeExpression|null $targetFilterLink
596
     * @param iterable|null            $targetFilterParams
597
     * @param iterable|null            $relationshipSorts
598
     *
599
     * @return string
600
     */
601 28
    private function innerJoinOneTable(
602
        string $fromAlias,
603
        string $fromColumn,
604
        string $targetTable,
605
        string $targetColumn,
606
        ?CompositeExpression $targetFilterLink,
607
        ?iterable $targetFilterParams,
608
        ?iterable $relationshipSorts
609
    ): string {
610 28
        $targetAlias   = $this->createAlias($targetTable);
611 28
        $joinCondition = $this->buildColumnName($fromAlias, $fromColumn) . '=' .
612 28
            $this->buildColumnName($targetAlias, $targetColumn);
613
614 28
        $this->innerJoin(
615 28
            $this->quoteTableName($fromAlias),
616 28
            $this->quoteTableName($targetTable),
617 28
            $this->quoteTableName($targetAlias),
618 28
            $joinCondition
619
        );
620
621 28
        if ($targetFilterLink !== null && $targetFilterParams !== null) {
622 28
            foreach ($targetFilterParams as $columnName => $operationsWithArgs) {
623 27
                assert(is_string($columnName) === true);
624 27
                $fullColumnName = $this->buildColumnName($targetAlias, $columnName);
625 27
                $this->applyFilter($targetFilterLink, $fullColumnName, $operationsWithArgs);
626
            }
627
        }
628 28
        if ($relationshipSorts !== null) {
629 20
            foreach ($relationshipSorts as $columnName => $isAsc) {
630 6
                assert(is_string($columnName) === true && is_bool($isAsc) === true);
631 6
                $fullColumnName = $this->buildColumnName($targetAlias, $columnName);
632 6
                $this->addOrderBy($fullColumnName, $isAsc === true ? 'ASC' : 'DESC');
633
            }
634
        }
635
636 28
        return $targetAlias;
637
    }
638
639
    /** @noinspection PhpTooManyParametersInspection
640
     * @param string                   $fromAlias
641
     * @param string                   $fromColumn
642
     * @param string                   $intTable
643
     * @param string                   $intToFromColumn
644
     * @param string                   $intToTargetColumn
645
     * @param string                   $targetTable
646
     * @param string                   $targetColumn
647
     * @param CompositeExpression|null $intFilterLink
648
     * @param iterable|null            $intFilterParams
649
     * @param CompositeExpression|null $targetFilterLink
650
     * @param iterable|null            $targetFilterParams
651
     * @param iterable|null            $targetSortParams
652
     *
653
     * @return string
654
     *
655
     * @SuppressWarnings(PHPMD.ExcessiveParameterList)
656
     */
657 9
    private function innerJoinTwoSequentialTables(
658
        string $fromAlias,
659
        string $fromColumn,
660
        string $intTable,
661
        string $intToFromColumn,
662
        string $intToTargetColumn,
663
        string $targetTable,
664
        string $targetColumn,
665
        ?CompositeExpression $intFilterLink,
666
        ?iterable $intFilterParams,
667
        ?CompositeExpression $targetFilterLink,
668
        ?iterable $targetFilterParams,
669
        ?iterable $targetSortParams
670
    ): string {
671 9
        $intNoSorting = null;
672 9
        $intAlias     = $this->innerJoinOneTable(
673 9
            $fromAlias,
674 9
            $fromColumn,
675 9
            $intTable,
676 9
            $intToFromColumn,
677 9
            $intFilterLink,
678 9
            $intFilterParams,
679 9
            $intNoSorting
680
        );
681 9
        $targetAlias  = $this->innerJoinOneTable(
682 9
            $intAlias,
683 9
            $intToTargetColumn,
684 9
            $targetTable,
685 9
            $targetColumn,
686 9
            $targetFilterLink,
687 9
            $targetFilterParams,
688 9
            $targetSortParams
689
        );
690
691 9
        return $targetAlias;
692
    }
693
694
    /**
695
     * @param string $tableName
696
     *
697
     * @return string
698
     */
699 58
    private function createAlias(string $tableName): string
700
    {
701 58
        $alias                          = $tableName . (++$this->aliasIdCounter);
702 58
        $this->knownAliases[$tableName] = $alias;
703
704 58
        return $alias;
705
    }
706
707
    /**
708
     * @inheritdoc
709
     */
710 57
    private function quoteTableName(string $tableName): string
711
    {
712 57
        return "`$tableName`";
713
    }
714
715
    /**
716
     * @inheritdoc
717
     */
718 9
    private function quoteColumnName(string $columnName): string
719
    {
720 9
        return "`$columnName`";
721
    }
722
723
    /**
724
     * @inheritdoc
725
     */
726 58
    private function buildColumnName(string $table, string $column): string
727
    {
728 58
        return "`$table`.`$column`";
729
    }
730
731
    /**
732
     * @param string $column
733
     *
734
     * @return string
735
     */
736 1
    public function getQuotedMainTableColumn(string $column): string
737
    {
738 1
        return $this->buildColumnName($this->getMainTableName(), $column);
739
    }
740
741
    /**
742
     * @param string $column
743
     *
744
     * @return string
745
     */
746 53
    public function getQuotedMainAliasColumn(string $column): string
747
    {
748 53
        return $this->buildColumnName($this->getMainAlias(), $column);
749
    }
750
751
    /**
752
     * @param CompositeExpression $filterLink
753
     * @param string              $fullColumnName
754
     * @param iterable            $operationsWithArgs
755
     *
756
     * @return void
757
     *
758
     * @SuppressWarnings(PHPMD.CyclomaticComplexity)
759
     */
760 54
    private function applyFilter(
761
        CompositeExpression $filterLink,
762
        string $fullColumnName,
763
        iterable $operationsWithArgs
764
    ): void {
765 54
        foreach ($operationsWithArgs as $operation => $arguments) {
766 54
            assert(is_int($operation));
767 54
            assert(
768 54
                is_array($arguments) || $arguments instanceof Generator,
769 54
                "Filter argument(s) for $fullColumnName must be iterable (an array or Generator)."
770
            );
771
            switch ($operation) {
772 54
                case FilterParameterInterface::OPERATION_EQUALS:
773 45
                    $expression = $this->expr()->eq($fullColumnName, $this->createSingleNamedParameter($arguments));
0 ignored issues
show
Documentation introduced by
$arguments is of type array|object<Generator>, but the function expects a object<Limoncello\Flute\Adapters\iterable>.

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

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

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

function acceptsInteger($int) { }

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

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
774 44
                    break;
775 19
                case FilterParameterInterface::OPERATION_NOT_EQUALS:
776 1
                    $expression = $this->expr()->neq($fullColumnName, $this->createSingleNamedParameter($arguments));
0 ignored issues
show
Documentation introduced by
$arguments is of type array|object<Generator>, but the function expects a object<Limoncello\Flute\Adapters\iterable>.

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

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

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

function acceptsInteger($int) { }

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

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
777 1
                    break;
778 19
                case FilterParameterInterface::OPERATION_LESS_THAN:
779 6
                    $expression = $this->expr()->lt($fullColumnName, $this->createSingleNamedParameter($arguments));
0 ignored issues
show
Documentation introduced by
$arguments is of type array|object<Generator>, but the function expects a object<Limoncello\Flute\Adapters\iterable>.

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

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

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

function acceptsInteger($int) { }

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

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
780 6
                    break;
781 19
                case FilterParameterInterface::OPERATION_LESS_OR_EQUALS:
782 7
                    $expression = $this->expr()->lte($fullColumnName, $this->createSingleNamedParameter($arguments));
0 ignored issues
show
Documentation introduced by
$arguments is of type array|object<Generator>, but the function expects a object<Limoncello\Flute\Adapters\iterable>.

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

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

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

function acceptsInteger($int) { }

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

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
783 7
                    break;
784 18
                case FilterParameterInterface::OPERATION_GREATER_THAN:
785 2
                    $expression = $this->expr()->gt($fullColumnName, $this->createSingleNamedParameter($arguments));
0 ignored issues
show
Documentation introduced by
$arguments is of type array|object<Generator>, but the function expects a object<Limoncello\Flute\Adapters\iterable>.

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

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

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

function acceptsInteger($int) { }

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

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
786 2
                    break;
787 17
                case FilterParameterInterface::OPERATION_GREATER_OR_EQUALS:
788 6
                    $expression = $this->expr()->gte($fullColumnName, $this->createSingleNamedParameter($arguments));
0 ignored issues
show
Documentation introduced by
$arguments is of type array|object<Generator>, but the function expects a object<Limoncello\Flute\Adapters\iterable>.

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

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

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

function acceptsInteger($int) { }

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

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
789 6
                    break;
790 12
                case FilterParameterInterface::OPERATION_LIKE:
791 9
                    $expression = $this->expr()->like($fullColumnName, $this->createSingleNamedParameter($arguments));
0 ignored issues
show
Documentation introduced by
$arguments is of type array|object<Generator>, but the function expects a object<Limoncello\Flute\Adapters\iterable>.

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

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

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

function acceptsInteger($int) { }

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

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
792 9
                    break;
793 6
                case FilterParameterInterface::OPERATION_NOT_LIKE:
794 1
                    $parameter  = $this->createSingleNamedParameter($arguments);
0 ignored issues
show
Documentation introduced by
$arguments is of type array|object<Generator>, but the function expects a object<Limoncello\Flute\Adapters\iterable>.

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

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

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

function acceptsInteger($int) { }

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

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
795 1
                    $expression = $this->expr()->notLike($fullColumnName, $parameter);
796 1
                    break;
797 6
                case FilterParameterInterface::OPERATION_IN:
798 6
                    $expression = $this->expr()->in($fullColumnName, $this->createNamedParameterArray($arguments));
0 ignored issues
show
Documentation introduced by
$arguments is of type array|object<Generator>, but the function expects a object<Limoncello\Flute\Adapters\iterable>.

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

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

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

function acceptsInteger($int) { }

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

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
799 6
                    break;
800 1
                case FilterParameterInterface::OPERATION_NOT_IN:
801 1
                    $expression = $this->expr()->notIn($fullColumnName, $this->createNamedParameterArray($arguments));
0 ignored issues
show
Documentation introduced by
$arguments is of type array|object<Generator>, but the function expects a object<Limoncello\Flute\Adapters\iterable>.

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

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

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

function acceptsInteger($int) { }

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

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
802 1
                    break;
803 1
                case FilterParameterInterface::OPERATION_IS_NULL:
804 1
                    $expression = $this->expr()->isNull($fullColumnName);
805 1
                    break;
806 1
                case FilterParameterInterface::OPERATION_IS_NOT_NULL:
807
                default:
808 1
                    $expression = $this->expr()->isNotNull($fullColumnName);
809 1
                    break;
810
            }
811
812 53
            $filterLink->add($expression);
813
        }
814
    }
815
816
    /**
817
     * @param iterable $arguments
818
     *
819
     * @return string
820
     */
821 52
    private function createSingleNamedParameter(iterable $arguments): string
822
    {
823 52
        foreach ($arguments as $argument) {
824 51
            $paramName = $this->createNamedParameter($this->getPdoValue($argument), $this->getPdoType($argument));
825
826 51
            return $paramName;
827
        }
828
829
        // arguments are empty
830 1
        throw new InvalidArgumentException();
831
    }
832
833
    /**
834
     * @param iterable $arguments
835
     *
836
     * @return string[]
837
     */
838 6
    private function createNamedParameterArray(iterable $arguments): array
839
    {
840 6
        $names = [];
841
842 6
        foreach ($arguments as $argument) {
843 6
            $names[] = $this->createNamedParameter($this->getPdoValue($argument), $this->getPdoType($argument));
844
        }
845
846 6
        return $names;
847
    }
848
849
    /**
850
     * @return Closure
851
     */
852 52
    private function getColumnToDatabaseMapper(): Closure
853
    {
854 52
        return $this->columnMapper;
855
    }
856
857
    /**
858
     * @param mixed $value
859
     *
860
     * @return mixed
861
     */
862 53
    private function getPdoValue($value)
863
    {
864 53
        return $value instanceof DateTimeInterface ? $this->convertDataTimeToDatabaseFormat($value) : $value;
865
    }
866
867
    /**
868
     * @param DateTimeInterface $dateTime
869
     *
870
     * @return string
871
     *
872
     * @SuppressWarnings(PHPMD.StaticAccess)
873
     */
874 1
    private function convertDataTimeToDatabaseFormat(DateTimeInterface $dateTime): string
875
    {
876 1
        if ($this->dtToDbConverter === null) {
877 1
            $type     = Type::getType(DateTimeType::DATETIME);
878 1
            $platform = $this->getConnection()->getDatabasePlatform();
879 1
            $this->dtToDbConverter = function (DateTimeInterface $dateTime) use ($type, $platform) : string {
880 1
                return $type->convertToDatabaseValue($dateTime, $platform);
881
            };
882
        }
883
884 1
        return call_user_func($this->dtToDbConverter, $dateTime);
885
    }
886
887
    /**
888
     * @param mixed $value
889
     *
890
     * @return int
891
     *
892
     * @SuppressWarnings(PHPMD.ElseExpression)
893
     */
894 53
    private function getPdoType($value): int
895
    {
896 53
        if (is_int($value) === true) {
897 40
            $type = PDO::PARAM_INT;
898 29
        } elseif (is_bool($value)) {
899 1
            $type = PDO::PARAM_BOOL;
900 28
        } elseif ($value instanceof DateTimeInterface) {
901 1
            $type = PDO::PARAM_STR;
902
        } else {
903 27
            assert(
904 27
                $value !== null,
905
                'It seems you are trying to use `null` with =, >, <, or etc operator. ' .
906 27
                'Use `is null` or `not null` instead.'
907
            );
908 27
            assert(is_string($value), "Only strings, booleans and integers are supported.");
909 27
            $type = PDO::PARAM_STR;
910
        }
911
912 53
        return $type;
913
    }
914
}
915