LikeConditionBuilder   A
last analyzed

Complexity

Total Complexity 14

Size/Duplication

Total Lines 89
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
eloc 35
dl 0
loc 89
rs 10
c 0
b 0
f 0
wmc 14

3 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 4 1
A parseOperator() 0 11 3
B build() 0 43 10
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Yiisoft\Db\QueryBuilder\Condition\Builder;
6
7
use Yiisoft\Db\Exception\Exception;
8
use Yiisoft\Db\Exception\InvalidArgumentException;
9
use Yiisoft\Db\Exception\InvalidConfigException;
10
use Yiisoft\Db\Exception\NotSupportedException;
11
use Yiisoft\Db\Expression\ExpressionBuilderInterface;
12
use Yiisoft\Db\Expression\ExpressionInterface;
13
use Yiisoft\Db\QueryBuilder\Condition\Interface\LikeConditionInterface;
14
use Yiisoft\Db\QueryBuilder\QueryBuilderInterface;
15
16
use function implode;
17
use function is_array;
18
use function preg_match;
19
use function str_contains;
20
use function strtoupper;
21
use function strtr;
22
23
/**
24
 * Build an object of {@see LikeCondition} into SQL expressions.
25
 */
26
class LikeConditionBuilder implements ExpressionBuilderInterface
27
{
28
    public function __construct(
29
        private QueryBuilderInterface $queryBuilder,
30
        private string|null $escapeSql = null
31
    ) {
32
    }
33
34
    /**
35
     * @var array Map of chars to their replacements in `LIKE` conditions. By default, it's configured to escape
36
     * `%`, `_` and `\` with `\`.
37
     */
38
    protected array $escapingReplacements = [
39
        '%' => '\%',
40
        '_' => '\_',
41
        '\\' => '\\\\',
42
    ];
43
44
    /**
45
     * Build SQL for {@see LikeCondition}.
46
     *
47
     * @throws Exception
48
     * @throws InvalidArgumentException
49
     * @throws InvalidConfigException
50
     * @throws NotSupportedException
51
     */
52
    public function build(LikeConditionInterface $expression, array &$params = []): string
53
    {
54
        $operator = strtoupper($expression->getOperator());
55
        $column = $expression->getColumn();
56
        $values = $expression->getValue();
57
        $escape = $expression->getEscapingReplacements();
58
59
        if ($escape === []) {
60
            $escape = $this->escapingReplacements;
61
        }
62
63
        [$andor, $not, $operator] = $this->parseOperator($operator);
64
65
        if (!is_array($values)) {
66
            $values = [$values];
67
        }
68
69
        if (empty($values)) {
70
            return $not ? '' : '0=1';
71
        }
72
73
        if ($column instanceof ExpressionInterface) {
74
            $column = $this->queryBuilder->buildExpression($column, $params);
75
        } elseif (!str_contains($column, '(')) {
76
            $column = $this->queryBuilder->quoter()->quoteColumnName($column);
77
        }
78
79
        $parts = [];
80
81
        /** @psalm-var string[] $values */
82
        foreach ($values as $value) {
83
            if ($value instanceof ExpressionInterface) {
84
                $phName = $this->queryBuilder->buildExpression($value, $params);
85
            } else {
86
                $phName = $this->queryBuilder->bindParam(
87
                    $escape === null ? $value : ('%' . strtr($value, $escape) . '%'),
88
                    $params
89
                );
90
            }
91
            $parts[] = "$column $operator $phName$this->escapeSql";
92
        }
93
94
        return implode($andor, $parts);
95
    }
96
97
    /**
98
     * Parses operator and returns its parts.
99
     *
100
     * @throws InvalidArgumentException
101
     *
102
     * @psalm-return array{0: string, 1: bool, 2: string}
103
     */
104
    protected function parseOperator(string $operator): array
105
    {
106
        if (!preg_match('/^(AND |OR |)((NOT |)I?LIKE)/', $operator, $matches)) {
107
            throw new InvalidArgumentException("Invalid operator in like condition: \"$operator\"");
108
        }
109
110
        $andor = ' ' . (!empty($matches[1]) ? $matches[1] : 'AND ');
111
        $not = !empty($matches[3]);
112
        $operator = $matches[2];
113
114
        return [$andor, $not, $operator];
115
    }
116
}
117