Passed
Pull Request — master (#153)
by
unknown
02:46
created

ColumnSchema::phpArrayType()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

Changes 0
Metric Value
cc 1
eloc 1
c 0
b 0
f 0
nc 1
nop 1
dl 0
loc 3
ccs 2
cts 2
cp 1
crap 1
rs 10
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Yiisoft\Db\Pgsql;
6
7
use JsonException;
8
use Yiisoft\Db\Expression\ArrayExpression;
9
use Yiisoft\Db\Expression\ExpressionInterface;
10
use Yiisoft\Db\Expression\JsonExpression;
11
use Yiisoft\Db\Schema\ColumnSchema as AbstractColumnSchema;
12
use Yiisoft\Db\Schema\Schema as AbstractSchema;
13
use function array_walk_recursive;
14
use function in_array;
15
use function is_array;
16
use function is_bool;
17
use function is_float;
18
use function is_string;
19
use function json_decode;
20
use function str_ends_with;
21
use function strtolower;
22
use const JSON_THROW_ON_ERROR;
23
24
/**
25
 * The class ColumnSchema for PostgreSQL database.
26
 */
27
final class ColumnSchema extends AbstractColumnSchema
28
{
29
    /**
30
     * @var string|null real DB type with [*] on end for array
31
     */
32
    private ?string $rawDbType = null;
33
34
    /**
35
     * @var string|null php type from pgsql array values
36
     */
37
    private ?string $phpArrayType = null;
38
39
    /**
40
     * @var int the dimension of array. Defaults to 0, means this column is not an array.
41
     */
42
    private int $dimension = 0;
43
44
    /**
45
     * @var string|null name of associated sequence if column is auto-incremental.
46
     */
47
    private ?string $sequenceName = null;
48
49 106
    public function rawDbType(?string $value): void
50
    {
51 106
        $this->rawDbType = $value;
52
    }
53
54 106
    public function phpArrayType(string $type): void
55
    {
56 106
        $this->phpArrayType = $type;
57
    }
58
59
    /**
60
     * Return type of PgSql array values
61
     *
62
     * @return string|null
63
     */
64 2
    public function getPhpArrayType(): ?string
65
    {
66 2
        return $this->phpArrayType;
67
    }
68
69
    /**
70
     * Check this column is PgSql array type or not
71
     *
72
     * @return bool
73
     */
74 107
    public function isPgSqlArray(): bool
75
    {
76 107
        if ($this->rawDbType) {
77 106
            return str_ends_with($this->rawDbType, ']');
78
        }
79
80 1
        return $this->dimension > 0;
81
    }
82
83
    /**
84
     * Converts the input value according to {@see type} and {@see dbType} for use in a db query.
85
     *
86
     * If the value is null or an {@see Expression}, it will not be converted.
87
     *
88
     * @param mixed $value input value
89
     *
90
     * @return mixed converted value. This may also be an array containing the value as the first element and the PDO
91
     * type as the second element.
92
     */
93 58
    public function dbTypecast(mixed $value): mixed
94
    {
95 58
        if ($value === null) {
96 6
            return null;
97
        }
98
99 58
        if ($value instanceof ExpressionInterface) {
100 13
            return $value;
101
        }
102
103 56
        if ($this->dimension > 0) {
104 1
            return new ArrayExpression($value, $this->getDbType(), $this->dimension);
105
        }
106
107 56
        if (in_array($this->getDbType(), [AbstractSchema::TYPE_JSON, Schema::TYPE_JSONB], true)) {
108 1
            return new JsonExpression($value, $this->getDbType());
109
        }
110
111 56
        return $this->typecast($value);
112
    }
113
114
    /**
115
     * Converts the input value according to {@see phpType} after retrieval from the database.
116
     *
117
     * If the value is null or an {@see Expression}, it will not be converted.
118
     *
119
     * @param mixed $value input value
120
     *
121
     *@throws JsonException
122
     *
123
     * @return mixed converted value
124
     */
125 33
    public function phpTypecast(mixed $value): mixed
126
    {
127 33
        if ($this->isPgSqlArray()) {
128 1
            if (is_string($value) || $value === null) {
129 1
                $value = $this->getArrayParser()->parse($value);
130
            }
131
132 1
            if (is_array($value)) {
133 1
                array_walk_recursive($value, function (?string &$val) {
134
                    /** @var mixed */
135 1
                    $val = $this->phpTypecastValue($val);
136
                });
137
            } else {
138 1
                return null;
139
            }
140
141 1
            return $value;
142
        }
143
144 33
        return $this->phpTypecastValue($value);
145
    }
146
147
    /**
148
     * Cast mixed value to PHP boolean type
149
     *
150
     * @param mixed $value
151
     * @return bool|null
152
     */
153 2
    private static function castBooleanValue(mixed $value): ?bool
154
    {
155 2
        if (is_bool($value) || $value === null) {
156 1
            return $value;
157
        }
158
        /** @var mixed $value */
159 1
        $value = is_string($value) ? strtolower($value) : $value;
160
161 1
        return match ($value) {
162 1
            't', 'true' => true,
163 1
            'f', 'false' => false,
164 1
            default => (bool) $value,
165
        };
166
    }
167
168
    /**
169
     * Casts $value after retrieving from the DBMS to PHP representation.
170
     *
171
     * @param mixed $value
172
     *
173
     * @throws JsonException
174
     *
175
     * @return mixed
176
     */
177 33
    protected function phpTypecastValue(mixed $value): mixed
178
    {
179 33
        if ($value === null) {
180 1
            return null;
181
        }
182
183 33
        if ($this->isPgSqlArray()) {
184 1
            return match ($this->phpArrayType) {
185 1
                AbstractSchema::PHP_TYPE_INTEGER => is_int($value) ? $value : (int) $value,
186
                AbstractSchema::PHP_TYPE_DOUBLE => is_float($value) ? $value : (float) $value,
187
                AbstractSchema::PHP_TYPE_BOOLEAN => self::castBooleanValue($value),
188 1
                AbstractSchema::PHP_TYPE_ARRAY => json_decode((string) $value, true, 512, JSON_THROW_ON_ERROR),
189 1
                default => $value,
190
            };
191
        }
192
193 33
        return match ($this->getType()) {
194 2
            AbstractSchema::TYPE_BOOLEAN => self::castBooleanValue($value),
195 29
            AbstractSchema::TYPE_JSON => json_decode((string) $value, true, 512, JSON_THROW_ON_ERROR),
196 33
            default => parent::phpTypecast($value),
197
        };
198
    }
199
200
    /**
201
     * Creates instance of ArrayParser.
202
     *
203
     * @return ArrayParser
204
     */
205 1
    protected function getArrayParser(): ArrayParser
206
    {
207 1
        return new ArrayParser();
208
    }
209
210
    /**
211
     * @return int Get the dimension of array. Defaults to 0, means this column is not an array.
212
     */
213 1
    public function getDimension(): int
214
    {
215 1
        return $this->dimension;
216
    }
217
218
    /**
219
     * @return string|null name of associated sequence if column is auto-incremental.
220
     */
221 73
    public function getSequenceName(): ?string
222
    {
223 73
        return $this->sequenceName;
224
    }
225
226
    /**
227
     * Set dimension of array. Defaults to 0, means this column is not an array.
228
     */
229 106
    public function dimension(int $dimension): void
230
    {
231 106
        $this->dimension = $dimension;
232
    }
233
234
    /**
235
     * Set name of associated sequence if column is auto-incremental.
236
     */
237 70
    public function sequenceName(?string $sequenceName): void
238
    {
239 70
        $this->sequenceName = $sequenceName;
240
    }
241
}
242