• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

dg / dibi / 23992579605

05 Apr 2026 02:31AM UTC coverage: 77.838%. Remained the same
23992579605

push

github

dg
drivers: escape*() methods moved to Engine [WIP]

101 of 113 new or added lines in 8 files covered. (89.38%)

162 existing lines in 9 files now uncovered.

1721 of 2211 relevant lines covered (77.84%)

0.78 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

56.25
/src/Dibi/Helpers.php
1
<?php declare(strict_types=1);
2

3
/**
4
 * This file is part of the Dibi, smart database abstraction layer (https://dibi.nette.org)
5
 * Copyright (c) 2005 David Grudl (https://davidgrudl.com)
6
 */
7

8
namespace Dibi;
9

10
use function array_map, array_unique, explode, fclose, fgets, fopen, fstat, getenv, htmlspecialchars, is_float, is_int, is_string, levenshtein, max, mb_strlen, ob_end_flush, ob_get_clean, ob_start, preg_match, preg_replace, preg_replace_callback, rtrim, set_time_limit, str_ends_with, str_repeat, str_starts_with, strlen, strtoupper, strval, substr, trim, wordwrap;
11
use const PHP_SAPI;
12

13

14
class Helpers
15
{
16
        private static HashMap $types;
17

18

19
        /**
20
         * Prints out a syntax highlighted version of the SQL command or Result.
21
         */
22
        public static function dump(string|Result|null $sql = null, bool $return = false): ?string
23
        {
24
                ob_start();
×
25
                if ($sql instanceof Result && PHP_SAPI === 'cli') {
×
26
                        $hasColors = (str_starts_with((string) getenv('TERM'), 'xterm'));
×
27
                        $maxLen = 0;
×
28
                        foreach ($sql as $i => $row) {
×
29
                                if ($i === 0) {
×
30
                                        foreach ($row as $col => $foo) {
×
31
                                                $len = mb_strlen((string) $col);
×
32
                                                $maxLen = max($len, $maxLen);
×
33
                                        }
34
                                }
35

36
                                echo $hasColors ? "\033[1;37m#row: $i\033[0m\n" : "#row: $i\n";
×
37
                                foreach ($row as $col => $val) {
×
38
                                        $spaces = $maxLen - mb_strlen((string) $col) + 2;
×
39
                                        echo "$col" . str_repeat(' ', $spaces) . "$val\n";
×
40
                                }
41

42
                                echo "\n";
×
43
                        }
44

45
                        echo empty($row) ? "empty result set\n\n" : "\n";
×
46

47
                } elseif ($sql instanceof Result) {
×
48
                        foreach ($sql as $i => $row) {
×
49
                                if ($i === 0) {
×
50
                                        echo "\n<table class=\"dump\">\n<thead>\n\t<tr>\n\t\t<th>#row</th>\n";
×
51
                                        foreach ($row as $col => $foo) {
×
52
                                                echo "\t\t<th>" . htmlspecialchars((string) $col) . "</th>\n";
×
53
                                        }
54

55
                                        echo "\t</tr>\n</thead>\n<tbody>\n";
×
56
                                }
57

58
                                echo "\t<tr>\n\t\t<th>", $i, "</th>\n";
×
59
                                foreach ($row as $col) {
×
60
                                        echo "\t\t<td>", htmlspecialchars((string) $col), "</td>\n";
×
61
                                }
62

63
                                echo "\t</tr>\n";
×
64
                        }
65

66
                        echo empty($row)
×
67
                                ? '<p><em>empty result set</em></p>'
×
68
                                : "</tbody>\n</table>\n";
×
69

70
                } else {
71
                        if ($sql === null) {
×
72
                                $sql = \dibi::$sql;
×
73
                        }
74

75
                        $keywords1 = 'SELECT|(?:ON\s+DUPLICATE\s+KEY)?UPDATE|INSERT(?:\s+INTO)?|REPLACE(?:\s+INTO)?|DELETE|CALL|UNION|FROM|WHERE|HAVING|GROUP\s+BY|ORDER\s+BY|LIMIT|OFFSET|FETCH\s+NEXT|SET|VALUES|LEFT\s+JOIN|INNER\s+JOIN|TRUNCATE|START\s+TRANSACTION|BEGIN|COMMIT|ROLLBACK(?:\s+TO\s+SAVEPOINT)?|(?:RELEASE\s+)?SAVEPOINT';
×
76
                        $keywords2 = 'ALL|DISTINCT|DISTINCTROW|IGNORE|AS|USING|ON|AND|OR|IN|IS|NOT|NULL|LIKE|RLIKE|REGEXP|TRUE|FALSE';
×
77

78
                        // insert new lines
79
                        $sql = " $sql ";
×
80
                        $sql = preg_replace("#(?<=[\\s,(])($keywords1)(?=[\\s,)])#i", "\n\$1", $sql);
×
81

82
                        // reduce spaces
83
                        $sql = preg_replace('#[ \t]{2,}#', ' ', $sql);
×
84

85
                        $sql = wordwrap($sql, 100);
×
86
                        $sql = preg_replace("#([ \t]*\r?\n){2,}#", "\n", $sql);
×
87

88
                        // syntax highlight
89
                        $highlighter = "#(/\\*.+?\\*/)|(\\*\\*.+?\\*\\*)|(?<=[\\s,(])($keywords1)(?=[\\s,)])|(?<=[\\s,(=])($keywords2)(?=[\\s,)=])#is";
×
90
                        if (PHP_SAPI === 'cli') {
×
91
                                if (str_starts_with((string) getenv('TERM'), 'xterm')) {
×
92
                                        $sql = preg_replace_callback($highlighter, function (array $m) {
×
93
                                                if (!empty($m[1])) { // comment
94
                                                        return "\033[1;30m" . $m[1] . "\033[0m";
95

96
                                                } elseif (!empty($m[2])) { // error
97
                                                        return "\033[1;31m" . $m[2] . "\033[0m";
98

99
                                                } elseif (!empty($m[3])) { // most important keywords
100
                                                        return "\033[1;34m" . $m[3] . "\033[0m";
101

102
                                                } elseif ($m[4] !== '') { // other keywords
103
                                                        return "\033[1;32m" . $m[4] . "\033[0m";
104
                                                }
105
                                        }, $sql);
×
106
                                }
107

108
                                echo trim($sql) . "\n\n";
×
109

110
                        } else {
111
                                $sql = htmlspecialchars($sql);
×
112
                                $sql = preg_replace_callback($highlighter, function (array $m) {
×
113
                                        if (!empty($m[1])) { // comment
114
                                                return '<em style="color:gray">' . $m[1] . '</em>';
115

116
                                        } elseif (!empty($m[2])) { // error
117
                                                return '<strong style="color:red">' . $m[2] . '</strong>';
118

119
                                        } elseif (!empty($m[3])) { // most important keywords
120
                                                return '<strong style="color:blue">' . $m[3] . '</strong>';
121

122
                                        } elseif ($m[4] !== '') { // other keywords
123
                                                return '<strong style="color:green">' . $m[4] . '</strong>';
124
                                        }
125
                                }, $sql);
×
126
                                echo '<pre class="dump">', trim($sql), "</pre>\n\n";
×
127
                        }
128
                }
129

130
                if ($return) {
×
131
                        return ob_get_clean();
×
132
                } else {
133
                        ob_end_flush();
×
134
                        return null;
×
135
                }
136
        }
137

138

139
        /**
140
         * Finds the best suggestion.
141
         * @param  string[]  $items
142
         * @internal
143
         */
144
        public static function getSuggestion(array $items, string $value): ?string
1✔
145
        {
146
                $best = null;
1✔
147
                $min = (strlen($value) / 4 + 1) * 10 + .1;
1✔
148
                $items = array_map(strval(...), $items);
1✔
149
                foreach (array_unique($items) as $item) {
1✔
150
                        if (($len = levenshtein($item, $value, 10, 11, 10)) > 0 && $len < $min) {
1✔
151
                                $min = $len;
1✔
152
                                $best = $item;
1✔
153
                        }
154
                }
155

156
                return $best;
1✔
157
        }
158

159

160
        /**
161
         * Heuristic type detection.
162
         * @internal
163
         */
164
        public static function detectType(string $type): ?string
1✔
165
        {
166
                $patterns = [
1✔
167
                        '^_' => Type::Text, // PostgreSQL arrays
1✔
168
                        'RANGE$' => Type::Text, // PostgreSQL range types
1✔
169
                        'BYTEA|BLOB|BIN' => Type::Binary,
1✔
170
                        'TEXT|CHAR|POINT|INTERVAL|STRING' => Type::Text,
1✔
171
                        'YEAR|BYTE|COUNTER|SERIAL|INT|LONG|SHORT|^TINY$' => Type::Integer,
1✔
172
                        'CURRENCY|REAL|MONEY|FLOAT|DOUBLE|DECIMAL|NUMERIC|NUMBER' => Type::Float,
1✔
173
                        '^TIME$' => Type::Time,
1✔
174
                        'TIME' => Type::DateTime, // DATETIME, TIMESTAMP
1✔
175
                        'DATE' => Type::Date,
1✔
176
                        'BOOL' => Type::Bool,
1✔
177
                        'JSON' => Type::JSON,
1✔
178
                ];
179

180
                foreach ($patterns as $s => $val) {
1✔
181
                        if (preg_match("#$s#i", $type)) {
1✔
182
                                return $val;
1✔
183
                        }
184
                }
185

186
                return null;
1✔
187
        }
188

189

190
        /** @internal */
191
        public static function getTypeCache(): HashMap
192
        {
193
                if (!isset(self::$types)) {
1✔
194
                        self::$types = new HashMap(self::detectType(...));
1✔
195
                }
196

197
                return self::$types;
1✔
198
        }
199

200

201
        /**
202
         * Apply configuration alias or default values.
203
         * @param  array<string, mixed>  $config
204
         */
205
        public static function alias(array &$config, string $key, string $alias): void
1✔
206
        {
207
                $foo = &$config;
1✔
208
                foreach (explode('|', $key) as $key) {
1✔
209
                        $foo = &$foo[$key];
1✔
210
                }
211

212
                if (!isset($foo) && isset($config[$alias])) {
1✔
213
                        $foo = $config[$alias];
1✔
214
                        unset($config[$alias]);
1✔
215
                }
216
        }
1✔
217

218

219
        /**
220
         * Import SQL dump from file.
221
         * Returns count of sql commands
222
         * @param  (callable(int, float|null): void)|null  $onProgress
223
         */
224
        public static function loadFromFile(Connection $connection, string $file, ?callable $onProgress = null): int
1✔
225
        {
226
                @set_time_limit(0); // intentionally @
1✔
227

228
                $handle = @fopen($file, 'r'); // intentionally @
1✔
229
                if (!$handle) {
1✔
230
                        throw new \RuntimeException("Cannot open file '$file'.");
×
231
                }
232

233
                $stat = fstat($handle);
1✔
234
                $count = $size = 0;
1✔
235
                $delimiter = ';';
1✔
236
                $sql = '';
1✔
237
                $driver = $connection->getDriver();
1✔
238
                while (($s = fgets($handle)) !== false) {
1✔
239
                        $size += strlen($s);
1✔
240
                        if (strtoupper(substr($s, 0, 10)) === 'DELIMITER ') {
1✔
241
                                $delimiter = trim(substr($s, 10));
×
242

243
                        } elseif (str_ends_with($ts = rtrim($s), $delimiter)) {
1✔
244
                                $sql .= substr($ts, 0, -strlen($delimiter));
1✔
245
                                $driver->query($sql);
1✔
246
                                $sql = '';
1✔
247
                                $count++;
1✔
248
                                if ($onProgress) {
1✔
UNCOV
249
                                        $onProgress($count, $stat ? $size * 100 / $stat['size'] : null);
×
250
                                }
251
                        } else {
252
                                $sql .= $s;
1✔
253
                        }
254
                }
255

256
                if (rtrim($sql) !== '') {
1✔
257
                        $driver->query($sql);
1✔
258
                        $count++;
1✔
259
                        if ($onProgress) {
1✔
UNCOV
260
                                $onProgress($count, $stat ? 100 : null);
×
261
                        }
262
                }
263

264
                fclose($handle);
1✔
265
                return $count;
1✔
266
        }
267

268

269
        /** @internal */
270
        public static function false2Null(mixed $val): mixed
1✔
271
        {
272
                return $val === false ? null : $val;
1✔
273
        }
274

275

276
        /** @internal */
277
        public static function intVal(mixed $value): int
1✔
278
        {
279
                if (is_int($value)) {
1✔
280
                        return $value;
1✔
281
                } elseif (is_string($value) && preg_match('#-?\d++\z#A', $value)) {
1✔
282
                        if (is_float($value * 1)) {
1✔
283
                                throw new Exception("Number $value is greater than integer.");
1✔
284
                        }
285

286
                        return (int) $value;
1✔
287
                } else {
288
                        throw new Exception("Expected number, '$value' given.");
1✔
289
                }
290
        }
291
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc