Code Coverage |
||||||||||||||||
Lines |
Branches |
Paths |
Functions and Methods |
Classes and Traits |
||||||||||||
| Total | |
100.00% |
196 / 196 |
|
84.58% |
203 / 240 |
|
8.84% |
55 / 622 |
|
54.84% |
17 / 31 |
CRAP | |
0.00% |
0 / 1 |
| Debug | |
100.00% |
196 / 196 |
|
84.58% |
203 / 240 |
|
8.84% |
55 / 622 |
|
100.00% |
31 / 31 |
8943.39 | |
100.00% |
1 / 1 |
| query | |
100.00% |
17 / 17 |
|
100.00% |
22 / 22 |
|
1.23% |
4 / 324 |
|
100.00% |
1 / 1 |
127.57 | |||
| interpolate | |
100.00% |
5 / 5 |
|
100.00% |
4 / 4 |
|
100.00% |
2 / 2 |
|
100.00% |
1 / 1 |
2 | |||
| enabled | |
100.00% |
2 / 2 |
|
100.00% |
3 / 3 |
|
100.00% |
2 / 2 |
|
100.00% |
1 / 1 |
2 | |||
| prints | |
100.00% |
2 / 2 |
|
100.00% |
3 / 3 |
|
100.00% |
2 / 2 |
|
100.00% |
1 / 1 |
2 | |||
| writesTranslated | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
| writesInterpolated | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
| printQuery | |
100.00% |
6 / 6 |
|
100.00% |
3 / 3 |
|
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
2 | |||
| value | |
100.00% |
10 / 10 |
|
86.67% |
13 / 15 |
|
71.43% |
5 / 7 |
|
100.00% |
1 / 1 |
8.14 | |||
| prepareQuery | |
100.00% |
21 / 21 |
|
100.00% |
8 / 8 |
|
10.00% |
1 / 10 |
|
100.00% |
1 / 1 |
9.56 | |||
| restoreQuery | |
100.00% |
3 / 3 |
|
85.71% |
6 / 7 |
|
50.00% |
2 / 4 |
|
100.00% |
1 / 1 |
2.50 | |||
| interpolateNamed | |
100.00% |
9 / 9 |
|
75.00% |
3 / 4 |
|
50.00% |
1 / 2 |
|
100.00% |
1 / 1 |
4.12 | |||
| interpolatePositional | |
100.00% |
9 / 9 |
|
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
| flag | |
100.00% |
4 / 4 |
|
100.00% |
4 / 4 |
|
40.00% |
2 / 5 |
|
100.00% |
1 / 1 |
4.94 | |||
| env | |
100.00% |
7 / 7 |
|
81.82% |
9 / 11 |
|
20.00% |
2 / 10 |
|
100.00% |
1 / 1 |
12.19 | |||
| dir | |
100.00% |
8 / 8 |
|
100.00% |
8 / 8 |
|
66.67% |
2 / 3 |
|
100.00% |
1 / 1 |
7.33 | |||
| writeEnv | |
100.00% |
4 / 4 |
|
100.00% |
3 / 3 |
|
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
3 | |||
| write | |
100.00% |
4 / 4 |
|
80.00% |
8 / 10 |
|
25.00% |
2 / 8 |
|
100.00% |
1 / 1 |
15.55 | |||
| relativeToRoots | |
100.00% |
10 / 10 |
|
73.91% |
17 / 23 |
|
1.56% |
1 / 64 |
|
100.00% |
1 / 1 |
69.05 | |||
| sessionPath | |
100.00% |
5 / 5 |
|
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
| sourceName | |
100.00% |
8 / 8 |
|
75.00% |
9 / 12 |
|
22.22% |
2 / 9 |
|
100.00% |
1 / 1 |
11.53 | |||
| session | |
100.00% |
6 / 6 |
|
100.00% |
3 / 3 |
|
100.00% |
2 / 2 |
|
100.00% |
1 / 1 |
2 | |||
| sessionInfo | |
100.00% |
15 / 15 |
|
100.00% |
7 / 7 |
|
60.00% |
3 / 5 |
|
100.00% |
1 / 1 |
5.02 | |||
| requestTime | |
100.00% |
9 / 9 |
|
92.31% |
12 / 13 |
|
11.11% |
3 / 27 |
|
100.00% |
1 / 1 |
31.28 | |||
| argv | |
100.00% |
1 / 1 |
|
75.00% |
3 / 4 |
|
50.00% |
1 / 2 |
|
100.00% |
1 / 1 |
1.12 | |||
| sessionLabel | |
100.00% |
2 / 2 |
|
42.86% |
3 / 7 |
|
33.33% |
1 / 3 |
|
100.00% |
1 / 1 |
3.19 | |||
| uriLabel | |
100.00% |
9 / 9 |
|
65.22% |
15 / 23 |
|
1.92% |
2 / 104 |
|
100.00% |
1 / 1 |
39.96 | |||
| hash | |
100.00% |
1 / 1 |
|
75.00% |
3 / 4 |
|
50.00% |
1 / 2 |
|
100.00% |
1 / 1 |
1.12 | |||
| server | |
100.00% |
4 / 4 |
|
100.00% |
10 / 10 |
|
40.00% |
4 / 10 |
|
100.00% |
1 / 1 |
10.40 | |||
| counter | |
100.00% |
2 / 2 |
|
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
| safeRelativePath | |
100.00% |
8 / 8 |
|
85.71% |
12 / 14 |
n/a |
0 / 0 |
|
100.00% |
1 / 1 |
7 | ||||
| safeSegment | |
100.00% |
3 / 3 |
|
70.00% |
7 / 10 |
|
12.50% |
1 / 8 |
|
100.00% |
1 / 1 |
4.68 | |||
| 1 | <?php |
| 2 | |
| 3 | declare(strict_types=1); |
| 4 | |
| 5 | namespace Celemas\Quma; |
| 6 | |
| 7 | use DateTimeImmutable; |
| 8 | use RuntimeException; |
| 9 | |
| 10 | /** |
| 11 | * @internal |
| 12 | * |
| 13 | * Coverage ignores mark defensive filesystem/global-state race branches that are |
| 14 | * not deterministic or meaningful to exercise in tests. |
| 15 | */ |
| 16 | final class Debug |
| 17 | { |
| 18 | public const string ENV_DEBUG = 'QUMA_DEBUG'; |
| 19 | public const string ENV_PRINT = 'QUMA_DEBUG_PRINT'; |
| 20 | public const string ENV_TRANSLATED = 'QUMA_DEBUG_TRANSLATED'; |
| 21 | public const string ENV_INTERPOLATED = 'QUMA_DEBUG_INTERPOLATED'; |
| 22 | |
| 23 | private static ?string $sessionKey = null; |
| 24 | private static ?string $session = null; |
| 25 | private static ?string $fallbackTime = null; |
| 26 | private static int $counter = 0; |
| 27 | |
| 28 | public static function query(Database $db, string $query, Args $args, ?string $sourcePath): void |
| 29 | { |
| 30 | $print = self::prints(); |
| 31 | $writeTranslated = self::writesTranslated(); |
| 32 | $writeInterpolated = self::writesInterpolated(); |
| 33 | |
| 34 | if (!$print && !$writeTranslated && !$writeInterpolated) { |
| 35 | return; |
| 36 | } |
| 37 | |
| 38 | $path = $writeTranslated || $writeInterpolated |
| 39 | ? self::sessionPath($sourcePath, $db->getSqlDirs()) |
| 40 | : null; |
| 41 | |
| 42 | if ($writeTranslated) { |
| 43 | self::writeEnv(self::ENV_TRANSLATED, $path, $query); |
| 44 | } |
| 45 | |
| 46 | if (!$print && !$writeInterpolated) { |
| 47 | return; |
| 48 | } |
| 49 | |
| 50 | $interpolated = self::interpolate($query, $args); |
| 51 | |
| 52 | if ($print) { |
| 53 | self::printQuery($interpolated); |
| 54 | } |
| 55 | |
| 56 | if ($writeInterpolated) { |
| 57 | self::writeEnv(self::ENV_INTERPOLATED, $path, $interpolated); |
| 58 | } |
| 59 | } |
| 60 | |
| 61 | public static function interpolate(string $query, Args $args): string |
| 62 | { |
| 63 | $prep = self::prepareQuery($query); |
| 64 | |
| 65 | if ($args->type() === ArgType::Named) { |
| 66 | $interpolated = self::interpolateNamed($prep->query, $args->getNamed()); |
| 67 | } else { |
| 68 | $interpolated = self::interpolatePositional($prep->query, $args->get()); |
| 69 | } |
| 70 | |
| 71 | return self::restoreQuery($interpolated, $prep); |
| 72 | } |
| 73 | |
| 74 | public static function enabled(): bool |
| 75 | { |
| 76 | $value = self::env(self::ENV_DEBUG); |
| 77 | |
| 78 | return $value !== null && self::flag($value); |
| 79 | } |
| 80 | |
| 81 | public static function prints(): bool |
| 82 | { |
| 83 | $value = self::env(self::ENV_PRINT); |
| 84 | |
| 85 | return $value !== null && self::flag($value); |
| 86 | } |
| 87 | |
| 88 | public static function writesTranslated(): bool |
| 89 | { |
| 90 | return self::env(self::ENV_TRANSLATED) !== null; |
| 91 | } |
| 92 | |
| 93 | public static function writesInterpolated(): bool |
| 94 | { |
| 95 | return self::env(self::ENV_INTERPOLATED) !== null; |
| 96 | } |
| 97 | |
| 98 | private static function printQuery(string $query): void |
| 99 | { |
| 100 | $msg = |
| 101 | "\n\n-----------------------------------------------\n\n" |
| 102 | . $query |
| 103 | . "\n------------------------------------------------\n"; |
| 104 | |
| 105 | if (($_SERVER['SERVER_SOFTWARE'] ?? null) !== null) { |
| 106 | // @codeCoverageIgnoreStart |
| 107 | error_log($msg); |
| 108 | |
| 109 | // @codeCoverageIgnoreEnd |
| 110 | } else { |
| 111 | echo $msg; |
| 112 | } |
| 113 | } |
| 114 | |
| 115 | private static function value(mixed $value): string |
| 116 | { |
| 117 | if (is_string($value)) { |
| 118 | return "'" . $value . "'"; |
| 119 | } |
| 120 | |
| 121 | if (is_array($value)) { |
| 122 | $encoded = json_encode($value); |
| 123 | |
| 124 | return "'" . ($encoded !== false ? $encoded : '[]') . "'"; |
| 125 | } |
| 126 | |
| 127 | if (is_null($value)) { |
| 128 | return 'NULL'; |
| 129 | } |
| 130 | |
| 131 | if (is_bool($value)) { |
| 132 | return $value ? 'true' : 'false'; |
| 133 | } |
| 134 | |
| 135 | return (string) $value; |
| 136 | } |
| 137 | |
| 138 | private static function prepareQuery(string $query): PreparedQuery |
| 139 | { |
| 140 | $patterns = [ |
| 141 | Query::PATTERN_BLOCK, |
| 142 | Query::PATTERN_STRING, |
| 143 | Query::PATTERN_COMMENT_MULTI, |
| 144 | Query::PATTERN_COMMENT_SINGLE, |
| 145 | ]; |
| 146 | |
| 147 | $swaps = []; |
| 148 | $i = 0; |
| 149 | |
| 150 | do { |
| 151 | $found = false; |
| 152 | |
| 153 | foreach ($patterns as $pattern) { |
| 154 | $matches = []; |
| 155 | |
| 156 | if (preg_match($pattern, $query, $matches) === 1) { |
| 157 | $match = $matches[0]; |
| 158 | $replacement = "___CHUCK_REPLACE_{$i}___"; |
| 159 | assert($match !== '', 'Query placeholder match must not be empty.'); |
| 160 | $swaps[$replacement] = $match; |
| 161 | |
| 162 | $query = preg_replace($pattern, $replacement, $query, limit: 1) ?? $query; |
| 163 | $found = true; |
| 164 | $i++; |
| 165 | |
| 166 | break; |
| 167 | } |
| 168 | } |
| 169 | } while ($found); |
| 170 | |
| 171 | return new PreparedQuery($query, $swaps); |
| 172 | } |
| 173 | |
| 174 | private static function restoreQuery(string $query, PreparedQuery $prep): string |
| 175 | { |
| 176 | foreach ($prep->swaps as $swap => $replacement) { |
| 177 | $query = str_replace($swap, $replacement, $query); |
| 178 | } |
| 179 | |
| 180 | return $query; |
| 181 | } |
| 182 | |
| 183 | /** @param array<array-key, mixed> $args */ |
| 184 | private static function interpolateNamed(string $query, array $args): string |
| 185 | { |
| 186 | $map = []; |
| 187 | |
| 188 | array_walk( |
| 189 | $args, |
| 190 | static function (mixed $value, int|string $key) use (&$map): void { |
| 191 | if (is_string($key) && $key !== '') { |
| 192 | $map[':' . $key] = self::value($value); |
| 193 | } |
| 194 | }, |
| 195 | ); |
| 196 | |
| 197 | return strtr($query, $map); |
| 198 | } |
| 199 | |
| 200 | /** @param array<array-key, mixed> $args */ |
| 201 | private static function interpolatePositional(string $query, array $args): string |
| 202 | { |
| 203 | $result = $query; |
| 204 | |
| 205 | array_walk( |
| 206 | $args, |
| 207 | static function (mixed $value) use (&$result): void { |
| 208 | $replaced = preg_replace('/\\?/', self::value($value), $result, 1); |
| 209 | $result = $replaced ?? $result; |
| 210 | }, |
| 211 | ); |
| 212 | |
| 213 | return $result; |
| 214 | } |
| 215 | |
| 216 | private static function flag(string $value): bool |
| 217 | { |
| 218 | return match (strtolower($value)) { |
| 219 | '1', 'true', 'yes', 'on' => true, |
| 220 | default => false, |
| 221 | }; |
| 222 | } |
| 223 | |
| 224 | private static function env(string $name): ?string |
| 225 | { |
| 226 | $value = getenv($name); |
| 227 | |
| 228 | if ($value === false) { |
| 229 | $value = $_SERVER[$name] ?? $_ENV[$name] ?? null; |
| 230 | } |
| 231 | |
| 232 | if (!is_string($value)) { |
| 233 | return null; |
| 234 | } |
| 235 | |
| 236 | $value = trim($value); |
| 237 | |
| 238 | return $value === '' ? null : $value; |
| 239 | } |
| 240 | |
| 241 | /** @return non-empty-string|null */ |
| 242 | private static function dir(string $name): ?string |
| 243 | { |
| 244 | $dir = self::env($name); |
| 245 | |
| 246 | if ($dir === null) { |
| 247 | return null; // @codeCoverageIgnore |
| 248 | } |
| 249 | |
| 250 | if (!is_dir($dir)) { |
| 251 | throw new RuntimeException("Quma debug directory does not exist for {$name}: {$dir}"); |
| 252 | } |
| 253 | |
| 254 | if (!is_writable($dir)) { |
| 255 | throw new RuntimeException("Quma debug directory is not writable for {$name}: {$dir}"); // @codeCoverageIgnore |
| 256 | } |
| 257 | |
| 258 | $path = realpath($dir); |
| 259 | |
| 260 | if ($path === false || $path === '') { |
| 261 | throw new RuntimeException("Quma debug directory does not exist for {$name}: {$dir}"); // @codeCoverageIgnore |
| 262 | } |
| 263 | |
| 264 | return $path; |
| 265 | } |
| 266 | |
| 267 | private static function writeEnv(string $name, ?string $relative, string $source): void |
| 268 | { |
| 269 | if ($relative === null) { |
| 270 | return; // @codeCoverageIgnore |
| 271 | } |
| 272 | |
| 273 | $dir = self::dir($name); |
| 274 | |
| 275 | if ($dir === null) { |
| 276 | return; // @codeCoverageIgnore |
| 277 | } |
| 278 | |
| 279 | self::write($dir, $relative, $source); |
| 280 | } |
| 281 | |
| 282 | private static function write( |
| 283 | string $dir, |
| 284 | string $relative, |
| 285 | string $source, |
| 286 | ): void { |
| 287 | $path = $dir . DIRECTORY_SEPARATOR . self::safeRelativePath($relative); |
| 288 | $targetDir = dirname($path); |
| 289 | |
| 290 | if (!is_dir($targetDir) && !mkdir($targetDir, 0o775, true) && !is_dir($targetDir)) { |
| 291 | throw new RuntimeException('Could not create Quma debug directory: ' . $targetDir); // @codeCoverageIgnore |
| 292 | } |
| 293 | |
| 294 | if (file_put_contents($path, $source, LOCK_EX) === false) { |
| 295 | throw new RuntimeException('Could not write Quma debug file: ' . $path); // @codeCoverageIgnore |
| 296 | } |
| 297 | } |
| 298 | |
| 299 | /** @param array<array-key, mixed> $roots */ |
| 300 | private static function relativeToRoots(string $sourcePath, array $roots): string |
| 301 | { |
| 302 | $realSource = realpath($sourcePath); |
| 303 | $source = $realSource !== false ? $realSource : $sourcePath; |
| 304 | |
| 305 | foreach ($roots as $root) { |
| 306 | if (!is_string($root) || $root === '') { |
| 307 | continue; // @codeCoverageIgnore |
| 308 | } |
| 309 | |
| 310 | $realRoot = realpath($root); |
| 311 | $root = $realRoot !== false ? $realRoot : $root; |
| 312 | $prefix = rtrim($root, DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR; |
| 313 | |
| 314 | if (str_starts_with($source, $prefix)) { |
| 315 | $relative = substr($source, strlen($prefix)); |
| 316 | |
| 317 | return $relative === '' ? basename($sourcePath) : $relative; |
| 318 | } |
| 319 | } |
| 320 | |
| 321 | return basename($sourcePath); // @codeCoverageIgnore |
| 322 | } |
| 323 | |
| 324 | /** @param array<array-key, mixed> $roots */ |
| 325 | private static function sessionPath(?string $sourcePath, array $roots): string |
| 326 | { |
| 327 | return self::session() |
| 328 | . DIRECTORY_SEPARATOR |
| 329 | . self::counter() |
| 330 | . '--' |
| 331 | . self::sourceName($sourcePath, $roots); |
| 332 | } |
| 333 | |
| 334 | /** @param array<array-key, mixed> $roots */ |
| 335 | private static function sourceName(?string $sourcePath, array $roots): string |
| 336 | { |
| 337 | if ($sourcePath === null) { |
| 338 | return 'execute.sql'; |
| 339 | } |
| 340 | |
| 341 | $relative = self::relativeToRoots($sourcePath, $roots); |
| 342 | $dir = dirname($relative); |
| 343 | $name = pathinfo($relative, PATHINFO_FILENAME); |
| 344 | $name = $name !== '' ? $name : 'query'; |
| 345 | $path = ($dir === '.' ? '' : $dir . DIRECTORY_SEPARATOR) . $name . '.sql'; |
| 346 | |
| 347 | return preg_replace('/[\\/]+/', '--', $path) ?? 'query.sql'; |
| 348 | } |
| 349 | |
| 350 | private static function session(): string |
| 351 | { |
| 352 | [$key, $session] = self::sessionInfo(); |
| 353 | |
| 354 | if (self::$sessionKey !== $key) { |
| 355 | self::$sessionKey = $key; |
| 356 | self::$session = $session; |
| 357 | self::$counter = 0; |
| 358 | } |
| 359 | |
| 360 | return self::$session ?? $session; |
| 361 | } |
| 362 | |
| 363 | /** @return array{0: string, 1: string} */ |
| 364 | private static function sessionInfo(): array |
| 365 | { |
| 366 | $explicit = self::env('QUMA_DEBUG_SESSION'); |
| 367 | |
| 368 | if ($explicit !== null) { |
| 369 | return ['env:' . $explicit, self::sessionLabel($explicit)]; |
| 370 | } |
| 371 | |
| 372 | $method = self::server('REQUEST_METHOD'); |
| 373 | $requestUri = self::server('REQUEST_URI'); |
| 374 | |
| 375 | if ($method !== null || $requestUri !== null) { |
| 376 | $method = strtoupper($method ?? 'HTTP'); |
| 377 | $uri = $requestUri ?? self::server('SCRIPT_NAME') ?? self::server('PHP_SELF') ?? '/'; |
| 378 | $time = self::requestTime(); |
| 379 | $label = self::uriLabel($uri); |
| 380 | $hash = self::hash([$time, $method, $uri, (string) getmypid()]); |
| 381 | |
| 382 | return ["http:{$time}:{$method}:{$uri}", "{$time}--{$method}--{$label}--{$hash}"]; |
| 383 | } |
| 384 | |
| 385 | $time = self::requestTime(); |
| 386 | $hash = self::hash([$time, (string) getmypid(), self::argv()]); |
| 387 | |
| 388 | return ["cli:{$time}", "{$time}--cli--{$hash}"]; |
| 389 | } |
| 390 | |
| 391 | private static function requestTime(): string |
| 392 | { |
| 393 | $time = self::server('REQUEST_TIME_FLOAT'); |
| 394 | |
| 395 | if ($time !== null && is_numeric($time)) { |
| 396 | $date = DateTimeImmutable::createFromFormat('U.u', sprintf('%.6F', (float) $time)); |
| 397 | |
| 398 | if ($date instanceof DateTimeImmutable) { |
| 399 | return $date->format('Ymd-His-u'); |
| 400 | } |
| 401 | } |
| 402 | |
| 403 | $time = self::server('REQUEST_TIME'); |
| 404 | |
| 405 | if ($time !== null && ctype_digit($time)) { |
| 406 | return new DateTimeImmutable('@' . $time)->format('Ymd-His') . '-000000'; |
| 407 | } |
| 408 | |
| 409 | return self::$fallbackTime ??= new DateTimeImmutable()->format('Ymd-His-u'); |
| 410 | } |
| 411 | |
| 412 | private static function argv(): string |
| 413 | { |
| 414 | return implode(' ', $_SERVER['argv'] ?? []); |
| 415 | } |
| 416 | |
| 417 | private static function sessionLabel(string $value): string |
| 418 | { |
| 419 | $label = self::safeSegment($value); |
| 420 | |
| 421 | return strlen($label) <= 96 ? $label : substr($label, 0, 96); |
| 422 | } |
| 423 | |
| 424 | private static function uriLabel(string $uri): string |
| 425 | { |
| 426 | $path = parse_url($uri, PHP_URL_PATH); |
| 427 | $path = is_string($path) && $path !== '' ? $path : '/'; |
| 428 | $label = trim($path, '/'); |
| 429 | |
| 430 | if ($label === '') { |
| 431 | return 'root'; |
| 432 | } |
| 433 | |
| 434 | $label = preg_replace('/[^A-Za-z0-9._:-]+/', '-', $label) ?? 'request'; |
| 435 | $label = trim($label, '-.'); |
| 436 | $label = $label !== '' ? $label : 'request'; |
| 437 | |
| 438 | return strlen($label) <= 64 ? $label : substr($label, 0, 64); |
| 439 | } |
| 440 | |
| 441 | /** @param list<string> $parts */ |
| 442 | private static function hash(array $parts): string |
| 443 | { |
| 444 | return substr(hash('xxh128', implode("\0", $parts)), 0, 8); |
| 445 | } |
| 446 | |
| 447 | private static function server(string $name): ?string |
| 448 | { |
| 449 | $value = $_SERVER[$name] ?? null; |
| 450 | |
| 451 | if (is_float($value) || is_int($value)) { |
| 452 | return (string) $value; |
| 453 | } |
| 454 | |
| 455 | return is_string($value) && $value !== '' ? $value : null; |
| 456 | } |
| 457 | |
| 458 | private static function counter(): string |
| 459 | { |
| 460 | self::$counter++; |
| 461 | |
| 462 | return str_pad((string) self::$counter, 4, '0', STR_PAD_LEFT); |
| 463 | } |
| 464 | |
| 465 | private static function safeRelativePath(string $path): string |
| 466 | { |
| 467 | $parts = preg_split('/[\\/]+/', $path, -1, PREG_SPLIT_NO_EMPTY); |
| 468 | |
| 469 | if (!is_array($parts) || count($parts) === 0) { |
| 470 | return 'query.sql'; // @codeCoverageIgnore |
| 471 | } |
| 472 | |
| 473 | $result = []; |
| 474 | |
| 475 | foreach ($parts as $part) { |
| 476 | if ($part === '.' || $part === '..') { |
| 477 | continue; // @codeCoverageIgnore |
| 478 | } |
| 479 | |
| 480 | $result[] = self::safeSegment($part); |
| 481 | } |
| 482 | |
| 483 | $path = implode(DIRECTORY_SEPARATOR, $result); |
| 484 | |
| 485 | return $path !== '' ? $path : 'query.sql'; |
| 486 | } |
| 487 | |
| 488 | private static function safeSegment(string $segment): string |
| 489 | { |
| 490 | $result = preg_replace('/[^A-Za-z0-9._:-]+/', '_', $segment); |
| 491 | $result = trim($result ?? '', '.'); |
| 492 | |
| 493 | return $result === '' ? '_' : $result; |
| 494 | } |
| 495 | } |
Below are the source code lines that represent each code branch as identified by Xdebug. Please note a branch is not
necessarily coterminous with a line, a line may contain multiple branches and therefore show up more than once.
Please also be aware that some branches may be implicit rather than explicit, e.g. an if statement
always has an else as part of its logical flow even if you didn't write one.
| 414 | return implode(' ', $_SERVER['argv'] ?? []); |
| 414 | return implode(' ', $_SERVER['argv'] ?? []); |
| 414 | return implode(' ', $_SERVER['argv'] ?? []); |
| 414 | return implode(' ', $_SERVER['argv'] ?? []); |
| 415 | } |
| 460 | self::$counter++; |
| 461 | |
| 462 | return str_pad((string) self::$counter, 4, '0', STR_PAD_LEFT); |
| 463 | } |
| 242 | private static function dir(string $name): ?string |
| 243 | { |
| 244 | $dir = self::env($name); |
| 245 | |
| 246 | if ($dir === null) { |
| 250 | if (!is_dir($dir)) { |
| 251 | throw new RuntimeException("Quma debug directory does not exist for {$name}: {$dir}"); |
| 254 | if (!is_writable($dir)) { |
| 258 | $path = realpath($dir); |
| 259 | |
| 260 | if ($path === false || $path === '') { |
| 260 | if ($path === false || $path === '') { |
| 260 | if ($path === false || $path === '') { |
| 264 | return $path; |
| 265 | } |
| 76 | $value = self::env(self::ENV_DEBUG); |
| 77 | |
| 78 | return $value !== null && self::flag($value); |
| 78 | return $value !== null && self::flag($value); |
| 78 | return $value !== null && self::flag($value); |
| 79 | } |
| 224 | private static function env(string $name): ?string |
| 225 | { |
| 226 | $value = getenv($name); |
| 227 | |
| 228 | if ($value === false) { |
| 229 | $value = $_SERVER[$name] ?? $_ENV[$name] ?? null; |
| 230 | } |
| 231 | |
| 232 | if (!is_string($value)) { |
| 232 | if (!is_string($value)) { |
| 233 | return null; |
| 236 | $value = trim($value); |
| 236 | $value = trim($value); |
| 236 | $value = trim($value); |
| 236 | $value = trim($value); |
| 237 | |
| 238 | return $value === '' ? null : $value; |
| 238 | return $value === '' ? null : $value; |
| 238 | return $value === '' ? null : $value; |
| 238 | return $value === '' ? null : $value; |
| 239 | } |
| 216 | private static function flag(string $value): bool |
| 217 | { |
| 218 | return match (strtolower($value)) { |
| 219 | '1', 'true', 'yes', 'on' => true, |
| 220 | default => false, |
| 220 | default => false, |
| 221 | }; |
| 222 | } |
| 442 | private static function hash(array $parts): string |
| 443 | { |
| 444 | return substr(hash('xxh128', implode("\0", $parts)), 0, 8); |
| 444 | return substr(hash('xxh128', implode("\0", $parts)), 0, 8); |
| 444 | return substr(hash('xxh128', implode("\0", $parts)), 0, 8); |
| 444 | return substr(hash('xxh128', implode("\0", $parts)), 0, 8); |
| 445 | } |
| 61 | public static function interpolate(string $query, Args $args): string |
| 62 | { |
| 63 | $prep = self::prepareQuery($query); |
| 64 | |
| 65 | if ($args->type() === ArgType::Named) { |
| 65 | if ($args->type() === ArgType::Named) { |
| 66 | $interpolated = self::interpolateNamed($prep->query, $args->getNamed()); |
| 68 | $interpolated = self::interpolatePositional($prep->query, $args->get()); |
| 69 | } |
| 70 | |
| 71 | return self::restoreQuery($interpolated, $prep); |
| 71 | return self::restoreQuery($interpolated, $prep); |
| 72 | } |
| 184 | private static function interpolateNamed(string $query, array $args): string |
| 185 | { |
| 186 | $map = []; |
| 187 | |
| 188 | array_walk( |
| 189 | $args, |
| 190 | static function (mixed $value, int|string $key) use (&$map): void { |
| 191 | if (is_string($key) && $key !== '') { |
| 192 | $map[':' . $key] = self::value($value); |
| 193 | } |
| 194 | }, |
| 195 | ); |
| 196 | |
| 197 | return strtr($query, $map); |
| 197 | return strtr($query, $map); |
| 197 | return strtr($query, $map); |
| 197 | return strtr($query, $map); |
| 198 | } |
| 201 | private static function interpolatePositional(string $query, array $args): string |
| 202 | { |
| 203 | $result = $query; |
| 204 | |
| 205 | array_walk( |
| 206 | $args, |
| 207 | static function (mixed $value) use (&$result): void { |
| 208 | $replaced = preg_replace('/\\?/', self::value($value), $result, 1); |
| 209 | $result = $replaced ?? $result; |
| 210 | }, |
| 211 | ); |
| 212 | |
| 213 | return $result; |
| 214 | } |
| 138 | private static function prepareQuery(string $query): PreparedQuery |
| 139 | { |
| 140 | $patterns = [ |
| 141 | Query::PATTERN_BLOCK, |
| 142 | Query::PATTERN_STRING, |
| 143 | Query::PATTERN_COMMENT_MULTI, |
| 144 | Query::PATTERN_COMMENT_SINGLE, |
| 145 | ]; |
| 146 | |
| 147 | $swaps = []; |
| 148 | $i = 0; |
| 149 | |
| 150 | do { |
| 151 | $found = false; |
| 151 | $found = false; |
| 152 | |
| 153 | foreach ($patterns as $pattern) { |
| 153 | foreach ($patterns as $pattern) { |
| 154 | $matches = []; |
| 155 | |
| 156 | if (preg_match($pattern, $query, $matches) === 1) { |
| 157 | $match = $matches[0]; |
| 158 | $replacement = "___CHUCK_REPLACE_{$i}___"; |
| 159 | assert($match !== '', 'Query placeholder match must not be empty.'); |
| 160 | $swaps[$replacement] = $match; |
| 161 | |
| 162 | $query = preg_replace($pattern, $replacement, $query, limit: 1) ?? $query; |
| 163 | $found = true; |
| 164 | $i++; |
| 165 | |
| 166 | break; |
| 153 | foreach ($patterns as $pattern) { |
| 153 | foreach ($patterns as $pattern) { |
| 154 | $matches = []; |
| 155 | |
| 156 | if (preg_match($pattern, $query, $matches) === 1) { |
| 157 | $match = $matches[0]; |
| 158 | $replacement = "___CHUCK_REPLACE_{$i}___"; |
| 159 | assert($match !== '', 'Query placeholder match must not be empty.'); |
| 160 | $swaps[$replacement] = $match; |
| 161 | |
| 162 | $query = preg_replace($pattern, $replacement, $query, limit: 1) ?? $query; |
| 163 | $found = true; |
| 164 | $i++; |
| 165 | |
| 166 | break; |
| 167 | } |
| 168 | } |
| 169 | } while ($found); |
| 171 | return new PreparedQuery($query, $swaps); |
| 172 | } |
| 98 | private static function printQuery(string $query): void |
| 99 | { |
| 100 | $msg = |
| 101 | "\n\n-----------------------------------------------\n\n" |
| 102 | . $query |
| 103 | . "\n------------------------------------------------\n"; |
| 104 | |
| 105 | if (($_SERVER['SERVER_SOFTWARE'] ?? null) !== null) { |
| 111 | echo $msg; |
| 112 | } |
| 113 | } |
| 113 | } |
| 83 | $value = self::env(self::ENV_PRINT); |
| 84 | |
| 85 | return $value !== null && self::flag($value); |
| 85 | return $value !== null && self::flag($value); |
| 85 | return $value !== null && self::flag($value); |
| 86 | } |
| 28 | public static function query(Database $db, string $query, Args $args, ?string $sourcePath): void |
| 29 | { |
| 30 | $print = self::prints(); |
| 31 | $writeTranslated = self::writesTranslated(); |
| 32 | $writeInterpolated = self::writesInterpolated(); |
| 33 | |
| 34 | if (!$print && !$writeTranslated && !$writeInterpolated) { |
| 34 | if (!$print && !$writeTranslated && !$writeInterpolated) { |
| 34 | if (!$print && !$writeTranslated && !$writeInterpolated) { |
| 34 | if (!$print && !$writeTranslated && !$writeInterpolated) { |
| 34 | if (!$print && !$writeTranslated && !$writeInterpolated) { |
| 35 | return; |
| 38 | $path = $writeTranslated || $writeInterpolated |
| 38 | $path = $writeTranslated || $writeInterpolated |
| 38 | $path = $writeTranslated || $writeInterpolated |
| 39 | ? self::sessionPath($sourcePath, $db->getSqlDirs()) |
| 38 | $path = $writeTranslated || $writeInterpolated |
| 39 | ? self::sessionPath($sourcePath, $db->getSqlDirs()) |
| 40 | : null; |
| 38 | $path = $writeTranslated || $writeInterpolated |
| 39 | ? self::sessionPath($sourcePath, $db->getSqlDirs()) |
| 40 | : null; |
| 41 | |
| 42 | if ($writeTranslated) { |
| 43 | self::writeEnv(self::ENV_TRANSLATED, $path, $query); |
| 44 | } |
| 45 | |
| 46 | if (!$print && !$writeInterpolated) { |
| 46 | if (!$print && !$writeInterpolated) { |
| 46 | if (!$print && !$writeInterpolated) { |
| 46 | if (!$print && !$writeInterpolated) { |
| 47 | return; |
| 50 | $interpolated = self::interpolate($query, $args); |
| 51 | |
| 52 | if ($print) { |
| 53 | self::printQuery($interpolated); |
| 54 | } |
| 55 | |
| 56 | if ($writeInterpolated) { |
| 56 | if ($writeInterpolated) { |
| 57 | self::writeEnv(self::ENV_INTERPOLATED, $path, $interpolated); |
| 58 | } |
| 59 | } |
| 59 | } |
| 300 | private static function relativeToRoots(string $sourcePath, array $roots): string |
| 301 | { |
| 302 | $realSource = realpath($sourcePath); |
| 303 | $source = $realSource !== false ? $realSource : $sourcePath; |
| 303 | $source = $realSource !== false ? $realSource : $sourcePath; |
| 303 | $source = $realSource !== false ? $realSource : $sourcePath; |
| 303 | $source = $realSource !== false ? $realSource : $sourcePath; |
| 304 | |
| 305 | foreach ($roots as $root) { |
| 305 | foreach ($roots as $root) { |
| 306 | if (!is_string($root) || $root === '') { |
| 306 | if (!is_string($root) || $root === '') { |
| 306 | if (!is_string($root) || $root === '') { |
| 310 | $realRoot = realpath($root); |
| 311 | $root = $realRoot !== false ? $realRoot : $root; |
| 311 | $root = $realRoot !== false ? $realRoot : $root; |
| 311 | $root = $realRoot !== false ? $realRoot : $root; |
| 311 | $root = $realRoot !== false ? $realRoot : $root; |
| 312 | $prefix = rtrim($root, DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR; |
| 313 | |
| 314 | if (str_starts_with($source, $prefix)) { |
| 314 | if (str_starts_with($source, $prefix)) { |
| 314 | if (str_starts_with($source, $prefix)) { |
| 314 | if (str_starts_with($source, $prefix)) { |
| 315 | $relative = substr($source, strlen($prefix)); |
| 315 | $relative = substr($source, strlen($prefix)); |
| 315 | $relative = substr($source, strlen($prefix)); |
| 315 | $relative = substr($source, strlen($prefix)); |
| 316 | |
| 317 | return $relative === '' ? basename($sourcePath) : $relative; |
| 317 | return $relative === '' ? basename($sourcePath) : $relative; |
| 317 | return $relative === '' ? basename($sourcePath) : $relative; |
| 317 | return $relative === '' ? basename($sourcePath) : $relative; |
| 305 | foreach ($roots as $root) { |
| 393 | $time = self::server('REQUEST_TIME_FLOAT'); |
| 394 | |
| 395 | if ($time !== null && is_numeric($time)) { |
| 395 | if ($time !== null && is_numeric($time)) { |
| 395 | if ($time !== null && is_numeric($time)) { |
| 395 | if ($time !== null && is_numeric($time)) { |
| 395 | if ($time !== null && is_numeric($time)) { |
| 395 | if ($time !== null && is_numeric($time)) { |
| 396 | $date = DateTimeImmutable::createFromFormat('U.u', sprintf('%.6F', (float) $time)); |
| 397 | |
| 398 | if ($date instanceof DateTimeImmutable) { |
| 399 | return $date->format('Ymd-His-u'); |
| 403 | $time = self::server('REQUEST_TIME'); |
| 404 | |
| 405 | if ($time !== null && ctype_digit($time)) { |
| 405 | if ($time !== null && ctype_digit($time)) { |
| 405 | if ($time !== null && ctype_digit($time)) { |
| 406 | return new DateTimeImmutable('@' . $time)->format('Ymd-His') . '-000000'; |
| 409 | return self::$fallbackTime ??= new DateTimeImmutable()->format('Ymd-His-u'); |
| 410 | } |
| 174 | private static function restoreQuery(string $query, PreparedQuery $prep): string |
| 175 | { |
| 176 | foreach ($prep->swaps as $swap => $replacement) { |
| 176 | foreach ($prep->swaps as $swap => $replacement) { |
| 176 | foreach ($prep->swaps as $swap => $replacement) { |
| 177 | $query = str_replace($swap, $replacement, $query); |
| 177 | $query = str_replace($swap, $replacement, $query); |
| 177 | $query = str_replace($swap, $replacement, $query); |
| 176 | foreach ($prep->swaps as $swap => $replacement) { |
| 177 | $query = str_replace($swap, $replacement, $query); |
| 176 | foreach ($prep->swaps as $swap => $replacement) { |
| 177 | $query = str_replace($swap, $replacement, $query); |
| 178 | } |
| 179 | |
| 180 | return $query; |
| 181 | } |
| 465 | private static function safeRelativePath(string $path): string |
| 466 | { |
| 467 | $parts = preg_split('/[\\/]+/', $path, -1, PREG_SPLIT_NO_EMPTY); |
| 468 | |
| 469 | if (!is_array($parts) || count($parts) === 0) { |
| 469 | if (!is_array($parts) || count($parts) === 0) { |
| 469 | if (!is_array($parts) || count($parts) === 0) { |
| 473 | $result = []; |
| 474 | |
| 475 | foreach ($parts as $part) { |
| 475 | foreach ($parts as $part) { |
| 476 | if ($part === '.' || $part === '..') { |
| 476 | if ($part === '.' || $part === '..') { |
| 476 | if ($part === '.' || $part === '..') { |
| 483 | $path = implode(DIRECTORY_SEPARATOR, $result); |
| 483 | $path = implode(DIRECTORY_SEPARATOR, $result); |
| 483 | $path = implode(DIRECTORY_SEPARATOR, $result); |
| 484 | |
| 485 | return $path !== '' ? $path : 'query.sql'; |
| 485 | return $path !== '' ? $path : 'query.sql'; |
| 485 | return $path !== '' ? $path : 'query.sql'; |
| 485 | return $path !== '' ? $path : 'query.sql'; |
| 486 | } |
| 488 | private static function safeSegment(string $segment): string |
| 489 | { |
| 490 | $result = preg_replace('/[^A-Za-z0-9._:-]+/', '_', $segment); |
| 490 | $result = preg_replace('/[^A-Za-z0-9._:-]+/', '_', $segment); |
| 490 | $result = preg_replace('/[^A-Za-z0-9._:-]+/', '_', $segment); |
| 490 | $result = preg_replace('/[^A-Za-z0-9._:-]+/', '_', $segment); |
| 491 | $result = trim($result ?? '', '.'); |
| 491 | $result = trim($result ?? '', '.'); |
| 491 | $result = trim($result ?? '', '.'); |
| 491 | $result = trim($result ?? '', '.'); |
| 492 | |
| 493 | return $result === '' ? '_' : $result; |
| 493 | return $result === '' ? '_' : $result; |
| 493 | return $result === '' ? '_' : $result; |
| 493 | return $result === '' ? '_' : $result; |
| 494 | } |
| 447 | private static function server(string $name): ?string |
| 448 | { |
| 449 | $value = $_SERVER[$name] ?? null; |
| 450 | |
| 451 | if (is_float($value) || is_int($value)) { |
| 451 | if (is_float($value) || is_int($value)) { |
| 451 | if (is_float($value) || is_int($value)) { |
| 452 | return (string) $value; |
| 455 | return is_string($value) && $value !== '' ? $value : null; |
| 455 | return is_string($value) && $value !== '' ? $value : null; |
| 455 | return is_string($value) && $value !== '' ? $value : null; |
| 455 | return is_string($value) && $value !== '' ? $value : null; |
| 455 | return is_string($value) && $value !== '' ? $value : null; |
| 455 | return is_string($value) && $value !== '' ? $value : null; |
| 456 | } |
| 352 | [$key, $session] = self::sessionInfo(); |
| 353 | |
| 354 | if (self::$sessionKey !== $key) { |
| 355 | self::$sessionKey = $key; |
| 356 | self::$session = $session; |
| 357 | self::$counter = 0; |
| 358 | } |
| 359 | |
| 360 | return self::$session ?? $session; |
| 360 | return self::$session ?? $session; |
| 361 | } |
| 366 | $explicit = self::env('QUMA_DEBUG_SESSION'); |
| 367 | |
| 368 | if ($explicit !== null) { |
| 369 | return ['env:' . $explicit, self::sessionLabel($explicit)]; |
| 372 | $method = self::server('REQUEST_METHOD'); |
| 373 | $requestUri = self::server('REQUEST_URI'); |
| 374 | |
| 375 | if ($method !== null || $requestUri !== null) { |
| 375 | if ($method !== null || $requestUri !== null) { |
| 375 | if ($method !== null || $requestUri !== null) { |
| 376 | $method = strtoupper($method ?? 'HTTP'); |
| 377 | $uri = $requestUri ?? self::server('SCRIPT_NAME') ?? self::server('PHP_SELF') ?? '/'; |
| 378 | $time = self::requestTime(); |
| 379 | $label = self::uriLabel($uri); |
| 380 | $hash = self::hash([$time, $method, $uri, (string) getmypid()]); |
| 381 | |
| 382 | return ["http:{$time}:{$method}:{$uri}", "{$time}--{$method}--{$label}--{$hash}"]; |
| 385 | $time = self::requestTime(); |
| 386 | $hash = self::hash([$time, (string) getmypid(), self::argv()]); |
| 387 | |
| 388 | return ["cli:{$time}", "{$time}--cli--{$hash}"]; |
| 389 | } |
| 417 | private static function sessionLabel(string $value): string |
| 418 | { |
| 419 | $label = self::safeSegment($value); |
| 420 | |
| 421 | return strlen($label) <= 96 ? $label : substr($label, 0, 96); |
| 421 | return strlen($label) <= 96 ? $label : substr($label, 0, 96); |
| 421 | return strlen($label) <= 96 ? $label : substr($label, 0, 96); |
| 421 | return strlen($label) <= 96 ? $label : substr($label, 0, 96); |
| 421 | return strlen($label) <= 96 ? $label : substr($label, 0, 96); |
| 421 | return strlen($label) <= 96 ? $label : substr($label, 0, 96); |
| 421 | return strlen($label) <= 96 ? $label : substr($label, 0, 96); |
| 422 | } |
| 325 | private static function sessionPath(?string $sourcePath, array $roots): string |
| 326 | { |
| 327 | return self::session() |
| 328 | . DIRECTORY_SEPARATOR |
| 329 | . self::counter() |
| 330 | . '--' |
| 331 | . self::sourceName($sourcePath, $roots); |
| 332 | } |
| 335 | private static function sourceName(?string $sourcePath, array $roots): string |
| 336 | { |
| 337 | if ($sourcePath === null) { |
| 338 | return 'execute.sql'; |
| 341 | $relative = self::relativeToRoots($sourcePath, $roots); |
| 342 | $dir = dirname($relative); |
| 342 | $dir = dirname($relative); |
| 342 | $dir = dirname($relative); |
| 342 | $dir = dirname($relative); |
| 343 | $name = pathinfo($relative, PATHINFO_FILENAME); |
| 344 | $name = $name !== '' ? $name : 'query'; |
| 344 | $name = $name !== '' ? $name : 'query'; |
| 344 | $name = $name !== '' ? $name : 'query'; |
| 344 | $name = $name !== '' ? $name : 'query'; |
| 345 | $path = ($dir === '.' ? '' : $dir . DIRECTORY_SEPARATOR) . $name . '.sql'; |
| 345 | $path = ($dir === '.' ? '' : $dir . DIRECTORY_SEPARATOR) . $name . '.sql'; |
| 345 | $path = ($dir === '.' ? '' : $dir . DIRECTORY_SEPARATOR) . $name . '.sql'; |
| 345 | $path = ($dir === '.' ? '' : $dir . DIRECTORY_SEPARATOR) . $name . '.sql'; |
| 346 | |
| 347 | return preg_replace('/[\\/]+/', '--', $path) ?? 'query.sql'; |
| 348 | } |
| 424 | private static function uriLabel(string $uri): string |
| 425 | { |
| 426 | $path = parse_url($uri, PHP_URL_PATH); |
| 427 | $path = is_string($path) && $path !== '' ? $path : '/'; |
| 427 | $path = is_string($path) && $path !== '' ? $path : '/'; |
| 427 | $path = is_string($path) && $path !== '' ? $path : '/'; |
| 427 | $path = is_string($path) && $path !== '' ? $path : '/'; |
| 427 | $path = is_string($path) && $path !== '' ? $path : '/'; |
| 427 | $path = is_string($path) && $path !== '' ? $path : '/'; |
| 428 | $label = trim($path, '/'); |
| 428 | $label = trim($path, '/'); |
| 428 | $label = trim($path, '/'); |
| 428 | $label = trim($path, '/'); |
| 429 | |
| 430 | if ($label === '') { |
| 431 | return 'root'; |
| 434 | $label = preg_replace('/[^A-Za-z0-9._:-]+/', '-', $label) ?? 'request'; |
| 435 | $label = trim($label, '-.'); |
| 435 | $label = trim($label, '-.'); |
| 435 | $label = trim($label, '-.'); |
| 435 | $label = trim($label, '-.'); |
| 436 | $label = $label !== '' ? $label : 'request'; |
| 436 | $label = $label !== '' ? $label : 'request'; |
| 436 | $label = $label !== '' ? $label : 'request'; |
| 436 | $label = $label !== '' ? $label : 'request'; |
| 437 | |
| 438 | return strlen($label) <= 64 ? $label : substr($label, 0, 64); |
| 438 | return strlen($label) <= 64 ? $label : substr($label, 0, 64); |
| 438 | return strlen($label) <= 64 ? $label : substr($label, 0, 64); |
| 438 | return strlen($label) <= 64 ? $label : substr($label, 0, 64); |
| 438 | return strlen($label) <= 64 ? $label : substr($label, 0, 64); |
| 438 | return strlen($label) <= 64 ? $label : substr($label, 0, 64); |
| 438 | return strlen($label) <= 64 ? $label : substr($label, 0, 64); |
| 439 | } |
| 115 | private static function value(mixed $value): string |
| 116 | { |
| 117 | if (is_string($value)) { |
| 118 | return "'" . $value . "'"; |
| 121 | if (is_array($value)) { |
| 122 | $encoded = json_encode($value); |
| 123 | |
| 124 | return "'" . ($encoded !== false ? $encoded : '[]') . "'"; |
| 124 | return "'" . ($encoded !== false ? $encoded : '[]') . "'"; |
| 124 | return "'" . ($encoded !== false ? $encoded : '[]') . "'"; |
| 124 | return "'" . ($encoded !== false ? $encoded : '[]') . "'"; |
| 127 | if (is_null($value)) { |
| 128 | return 'NULL'; |
| 131 | if (is_bool($value)) { |
| 132 | return $value ? 'true' : 'false'; |
| 132 | return $value ? 'true' : 'false'; |
| 132 | return $value ? 'true' : 'false'; |
| 132 | return $value ? 'true' : 'false'; |
| 135 | return (string) $value; |
| 136 | } |
| 283 | string $dir, |
| 284 | string $relative, |
| 285 | string $source, |
| 286 | ): void { |
| 287 | $path = $dir . DIRECTORY_SEPARATOR . self::safeRelativePath($relative); |
| 288 | $targetDir = dirname($path); |
| 288 | $targetDir = dirname($path); |
| 288 | $targetDir = dirname($path); |
| 288 | $targetDir = dirname($path); |
| 289 | |
| 290 | if (!is_dir($targetDir) && !mkdir($targetDir, 0o775, true) && !is_dir($targetDir)) { |
| 290 | if (!is_dir($targetDir) && !mkdir($targetDir, 0o775, true) && !is_dir($targetDir)) { |
| 290 | if (!is_dir($targetDir) && !mkdir($targetDir, 0o775, true) && !is_dir($targetDir)) { |
| 290 | if (!is_dir($targetDir) && !mkdir($targetDir, 0o775, true) && !is_dir($targetDir)) { |
| 290 | if (!is_dir($targetDir) && !mkdir($targetDir, 0o775, true) && !is_dir($targetDir)) { |
| 294 | if (file_put_contents($path, $source, LOCK_EX) === false) { |
| 297 | } |
| 267 | private static function writeEnv(string $name, ?string $relative, string $source): void |
| 268 | { |
| 269 | if ($relative === null) { |
| 273 | $dir = self::dir($name); |
| 274 | |
| 275 | if ($dir === null) { |
| 279 | self::write($dir, $relative, $source); |
| 280 | } |
| 95 | return self::env(self::ENV_INTERPOLATED) !== null; |
| 96 | } |
| 90 | return self::env(self::ENV_TRANSLATED) !== null; |
| 91 | } |
| 190 | static function (mixed $value, int|string $key) use (&$map): void { |
| 191 | if (is_string($key) && $key !== '') { |
| 191 | if (is_string($key) && $key !== '') { |
| 191 | if (is_string($key) && $key !== '') { |
| 192 | $map[':' . $key] = self::value($value); |
| 193 | } |
| 194 | }, |
| 194 | }, |
| 207 | static function (mixed $value) use (&$result): void { |
| 208 | $replaced = preg_replace('/\\?/', self::value($value), $result, 1); |
| 209 | $result = $replaced ?? $result; |
| 210 | }, |