Passed
Push — master ( 22c6d1...ab49a2 )
by Def
02:32
created

Query::getIndexBy()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 0
Metric Value
eloc 1
c 0
b 0
f 0
dl 0
loc 3
ccs 0
cts 0
cp 0
rs 10
cc 1
nc 1
nop 0
crap 2
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Yiisoft\Db\Query;
6
7
use Closure;
8
use Throwable;
9
use Yiisoft\Db\Command\CommandInterface;
10
use Yiisoft\Db\Connection\ConnectionInterface;
11
use Yiisoft\Db\Exception\Exception;
12
use Yiisoft\Db\Exception\InvalidArgumentException;
13
use Yiisoft\Db\Exception\InvalidConfigException;
14
use Yiisoft\Db\Exception\NotSupportedException;
15
use Yiisoft\Db\Expression\ExpressionInterface;
16
use Yiisoft\Db\Helper\ArrayHelper;
17
use Yiisoft\Db\QueryBuilder\QueryBuilderInterface;
18
19
use function array_merge;
20
use function count;
21
use function is_array;
22
use function is_int;
23
use function is_string;
24
use function key;
25
use function preg_match;
26
use function preg_split;
27
use function reset;
28
use function str_contains;
29
use function strcasecmp;
30
use function strlen;
31
use function substr;
32
use function trim;
33
34
/**
35
 * Query represents a SELECT SQL statement in a way that is independent of DBMS.
36
 *
37
 * Query provides a set of methods to facilitate the specification of different clauses in a SELECT statement. These
38
 * methods can be chained together.
39
 *
40
 * By calling {@see createCommand()}, we can get a {@see Command} instance which can be further used to perform/execute
41
 * the DB query against a database.
42
 *
43
 * For example,
44
 *
45
 * ```php
46
 * $query = new Query;
47
 * // compose the query
48
 * $query->select('id, name')
49
 *     ->from('user')
50
 *     ->limit(10);
51
 * // build and execute the query
52
 * $rows = $query->all();
53
 * // alternatively, you can create DB command and execute it
54
 * $command = $query->createCommand();
55
 * // $command->sql returns the actual SQL
56
 * $rows = $command->queryAll();
57
 * ```
58
 *
59
 * Query internally uses the {@see QueryBuilder} class to generate the SQL statement.
60
 */
