Passed
Pull Request — master (#303)
by
unknown
03:33
created

ColumnSchema   A

Complexity

Total Complexity 36

Size/Duplication

Total Lines 248
Duplicated Lines 0 %

Test Coverage

Coverage 95.18%

Importance

Changes 3
Bugs 0 Features 0
Metric Value
eloc 71
c 3
b 0
f 0
dl 0
loc 248
ccs 79
cts 83
cp 0.9518
rs 9.52
wmc 36

14 Methods

Rating   Name   Duplication   Size   Complexity  
A phpTypecastValue() 0 17 4
A getDimension() 0 3 1
A getArrayParser() 0 3 1
A getCompositeParser() 0 3 1
A phpTypecast() 0 20 4
A phpTypecastComposite() 0 30 6
A dbTypecastArray() 0 18 4
A getColumns() 0 3 1
A sequenceName() 0 3 1
A columns() 0 3 1
A dbTypecast() 0 15 5
A dimension() 0 3 1
A getSequenceName() 0 3 1
A dbTypecastValue() 0 20 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[]|null Columns metadata of the composite type.
64
     * @psalm-var array<string, ColumnSchemaInterface>|null
65
     */
66
    private array|null $columns = null;
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. This may also be an array containing the value as the first element and the PDO
76
     * type as the second element.
77
     */
78 89
    public function dbTypecast(mixed $value): mixed
79
    {
80 89
        if ($this->dimension > 0) {
81 4
            if ($value === null || $value instanceof ExpressionInterface) {
82 2
                return $value;
83
            }
84
85 3
            if ($this->getType() === Schema::TYPE_COMPOSITE) {
86 1
                $value = $this->dbTypecastArray($value, $this->dimension);
87
            }
88
89 3
            return new ArrayExpression($value, $this->getDbType(), $this->dimension);
90
        }
91
92 88
        return $this->dbTypecastValue($value);
93
    }
94
95
    /**
96
     * @param int $dimension Should be more than 0
97
     */
98 1
    private function dbTypecastArray(mixed $value, int $dimension): array|null
99
    {
100 1
        $items = [];
101
102 1
        if (!is_iterable($value)) {
103
            return $items;
104
        }
105
106
        /** @psalm-var mixed $val */
107 1
        foreach ($value as $val) {
108 1
            if ($dimension > 1) {
109
                $items[] = $this->dbTypecastArray($val, $dimension - 1);
110
            } else {
111 1
                $items[] = $this->dbTypecastValue($val);
112
            }
113
        }
114
115 1
        return $items;
116
    }
117
118 88
    private function dbTypecastValue(mixed $value): mixed
119
    {
120 88
        if ($value === null || $value instanceof ExpressionInterface) {
121 30
            return $value;
122
        }
123
124 83
        return match ($this->getType()) {
125 83
            SchemaInterface::TYPE_JSON => new JsonExpression($value, $this->getDbType()),
126
127 83
            SchemaInterface::TYPE_BINARY => is_string($value)
128 3
                ? new Param($value, PDO::PARAM_LOB) // explicitly setup PDO param type for binary column
129 3
                : $this->typecast($value),
130
131 83
            Schema::TYPE_BIT => is_int($value)
132 1
                ? str_pad(decbin($value), (int) $this->getSize(), '0', STR_PAD_LEFT)
133 1
                : $this->typecast($value),
134
135 83
            Schema::TYPE_COMPOSITE => new CompositeExpression($value, $this->getDbType(), $this->columns),
136
137 83
            default => $this->typecast($value),
138 83
        };
139
    }
140
141
    /**
142
     * Converts the input value according to {@see phpType} after retrieval from the database.
143
     *
144
     * If the value is null or an {@see Expression}, it won't be converted.
145
     *
146
     * @param mixed $value The input value
147
     *
148
     * @throws JsonException
149
     *
150
     * @return mixed The converted value
151
     */
152 97
    public function phpTypecast(mixed $value): mixed
153
    {
154 97
        if ($this->dimension > 0) {
155 12
            if (is_string($value)) {
156 12
                $value = $this->getArrayParser()->parse($value);
157
            }
158
159 12
            if (is_array($value)) {
160 12
                array_walk_recursive($value, function (string|null &$val) {
161
                    /** @psalm-var mixed $val */
162 12
                    $val = $this->phpTypecastValue($val);
163 12
                });
164
            } else {
165 1
                return null;
166
            }
167
168 12
            return $value;
169
        }
170
171 91
        return $this->phpTypecastValue($value);
172
    }
173
174
    /**
175
     * Casts $value after retrieving from the DBMS to PHP representation.
176
     *
177
     * @throws JsonException
178
     */
179 97
    protected function phpTypecastValue(mixed $value): mixed
180
    {
181 97
        if ($value === null) {
182 11
            return null;
183
        }
184
185 97
        return match ($this->getType()) {
186 97
            Schema::TYPE_BIT => is_string($value) ? bindec($value) : $value,
187
188 97
            SchemaInterface::TYPE_BOOLEAN => $value && $value !== 'f',
189
190 97
            SchemaInterface::TYPE_JSON
191 97
                => json_decode((string) $value, true, 512, JSON_THROW_ON_ERROR),
192
193 97
            Schema::TYPE_COMPOSITE => $this->phpTypecastComposite($value),
194
195 97
            default => parent::phpTypecast($value),
196 97
        };
197
    }
198
199 5
    private function phpTypecastComposite(mixed $value): array|null
200
    {
201 5
        if ($this->columns === null) {
202
            return null;
203
        }
204
205 5
        if (is_string($value)) {
206 5
            $value = $this->getCompositeParser()->parse($value);
207
        }
208
209 5
        if (!is_iterable($value)) {
210
            return null;
211
        }
212
213 5
        $fields = [];
214 5
        $columnNames = array_keys($this->columns);
215
216
        /**
217
         * @psalm-var int|string $key
218
         * @psalm-var mixed $val
219
         */
220 5
        foreach ($value as $key => $val) {
221 5
            $columnName = $columnNames[$key] ?? $key;
222
223 5
            if (isset($this->columns[$columnName])) {
224 5
                $fields[$columnName] = $this->columns[$columnName]->phpTypecast($val);
225
            }
226
        }
227
228 5
        return $fields;
229
    }
230
231
    /**
232
     * Creates instance of ArrayParser.
233
     */
234 12
    protected function getArrayParser(): ArrayParser
235
    {
236 12
        return new ArrayParser();
237
    }
238
239
    /**
240
     * @return int Get the dimension of the array.
241
     *
242
     * Defaults to 0, means this column isn't an array.
243
     */
244 6
    public function getDimension(): int
245
    {
246 6
        return $this->dimension;
247
    }
248
249
    /**
250
     * @return string|null name of an associated sequence if column is auto incremental.
251
     */
252 99
    public function getSequenceName(): string|null
253
    {
254 99
        return $this->sequenceName;
255
    }
256
257
    /**
258
     * Set dimension of an array.
259
     *
260
     * Defaults to 0, means this column isn't an array.
261
     */
262 160
    public function dimension(int $dimension): void
263
    {
264 160
        $this->dimension = $dimension;
265
    }
266
267
    /**
268
     * Set the name of an associated sequence if a column is auto incremental.
269
     */
270 84
    public function sequenceName(string|null $sequenceName): void
271
    {
272 84
        $this->sequenceName = $sequenceName;
273
    }
274
275
    /**
276
     * @param ColumnSchemaInterface[]|null $columns The columns metadata of the composite type.
277
     * @psalm-param array<string, ColumnSchemaInterface>|null $columns
278
     */
279 5
    public function columns(array|null $columns): void
280
    {
281 5
        $this->columns = $columns;
282
    }
283
284
    /**
285
     * @return ColumnSchemaInterface[]|null Columns metadata of the composite type.
286
     */
287 5
    public function getColumns(): array|null
288
    {
289 5
        return $this->columns;
290
    }
291
292
    /**
293
     * Creates instance of CompositeParser.
294
     */
295 5
    private function getCompositeParser(): CompositeParser
296
    {
297 5
        return new CompositeParser();
298
    }
299
}
300