Issues (21)

src/Builder/ArrayExpressionBuilder.php (2 issues)

Labels
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Yiisoft\Db\Pgsql\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\ArrayExpression;
12
use Yiisoft\Db\Expression\ExpressionBuilderInterface;
13
use Yiisoft\Db\Expression\ExpressionInterface;
14
use Yiisoft\Db\Expression\JsonExpression;
15
use Yiisoft\Db\Query\QueryInterface;
16
use Yiisoft\Db\QueryBuilder\QueryBuilderInterface;
17
18
use function implode;
19
use function in_array;
20
use function is_iterable;
21
use function str_repeat;
22
23
/**
24
 * Builds expressions for {@see ArrayExpression} for PostgreSQL Server.
25
 */
26
final class ArrayExpressionBuilder implements ExpressionBuilderInterface
27
{
28 30
    public function __construct(private QueryBuilderInterface $queryBuilder)
29
    {
30 30
    }
31
32
    /**
33
     * The Method builds the raw SQL from the expression that won't be additionally escaped or quoted.
34
     *
35
     * @param ArrayExpression $expression The expression build.
36
     * @param array $params The binding parameters.
37
     *
38
     * @throws Exception
39
     * @throws InvalidArgumentException
40
     * @throws InvalidConfigException
41
     * @throws NotSupportedException
42
     *
43
     * @return string The raw SQL that won't be additionally escaped or quoted.
44
     */
45 30
    public function build(ExpressionInterface $expression, array &$params = []): string
46
    {
47
        /** @psalm-var array|mixed|QueryInterface $value */
48 30
        $value = $expression->getValue();
0 ignored issues
show
The method getValue() does not exist on Yiisoft\Db\Expression\ExpressionInterface. It seems like you code against a sub-type of Yiisoft\Db\Expression\ExpressionInterface such as Yiisoft\Db\Expression\ArrayExpression or Yiisoft\Db\Expression\JsonExpression or Yiisoft\Db\Pgsql\StructuredExpression or Yiisoft\Db\Command\Param or Yiisoft\Db\QueryBuilder\...lumnsConditionInterface or Yiisoft\Db\QueryBuilder\...impleConditionInterface or Yiisoft\Db\QueryBuilder\...\LikeConditionInterface. ( Ignorable by Annotation )

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

48
        /** @scrutinizer ignore-call */ 
49
        $value = $expression->getValue();
Loading history...
49
50 30
        if ($value === null) {
51 1
            return 'NULL';
52
        }
53
54 30
        if ($value instanceof QueryInterface) {
55 1
            [$sql, $params] = $this->queryBuilder->build($value, $params);
56 1
            return $this->buildSubqueryArray($sql, $expression);
57
        }
58
59
        /** @psalm-var string[] $placeholders */
60 29
        $placeholders = $this->buildPlaceholders($expression, $params);
61
62 29
        return 'ARRAY[' . implode(', ', $placeholders) . ']' . $this->getTypeHint($expression);
63
    }
64
65
    /**
66
     * Builds a placeholder array out of $expression values.
67
     *
68
     * @param array $params The binding parameters.
69
     *
70
     * @throws Exception
71
     * @throws InvalidArgumentException
72
     * @throws InvalidConfigException
73
     * @throws NotSupportedException
74
     */
75 29
    private function buildPlaceholders(ArrayExpression $expression, array &$params): array
76
    {
77 29
        $placeholders = [];
78
79
        /** @psalm-var mixed $value */
80 29
        $value = $expression->getValue();
81
82 29
        if (!is_iterable($value)) {
83 2
            return $placeholders;
84
        }
85
86 27
        if ($expression->getDimension() > 1) {
87
            /** @psalm-var mixed $item */
88 4
            foreach ($value as $item) {
89 4
                $placeholders[] = $this->build($this->unnestArrayExpression($expression, $item), $params);
90
            }
91 4
            return $placeholders;
92
        }
93
94
        /** @psalm-var ExpressionInterface|int $item */
95 27
        foreach ($value as $item) {
96 26
            if ($item instanceof QueryInterface) {
97 1
                [$sql, $params] = $this->queryBuilder->build($item, $params);
98 1
                $placeholders[] = $this->buildSubqueryArray($sql, $expression);
99 1
                continue;
100
            }
101
102 25
            $item = $this->typecastValue($expression, $item);
103
104 25
            if ($item instanceof ExpressionInterface) {
105 6
                $placeholders[] = $this->queryBuilder->buildExpression($item, $params);
106
            } else {
107 22
                $placeholders[] = $this->queryBuilder->bindParam($item, $params);
108
            }
109
        }
110
111 27
        return $placeholders;
112
    }
113
114 4
    private function unnestArrayExpression(ArrayExpression $expression, mixed $value): ArrayExpression
115
    {
116 4
        return new ArrayExpression($value, $expression->getType(), $expression->getDimension() - 1);
117
    }
118
119
    /**
120
     * @return string The typecast expression based on {@see type}.
121
     */
122 30
    private function getTypeHint(ArrayExpression $expression): string
123
    {
124 30
        $type = $expression->getType();
125
126 30
        if ($type === null) {
127 21
            return '';
128
        }
129
130 10
        $dimension = $expression->getDimension();
131
132 10
        return '::' . $type . str_repeat('[]', $dimension);
133
    }
134
135
    /**
136
     * Build an array expression from a sub-query SQL.
137
     *
138
     * @param string $sql The sub-query SQL.
139
     * @param ArrayExpression $expression The array expression.
140
     *
141
     * @return string The sub-query array expression.
142
     */
143 2
    private function buildSubqueryArray(string $sql, ArrayExpression $expression): string
144
    {
145 2
        return 'ARRAY(' . $sql . ')' . $this->getTypeHint($expression);
146
    }
147
148
    /**
149
     * @return array|bool|ExpressionInterface|float|int|JsonExpression|string|null The cast value or expression.
150
     */
151 25
    private function typecastValue(
152
        ArrayExpression $expression,
153
        array|bool|float|int|string|ExpressionInterface|null $value
154
    ): array|bool|float|int|string|JsonExpression|ExpressionInterface|null {
155 25
        if ($value instanceof ExpressionInterface) {
0 ignored issues
show
$value is never a sub-type of Yiisoft\Db\Expression\ExpressionInterface.
Loading history...
156 5
            return $value;
157
        }
158
159 23
        if (in_array($expression->getType(), ['json', 'jsonb'], true)) {
160 2
            return new JsonExpression($value);
161
        }
162
163 22
        return $value;
164
    }
165
}
166