Passed
Push — float-arrays ( 02f420...1b8dd5 )
by Martin
26:39 queued 11:41
created

DataStructure::formatValue()   B

Complexity

Conditions 10
Paths 13

Size

Total Lines 53
Code Lines 25

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 24
CRAP Score 10.0454

Importance

Changes 0
Metric Value
cc 10
eloc 25
c 0
b 0
f 0
nc 13
nop 1
dl 0
loc 53
ccs 24
cts 26
cp 0.9231
crap 10.0454
rs 7.6666

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
3
declare(strict_types=1);
4
5
namespace MartinGeorgiev\Utils;
6
7
/**
8
 * Util class with helpers for working with PostgreSQL data structures.
9
 *
10
 * @since 0.9
11
 *
12
 * @author Martin Georgiev <[email protected]>
13
 */
14
class DataStructure
15
{
16
    private const POSTGRESQL_EMPTY_ARRAY = '{}';
17
18
    private const POSTGRESQL_NULL_VALUE = 'null';
19
20
    /**
21
     * This method supports only single-dimensioned text arrays and
22
     * relays on the default escaping strategy in PostgreSQL (double quotes).
23
     */
24 20
    public static function transformPostgresTextArrayToPHPArray(string $postgresArray): array
25
    {
26 20
        $trimmed = \trim($postgresArray);
27
28 20
        if ($trimmed === '' || \strtolower($trimmed) === self::POSTGRESQL_NULL_VALUE) {
29
            return [];
30
        }
31
32 20
        if (\str_contains($trimmed, '},{') || \str_starts_with($trimmed, '{{')) {
33 1
            throw new \InvalidArgumentException('Only single-dimensioned arrays are supported');
34
        }
35
36 19
        if ($trimmed === self::POSTGRESQL_EMPTY_ARRAY) {
37 1
            return [];
38
        }
39
40 18
        $jsonArray = '['.\trim($trimmed, '{}').']';
41
42
        /** @var array<int, mixed>|null $decoded */
43 18
        $decoded = \json_decode($jsonArray, true, 512, JSON_BIGINT_AS_STRING);
44 18
        if ($decoded === null && \json_last_error() !== JSON_ERROR_NONE) {
45 1
            throw new \InvalidArgumentException('Invalid array format: '.\json_last_error_msg());
46
        }
47
48 17
        return \array_map(
49 17
            static fn (mixed $value): mixed => \is_string($value) ? self::unescapeString($value) : $value,
50 17
            (array) $decoded
51 17
        );
52
    }
53
54
    /**
55
     * This method supports only single-dimensioned PHP arrays.
56
     * This method relays on the default escaping strategy in PostgreSQL (double quotes).
57
     */
58 20
    public static function transformPHPArrayToPostgresTextArray(array $phpArray): string
59
    {
60 20
        if ($phpArray === []) {
61 1
            return self::POSTGRESQL_EMPTY_ARRAY;
62
        }
63
64 19
        if (\array_filter($phpArray, 'is_array')) {
65 1
            throw new \InvalidArgumentException('Only single-dimensioned arrays are supported');
66
        }
67
68
        /** @var array<int|string, string> */
69 18
        $processed = \array_map(
70 18
            static fn (mixed $value): string => self::formatValue($value),
71 18
            $phpArray
72 18
        );
73
74 18
        return '{'.\implode(',', $processed).'}';
75
    }
76
77
    /**
78
     * Formats a single value for PostgreSQL array.
79
     */
80 18
    private static function formatValue(mixed $value): string
81
    {
82
        // Handle null
83 18
        if ($value === null) {
84
            return 'NULL';
85
        }
86
87
        // Handle actual numbers
88 18
        if (\is_int($value) || \is_float($value)) {
89 6
            return (string) $value;
90
        }
91
92
        // Handle booleans
93 16
        if (\is_bool($value)) {
94 1
            return $value ? 'true' : 'false';
95
        }
96
97
        // Handle objects that implement __toString()
98 15
        if (\is_object($value)) {
99 1
            if (\method_exists($value, '__toString')) {
100 1
                $stringValue = $value->__toString();
101
            } else {
102
                // For objects without __toString, use a default representation
103
                $stringValue = $value::class;
104
            }
105
        } else {
106
            // For all other types, force string conversion
107
            // This covers strings, resources, and other types
108 14
            $stringValue = match (true) {
109 14
                \is_resource($value) => '(resource)',
110 13
                default => (string) $value // @phpstan-ignore-line
111 14
            };
112
        }
113
114 15
        \assert(\is_string($stringValue));
115
116
        // Handle empty string
117 15
        if ($stringValue === '') {
118 1
            return '""';
119
        }
120
121 14
        if (self::isNumericSimple($stringValue)) {
122 8
            return '"'.$stringValue.'"';
123
        }
124
125
        // Double the backslashes and escape quotes
126 7
        $escaped = \str_replace(
127 7
            ['\\', '"'],
128 7
            ['\\\\', '\"'],
129 7
            $stringValue
130 7
        );
131
132 7
        return '"'.$escaped.'"';
133
    }
134
135 14
    private static function isNumericSimple(string $value): bool
136
    {
137
        // Fast path for obvious numeric strings
138 14
        if ($value === '' || $value[0] === '"') {
139 1
            return false;
140
        }
141
142
        // Handle scientific notation
143 14
        $lower = \strtolower($value);
144 14
        if (\str_contains($lower, 'e')) {
145 8
            $value = \str_replace('e', '', $lower);
146
        }
147
148
        // Use built-in numeric check
149 14
        return \is_numeric($value);
150
    }
151
152 14
    private static function unescapeString(string $value): string
153
    {
154
        // First handle escaped quotes
155 14
        $value = \str_replace('\"', '___QUOTE___', $value);
156
157
        // Handle double backslashes
158 14
        $value = \str_replace('\\\\', '___DBLBACK___', $value);
159
160
        // Handle remaining single backslashes
161 14
        $value = \str_replace('\\', '\\', $value);
162
163
        // Restore double backslashes
164 14
        $value = \str_replace('___DBLBACK___', '\\\\', $value);
165
166
        // Finally restore quotes
167 14
        return \str_replace('___QUOTE___', '"', $value);
168
    }
169
}
170