Completed
Push — master ( 216818...cff73b )
by Neomerx
02:06
created

ModelQueryBuilder::getMainAlias()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

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