Passed
Pull Request — master (#303)
by Sergei
05:41 queued 01:07
created

ColumnSchema::dbTypecastArray()   A

Complexity

Conditions 6
Paths 6

Size

Total Lines 26
Code Lines 12

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 12
CRAP Score 6

Importance

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