61
class Query implements QueryInterface
62
{
63
    protected array $select = [];
64
    protected string|null $selectOption = null;
65
    protected bool|null $distinct = null;
66
    protected array $from = [];
67
    protected array $groupBy = [];
68
    protected array|ExpressionInterface|string|null $having = null;
69
    protected array $join = [];
70
    private array $orderBy = [];
71
    protected array $params = [];
72
    protected array $union = [];
73
    protected array $withQueries = [];
74
    private bool $emulateExecution = false;
75
    private Closure|string|null $indexBy = null;
76
    private ExpressionInterface|int|null $limit = null;
77
    private ExpressionInterface|int|null $offset = null;
78
    private array|string|ExpressionInterface|null $where = null;
79
80
    public function __construct(private ConnectionInterface $db)
81
    {
82
    }
83
84
    /**
85
     * Returns the SQL representation of Query.
86
     *
87
     * @return string
88
     */
89 1891
    public function __toString(): string
90
    {
91 1891
        return serialize($this);
92 1891
    }
93
94
    public function addGroupBy(array|string|ExpressionInterface $columns): static
95
    {
96
        if ($columns instanceof ExpressionInterface) {
0 ignored issues
show
introduced by
$columns is never a sub-type of Yiisoft\Db\Expression\ExpressionInterface.
Loading history...
97
            $columns = [$columns];
98
        } elseif (!is_array($columns)) {
0 ignored issues
show
introduced by
The condition is_array($columns) is always true.
Loading history...
99
            $columns = preg_split('/\s*,\s*/', trim($columns), -1, PREG_SPLIT_NO_EMPTY);
100
        }
101
102
        if ($this->groupBy === []) {
103 127
            $this->groupBy = $columns;
104
        } else {
105 127
            $this->groupBy = array_merge($this->groupBy, $columns);
106
        }
107 127
108
        return $this;
109 127
    }
110
111 127
    public function addOrderBy(array|string|ExpressionInterface $columns): static
112
    {
113
        $columns = $this->normalizeOrderBy($columns);
114
115
        if ($this->orderBy === []) {
116
            $this->orderBy = $columns;
117
        } else {
118
            $this->orderBy = array_merge($this->orderBy, $columns);
119
        }
120
121
        return $this;
122
    }
123
124 915
    public function addParams(array $params): static
125
    {
126 915
        if (!empty($params)) {
127
            if (empty($this->params)) {
128
                $this->params = $params;
129
            } else {
130
                /**
131
                 * @psalm-var array $params
132
                 * @psalm-var mixed $value
133
                 */
134
                foreach ($params as $name => $value) {
135
                    if (is_int($name)) {
136
                        $this->params[] = $value;
137
                    } else {
138
                        $this->params[$name] = $value;
139
                    }
140
                }
141
            }
142
        }
143
144
        return $this;
145
    }
146
147
    public function andFilterHaving(array $condition): static
148
    {
149
        $condition = $this->filterCondition($condition);
150
151 30
        if ($condition !== []) {
152
            $this->andHaving($condition);
153 30
        }
154 30
155 30
        return $this;
156 30
    }
157 30
158
    public function andFilterWhere(array $condition): static
159
    {
160
        $condition = $this->filterCondition($condition);
161
162
        if ($condition !== []) {
163
            $this->andWhere($condition);
164
        }
165
166
        return $this;
167
    }
168
169
    public function andHaving(array|string|ExpressionInterface $condition, array $params = []): static
170
    {
171
        if ($this->having === null) {
172
            $this->having = $condition;
173
        } else {
174
            $this->having = ['and', $this->having, $condition];
175
        }
176
177 10
        $this->addParams($params);
178
179 10
        return $this;
180 10
    }
181 10
182 10
    public function addSelect(array|string|ExpressionInterface $columns): static
183 10
    {
184
        if ($this->select === []) {
185
            return $this->select($columns);
186
        }
187
188
        $this->select = array_merge($this->select, $this->normalizeSelect($columns));
189
190
        return $this;
191
    }
192
193
    public function andFilterCompare(string $name, string|null $value, string $defaultOperator = '='): static
194
    {
195 311
        $operator = $defaultOperator;
196
197 311
        if (preg_match('/^(<>|>=|>|<=|<|=)/', (string) $value, $matches)) {
198 15
            $operator = $matches[1];
199
            $value = substr((string) $value, strlen($operator));
200
        }
201 301
202
        return $this->andFilterWhere([$operator, $name, $value]);
203 301
    }
204
205
    public function andWhere($condition, array $params = []): static
206
    {
207
        if ($this->where === null) {
208
            $this->where = $condition;
209
        } elseif (is_array($this->where) && isset($this->where[0]) && strcasecmp((string) $this->where[0], 'and') === 0) {
210
            $this->where[] = $condition;
211
        } else {
212
            $this->where = ['and', $this->where, $condition];
213
        }
214
215
        $this->addParams($params);
216 520
217
        return $this;
218 520
    }
219 500
220
    public function all(): array
221
    {
222 55
        return match ($this->emulateExecution) {
223
            true => [],
224 55
            false => $this->populate($this->createCommand()->queryAll()),
225 55
        };
226
    }
227
228 55
    public function average(string $q): int|float|null|string
229
    {
230
        return match ($this->emulateExecution) {
231
            true => null,
232
            false => is_numeric($avg = $this->queryScalar("AVG($q)")) ? $avg : null,
233
        };
234
    }
235
236
    public function batch(int $batchSize = 100): BatchQueryResultInterface
237
    {
238
        return $this->db->createBatchQueryResult($this)->batchSize($batchSize);
239
    }
240
241 432
    /**
242
     * @psalm-suppress MixedArrayOffset
243 432
     *
244 10
     * @throws Exception
245
     * @throws InvalidConfigException
246
     * @throws NotSupportedException
247 422
     * @throws Throwable
248
     */
249
    public function column(): array
250
    {
251
        if ($this->emulateExecution) {
252
            return [];
253
        }
254
255
        if ($this->indexBy === null) {
256
            return $this->createCommand()->queryColumn();
257
        }
258
259
        if (is_string($this->indexBy) && count($this->select) === 1) {
260 25
            if (!str_contains($this->indexBy, '.') && count($tables = $this->getTablesUsedInFrom()) > 0) {
261
                $this->select[] = key($tables) . '.' . $this->indexBy;
262 25
            } else {
263 10
                $this->select[] = $this->indexBy;
264
            }
265
        }
266 15
267
        $rows = $this->createCommand()->queryAll();
268
        $results = [];
269
        $column = null;
270
        if (is_string($this->indexBy)) {
271
            if (($dotPos = strpos($this->indexBy, '.')) === false) {
272
                $column = $this->indexBy;
273
            } else {
274
                $column = substr($this->indexBy, $dotPos + 1);
275
            }
276
        }
277
278 25
        /** @psalm-var array<array-key, array<string, string>> $rows */
279
        foreach ($rows as $row) {
280 25
            $value = reset($row);
281 10
282
            if ($this->indexBy instanceof Closure) {
283
                $results[($this->indexBy)($row)] = $value;
284 15
            } else {
285 15
                $results[$row[$column] ?? $row[$this->indexBy]] = $value;
286
            }
287
        }
288 5
289 5
        return $results;
290 5
    }
291
292
    public function count(string $q = '*'): int|string
293
    {
294
        return match ($this->emulateExecution) {
295
            true => 0,
296 5
            false => is_numeric($count = $this->queryScalar("COUNT($q)")) ? (int) $count : 0,
297 5
        };
298 5
    }
299 5
300
    public function createCommand(): CommandInterface
301 5
    {
302 5
        [$sql, $params] = $this->db->getQueryBuilder()->build($this);
303
        return $this->db->createCommand($sql, $params);
304 5
    }
305
306
    public function distinct(bool|null $value = true): static
307
    {
308 5
        $this->distinct = $value;
309
310
        return $this;
311
    }
312
313
    public function each(int $batchSize = 100): BatchQueryResultInterface
314
    {
315
        return $this->db->createBatchQueryResult($this, true)->batchSize($batchSize);
316
    }
317
318
    /**
319
     * @throws Exception|InvalidConfigException|Throwable
320
     */
321
    public function exists(): bool
322 95
    {
323
        if ($this->emulateExecution) {
324 95
            return false;
325 10
        }
326
327
        $command = $this->createCommand();
328 95
        $params = $command->getParams();
329
        $command->setSql($this->db->getQueryBuilder()->selectExists($command->getSql()));
330
        $command->bindValues($params);
331
332
        return (bool) $command->queryScalar();
333
    }
334
335
    public function emulateExecution(bool $value = true): static
336
    {
337
        $this->emulateExecution = $value;
338
339
        return $this;
340
    }
341 15
342
    public function filterHaving(array $condition): static
343 15
    {
344 10
        $condition = $this->filterCondition($condition);
345
346
        if ($condition !== []) {
347 5
            $this->having($condition);
348
        }
349
350
        return $this;
351
    }
352
353
    public function filterWhere(array $condition): static
354
    {
355
        $condition = $this->filterCondition($condition);
356
357
        if ($condition !== []) {
358
            $this->where($condition);
359
        }
360 15
361
        return $this;
362 15
    }
363 10
364
    public function from(array|ExpressionInterface|string $tables): static
365
    {
366 5
        if ($tables instanceof ExpressionInterface) {
0 ignored issues
show
introduced by
$tables is never a sub-type of Yiisoft\Db\Expression\ExpressionInterface.
Loading history...
367
            $tables = [$tables];
368
        }
369
370
        if (is_string($tables)) {
0 ignored issues
show
introduced by
The condition is_string($tables) is always false.
Loading history...
371
            $tables = preg_split('/\s*,\s*/', trim($tables), -1, PREG_SPLIT_NO_EMPTY);
372
        }
373
374
        $this->from = $tables;
375
376
        return $this;
377
    }
378
379 15
    public function getDistinct(): bool|null
380
    {
381 15
        return $this->distinct;
382
    }
383
384
    public function getFrom(): array
385
    {
386
        return $this->from;
387
    }
388
389
    public function getGroupBy(): array
390
    {
391
        return $this->groupBy;
392
    }
393
394 15
    public function getHaving(): string|array|ExpressionInterface|null
395
    {
396 15
        return $this->having;
397
    }
398
399
    public function getIndexBy(): Closure|string|null
400
    {
401
        return $this->indexBy;
402
    }
403
404
    public function getJoin(): array
405
    {
406 25
        return $this->join;
407
    }
408 25
409 10
    public function getLimit(): ExpressionInterface|int|null
410
    {
411
        return $this->limit;
412 15
    }
413 15
414 15
    public function getOffset(): ExpressionInterface|int|null
415 15
    {
416
        return $this->offset;
417 15
    }
418
419
    public function getOrderBy(): array
420
    {
421
        return $this->orderBy;
422
    }
423
424
    public function getParams(): array
425
    {
426
        return $this->params;
427
    }
428
429
    public function getSelect(): array
430
    {
431 95
        return $this->select;
432
    }
433 95
434 10
    public function getSelectOption(): string|null
435
    {
436
        return $this->selectOption;
437
    }
438 95
439 95
    public function getTablesUsedInFrom(): array
440 95
    {
441 95
        return $this->db->getQuoter()->cleanUpTableNames($this->from);
442 95
    }
443
444 95
    public function getUnion(): array
445 95
    {
446 95
        return $this->union;
447 95
    }
448
449 95
    public function getWhere(): array|string|ExpressionInterface|null
450 95
    {
451 95
        return $this->where;
452 95
    }
453
454
    public function getWithQueries(): array
455 95
    {
456
        return $this->withQueries;
457
    }
458
459
    public function groupBy(array|string|ExpressionInterface $columns): static
460 95
    {
461 95
        if ($columns instanceof ExpressionInterface) {
0 ignored issues
show
introduced by
$columns is never a sub-type of Yiisoft\Db\Expression\ExpressionInterface.
Loading history...
462 95
            $columns = [$columns];
463 95
        } elseif (!is_array($columns)) {
0 ignored issues
show
introduced by
The condition is_array($columns) is always true.
Loading history...
464
            $columns = preg_split('/\s*,\s*/', trim($columns), -1, PREG_SPLIT_NO_EMPTY);
465 95
        }
466
        $this->groupBy = $columns;
467
468
        return $this;
469 95
    }
470
471
    public function having(array|ExpressionInterface|string|null $condition, array $params = []): static
472 10
    {
473 10
        $this->having = $condition;
474 10
        $this->addParams($params);
475 10
476
        return $this;
477 10
    }
478
479 10
    public function indexBy(Closure|string|null $column): static
480
    {
481
        $this->indexBy = $column;
482
483
        return $this;
484
    }
485
486
    public function innerJoin(array|string $table, array|string $on = '', array $params = []): static
487
    {
488
        $this->join[] = ['INNER JOIN', $table, $on];
489
490
        return $this->addParams($params);
491
    }
492 110
493
    public function join(string $type, array|string $table, array|string $on = '', array $params = []): static
494 110
    {
495
        $this->join[] = [$type, $table, $on];
496
497
        return $this->addParams($params);
498 110
    }
499 105
500 5
    public function leftJoin(array|string $table, array|string $on = '', array $params = []): static
501
    {
502 5
        $this->join[] = ['LEFT JOIN', $table, $on];
503
504
        return $this->addParams($params);
505 5
    }
506
507
    public function limit(ExpressionInterface|int|null $limit): static
508 105
    {
509
        $this->limit = $limit;
510
511
        return $this;
512
    }
513
514
    public function max(string $q): int|float|null|string
515
    {
516
        $max = $this->queryScalar("MAX($q)");
517
518
        return is_numeric($max) ? $max : null;
519
    }
520
521
    public function min(string $q): int|float|null|string
522 260
    {
523
        $min = $this->queryScalar("MIN($q)");
524 260
525 260
        return is_numeric($min) ? $min : null;
526 260
    }
527
528 225
    public function offset(ExpressionInterface|int|null $offset): static
529
    {
530
        $this->offset = $offset;
531
532
        return $this;
533
    }
534
535
    public function one(): array|object|null
536
    {
537
        return match ($this->emulateExecution) {
538
            true => null,
539
            false => $this->createCommand()->queryOne(),
540
        };
541
    }
542
543
    public function orderBy(array|string|ExpressionInterface $columns): static
544
    {
545
        $this->orderBy = $this->normalizeOrderBy($columns);
546
547
        return $this;
548
    }
549
550
    public function orFilterHaving(array $condition): static
551
    {
552
        $condition = $this->filterCondition($condition);
553
554
        if ($condition !== []) {
555
            $this->orHaving($condition);
556
        }
557
558 225
        return $this;
559 225
    }
560 15
561
    public function orFilterWhere(array $condition): static
562 220
    {
563
        $condition = $this->filterCondition($condition);
564
565
        if ($condition !== []) {
566
            $this->orWhere($condition);
567 260
        }
568 10
569 5
        return $this;
570 5
    }
571
572
    public function orHaving(array|string|ExpressionInterface $condition, array $params = []): static
573 5
    {
574 250
        if ($this->having === null) {
575 5
            $this->having = $condition;
576
        } else {
577 245
            $this->having = ['or', $this->having, $condition];
578
        }
579
580
        $this->addParams($params);
581 255
582
        return $this;
583
    }
584
585
    public function orWhere(array|string|ExpressionInterface $condition, array $params = []): static
586
    {
587
        if ($this->where === null) {
588
            $this->where = $condition;
589
        } else {
590
            $this->where = ['or', $this->where, $condition];
591 255
        }
592
593 255
        $this->addParams($params);
594 255
595 250
        return $this;
596
    }
597
598 20
    public function params(array $params): static
599
    {
600
        $this->params = $params;
601
602
        return $this;
603
    }
604
605
    /**
606
     * @psalm-suppress MixedArrayOffset
607
     */
608
    public function populate(array $rows): array
609
    {
610
        if ($this->indexBy === null) {
611
            return $rows;
612
        }
613
614
        $result = [];
615
616
        /** @psalm-var array[][] $row */
617
        foreach ($rows as $row) {
618
            $result[ArrayHelper::getValueByPath($row, $this->indexBy)] = $row;
619
        }
620
621
        return $result;
622
    }
623 796
624
    public function prepare(QueryBuilderInterface $builder): QueryInterface
625 796
    {
626 796
        return $this;
627
    }
628 796
629
    public function rightJoin(array|string $table, array|string $on = '', array $params = []): static
630
    {
631
        $this->join[] = ['RIGHT JOIN', $table, $on];
632
633
        return $this->addParams($params);
634
    }
635
636
    public function scalar(): bool|int|null|string|float
637
    {
638
        return match ($this->emulateExecution) {
639
            true => null,
640
            false => $this->createCommand()->queryScalar(),
641
        };
642
    }
643
644
    public function select(array|string|ExpressionInterface $columns, string $option = null): static
645
    {
646
        $this->select = $this->normalizeSelect($columns);
647
        $this->selectOption = $option;
648 15
649
        return $this;
650 15
    }
651
652
    public function selectOption(string|null $value): static
653
    {
654 15
        $this->selectOption = $value;
655
656
        return $this;
657
    }
658 15
659
    public function setJoin(array $value): static
660 15
    {
661
        $this->join = $value;
662
663
        return $this;
664
    }
665
666
    public function setUnion(array $value): static
667
    {
668
        $this->union = $value;
669
670 796
        return $this;
671
    }
672 796
673 5
    public function shouldEmulateExecution(): bool
674 796
    {
675 113
        return $this->emulateExecution;
676
    }
677
678 796
    public function sum(string $q): int|float|null|string
679 796
    {
680 355
        return match ($this->emulateExecution) {
681
            true => null,
682 182
            false => is_numeric($sum = $this->queryScalar("SUM($q)")) ? $sum : null,
683 182
        };
684
    }
685 348
686
    public function union(QueryInterface|string $sql, bool $all = false): static
687 343
    {
688 343
        $this->union[] = ['query' => $sql, 'all' => $all];
689 343
690
        return $this;
691
    }
692 26
693 26
    public function where(array|string|ExpressionInterface|null $condition, array $params = []): static
694
    {
695 342
        $this->where = $condition;
696
        $this->addParams($params);
697 286
698 286
        return $this;
699
    }
700
701
    public function withQuery(QueryInterface|string $query, string $alias, bool $recursive = false): static
702 106
    {
703
        $this->withQueries[] = ['query' => $query, 'alias' => $alias, 'recursive' => $recursive];
704
705 796
        return $this;
706
    }
707
708
    public function withQueries(array $withQueries): static
709
    {
710
        $this->withQueries = $withQueries;
711
712
        return $this;
713
    }
714
715 627
    /**
716
     * Queries a scalar value by setting {@see select} first.
717 627
     *
718
     * Restores the value of select to make this query reusable.
719 627
     *
720
     * @param ExpressionInterface|string $selectExpression
721
     *
722
     * @throws Exception
723
     * @throws InvalidArgumentException
724
     * @throws InvalidConfigException
725
     * @throws NotSupportedException
726
     * @throws Throwable
727
     *
728
     * @return bool|float|int|string|null
729
     *
730
     * @psalm-suppress PossiblyUndefinedVariable
731
     */
732
    protected function queryScalar(string|ExpressionInterface $selectExpression): bool|int|null|string|float
733
    {
734
        if ($this->emulateExecution) {
735
            return null;
736
        }
737
738
        if (
739
            !$this->distinct
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->distinct of type boolean|null is loosely compared to false; this is ambiguous if the boolean can be false. You might want to explicitly use !== null instead.

If an expression can have both false, and null as possible values. It is generally a good practice to always use strict comparison to clearly distinguish between those two values.

$a = canBeFalseAndNull();

// Instead of
if ( ! $a) { }

// Better use one of the explicit versions:
if ($a !== null) { }
if ($a !== false) { }
if ($a !== null && $a !== false) { }
Loading history...
740
            && empty($this->groupBy)
741
            && empty($this->having)
742
            && empty($this->union)
743
            && empty($this->with)
744
        ) {
745
            $select = $this->select;
746
            $order = $this->orderBy;
747
            $limit = $this->limit;
748
            $offset = $this->offset;
749
750
            $this->select = [$selectExpression];
751
            $this->orderBy = [];
752
            $this->limit = null;
753
            $this->offset = null;
754
755
            $command = $this->createCommand();
756
757
            $this->select = $select;
758
            $this->orderBy = $order;
759 970
            $this->limit = $limit;
760
            $this->offset = $offset;
761 970
762 16
            return $command->queryScalar();
763
        }
764 970
765 263
        $query = (new self($this->db))->select($selectExpression)->from(['c' => $this]);
766
        [$sql, $params] = $this->db->getQueryBuilder()->build($query);
767 970
        $command = $this->db->createCommand($sql, $params);
768
769 970
        return $command->queryScalar();
770
    }
771
772
    /**
773
     * Removes {@see isEmpty()|empty operands} from the given query condition.
774
     *
775
     * @param array|string $condition the original condition
776
     *
777
     * @return array|string the condition with {@see isEmpty()|empty operands} removed.
778
     */
779
    private function filterCondition(array|string $condition): array|string
780
    {
781
        if (!is_array($condition)) {
0 ignored issues
show
introduced by
The condition is_array($condition) is always true.
Loading history...
782
            return $condition;
783
        }
784
785
        if (!isset($condition[0])) {
786
            /** hash format: 'column1' => 'value1', 'column2' => 'value2', ... */
787
            /** @var mixed $value */
788
            foreach ($condition as $name => $value) {
789
                if ($this->isEmpty($value)) {
790
                    unset($condition[$name]);
791 1375
                }
792
            }
793 1375
794 1375
            return $condition;
795
        }
796 1375
797
        /** operator format: operator, operand 1, operand 2, ... */
798
        /** @var string */
799
        $operator = array_shift($condition);
800
801
        switch (strtoupper($operator)) {
802
            case 'NOT':
803
            case 'AND':
804
            case 'OR':
805
                /** @psalm-var array<array-key, array|string> $condition */
806
                foreach ($condition as $i => $operand) {
807
                    $subCondition = $this->filterCondition($operand);
808
                    if ($this->isEmpty($subCondition)) {
809
                        unset($condition[$i]);
810
                    } else {
811
                        $condition[$i] = $subCondition;
812
                    }
813 348
                }
814
815 348
                if (empty($condition)) {
816 323
                    return [];
817 131
                }
818 6
819
                break;
820 131
            case 'BETWEEN':
821
            case 'NOT BETWEEN':
822
                if (array_key_exists(1, $condition) && array_key_exists(2, $condition)) {
823 348
                    if ($this->isEmpty($condition[1]) || $this->isEmpty($condition[2])) {
824
                        return [];
825 348
                    }
826
                } else {
827
                    return [];
828
                }
829
830
                break;
831
            default:
832
                if (array_key_exists(1, $condition) && $this->isEmpty($condition[1])) {
833
                    return [];
834
                }
835
        }
836
837
        array_unshift($condition, $operator);
838
839
        return $condition;
840
    }
841
842 6
    /**
843
     * Returns a value indicating whether the give value is "empty".
844 6
     *
845
     * The value is considered "empty", if one of the following conditions is satisfied:
846
     *
847 6
     * - it is `null`,
848
     * - an empty string (`''`),
849
     * - a string containing only whitespace characters,
850 6
     * - or an empty array.
851
     *
852 6
     * @param mixed $value
853
     *
854
     * @return bool if the value is empty
855
     */
856
    private function isEmpty(mixed $value): bool
857
    {
858
        return $value === '' || $value === [] || $value === null || (is_string($value) && trim($value) === '');
859
    }
860
861
    /**
862
     * Normalizes format of ORDER BY data.
863
     *
864
     * @param array|ExpressionInterface|string $columns the columns value to normalize.
865
     *
866
     * See {@see orderBy} and {@see addOrderBy}.
867
     */
868
    private function normalizeOrderBy(array|string|ExpressionInterface $columns): array
869
    {
870
        if ($columns instanceof ExpressionInterface) {
0 ignored issues
show
introduced by
$columns is never a sub-type of Yiisoft\Db\Expression\ExpressionInterface.
Loading history...
871
            return [$columns];
872
        }
873
874
        if (is_array($columns)) {
0 ignored issues
show
introduced by
The condition is_array($columns) is always true.
Loading history...
875
            return $columns;
876
        }
877
878
        $columns = preg_split('/\s*,\s*/', trim($columns), -1, PREG_SPLIT_NO_EMPTY);
879
        $result = [];
880
881
        foreach ($columns as $column) {
882
            if (preg_match('/^(.*?)\s+(asc|desc)$/i', $column, $matches)) {
883
                $result[$matches[1]] = strcasecmp($matches[2], 'desc') ? SORT_ASC : SORT_DESC;
884 5
            } else {
885
                $result[$column] = SORT_ASC;
886 5
            }
887 5
        }
888 5
889
        return $result;
890 5
    }
891
892
    /**
893 5
     * Normalizes the SELECT columns passed to {@see select()} or {@see addSelect()}.
894
     */
895
    private function normalizeSelect(array|ExpressionInterface|string $columns): array
896
    {
897
        if ($columns instanceof ExpressionInterface) {
0 ignored issues
show
introduced by
$columns is never a sub-type of Yiisoft\Db\Expression\ExpressionInterface.
Loading history...
898
            $columns = [$columns];
899
        } elseif (!is_array($columns)) {
0 ignored issues
show
introduced by
The condition is_array($columns) is always true.
Loading history...
900
            $columns = preg_split('/\s*,\s*/', trim($columns), -1, PREG_SPLIT_NO_EMPTY);
901
        }
902
903
        $select = [];
904
905
        /** @psalm-var array<array-key, ExpressionInterface|string> $columns */
906
        foreach ($columns as $columnAlias => $columnDefinition) {
907
            if (is_string($columnAlias)) {
908
                // Already in the normalized format, good for them.
909
                $select[$columnAlias] = $columnDefinition;
910
                continue;
911
            }
912
913
            if (is_string($columnDefinition)) {
914
                if (
915
                    preg_match('/^(.*?)(?i:\s+as\s+|\s+)([\w\-_.]+)$/', $columnDefinition, $matches) &&
916
                    !preg_match('/^\d+$/', $matches[2]) &&
917
                    !str_contains($matches[2], '.')
918
                ) {
919
                    /** Using "columnName as alias" or "columnName alias" syntax */
920
                    $select[$matches[2]] = $matches[1];
921
                    continue;
922
                }
923
                if (!str_contains($columnDefinition, '(')) {
924
                    /** Normal column name, just alias it to itself to ensure it's not selected twice */
925
                    $select[$columnDefinition] = $columnDefinition;
926 110
                    continue;
927
                }
928 110
            }
929
930 110
            // Either a string calling a function, DB expression, or sub-query
931
            /** @var string */
932
            $select[] = $columnDefinition;
933
        }
934
935
        return $select;
936
    }
937
}
938