Passed
Pull Request — master (#153)
by
unknown
03:15
created

ArrayParser::parse()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 11
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 6
CRAP Score 3

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 3
eloc 5
c 1
b 0
f 0
nc 3
nop 1
dl 0
loc 11
ccs 6
cts 6
cp 1
crap 3
rs 10
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Yiisoft\Db\Pgsql;
6
7
use JsonException;
8
use function in_array;
9
use function json_decode;
10
use function strlen;
11
use const JSON_THROW_ON_ERROR;
12
13
/**
14
 * The class converts PostgresSQL array representation to PHP array.
15
 */
16
final class ArrayParser
17
{
18
    /**
19
     * @var string Character used in array
20
     */
21
    private string $delimiter = ',';
22
23
    /**
24
     * @var string|null cast array values to php type
25
     */
26
    private ?string $typeCast = null;
27
28
    /**
29
     * Convert array from PostgresSQL to PHP
30
     *
31
     * @param string|null $value string to be converted
32
     *
33
     * @return array|null
34
     */
35 2
    public function parse(?string $value): ?array
36
    {
37 2
        if ($value === null) {
38 1
            return null;
39
        }
40
41 2
        if ($value === '{}') {
42 1
            return [];
43
        }
44
45 2
        return $this->parseArray($value);
46
    }
47
48 1
    public function withTypeCast(?string $typeCast): self
49
    {
50 1
        $new = clone $this;
51 1
        $new->typeCast = $typeCast;
52
53 1
        return $new;
54
    }
55
56
    /**
57
     * Pares PgSQL array encoded in string.
58
     *
59
     * @param string $value
60
     * @param int $i parse starting position.
61
     *
62
     * @return array
63
     * @throws JsonException
64
     */
65 2
    private function parseArray(string $value, int &$i = 0): array
66
    {
67 2
        $result = [];
68 2
        $len = strlen($value);
69
70 2
        for (++$i; $i < $len; ++$i) {
71 2
            switch ($value[$i]) {
72 2
                case '{':
73 2
                    $result[] = $this->parseArray($value, $i);
74 2
                    break;
75 2
                case '}':
76 2
                    break 2;
77 2
                case $this->delimiter:
78
                    /** `{}` case */
79 2
                    if (empty($result)) {
80 1
                        $result[] = null;
81
                    }
82
83
                    /** `{,}` case */
84 2
                    if (in_array($value[$i + 1], [$this->delimiter, '}'], true)) {
85 1
                        $result[] = null;
86
                    }
87 2
                    break;
88
                default:
89
                    /** @var mixed */
90 2
                    $result[] = $this->parseString($value, $i);
91
            }
92
        }
93
94 2
        return $result;
95
    }
96
97
    /**
98
     * Parses PgSQL encoded string.
99
     *
100
     * @param string $value
101
     * @param int $i parse starting position.
102
     *
103
     * @return mixed
104
     * @throws JsonException
105
     */
106 2
    private function parseString(string $value, int &$i): mixed
107
    {
108 2
        $isQuoted = $value[$i] === '"';
109 2
        $stringEndChars = $isQuoted ? ['"'] : [$this->delimiter, '}'];
110 2
        $result = '';
111 2
        $len = strlen($value);
112
113 2
        for ($i += $isQuoted ? 1 : 0; $i < $len; ++$i) {
114 2
            if (in_array($value[$i], ['\\', '"'], true) && in_array($value[$i + 1], [$value[$i], '"'], true)) {
115 2
                ++$i;
116 2
            } elseif (in_array($value[$i], $stringEndChars, true)) {
117 2
                break;
118
            }
119
120 2
            $result .= $value[$i];
121
        }
122
123 2
        $i -= $isQuoted ? 0 : 1;
124
125 2
        if (!$isQuoted && $result === 'NULL') {
126 2
            return null;
127
        }
128
129 2
        return match ($this->typeCast) {
130 1
            Schema::PHP_TYPE_INTEGER => (int) $result,
131
            Schema::PHP_TYPE_DOUBLE => (float) $result,
132 1
            Schema::PHP_TYPE_ARRAY => json_decode($result, true, 512, JSON_THROW_ON_ERROR),
133
            Schema::PHP_TYPE_BOOLEAN => ColumnSchema::castBooleanValue($result),
134 2
            default => $result,
135
        };
136
    }
137
}
138