Passed
Pull Request — master (#343)
by Sergei
02:09
created

LikeConditionBuilder::parseOperator()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 11
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 3
eloc 6
nc 3
nop 1
dl 0
loc 11
rs 10
c 0
b 0
f 0
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Yiisoft\Db\QueryBuilder\Conditions\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\Conditions\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 strtoupper;
20
21
/**
22
 * Class LikeConditionBuilder builds objects of {@see LikeCondition}.
23
 */
24
class LikeConditionBuilder implements ExpressionBuilderInterface
25
{
26
    public function __construct(private QueryBuilderInterface $queryBuilder)
27
    {
28
    }
29
30
    /**
31
     * @var array map of chars to their replacements in LIKE conditions. By default, it's configured to escape
32
     * `%`, `_` and `\` with `\`.
33
     */
34
    protected array $escapingReplacements = [
35
        '%' => '\%',
36
        '_' => '\_',
37
        '\\' => '\\\\',
38
    ];
39
    protected ?string $escapeCharacter = null;
40
41
    /**
42
     * @throws Exception|InvalidArgumentException|InvalidConfigException|NotSupportedException
43
     */
44
    public function build(LikeConditionInterface $expression, array &$params = []): string
45
    {
46
        $operator = strtoupper($expression->getOperator());
47
        $column = $expression->getColumn();
48
        $values = $expression->getValue();
49
        $escape = $expression->getEscapingReplacements();
50
51
        if ($escape === []) {
52
            $escape = $this->escapingReplacements;
53
        }
54
55
        [$andor, $not, $operator] = $this->parseOperator($operator);
56
57
        if (!is_array($values)) {
58
            $values = [$values];
59
        }
60
61
        if (empty($values)) {
62
            return $not ? '' : '0=1';
63
        }
64
65
        if ($column instanceof ExpressionInterface) {
66
            $column = $this->queryBuilder->buildExpression($column, $params);
67
        } elseif (!str_contains($column, '(')) {
68
            $column = $this->queryBuilder->quoter()->quoteColumnName($column);
69
        }
70
71
        $escapeSql = $this->getEscapeSql();
72
        $parts = [];
73
74
        /** @psalm-var string[] $values */
75
        foreach ($values as $value) {
76
            if ($value instanceof ExpressionInterface) {
77
                $phName = $this->queryBuilder->buildExpression($value, $params);
78
            } else {
79
                $phName = $this->queryBuilder->bindParam(
80
                    $escape === null ? $value : ('%' . strtr($value, $escape) . '%'),
81
                    $params
82
                );
83
            }
84
            $parts[] = "{$column} {$operator} {$phName}{$escapeSql}";
85
        }
86
87
        return implode($andor, $parts);
88
    }
89
90
    /**
91
     * @param string $operator
92
     *
93
     * @throws InvalidArgumentException
94
     *
95
     * @return array
96
     *
97
     * @psalm-return array{0: string, 1: bool, 2: string}
98
     */
99
    protected function parseOperator(string $operator): array
100
    {
101
        if (!preg_match('/^(AND |OR |)((NOT |)I?LIKE)/', $operator, $matches)) {
102
            throw new InvalidArgumentException("Invalid operator '$operator'.");
103
        }
104
105
        $andor = ' ' . (!empty($matches[1]) ? $matches[1] : 'AND ');
106
        $not = !empty($matches[3]);
107
        $operator = $matches[2];
108
109
        return [$andor, $not, $operator];
110
    }
111
112
    /**
113
     * @return string character used to escape special characters in LIKE conditions. By default,
114
     * it's assumed to be `\`.
115
     */
116
    private function getEscapeSql(): string
117
    {
118
        if ($this->escapeCharacter !== null) {
119
            return " ESCAPE '{$this->escapeCharacter}'";
120
        }
121
122
        return '';
123
    }
124
}
125