Passed
Push — main ( 339e98...90c0a6 )
by Martin
40:38 queued 25:40
created

PHPArrayToPostgresValueTransformer   A

Complexity

Total Complexity 15

Size/Duplication

Total Lines 84
Duplicated Lines 0 %

Test Coverage

Coverage 97.37%

Importance

Changes 1
Bugs 1 Features 0
Metric Value
eloc 39
c 1
b 1
f 0
dl 0
loc 84
ccs 37
cts 38
cp 0.9737
rs 10
wmc 15

2 Methods

Rating   Name   Duplication   Size   Complexity  
A transformToPostgresTextArray() 0 17 3
C formatValue() 0 51 12
1
<?php
2
3
declare(strict_types=1);
4
5
namespace MartinGeorgiev\Utils;
6
7
use MartinGeorgiev\Utils\Exception\InvalidArrayFormatException;
8
9
/**
10
 * Handles transformation from PHP values to PostgreSQL values.
11
 *
12
 * @since 3.0
13
 *
14
 * @author Martin Georgiev <[email protected]>
15
 */
16
class PHPArrayToPostgresValueTransformer
17
{
18
    private const POSTGRESQL_EMPTY_ARRAY = '{}';
19
20
    /**
21
     * Transforms a PHP array to a PostgreSQL text array.
22
     * This method supports only single-dimensioned PHP arrays.
23
     * This method relays on the default escaping strategy in PostgreSQL (double quotes).
24
     *
25
     * @throws InvalidArrayFormatException when the input is a multi-dimensional array or has invalid format
26
     */
27 26
    public static function transformToPostgresTextArray(array $phpArray): string
28
    {
29 26
        if ($phpArray === []) {
30 1
            return self::POSTGRESQL_EMPTY_ARRAY;
31
        }
32
33 25
        if (\array_filter($phpArray, 'is_array')) {
34 4
            throw InvalidArrayFormatException::multiDimensionalArrayNotSupported();
35
        }
36
37
        /** @var array<int|string, string> */
38 21
        $processed = \array_map(
39 21
            static fn (mixed $value): string => self::formatValue($value),
40 21
            $phpArray
41 21
        );
42
43 21
        return '{'.\implode(',', $processed).'}';
44
    }
45
46
    /**
47
     * Formats a single value for PostgreSQL array.
48
     */
49 21
    private static function formatValue(mixed $value): string
50
    {
51 21
        if ($value === null) {
52 2
            return 'NULL';
53
        }
54
55 21
        if (\is_int($value) || \is_float($value)) {
56 4
            return (string) $value;
57
        }
58
59 19
        if (\is_bool($value)) {
60 2
            return $value ? 'true' : 'false';
61
        }
62
63 18
        if (\is_object($value)) {
64 3
            if (\method_exists($value, '__toString')) {
65 2
                $stringValue = $value->__toString();
66
            } else {
67
                // For objects without __toString, use a default representation
68 1
                $stringValue = $value::class;
69
            }
70 16
        } elseif (\is_resource($value)) {
71 1
            $stringValue = '(resource)';
72
        } else {
73 15
            $valueType = \get_debug_type($value);
74
75 15
            if ($valueType === 'string') {
76 14
                $stringValue = $value;
77 1
            } elseif (\in_array($valueType, ['int', 'float', 'bool'], true)) {
78
                /** @var bool|float|int $value */
79
                $stringValue = (string) $value;
80
            } else {
81 1
                $stringValue = $valueType;
82
            }
83
        }
84
85 18
        \assert(\is_string($stringValue));
86
87 18
        if ($stringValue === '') {
88 2
            return '""';
89
        }
90
91
        // Make sure strings are quoted, PostgreSQL will handle this gracefully
92
        // Double the backslashes and escape quotes
93 18
        $escaped = \str_replace(
94 18
            ['\\', '"'],
95 18
            ['\\\\', '\"'],
96 18
            $stringValue
97 18
        );
98
99 18
        return '"'.$escaped.'"';
100
    }
101
}
102