Test Failed
Pull Request — master (#307)
by
unknown
04:01
created

ColumnSchema::phpTypecast()   B

Complexity

Conditions 8
Paths 7

Size

Total Lines 28
Code Lines 13

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 11
CRAP Score 8

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 8
eloc 13
c 1
b 0
f 0
nc 7
nop 1
dl 0
loc 28
ccs 11
cts 11
cp 1
crap 8
rs 8.4444
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\SchemaInterface;
15
use function array_walk_recursive;
16
use function bindec;
17
use function decbin;
18
use function is_array;
19
use function is_int;
20
use function is_string;
21
use function json_decode;
22
use function str_pad;
23
24
/**
25
 * Represents the metadata of a column in a database table for PostgreSQL Server.
26
 *
27
 * It provides information about the column's name, type, size, precision, and other details.
28
 *
29
 * It's used to store and retrieve metadata about a column in a database table and is typically used in conjunction with
30
 * the {@see TableSchema}, which represents the metadata of a database table as a whole.
31
 *
32
 * The following code shows how to use:
33
 *
34
 * ```php
35
 * use Yiisoft\Db\Pgsql\ColumnSchema;
36
 *
37
 * $column = new ColumnSchema();
38
 * $column->name('id');
39
 * $column->allowNull(false);
40
 * $column->dbType('integer');
41
 * $column->phpType('integer');
42
 * $column->type('integer');
43
 * $column->defaultValue(0);
44
 * $column->autoIncrement(true);
45
 * $column->primaryKey(true);
46
 * ```
47
 */
48
final class ColumnSchema extends AbstractColumnSchema
49
{
50
    /**
51
     * @var int The dimension of array. Defaults to 0, means this column isn't an array.
52
     */
53
    private int $dimension = 0;
54
55
    /**
56
     * @var string|null Name of an associated sequence if column is auto incremental.
57
     */
58
    private string|null $sequenceName = null;
59
60
    /**
61
     * Converts the input value according to {@see type} and {@see dbType} for use in a db query.
62
     *
63
     * If the value is null or an {@see Expression}, it won't be converted.
64
     *
65
     * @param mixed $value input value
66
     *
67
     * @return mixed Converted value.
68
     */
69
    public function dbTypecast(mixed $value): mixed
70 89
    {
71
        if ($value === null || $value instanceof ExpressionInterface) {
72 89
            return $value;
73 30
        }
74
75
        if ($this->dimension > 0) {
76 84
            return new ArrayExpression($value, $this->getDbType(), $this->dimension);
77 2
        }
78
79
        return match ($this->getType()) {
80 83
            SchemaInterface::TYPE_JSON => new JsonExpression($value, $this->getDbType()),
81 83
82
            SchemaInterface::TYPE_BINARY => is_string($value)
83 83
                ? new Param($value, PDO::PARAM_LOB) // explicitly setup PDO param type for binary column
84 3
                : $this->typecast($value),
85 3
86
            Schema::TYPE_BIT => is_int($value)
87 83
                ? str_pad(decbin($value), (int) $this->getSize(), '0', STR_PAD_LEFT)
88 1
                : (string) $value,
89 2
90
            default => $this->typecast($value),
91 83
        };
92 83
    }
93
94
    /**
95
     * Converts the input value according to {@see phpType} after retrieval from the database.
96
     *
97
     * If the value is null or an {@see Expression}, it won't be converted.
98
     *
99
     * @param mixed $value The input value
100
     *
101
     * @throws JsonException
102
     *
103
     * @return mixed The converted value
104
     */
105
    public function phpTypecast(mixed $value): mixed
106 96
    {
107
        if (is_string($value) && $rangeParser = $this->getRangeParser()) {
108 96
            return $rangeParser->parse($value);
109 10
        }
110 10
111
        if (is_string($value) && $multiRangeParser = $this->getMultiRangeParser()) {
112
            return $multiRangeParser->parse($value);
113 10
        }
114 1
115
        if ($this->dimension > 0) {
116
            if (is_string($value)) {
117 10
                $value = $this->getArrayParser()->parse($value);
118
            }
119 10
120 10
            if (!is_array($value)) {
121
                return null;
122 10
            }
123
124
            array_walk_recursive($value, function (mixed &$val) {
125 90
                /** @psalm-var mixed $val */
126
                $val = $this->phpTypecastValue($val);
127
            });
128
129
            return $value;
130
        }
131
132
        return $this->phpTypecastValue($value);
133 96
    }
134
135 96
    /**
136 9
     * Casts $value after retrieving from the DBMS to PHP representation.
137
     *
138
     * @throws JsonException
139 96
     */
140 96
    private function phpTypecastValue(mixed $value): mixed
141
    {
142 96
        if ($value === null) {
143
            return null;
144 96
        }
145 96
146
        return match ($this->getType()) {
147 96
            Schema::TYPE_BIT => is_string($value) ? bindec($value) : $value,
148 96
149
            SchemaInterface::TYPE_BOOLEAN => $value && $value !== 'f',
150
151
            SchemaInterface::TYPE_JSON
152
                => json_decode((string) $value, true, 512, JSON_THROW_ON_ERROR),
153
154 10
            default => parent::phpTypecast($value),
155
        };
156 10
    }
157
158
    /**
159
     * Creates instance of ArrayParser.
160
     */
161
    private function getArrayParser(): ArrayParser
162
    {
163
        return new ArrayParser();
164 1
    }
165
166 1
    /**
167
     * @psalm-suppress PossiblyNullArgument
168
     */
169
    private function getRangeParser(): ?RangeParser
170
    {
171
        if ($this->getDbType() !== null && RangeParser::isAllowedType($this->getDbType())) {
172 97
            return new RangeParser($this->getDbType());
173
        }
174 97
175
        return null;
176
    }
177
178
    /**
179
     * @psalm-suppress PossiblyNullArgument
180
     */
181
    private function getMultiRangeParser(): ?MultiRangeParser
182 159
    {
183
        if ($this->getDbType() !== null && MultiRangeParser::isAllowedType($this->getDbType())) {
184 159
            return new MultiRangeParser($this->getDbType());
185
        }
186
187
        return null;
188
    }
189
190 82
    /**
191
     * @return int Get the dimension of the array.
192 82
     *
193
     * Defaults to 0, means this column isn't an array.
194
     */
195
    public function getDimension(): int
196
    {
197
        return $this->dimension;
198
    }
199
200
    /**
201
     * @return string|null name of an associated sequence if column is auto incremental.
202
     */
203
    public function getSequenceName(): string|null
204
    {
205
        return $this->sequenceName;
206
    }
207
208
    /**
209
     * Set dimension of an array.
210
     *
211
     * Defaults to 0, means this column isn't an array.
212
     */
213
    public function dimension(int $dimension): void
214
    {
215
        $this->dimension = $dimension;
216
    }
217
218
    /**
219
     * Set the name of an associated sequence if a column is auto incremental.
220
     */
221
    public function sequenceName(string|null $sequenceName): void
222
    {
223
        $this->sequenceName = $sequenceName;
224
    }
225
}
226