tools/phpstan/constants.stub.php * * @package XC_VM */ $root = dirname(__DIR__, 2) . '/src'; $dir = new RecursiveIteratorIterator(new RecursiveDirectoryIterator($root, FilesystemIterator::SKIP_DOTS)); $consts = []; foreach ($dir as $f) { if ($f->getExtension() !== 'php') { continue; } $src = @file_get_contents($f->getPathname()); if ($src === false || strpos($src, 'define(') === false) { continue; } $tokens = token_get_all($src); $n = count($tokens); for ($i = 0; $i < $n; $i++) { $t = $tokens[$i]; if (!(is_array($t) && $t[0] === T_STRING && $t[1] === 'define')) { continue; } $name = null; $k = $i + 1; for (; $k < $n && $k < $i + 40; $k++) { $tk = $tokens[$k]; if (is_array($tk) && $tk[0] === T_CONSTANT_ENCAPSED_STRING) { $name = trim($tk[1], "'\""); break; } if ($tk === ')') { break; } } if (!$name || !preg_match('/^[A-Z_][A-Z0-9_]*$/', $name) || isset($consts[$name])) { continue; } $hasIntLit = $hasStrLit = $hasConcat = $hasBool = false; $isIntOnly = true; $seen = false; $d = 0; for ($m = $k + 1; $m < $n && $m < $k + 80; $m++) { $tm = $tokens[$m]; if ($tm === '(') { $d++; continue; } if ($tm === ')') { if ($d === 0) break; $d--; continue; } if ($tm === ',' && $d === 0) { $seen = true; continue; } if (!$seen) { continue; } if (is_array($tm)) { if ($tm[0] === T_LNUMBER || $tm[0] === T_DNUMBER) { $hasIntLit = true; } elseif ($tm[0] === T_CONSTANT_ENCAPSED_STRING) { $hasStrLit = true; $isIntOnly = false; } elseif ($tm[0] === T_STRING && in_array(strtolower($tm[1]), ['true', 'false'], true)) { $hasBool = true; $isIntOnly = false; } elseif ($tm[0] === T_WHITESPACE) { /* skip */ } else { $isIntOnly = false; } } else { if ($tm === '.') { $hasConcat = true; $isIntOnly = false; } else { $isIntOnly = false; } } } if ($hasConcat || $hasStrLit) { $type = 'string'; } elseif ($hasBool && !$hasIntLit) { $type = 'bool'; } elseif ($hasIntLit && $isIntOnly) { $type = 'int'; } else { $type = 'mixed'; } $consts[$name] = $type; } } // Explicit type overrides for constants whose value the heuristic cannot // classify (defined from variables or function calls). Keeps the stub accurate // and prevents false null/mixed positives at call sites. $overrides = [ 'HOST' => 'string', // trim(explode(':', HTTP_HOST)[0]) 'PAGE_NAME' => 'string', 'PHP_ERRORS' => 'bool', // dev-mode flag ]; foreach ($overrides as $name => $type) { if (isset($consts[$name])) { $consts[$name] = $type; } } ksort($consts); echo " $type) { // Use mt_rand()-based expressions so PHPStan infers a GENERAL type // (int/string/bool) and never constant-folds to a literal (0/'') — a // literal would create false positives like division-by-zero or // always-false comparisons at call sites. $expr = match ($type) { 'string' => "(string) mt_rand()", 'int' => "mt_rand()", 'bool' => "(bool) mt_rand()", default => "json_decode((string) mt_rand(), true)", }; echo "if (!defined('$name')) define('$name', $expr);\n"; }