Files

140 lines
4.0 KiB
PHP
Raw Permalink Normal View History

2026-06-14 14:00:24 +08:00
<?php
/**
* PHP 8 运行环境检测(版本、扩展、可选语法扫描)
*/
class CloakPhpCompat
{
/** @var string 项目要求的最低 PHP 版本 */
public const MIN_PHP_VERSION = '8.0.0';
/** @var string[] 必需扩展 */
public const REQUIRED_EXTENSIONS = [
'curl',
'json',
'mbstring',
'mysqli',
'session',
'dom',
];
/** @var string[] 推荐扩展(缺失时警告,不阻断) */
public const RECOMMENDED_EXTENSIONS = [
'redis',
'openssl',
];
/**
* 入口引导时调用:不满足最低版本则终止并输出说明
*
* @return void
*/
public static function ensureRuntime()
{
$report = self::buildReport();
if ($report['ok']) {
return;
}
$lines = ['Cloaka 需要 PHP ' . self::MIN_PHP_VERSION . ' 或更高版本。', ''];
foreach ($report['errors'] as $err) {
$lines[] = ' - ' . $err;
}
$lines[] = '';
$lines[] = '当前 PHP' . PHP_VERSION;
$lines[] = '请升级 PHP 或运行:php tools/regression_test.php';
$message = implode("\n", $lines);
if (PHP_SAPI === 'cli') {
fwrite(STDERR, $message . "\n");
exit(1);
}
if (!headers_sent()) {
header('Content-Type: text/plain; charset=utf-8', true, 503);
}
echo $message;
exit(1);
}
/**
* @return array{ok:bool,php_version:string,errors:array,warnings:array,extensions:array}
*/
public static function buildReport()
{
$errors = [];
$warnings = [];
if (version_compare(PHP_VERSION, self::MIN_PHP_VERSION, '<')) {
$errors[] = 'PHP 版本过低(需要 >= ' . 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<int,string>}
*/
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];
}
}