Passed
Pull Request — master (#380)
by Wilmer
04:10 queued 01:20
created

LikeConditionBuilder::getEscapeSql()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 7
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 2
eloc 3
nc 2
nop 0
dl 0
loc 7
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(
27
        private QueryBuilderInterface $queryBuilder,
28
        private string|null $escapeSql = null
29
    ) {
30
    }
31
32
    /**
33
     * @var array map of chars to their replacements in LIKE conditions. By default, it's configured to escape
34
     * `%`, `_` and `\` with `\`.
35
     */
36
    protected array $escapingReplacements = [
37
        '%' => '\%',
38
        '_' => '\_',
39
        '\\' => '\\\\',
40
    ];
41
42
    /**
43
     * @throws Exception|InvalidArgumentException|InvalidConfigException|NotSupportedException
44
     */
45
    public function build(LikeConditionInterface $expression, array &$params = []): string
46
    {
47
        $operator = strtoupper($expression->getOperator());
48
        $column = $expression->getColumn();
49
        $values = $expression->getValue();
50
        $escape = $expression->getEscapingReplacements();
51
52
        if ($escape === []) {
53
            $escape = $this->escapingReplacements;
54
        }
55
56
        [$andor, $not, $operator] = $this->parseOperator($operator);
57
58
        if (!is_array($values)) {
59
            $values = [$values];
60
        }
61
62
        if (empty($values)) {
63
            return $not ? '' : '0=1';
64
        }
65
66
        if ($column instanceof ExpressionInterface) {
67
            $column = $this->queryBuilder->buildExpression($column, $params);
68
        } elseif (!str_contains($column, '(')) {
69
            $column = $this->queryBuilder->quoter()->quoteColumnName($column);
70
        }
71
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}{$this->escapeSql}";
85
        }
86
87
        return implode($andor, $parts);
88
    }
89
90
    /**
91
     * @throws InvalidArgumentException
92
     *
93
     * @psalm-return array{0: string, 1: bool, 2: string}
94
     */
95
    protected function parseOperator(string $operator): array
96
    {
97
        if (!preg_match('/^(AND |OR |)((NOT |)I?LIKE)/', $operator, $matches)) {
98
            throw new InvalidArgumentException("Invalid operator in like condition: \"{$operator}\"");
99
        }
100
101
        $andor = ' ' . (!empty($matches[1]) ? $matches[1] : 'AND ');
102
        $not = !empty($matches[3]);
103
        $operator = $matches[2];
104
105
        return [$andor, $not, $operator];
106
    }
107
}
108