Passed
Pull Request — master (#806)
by Sergei
02:16
created

ExpressionBuilder::getUniqueName()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 9
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 6

Importance

Changes 0
Metric Value
cc 2
eloc 4
nc 2
nop 2
dl 0
loc 9
rs 10
c 0
b 0
f 0
ccs 0
cts 0
cp 0
crap 6
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Yiisoft\Db\Expression;
6
7
use Yiisoft\Db\QueryBuilder\QueryBuilderInterface;
8
9
use function array_intersect_key;
10
use function array_merge;
11
use function preg_quote;
12
use function preg_replace;
13
14
/**
15
 * It's used to build expressions for use in database queries.
16
 *
17
 * It provides a methods {@see build()} for creating various types of expressions, such as conditions, joins, and
18
 * ordering clauses.
19 333
 *
20
 * These expressions can be used with the query builder to build complex and customizable database queries
21 333
 * {@see Expression} class.
22
 */
23 333
class ExpressionBuilder implements ExpressionBuilderInterface
24
{
25
    public function __construct(private QueryBuilderInterface|null $queryBuilder = null)
26
    {
27
    }
28
29
    public function build(Expression $expression, array &$params = []): string
30
    {
31
        $sql = $expression->__toString();
32
        $expressionParams = $expression->getParams();
33
34
        if (empty($expressionParams)) {
35
            return $sql;
36
        }
37
38
        if ($this->queryBuilder === null || isset($expressionParams[0])) {
39
            $params = array_merge($params, $expressionParams);
40
            return $sql;
41
        }
42
43
        $sql = $this->appendParams($sql, $expressionParams, $params);
44
45
        return $this->replaceParamExpressions($sql, $expressionParams, $params);
46
    }
47
48
    private function appendParams(string $sql, array &$expressionParams, array &$params): string
49
    {
50
        $nonUniqueParams = array_intersect_key($expressionParams, $params);
51
        $params += $expressionParams;
52
53
        if (empty($nonUniqueParams)) {
54
            return $sql;
55
        }
56
57
        $patterns = [];
58
        $replacements = [];
59
60
        /** @var string $name */
61
        foreach ($nonUniqueParams as $name => $value) {
62
            $patterns[] = $this->getPattern($name);
63
            $uniqueName = $this->getUniqueName($name, $params);
64
65
            $replacements[] = $uniqueName[0] !== ':' ? ":$uniqueName" : $uniqueName;
66
67
            $params[$uniqueName] = $value;
68
            $expressionParams[$uniqueName] = $value;
69
            unset($expressionParams[$name]);
70
        }
71
72
        return preg_replace($patterns, $replacements, $sql, 1);
73
    }
74
75
    private function replaceParamExpressions(string $sql, array $expressionParams, array &$params): string
76
    {
77
        $patterns = [];
78
        $replacements = [];
79
80
        /** @var string $name */
81
        foreach ($expressionParams as $name => $value) {
82
            if (!$value instanceof ExpressionInterface) {
83
                continue;
84
            }
85
86
            $patterns[] = $this->getPattern($name);
87
            /** @psalm-suppress PossiblyNullReference */
88
            $replacements[] = $this->queryBuilder->buildExpression($value, $params);
0 ignored issues
show
Bug introduced by
The method buildExpression() does not exist on null. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

88
            /** @scrutinizer ignore-call */ 
89
            $replacements[] = $this->queryBuilder->buildExpression($value, $params);

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
89
90
            unset($params[$name]);
91
        }
92
93
        if (empty($patterns)) {
94
            return $sql;
95
        }
96
97
        return preg_replace($patterns, $replacements, $sql, 1);
98
    }
99
100
    /** @psalm-return non-empty-string */
101
    private function getPattern(string $name): string
102
    {
103
        if ($name[0] !== ':') {
104
            $name = ":$name";
105
        }
106
107
        return '/' . preg_quote($name, '/') . '\b/';
108
    }
109
110
    private function getUniqueName(string $name, array $params): string
111
    {
112
        $uniqueName = $name . '_0';
113
114
        for ($i = 1; isset($params[$uniqueName]); ++$i) {
115
            $uniqueName = $name . '_' . $i;
116
        }
117
118
        return $uniqueName;
119
    }
120
}
121