Passed
Push — 2.x ( 811329...687ce3 )
by Aleksei
18:29
created

Interpolator::interpolate()   B

Complexity

Conditions 9
Paths 4

Size

Total Lines 42
Code Lines 28

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 19
CRAP Score 9.0101

Importance

Changes 0
Metric Value
cc 9
eloc 28
nc 4
nop 2
dl 0
loc 42
ccs 19
cts 20
cp 0.95
crap 9.0101
rs 8.0555
c 0
b 0
f 0
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 BackedEnum;
0 ignored issues
show
Bug introduced by
The type BackedEnum was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
15
use Cycle\Database\Injection\ParameterInterface;
16
use DateTimeInterface;
17
use Stringable;
18
19
/**
20
 * Simple helper class used to interpolate query with given values. To be used for profiling and
21
 * debug purposes only.
22
 */
23
final class Interpolator
24
{
25
    /**
26
     * Injects parameters into statement. For debug purposes only.
27
     *
28
     * @psalm-param non-empty-string $query
29
     *
30 3730
     * @psalm-return non-empty-string
31
     */
32 3730
    public static function interpolate(string $query, iterable $parameters = []): string
33 3724
    {
34
        if ($parameters === []) {
35
            return $query;
36
        }
37 2026
38 2026
        $named = [];
39 20
        $unnamed = [];
40 20
41 20
        foreach ($parameters as $k => $v) {
42
            if (\is_int($k)) {
43
                $unnamed[] = $v;
44 20
            } else {
45
                $named[\ltrim($k, ':')] = $v;
46
            }
47 2022
        }
48 2022
49 2022
        return \preg_replace_callback(
50
            '/(?<dq>"(?:\\\\\"|[^"])*")|(?<sq>\'(?:\\\\\'|[^\'])*\')|(?<ph>\\?)|(?<named>:[a-z_\\d]+)/',
51
            static function ($match) use (&$named, &$unnamed) {
52
                $key = match (true) {
53
                    isset($match['named']) && '' !== $match['named'] => \ltrim($match['named'], ':'),
54 2026
                    isset($match['ph']) => $match['ph'],
55
                    default => null
56
                };
57
58
                switch (true) {
59
                    case '?' === $key:
60
                        if (null === \key($unnamed)) {
61
                            return $match[0];
62 2026
                        }
63
64 2026
                        $value = \current($unnamed);
65 432
                        \next($unnamed);
66
                        return self::resolveValue($value);
67
                    case isset($named[$key]) || \array_key_exists($key, $named):
68 2026
                        return self::resolveValue($named[$key]);
69 2026
                    default:
70
                        return $match[0];
71
                }
72 2026
            },
73 590
            $query
74
        );
75 2026
    }
76 24
77
    /**
78 2026
     * Get parameter value.
79
     *
80
     * @psalm-return non-empty-string
81 2026
     */
82 2026
    private static function resolveValue(mixed $parameter): string
83
    {
84 20
        if ($parameter instanceof ParameterInterface) {
85 12
            return self::resolveValue($parameter->getValue());
86
        }
87
88
        /** @since PHP 8.1 */
89 12
        if ($parameter instanceof BackedEnum) {
90 12
            $parameter = $parameter->value;
91
        }
92
93
        switch (\gettype($parameter)) {
94 8
            case 'boolean':
95
                return $parameter ? 'TRUE' : 'FALSE';
96
97
            case 'integer':
98
                return (string)$parameter;
99
100
            case 'NULL':
101
                return 'NULL';
102
103
            case 'double':
104
                return \sprintf('%F', $parameter);
105
106
            case 'string':
107
                return "'" . self::escapeStringValue($parameter, "'") . "'";
0 ignored issues
show
Unused Code introduced by
The call to Cycle\Database\Query\Int...or::escapeStringValue() has too many arguments starting with '''. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

107
                return "'" . self::/** @scrutinizer ignore-call */ escapeStringValue($parameter, "'") . "'";

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress. Please note the @ignore annotation hint above.

Loading history...
108 2022
109
            case 'object':
110
                if ($parameter instanceof Stringable) {
111
                    return "'" . self::escapeStringValue((string)$parameter, "'") . "'";
112
                }
113 2022
114 2022
                if ($parameter instanceof DateTimeInterface) {
115 2022
                    return "'" . $parameter->format(DateTimeInterface::ATOM) . "'";
116
                }
117
        }
118
119
        return '[UNRESOLVED]';
120
    }
121
122
    private static function escapeStringValue(string $value): string
123
    {
124
        return \strtr($value, [
125
            '\\%' => '\\%',
126
            '\\_' => '\\_',
127
            \chr(26) => '\\Z',
128
            \chr(0) => '\\0',
129
            "'" => "\\'",
130
            \chr(8) => '\\b',
131
            "\n" => '\\n',
132
            "\r" => '\\r',
133
            "\t" => '\\t',
134
            '\\' => '\\\\',
135
        ]);
136
    }
137
}
138