Passed
Pull Request — master (#540)
by Def
02:57
created

Query::populate()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 14
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 12

Importance

Changes 0
Metric Value
eloc 6
c 0
b 0
f 0
dl 0
loc 14
rs 10
ccs 0
cts 0
cp 0
cc 3
nc 3
nop 1
crap 12

1 Method

Rating   Name   Duplication   Size   Complexity  
A Query::prepare() 0 3 1
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
    protected array $orderBy = [];
71
    protected array $params = [];
72
    protected array $union = [];
73
    protected array $withQueries = [];
74
    protected Closure|string|null $indexBy = null;
75
    protected ExpressionInterface|int|null $limit = null;
76
    protected ExpressionInterface|int|null $offset = null;
77
    protected array|string|ExpressionInterface|null $where = null;
78
79
    private bool $emulateExecution = false;
80
81
    public function __construct(protected ConnectionInterface $db)
82
    {
83
    }
84
85
    /**
86
     * Returns the SQL representation of Query.
87
     *
88
     * @return string
89 1891
     */
90
    public function __toString(): string
91 1891
    {
92 1891
        return serialize($this);
93
    }
94
95
    public function addGroupBy(array|string|ExpressionInterface $columns): static
96
    {
97
        if ($columns instanceof ExpressionInterface) {
0 ignored issues
show
introduced by
$columns is never a sub-type of Yiisoft\Db\Expression\ExpressionInterface.
Loading history...
98
            $columns = [$columns];
99
        } elseif (!is_array($columns)) {
0 ignored issues
show
introduced by
The condition is_array($columns) is always true.
Loading history...
100
            $columns = preg_split('/\s*,\s*/', trim($columns), -1, PREG_SPLIT_NO_EMPTY);
101
        }
102
103 127
        if ($this->groupBy === []) {
104
            $this->groupBy = $columns;
105 127
        } else {
106
            $this->groupBy = array_merge($this->groupBy, $columns);
107 127
        }
108
109 127
        return $this;
110
    }
111 127
112
    public function addOrderBy(array|string|ExpressionInterface $columns): static
113
    {
114
        $columns = $this->normalizeOrderBy($columns);
115
116
        if ($this->orderBy === []) {
117
            $this->orderBy = $columns;
118
        } else {
119
            $this->orderBy = array_merge($this->orderBy, $columns);
120
        }
121
122
        return $this;
123
    }
124 915
125
    public function addParams(array $params): static
126 915
    {
127
        if (!empty($params)) {
128
            if (empty($this->params)) {
129
                $this->params = $params;
130
            } else {
131
                /**
132
                 * @psalm-var array $params
133
                 * @psalm-var mixed $value
134
                 */
135
                foreach ($params as $name => $value) {
136
                    if (is_int($name)) {
137
                        $this->params[] = $value;
138
                    } else {
139
                        $this->params[$name] = $value;
140
                    }
141
                }
142
            }
143
        }
144
145
        return $this;
146
    }
147
148
    public function andFilterHaving(array $condition): static
149
    {
150
        $condition = $this->filterCondition($condition);
151 30
152
        if ($condition !== []) {
153 30
            $this->andHaving($condition);
154 30
        }
155 30
156 30
        return $this;
157 30
    }
158
159
    public function andFilterWhere(array $condition): static
160
    {
161
        $condition = $this->filterCondition($condition);
162
163
        if ($condition !== []) {
164
            $this->andWhere($condition);
165
        }
166
167
        return $this;
168
    }
169
170
    public function andHaving(array|string|ExpressionInterface $condition, array $params = []): static
171
    {
172
        if ($this->having === null) {
173
            $this->having = $condition;
174
        } else {
175
            $this->having = ['and', $this->having, $condition];
176
        }
177 10
178
        $this->addParams($params);
179 10
180 10
        return $this;
181 10
    }
182 10
183 10
    public function addSelect(array|string|ExpressionInterface $columns): static
184
    {
185
        if ($this->select === []) {
186
            return $this->select($columns);
187
        }
188
189
        $this->select = array_merge($this->select, $this->normalizeSelect($columns));
190
191
        return $this;
192
    }
193
194
    public function andFilterCompare(string $name, string|null $value, string $defaultOperator = '='): static
195 311
    {
196
        $operator = $defaultOperator;
197 311
198 15
        if (preg_match('/^(<>|>=|>|<=|<|=)/', (string) $value, $matches)) {
199
            $operator = $matches[1];
200
            $value = substr((string) $value, strlen($operator));
201 301
        }
202
203 301
        return $this->andFilterWhere([$operator, $name, $value]);
204
    }
205
206
    public function andWhere($condition, array $params = []): static
207
    {
208
        if ($this->where === null) {
209
            $this->where = $condition;
210
        } elseif (is_array($this->where) && isset($this->where[0]) && strcasecmp((string) $this->where[0], 'and') === 0) {
211
            $this->where[] = $condition;
212
        } else {
213
            $this->where = ['and', $this->where, $condition];
214
        }
215
216 520
        $this->addParams($params);
217
218 520
        return $this;
219 500
    }
220
221
    public function all(): array
222 55
    {
223
        if ($this->emulateExecution === true) {
224 55
            return [];
225 55
        }
226
227
        return ArrayHelper::populate($this->createCommand()->queryAll(), $this->indexBy);
228 55
    }
229
230
    public function average(string $q): int|float|null|string
231
    {
232
        return match ($this->emulateExecution) {
233
            true => null,
234
            false => is_numeric($avg = $this->queryScalar("AVG($q)")) ? $avg : null,
235
        };
236
    }
237
238
    public function batch(int $batchSize = 100): BatchQueryResultInterface
239
    {
240
        return $this->db
241 432
            ->createBatchQueryResult($this)
242
            ->batchSize($batchSize)
243 432
            ->setPopulatedMethod(fn($rows, $indexBy = null) => ArrayHelper::populate($rows, $indexBy))
244 10
        ;
245
    }
246
247 422
    /**
248
     * @psalm-suppress MixedArrayOffset
249
     *
250
     * @throws Exception
251
     * @throws InvalidConfigException
252
     * @throws NotSupportedException
253
     * @throws Throwable
254
     */
255
    public function column(): array
256
    {
257
        if ($this->emulateExecution) {
258
            return [];
259
        }
260 25
261
        if ($this->indexBy === null) {
262 25
            return $this->createCommand()->queryColumn();
263 10
        }
264
265
        if (is_string($this->indexBy) && count($this->select) === 1) {
266 15
            if (!str_contains($this->indexBy, '.') && count($tables = $this->getTablesUsedInFrom()) > 0) {
267
                $this->select[] = key($tables) . '.' . $this->indexBy;
268
            } else {
269
                $this->select[] = $this->indexBy;
270
            }
271
        }
272
273
        $rows = $this->createCommand()->queryAll();
274
        $results = [];
275
        $column = null;
276
        if (is_string($this->indexBy)) {
277
            if (($dotPos = strpos($this->indexBy, '.')) === false) {
278 25
                $column = $this->indexBy;
279
            } else {
280 25
                $column = substr($this->indexBy, $dotPos + 1);
281 10
            }
282
        }
283
284 15
        /** @psalm-var array<array-key, array<string, string>> $rows */
285 15
        foreach ($rows as $row) {
286
            $value = reset($row);
287
288 5
            if ($this->indexBy instanceof Closure) {
289 5
                $results[($this->indexBy)($row)] = $value;
290 5
            } else {
291
                $results[$row[$column] ?? $row[$this->indexBy]] = $value;
292
            }
293
        }
294
295
        return $results;
296 5
    }
297 5
298 5
    public function count(string $q = '*'): int|string
299 5
    {
300
        return match ($this->emulateExecution) {
301 5
            true => 0,
302 5
            false => is_numeric($count = $this->queryScalar("COUNT($q)")) ? (int) $count : 0,
303
        };
304 5
    }
305
306
    public function createCommand(): CommandInterface
307
    {
308 5
        [$sql, $params] = $this->db->getQueryBuilder()->build($this);
309
        return $this->db->createCommand($sql, $params);
310
    }
311
312
    public function distinct(bool|null $value = true): static
313
    {
314
        $this->distinct = $value;
315
316
        return $this;
317
    }
318
319
    public function each(int $batchSize = 100): BatchQueryResultInterface
320
    {
321
        return $this->db
322 95
            ->createBatchQueryResult($this, true)
323
            ->batchSize($batchSize)
324 95
            ->setPopulatedMethod(fn($rows, $indexBy = null) => ArrayHelper::populate($rows, $indexBy))
325 10
        ;
326
    }
327
328 95
    /**
329
     * @throws Exception|InvalidConfigException|Throwable
330
     */
331
    public function exists(): bool
332
    {
333
        if ($this->emulateExecution) {
334
            return false;
335
        }
336
337
        $command = $this->createCommand();
338
        $params = $command->getParams();
339
        $command->setSql($this->db->getQueryBuilder()->selectExists($command->getSql()));
340
        $command->bindValues($params);
341 15
342
        return (bool) $command->queryScalar();
343 15
    }
344 10
345
    public function emulateExecution(bool $value = true): static
346
    {
347 5
        $this->emulateExecution = $value;
348
349
        return $this;
350
    }
351
352
    public function filterHaving(array $condition): static
353
    {
354
        $condition = $this->filterCondition($condition);
355
356
        if ($condition !== []) {
357
            $this->having($condition);
358
        }
359
360 15
        return $this;
361
    }
362 15
363 10
    public function filterWhere(array $condition): static
364
    {
365
        $condition = $this->filterCondition($condition);
366 5
367
        if ($condition !== []) {
368
            $this->where($condition);
369
        }
370
371
        return $this;
372
    }
373
374
    public function from(array|ExpressionInterface|string $tables): static
375
    {
376
        if ($tables instanceof ExpressionInterface) {
0 ignored issues
show
introduced by
$tables is never a sub-type of Yiisoft\Db\Expression\ExpressionInterface.
Loading history...
377
            $tables = [$tables];
378
        }
379 15
380
        if (is_string($tables)) {
0 ignored issues
show
introduced by
The condition is_string($tables) is always false.
Loading history...
381 15
            $tables = preg_split('/\s*,\s*/', trim($tables), -1, PREG_SPLIT_NO_EMPTY);
382
        }
383
384
        $this->from = $tables;
385
386
        return $this;
387
    }
388
389
    public function getDistinct(): bool|null
390
    {
391
        return $this->distinct;
392
    }
393
394 15
    public function getFrom(): array
395
    {
396 15
        return $this->from;
397
    }
398
399
    public function getGroupBy(): array
400
    {
401
        return $this->groupBy;
402
    }
403
404
    public function getHaving(): string|array|ExpressionInterface|null
405
    {
406 25
        return $this->having;
407
    }
408 25
409 10
    public function getIndexBy(): Closure|string|null
410
    {
411
        return $this->indexBy;
412 15
    }
413 15
414 15
    public function getJoin(): array
415 15
    {
416
        return $this->join;
417 15
    }
418
419
    public function getLimit(): ExpressionInterface|int|null
420
    {
421
        return $this->limit;
422
    }
423
424
    public function getOffset(): ExpressionInterface|int|null
425
    {
426
        return $this->offset;
427
    }
428
429
    public function getOrderBy(): array
430
    {
431 95
        return $this->orderBy;
432
    }
433 95
434 10
    public function getParams(): array
435
    {
436
        return $this->params;
437
    }
438 95
439 95
    public function getSelect(): array
440 95
    {
441 95
        return $this->select;
442 95
    }
443
444 95
    public function getSelectOption(): string|null
445 95
    {
446 95
        return $this->selectOption;
447 95
    }
448
449 95
    public function getTablesUsedInFrom(): array
450 95
    {
451 95
        return $this->db->getQuoter()->cleanUpTableNames($this->from);
452 95
    }
453
454
    public function getUnion(): array
455 95
    {
456
        return $this->union;
457
    }
458
459
    public function getWhere(): array|string|ExpressionInterface|null
460 95
    {
461 95
        return $this->where;
462 95
    }
463 95
464
    public function getWithQueries(): array
465 95
    {
466
        return $this->withQueries;
467
    }
468
469 95
    public function groupBy(array|string|ExpressionInterface $columns): static
470
    {
471
        if ($columns instanceof ExpressionInterface) {
0 ignored issues
show
introduced by
$columns is never a sub-type of Yiisoft\Db\Expression\ExpressionInterface.
Loading history...
472 10
            $columns = [$columns];
473 10
        } elseif (!is_array($columns)) {
0 ignored issues
show
introduced by
The condition is_array($columns) is always true.
Loading history...
474 10
            $columns = preg_split('/\s*,\s*/', trim($columns), -1, PREG_SPLIT_NO_EMPTY);
475 10
        }
476
        $this->groupBy = $columns;
477 10
478
        return $this;
479 10
    }
480
481
    public function having(array|ExpressionInterface|string|null $condition, array $params = []): static
482
    {
483
        $this->having = $condition;
484
        $this->addParams($params);
485
486
        return $this;
487
    }
488
489
    public function indexBy(Closure|string|null $column): static
490
    {
491
        $this->indexBy = $column;
492 110
493
        return $this;
494 110
    }
495
496
    public function innerJoin(array|string $table, array|string $on = '', array $params = []): static
497
    {
498 110
        $this->join[] = ['INNER JOIN', $table, $on];
499 105
500 5
        return $this->addParams($params);
501
    }
502 5
503
    public function join(string $type, array|string $table, array|string $on = '', array $params = []): static
504
    {
505 5
        $this->join[] = [$type, $table, $on];
506
507
        return $this->addParams($params);
508 105
    }
509
510
    public function leftJoin(array|string $table, array|string $on = '', array $params = []): static
511
    {
512
        $this->join[] = ['LEFT JOIN', $table, $on];
513
514
        return $this->addParams($params);
515
    }
516
517
    public function limit(ExpressionInterface|int|null $limit): static
518
    {
519
        $this->limit = $limit;
520
521
        return $this;
522 260
    }
523
524 260
    public function max(string $q): int|float|null|string
525 260
    {
526 260
        $max = $this->queryScalar("MAX($q)");
527
528 225
        return is_numeric($max) ? $max : null;
529
    }
530
531
    public function min(string $q): int|float|null|string
532
    {
533
        $min = $this->queryScalar("MIN($q)");
534
535
        return is_numeric($min) ? $min : null;
536
    }
537
538
    public function offset(ExpressionInterface|int|null $offset): static
539
    {
540
        $this->offset = $offset;
541
542
        return $this;
543
    }
544
545
    public function one(): array|null
546
    {
547
        return match ($this->emulateExecution) {
548
            true => null,
549
            false => $this->createCommand()->queryOne(),
550
        };
551
    }
552
553
    public function orderBy(array|string|ExpressionInterface $columns): static
554
    {
555
        $this->orderBy = $this->normalizeOrderBy($columns);
556
557
        return $this;
558 225
    }
559 225
560 15
    public function orFilterHaving(array $condition): static
561
    {
562 220
        $condition = $this->filterCondition($condition);
563
564
        if ($condition !== []) {
565
            $this->orHaving($condition);
566
        }
567 260
568 10
        return $this;
569 5
    }
570 5
571
    public function orFilterWhere(array $condition): static
572
    {
573 5
        $condition = $this->filterCondition($condition);
574 250
575 5
        if ($condition !== []) {
576
            $this->orWhere($condition);
577 245
        }
578
579
        return $this;
580
    }
581 255
582
    public function orHaving(array|string|ExpressionInterface $condition, array $params = []): static
583
    {
584
        if ($this->having === null) {
585
            $this->having = $condition;
586
        } else {
587
            $this->having = ['or', $this->having, $condition];
588
        }
589
590
        $this->addParams($params);
591 255
592
        return $this;
593 255
    }
594 255
595 250
    public function orWhere(array|string|ExpressionInterface $condition, array $params = []): static
596
    {
597
        if ($this->where === null) {
598 20
            $this->where = $condition;
599
        } else {
600
            $this->where = ['or', $this->where, $condition];
601
        }
602
603
        $this->addParams($params);
604
605
        return $this;
606
    }
607
608
    public function params(array $params): static
609
    {
610
        $this->params = $params;
611
612
        return $this;
613
    }
614
615
    public function prepare(QueryBuilderInterface $builder): QueryInterface
616
    {
617
        return $this;
618
    }
619
620
    public function rightJoin(array|string $table, array|string $on = '', array $params = []): static
621
    {
622
        $this->join[] = ['RIGHT JOIN', $table, $on];
623 796
624
        return $this->addParams($params);
625 796
    }
626 796
627
    public function scalar(): bool|int|null|string|float
628 796
    {
629
        return match ($this->emulateExecution) {
630
            true => null,
631
            false => $this->createCommand()->queryScalar(),
632
        };
633
    }
634
635
    public function select(array|string|ExpressionInterface $columns, string $option = null): static
636
    {
637
        $this->select = $this->normalizeSelect($columns);
638
        $this->selectOption = $option;
639
640
        return $this;
641
    }
642
643
    public function selectOption(string|null $value): static
644
    {
645
        $this->selectOption = $value;
646
647
        return $this;
648 15
    }
649
650 15
    public function setJoin(array $value): static
651
    {
652
        $this->join = $value;
653
654 15
        return $this;
655
    }
656
657
    public function setUnion(array $value): static
658 15
    {
659
        $this->union = $value;
660 15
661
        return $this;
662
    }
663
664
    public function shouldEmulateExecution(): bool
665
    {
666
        return $this->emulateExecution;
667
    }
668
669
    public function sum(string $q): int|float|null|string
670 796
    {
671
        return match ($this->emulateExecution) {
672 796
            true => null,
673 5
            false => is_numeric($sum = $this->queryScalar("SUM($q)")) ? $sum : null,
674 796
        };
675 113
    }
676
677
    public function union(QueryInterface|string $sql, bool $all = false): static
678 796
    {
679 796
        $this->union[] = ['query' => $sql, 'all' => $all];
680 355
681
        return $this;
682 182
    }
683 182
684
    public function where(array|string|ExpressionInterface|null $condition, array $params = []): static
685 348
    {
686
        $this->where = $condition;
687 343
        $this->addParams($params);
688 343
689 343
        return $this;
690
    }
691
692 26
    public function withQuery(QueryInterface|string $query, string $alias, bool $recursive = false): static
693 26
    {
694
        $this->withQueries[] = ['query' => $query, 'alias' => $alias, 'recursive' => $recursive];
695 342
696
        return $this;
697 286
    }
698 286
699
    public function withQueries(array $withQueries): static
700
    {
701
        $this->withQueries = $withQueries;
702 106
703
        return $this;
704
    }
705 796
706
    /**
707
     * Queries a scalar value by setting {@see select} first.
708
     *
709
     * Restores the value of select to make this query reusable.
710
     *
711
     * @param ExpressionInterface|string $selectExpression
712
     *
713
     * @throws Exception
714
     * @throws InvalidArgumentException
715 627
     * @throws InvalidConfigException
716
     * @throws NotSupportedException
717 627
     * @throws Throwable
718
     *
719 627
     * @return bool|float|int|string|null
720
     *
721
     * @psalm-suppress PossiblyUndefinedVariable
722
     */
723
    protected function queryScalar(string|ExpressionInterface $selectExpression): bool|int|null|string|float
724
    {
725
        if ($this->emulateExecution) {
726
            return null;
727
        }
728
729
        if (
730
            !$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...
731
            && empty($this->groupBy)
732
            && empty($this->having)
733
            && empty($this->union)
734
            && empty($this->with)
735
        ) {
736
            $select = $this->select;
737
            $order = $this->orderBy;
738
            $limit = $this->limit;
739
            $offset = $this->offset;
740
741
            $this->select = [$selectExpression];
742
            $this->orderBy = [];
743
            $this->limit = null;
744
            $this->offset = null;
745
746
            $command = $this->createCommand();
747
748
            $this->select = $select;
749
            $this->orderBy = $order;
750
            $this->limit = $limit;
751
            $this->offset = $offset;
752
753
            return $command->queryScalar();
754
        }
755
756
        $query = (new self($this->db))->select($selectExpression)->from(['c' => $this]);
757
        [$sql, $params] = $this->db->getQueryBuilder()->build($query);
758
        $command = $this->db->createCommand($sql, $params);
759 970
760
        return $command->queryScalar();
761 970
    }
762 16
763
    /**
764 970
     * Removes {@see isEmpty()|empty operands} from the given query condition.
765 263
     *
766
     * @param array|string $condition the original condition
767 970
     *
768
     * @return array|string the condition with {@see isEmpty()|empty operands} removed.
769 970
     */
770
    private function filterCondition(array|string $condition): array|string
771
    {
772
        if (!is_array($condition)) {
0 ignored issues
show
introduced by
The condition is_array($condition) is always true.
Loading history...
773
            return $condition;
774
        }
775
776
        if (!isset($condition[0])) {
777
            /** hash format: 'column1' => 'value1', 'column2' => 'value2', ... */
778
            /** @var mixed $value */
779
            foreach ($condition as $name => $value) {
780
                if ($this->isEmpty($value)) {
781
                    unset($condition[$name]);
782
                }
783
            }
784
785
            return $condition;
786
        }
787
788
        /** operator format: operator, operand 1, operand 2, ... */
789
        /** @var string */
790
        $operator = array_shift($condition);
791 1375
792
        switch (strtoupper($operator)) {
793 1375
            case 'NOT':
794 1375
            case 'AND':
795
            case 'OR':
796 1375
                /** @psalm-var array<array-key, array|string> $condition */
797
                foreach ($condition as $i => $operand) {
798
                    $subCondition = $this->filterCondition($operand);
799
                    if ($this->isEmpty($subCondition)) {
800
                        unset($condition[$i]);
801
                    } else {
802
                        $condition[$i] = $subCondition;
803
                    }
804
                }
805
806
                if (empty($condition)) {
807
                    return [];
808
                }
809
810
                break;
811
            case 'BETWEEN':
812
            case 'NOT BETWEEN':
813 348
                if (array_key_exists(1, $condition) && array_key_exists(2, $condition)) {
814
                    if ($this->isEmpty($condition[1]) || $this->isEmpty($condition[2])) {
815 348
                        return [];
816 323
                    }
817 131
                } else {
818 6
                    return [];
819
                }
820 131
821
                break;
822
            default:
823 348
                if (array_key_exists(1, $condition) && $this->isEmpty($condition[1])) {
824
                    return [];
825 348
                }
826
        }
827
828
        array_unshift($condition, $operator);
829
830
        return $condition;
831
    }
832
833
    /**
834
     * Returns a value indicating whether the give value is "empty".
835
     *
836
     * The value is considered "empty", if one of the following conditions is satisfied:
837
     *
838
     * - it is `null`,
839
     * - an empty string (`''`),
840
     * - a string containing only whitespace characters,
841
     * - or an empty array.
842 6
     *
843
     * @param mixed $value
844 6
     *
845
     * @return bool if the value is empty
846
     */
847 6
    private function isEmpty(mixed $value): bool
848
    {
849
        return $value === '' || $value === [] || $value === null || (is_string($value) && trim($value) === '');
850 6
    }
851
852 6
    /**
853
     * Normalizes format of ORDER BY data.
854
     *
855
     * @param array|ExpressionInterface|string $columns the columns value to normalize.
856
     *
857
     * See {@see orderBy} and {@see addOrderBy}.
858
     */
859
    private function normalizeOrderBy(array|string|ExpressionInterface $columns): array
860
    {
861
        if ($columns instanceof ExpressionInterface) {
0 ignored issues
show
introduced by
$columns is never a sub-type of Yiisoft\Db\Expression\ExpressionInterface.
Loading history...
862
            return [$columns];
863
        }
864
865
        if (is_array($columns)) {
0 ignored issues
show
introduced by
The condition is_array($columns) is always true.
Loading history...
866
            return $columns;
867
        }
868
869
        $columns = preg_split('/\s*,\s*/', trim($columns), -1, PREG_SPLIT_NO_EMPTY);
870
        $result = [];
871
872
        foreach ($columns as $column) {
873
            if (preg_match('/^(.*?)\s+(asc|desc)$/i', $column, $matches)) {
874
                $result[$matches[1]] = strcasecmp($matches[2], 'desc') ? SORT_ASC : SORT_DESC;
875
            } else {
876
                $result[$column] = SORT_ASC;
877
            }
878
        }
879
880
        return $result;
881
    }
882
883
    /**
884 5
     * Normalizes the SELECT columns passed to {@see select()} or {@see addSelect()}.
885
     */
886 5
    private function normalizeSelect(array|ExpressionInterface|string $columns): array
887 5
    {
888 5
        if ($columns instanceof ExpressionInterface) {
0 ignored issues
show
introduced by
$columns is never a sub-type of Yiisoft\Db\Expression\ExpressionInterface.
Loading history...
889
            $columns = [$columns];
890 5
        } elseif (!is_array($columns)) {
0 ignored issues
show
introduced by
The condition is_array($columns) is always true.
Loading history...
891
            $columns = preg_split('/\s*,\s*/', trim($columns), -1, PREG_SPLIT_NO_EMPTY);
892
        }
893 5
894
        $select = [];
895
896
        /** @psalm-var array<array-key, ExpressionInterface|string> $columns */
897
        foreach ($columns as $columnAlias => $columnDefinition) {
898
            if (is_string($columnAlias)) {
899
                // Already in the normalized format, good for them.
900
                $select[$columnAlias] = $columnDefinition;
901
                continue;
902
            }
903
904
            if (is_string($columnDefinition)) {
905
                if (
906
                    preg_match('/^(.*?)(?i:\s+as\s+|\s+)([\w\-_.]+)$/', $columnDefinition, $matches) &&
907
                    !preg_match('/^\d+$/', $matches[2]) &&
908
                    !str_contains($matches[2], '.')
909
                ) {
910
                    /** Using "columnName as alias" or "columnName alias" syntax */
911
                    $select[$matches[2]] = $matches[1];
912
                    continue;
913
                }
914
                if (!str_contains($columnDefinition, '(')) {
915
                    /** Normal column name, just alias it to itself to ensure it's not selected twice */
916
                    $select[$columnDefinition] = $columnDefinition;
917
                    continue;
918
                }
919
            }
920
921
            // Either a string calling a function, DB expression, or sub-query
922
            /** @var string */
923
            $select[] = $columnDefinition;
924
        }
925
926 110
        return $select;
927
    }
928
}
929