Passed
Push — main ( 6332bc...c58409 )
by Martin
25:59 queued 10:54
created

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