Passed
Pull Request — 2.x (#60)
by
unknown
17:12
created

Interpolator::findParams()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 18
Code Lines 12

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 4
CRAP Score 3

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 3
eloc 12
c 1
b 0
f 0
nc 3
nop 1
dl 0
loc 18
ccs 4
cts 4
cp 1
crap 3
rs 9.8666
1
<?php
2
3
/**
4
 * This file is part of Cycle ORM package.
5
 *
6
 * For the full copyright and license information, please view the LICENSE
7
 * file that was distributed with this source code.
8
 */
9
10
declare(strict_types=1);
11
12
namespace Cycle\Database\Query;
13
14
use Cycle\Database\Injection\ParameterInterface;
15
use DateTimeInterface;
16
17
/**
18
 * Simple helper class used to interpolate query with given values. To be used for profiling and
19
 * debug purposes only.
20
 */
21
final class Interpolator
22
{
23
    /**
24
     * Injects parameters into statement. For debug purposes only.
25
     *
26
     * @psalm-param non-empty-string $query
27
     *
28
     * @psalm-return non-empty-string
29
     */
30 3730
    public static function interpolate(string $query, iterable $parameters = []): string
31
    {
32 3730
        if ($parameters === []) {
33 3724
            return $query;
34
        }
35
36
        ['named' => $named, 'unnamed' => $unnamed] = self::normalizeParameters($parameters);
37 2026
        $params = self::findParams($query);
38 2026
39 20
        $caret = 0;
40 20
        $result = '';
41 20
        foreach ($params as $pos => $ph) {
42
            $result .= \substr($query, $caret, $pos - $caret);
43
            $caret = $pos + \strlen($ph);
44 20
            // find param
45
            $result .= match (true) {
46
                $ph === '?' && \count($unnamed) > 0 => self::resolveValue(\array_shift($unnamed)),
47 2022
                \array_key_exists($ph, $named) => self::resolveValue($named[$ph]),
48 2022
                default => $ph,
49 2022
            };
50
        }
51
        $result .= \substr($query, $caret);
52
53
        return $result;
54 2026
    }
55
56
    /**
57
     * Get parameter value.
58
     *
59
     * @psalm-return non-empty-string
60
     */
61
    private static function resolveValue(mixed $parameter): string
62 2026
    {
63
        if ($parameter instanceof ParameterInterface) {
64 2026
            return self::resolveValue($parameter->getValue());
65 432
        }
66
67
        switch (gettype($parameter)) {
68 2026
            case 'boolean':
69 2026
                return $parameter ? 'TRUE' : 'FALSE';
70
71
            case 'integer':
72 2026
                return (string)($parameter + 0);
73 590
74
            case 'NULL':
75 2026
                return 'NULL';
76 24
77
            case 'double':
78 2026
                return sprintf('%F', $parameter);
79
80
            case 'string':
81 2026
                return "'" . addcslashes($parameter, "'") . "'";
82 2026
83
            case 'object':
84 20
                if (method_exists($parameter, '__toString')) {
85 12
                    return "'" . addcslashes((string)$parameter, "'") . "'";
86
                }
87
88
                if ($parameter instanceof DateTimeInterface) {
89 12
                    return "'" . $parameter->format(DateTimeInterface::ATOM) . "'";
90 12
                }
91
        }
92
93
        return '[UNRESOLVED]';
94 8
    }
95
96
    /**
97
     * @return array<int, string>
98
     */
99
    private static function findParams(string $query): array
100
    {
101
        \preg_match_all(
102
            '/(?<dq>"(?:\\\\"|[^"])*")|(?<sq>\'[^\']*\')|(?<ph>\\?)|(?<named>:[a-z_]+)/',
103
            $query,
104
            $placeholders,
105
            PREG_OFFSET_CAPTURE|PREG_UNMATCHED_AS_NULL
106
        );
107
        $result = [];
108 2022
        foreach ([...$placeholders['named'], ...$placeholders['ph']] as $tuple) {
109
            if ($tuple[0] === null) {
110
                continue;
111
            }
112
            $result[$tuple[1]] = $tuple[0];
113 2022
        }
114 2022
        \ksort($result);
115 2022
116
        return $result;
117
    }
118
119
    /**
120
     * @return array{named: array, unnamed: array}
121
     */
122
    private static function normalizeParameters(iterable $parameters): array
123
    {
124
        $result = ['named' => [], 'unnamed' => []];
125
        foreach ($parameters as $k => $v) {
126
            if (\is_int($k)) {
127
                $result['unnamed'][$k] = $v;
128
            } else {
129
                $result['named'][':' . \ltrim($k, ':')] = $v;
130
            }
131
        }
132
        return $result;
133
    }
134
}
135