ColumnSchema::dbTypecastValue()   A
last analyzed

Complexity

Conditions 5
Paths 2

Size

Total Lines 20
Code Lines 12

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 12
CRAP Score 5

Importance

Changes 0
Metric Value
cc 5
eloc 12
nc 2
nop 1
dl 0
loc 20
rs 9.5555
c 0
b 0
f 0
ccs 12
cts 12
cp 1
crap 5
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Yiisoft\Db\Pgsql;
6
7
use JsonException;
8
use PDO;
9
use Yiisoft\Db\Command\Param;
10
use Yiisoft\Db\Expression\ArrayExpression;
11
use Yiisoft\Db\Expression\ExpressionInterface;
12
use Yiisoft\Db\Expression\JsonExpression;
13
use Yiisoft\Db\Schema\AbstractColumnSchema;
14
use Yiisoft\Db\Schema\ColumnSchemaInterface;
15
use Yiisoft\Db\Schema\SchemaInterface;
16
17
use function array_walk_recursive;
18
use function bindec;
19
use function decbin;
20
use function is_array;
21
use function is_int;
22
use function is_string;
23
use function json_decode;
24
use function str_pad;
25
26
/**
27
 * Represents the metadata of a column in a database table for PostgreSQL Server.
28
 *
29
 * It provides information about the column's name, type, size, precision, and other details.
30
 *
31
 * It's used to store and retrieve metadata about a column in a database table and is typically used in conjunction with
32
 * the {@see TableSchema}, which represents the metadata of a database table as a whole.
33
 *
34
 * The following code shows how to use:
35
 *
36
 * ```php
37
 * use Yiisoft\Db\Pgsql\ColumnSchema;
38
 *
39
 * $column = new ColumnSchema();
40
 * $column->name('id');
41
 * $column->allowNull(false);
42
 * $column->dbType('integer');
43
 * $column->phpType('integer');
44
 * $column->type('integer');
45
 * $column->defaultValue(0);
46
 * $column->autoIncrement(true);
47
 * $column->primaryKey(true);
48
 * ```
49
 */
