Passed
Branch dev (d14b82)
by Wilmer
12:57
created

ArrayExpressionBuilder::buildPlaceholders()   B

Complexity

Conditions 8
Paths 7

Size

Total Lines 45
Code Lines 19

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 20
CRAP Score 8

Importance

Changes 0
Metric Value
cc 8
eloc 19
c 0
b 0
f 0
nc 7
nop 2
dl 0
loc 45
ccs 20
cts 20
cp 1
crap 8
rs 8.4444
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Yiisoft\Db\Pgsql\Builder;
6
7
use Traversable;
8
use Yiisoft\Db\Exception\Exception;
9
use Yiisoft\Db\Exception\InvalidArgumentException;
10
use Yiisoft\Db\Exception\InvalidConfigException;
11
use Yiisoft\Db\Exception\NotSupportedException;
12
use Yiisoft\Db\Expression\ArrayExpression;
13
use Yiisoft\Db\Expression\ExpressionBuilderInterface;
14
use Yiisoft\Db\Expression\ExpressionInterface;
15
use Yiisoft\Db\Expression\JsonExpression;
16
use Yiisoft\Db\Pgsql\PDO\SchemaPDOPgsql;
17
use Yiisoft\Db\Query\Query;
18
use Yiisoft\Db\Query\QueryBuilderInterface;
0 ignored issues
show
Bug introduced by
The type Yiisoft\Db\Query\QueryBuilderInterface was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
19
use Yiisoft\Db\Query\QueryInterface;
20
use Yiisoft\Db\Schema\Schema;
21
22
use function get_class;
23
use function implode;
24
use function in_array;
25
use function is_array;
26
use function str_repeat;
27
28
/**
29
 * The class ArrayExpressionBuilder builds {@see ArrayExpression} for PostgresSQL DBMS.
30
 */
31
final class ArrayExpressionBuilder implements ExpressionBuilderInterface
32
{
33 27
    public function __construct(private QueryBuilderInterface $queryBuilder)
34
    {
35
    }
36
37
    /**
38
     * Method builds the raw SQL from the $expression that will not be additionally escaped or quoted.
39
     *
40
     * @param ExpressionInterface $expression the expression to be built.
41
     * @param array $params the binding parameters.
42
     *
43
     * @throws Exception|InvalidArgumentException|InvalidConfigException|NotSupportedException
44
     *
45
     * @return string the raw SQL that will not be additionally escaped or quoted.
46
     */
47 27
    public function build(ExpressionInterface $expression, array &$params = []): string
48
    {
49
        /**
50
         *  @var ArrayExpression $expression
51
         *  @var array|mixed|QueryInterface $value
52
         */
53 27
        $value = $expression->getValue();
54
55 27
        if ($value === null) {
56 1
            return 'NULL';
57
        }
58
59 27
        if ($value instanceof Query) {
60
            /** @var string $sql */
61 1
            [$sql, $params] = $this->queryBuilder->build($value, $params);
62 1
            return $this->buildSubqueryArray($sql, $expression);
63
        }
64
65 26
        $placeholders = $this->buildPlaceholders($expression, $params);
66
67 26
        return 'ARRAY[' . implode(', ', $placeholders) . ']' . $this->getTypehint($expression);
68
    }
69
70
    /**
71
     * Builds placeholders array out of $expression values.
72
     *
73
     * @param ExpressionInterface $expression
74
     * @param array $params the binding parameters.
75
     *
76
     * @throws Exception|InvalidArgumentException|InvalidConfigException|NotSupportedException
77
     *
78
     * @return array
79
     */
80 26
    protected function buildPlaceholders(ExpressionInterface $expression, array &$params): array
81
    {
82 26
        $placeholders = [];
83
84
        /**
85
         *  @var ArrayExpression $expression
86
         *  @var mixed $value
87
         */
88 26
        $value = $expression->getValue();
89
90 26
        if (!is_array($value) && !$value instanceof Traversable) {
91 2
            return $placeholders;
92
        }
93
94 24
        if ($expression->getDimension() > 1) {
95
            /** @var ExpressionInterface|int $item */
96 3
            foreach ($value as $item) {
97 3
                $placeholders[] = $this->build($this->unnestArrayExpression($expression, $item), $params);
98
            }
99 3
            return $placeholders;
100
        }
101
102
        /** @var ExpressionInterface|int $item */
103 24
        foreach ($value as $item) {
104 23
            if ($item instanceof Query) {
105
                /**
106
                 * @var string $sql
107
                 * @var array $params
108
                 */
109 1
                [$sql, $params] = $this->queryBuilder->build($item, $params);
110 1
                $placeholders[] = $this->buildSubqueryArray($sql, $expression);
111 1
                continue;
112
            }
113
114 22
            $item = $this->typecastValue($expression, $item);
115
116 22
            if ($item instanceof ExpressionInterface) {
117 4
                $placeholders[] = $this->queryBuilder->buildExpression($item, $params);
118 4
                continue;
119
            }
120
121 19
            $placeholders[] = $this->queryBuilder->bindParam($item, $params);
122
        }
123
124 24
        return $placeholders;
125
    }
126
127
    /**
128
     * @param ArrayExpression $expression
129
     * @param array|mixed|QueryInterface $value
130
     *
131
     * @return ArrayExpression
132
     */
133 3
    private function unnestArrayExpression(ArrayExpression $expression, mixed $value): ArrayExpression
134
    {
135 3
        $expressionClass = get_class($expression);
136
137 3
        return new $expressionClass($value, $expression->getType(), $expression->getDimension() - 1);
138
    }
139
140
    /**
141
     * @param ArrayExpression $expression
142
     *
143
     * @return string the typecast expression based on {@see type}.
144
     */
145 27
    protected function getTypeHint(ArrayExpression $expression): string
146
    {
147
        /** @var string|null $type */
148 27
        $type = $expression->getType();
149
150 27
        if ($type === null) {
151 20
            return '';
152
        }
153
154 8
        $dimension = $expression->getDimension();
155 8
        $result = '::' . $type;
156 8
        $result .= str_repeat('[]', $dimension);
157
158 8
        return $result;
159
    }
160
161
    /**
162
     * Build an array expression from a subquery SQL.
163
     *
164
     * @param string $sql the subquery SQL.
165
     * @param ArrayExpression $expression
166
     *
167
     * @return string the subquery array expression.
168
     */
169 2
    protected function buildSubqueryArray(string $sql, ArrayExpression $expression): string
170
    {
171 2
        return 'ARRAY(' . $sql . ')' . $this->getTypeHint($expression);
172
    }
173
174
    /**
175
     * Casts $value to use in $expression.
176
     *
177
     * @param ArrayExpression $expression
178
     * @param array|bool|ExpressionInterface|int|string|null $value
179
     *
180
     * @return array|bool|ExpressionInterface|int|JsonExpression|string|null
181
     */
182 22
    protected function typecastValue(
183
        ArrayExpression $expression,
184
        array|bool|int|string|ExpressionInterface|null $value
185
    ): array|bool|int|string|JsonExpression|ExpressionInterface|null {
186 22
        if ($value instanceof ExpressionInterface) {
0 ignored issues
show
introduced by
$value is never a sub-type of Yiisoft\Db\Expression\ExpressionInterface.
Loading history...
187 3
            return $value;
188
        }
189
190 20
        if (in_array($expression->getType(), [Schema::TYPE_JSON, SchemaPDOPgsql::TYPE_JSONB], true)) {
191 2
            return new JsonExpression($value);
192
        }
193
194 19
        return $value;
195
    }
196
}
197