= ' . self::MIN_PHP_VERSION . ',当前 ' . PHP_VERSION . ')'; } foreach (self::REQUIRED_EXTENSIONS as $ext) { if (!extension_loaded($ext)) { $errors[] = '缺少必需扩展:' . $ext; } } foreach (self::RECOMMENDED_EXTENSIONS as $ext) { if (!extension_loaded($ext)) { $warnings[] = '缺少推荐扩展:' . $ext . '(部分功能将降级)'; } } return [ 'ok' => empty($errors), 'php_version' => PHP_VERSION, 'errors' => $errors, 'warnings' => $warnings, 'extensions' => array_values(array_filter( array_merge(self::REQUIRED_EXTENSIONS, self::RECOMMENDED_EXTENSIONS), 'extension_loaded' )), ]; } /** * 对项目 PHP 文件执行 php -l(仅 CLI) * * @param string|null $root 项目根目录 * @return array{ok:bool,failures:array} */ public static function lintProject($root = null) { $root = $root ?: dirname(__DIR__); $failures = []; if (PHP_SAPI !== 'cli') { return ['ok' => true, 'failures' => [], 'skipped' => 'not_cli']; } $iterator = new RecursiveIteratorIterator( new RecursiveDirectoryIterator($root, FilesystemIterator::SKIP_DOTS) ); $skipDirs = ['vendor', 'node_modules', '.git']; foreach ($iterator as $file) { /** @var SplFileInfo $file */ if (!$file->isFile() || $file->getExtension() !== 'php') { continue; } $path = $file->getPathname(); foreach ($skipDirs as $skip) { if (strpos($path, DIRECTORY_SEPARATOR . $skip . DIRECTORY_SEPARATOR) !== false) { continue 2; } } $cmd = escapeshellarg(PHP_BINARY) . ' -l ' . escapeshellarg($path) . ' 2>&1'; $out = []; exec($cmd, $out, $code); if ($code !== 0) { $failures[] = $path . ': ' . implode(' ', $out); } } return ['ok' => empty($failures), 'failures' => $failures]; } }