50
final class ColumnSchema extends AbstractColumnSchema
51
{
52
    /**
53
     * @var int The dimension of array. Defaults to 0, means this column isn't an array.
54
     */
55
    private int $dimension = 0;
56
57
    /**
58
     * @var string|null Name of an associated sequence if column is auto incremental.
59
     */
60
    private string|null $sequenceName = null;
61
62
    /**
63
     * @var ColumnSchemaInterface[] Columns metadata of the structured type.
64
     * @psalm-var array<string, ColumnSchemaInterface>
65
     */
66
    private array $columns = [];
67
68
    /**
69
     * Converts the input value according to {@see type} and {@see dbType} for use in a db query.
70
     *
71
     * If the value is null or an {@see Expression}, it won't be converted.
72
     *
73
     * @param mixed $value input value
74
     *
75
     * @return mixed Converted value.
76
     */
77
    public function dbTypecast(mixed $value): mixed
78
    {
79 104
        if ($this->dimension > 0) {
80
            if ($value === null || $value instanceof ExpressionInterface) {
81 104
                return $value;
82 4
            }
83 2
84
            if ($this->getType() === Schema::TYPE_STRUCTURED) {
85
                $value = $this->dbTypecastArray($value, $this->dimension);
86 3
            }
87 1
88
            return new ArrayExpression($value, $this->getDbType(), $this->dimension);
89
        }
90 3
91
        return $this->dbTypecastValue($value);
92
    }
93 103
94
    /**
95
     * Recursively converts array values for use in a db query.
96
     *
97
     * @param mixed $value The array or iterable object.
98
     * @param int $dimension The array dimension. Should be more than 0.
99
     *
100
     * @return array|null Converted values.
101
     */
102
    private function dbTypecastArray(mixed $value, int $dimension): array|null
103
    {
104 1
        if ($value === null) {
105
            return null;
106 1
        }
107 1
108
        if (!is_iterable($value)) {
109
            return [];
110 1
        }
111 1
112
        $items = [];
113
114 1
        if ($dimension > 1) {
115
            foreach ($value as $val) {
116 1
                $items[] = $this->dbTypecastArray($val, $dimension - 1);
117 1
            }
118 1
        } else {
119
            foreach ($value as $val) {
120
                $items[] = $this->dbTypecastValue($val);
121 1
            }
122 1
        }
123
124
        return $items;
125
    }
126 1
127
    /**
128
     * Converts the input value for use in a db query.
129
     */
130
    private function dbTypecastValue(mixed $value): mixed
131
    {
132 103
        if ($value === null || $value instanceof ExpressionInterface) {
133
            return $value;
134 103
        }
135 38
136
        return match ($this->getType()) {
137
            SchemaInterface::TYPE_JSON => new JsonExpression($value, $this->getDbType()),
138 95
139 95
            SchemaInterface::TYPE_BINARY => is_string($value)
140
                ? new Param($value, PDO::PARAM_LOB) // explicitly setup PDO param type for binary column
141 95
                : $this->typecast($value),
142 3
143 3
            Schema::TYPE_BIT => is_int($value)
144
                ? str_pad(decbin($value), (int) $this->getSize(), '0', STR_PAD_LEFT)
145 95
                : (string) $value,
146 1
147 2
            Schema::TYPE_STRUCTURED => new StructuredExpression($value, $this->getDbType(), $this->columns),
148
149 95
            default => $this->typecast($value),
150
        };
151 95
    }
152 95
153
    /**
154
     * Converts the input value according to {@see phpType} after retrieval from the database.
155
     *
156
     * If the value is null or an {@see Expression}, it won't be converted.
157
     *
158
     * @param mixed $value The input value
159
     *
160
     * @throws JsonException
161
     *
162
     * @return mixed The converted value
163
     */
164
    public function phpTypecast(mixed $value): mixed
165
    {
166 101
        if ($this->dimension > 0) {
167
            if (is_string($value)) {
168 101
                $value = (new ArrayParser())->parse($value);
169 12
            }
170 12
171
            if (!is_array($value)) {
172
                return null;
173 12
            }
174 1
175
            array_walk_recursive($value, function (mixed &$val) {
176
                $val = $this->phpTypecastValue($val);
177 12
            });
178 12
179 12
            return $value;
180
        }
181 12
182
        return $this->phpTypecastValue($value);
183
    }
184 95
185
    /**
186
     * Casts $value after retrieving from the DBMS to PHP representation.
187
     *
188
     * @throws JsonException
189
     */
190
    private function phpTypecastValue(mixed $value): mixed
191
    {
192 101
        if ($value === null) {
193
            return null;
194 101
        }
195 11
196
        return match ($this->getType()) {
197
            Schema::TYPE_BIT => is_string($value) ? bindec($value) : $value,
198 101
199 101
            SchemaInterface::TYPE_BOOLEAN => $value && $value !== 'f',
200
201 101
            SchemaInterface::TYPE_JSON
202
                => json_decode((string) $value, true, 512, JSON_THROW_ON_ERROR),
203 101
204 101
            Schema::TYPE_STRUCTURED => $this->phpTypecastStructured($value),
205
206 101
            default => parent::phpTypecast($value),
207
        };
208 101
    }
209 101
210
    /**
211
     * Converts the input value according to the structured type after retrieval from the database.
212
     */
213
    private function phpTypecastStructured(mixed $value): array|null
214
    {
215 5
        if (is_string($value)) {
216
            $value = (new StructuredParser())->parse($value);
217 5
        }
218 5
219
        if (!is_iterable($value)) {
220
            return null;
221 5
        }
222 1
223
        $fields = [];
224
        $columnNames = array_keys($this->columns);
225 5
226 5
        /** @psalm-var int|string $columnName */
227
        foreach ($value as $columnName => $item) {
228
            $columnName = $columnNames[$columnName] ?? $columnName;
229 5
230 5
            if (isset($this->columns[$columnName])) {
231
                $item = $this->columns[$columnName]->phpTypecast($item);
232 5
            }
233 5
234
            $fields[$columnName] = $item;
235
        }
236 5
237
        return $fields;
238
    }
239 5
240
    /**
241
     * @return int Get the dimension of the array.
242
     *
243
     * Defaults to 0, means this column isn't an array.
244
     */
245
    public function getDimension(): int
246
    {
247 6
        return $this->dimension;
248
    }
249 6
250
    /**
251
     * @return string|null name of an associated sequence if column is auto incremental.
252
     */
253
    public function getSequenceName(): string|null
254
    {
255 102
        return $this->sequenceName;
256
    }
257 102
258
    /**
259
     * Set dimension of an array.
260
     *
261
     * Defaults to 0, means this column isn't an array.
262
     */
263
    public function dimension(int $dimension): void
264
    {
265 167
        $this->dimension = $dimension;
266
    }
267 167
268
    /**
269
     * Set the name of an associated sequence if a column is auto incremental.
270
     */
271
    public function sequenceName(string|null $sequenceName): void
272
    {
273 82
        $this->sequenceName = $sequenceName;
274
    }
275 82
276
    /**
277
     * Set columns of the structured type.
278
     *
279
     * @param ColumnSchemaInterface[] $columns The metadata of the structured type columns.
280
     * @psalm-param array<string, ColumnSchemaInterface> $columns
281
     */
282
    public function columns(array $columns): void
283
    {
284 5
        $this->columns = $columns;
285
    }
286 5
287
    /**
288
     * Get the metadata of the structured type columns.
289
     *
290
     * @return ColumnSchemaInterface[]
291
     */
292
    public function getColumns(): array
293
    {
294 5
        return $this->columns;
295
    }
296
}
297