140 lines
4.0 KiB
PHP
Executable File
140 lines
4.0 KiB
PHP
Executable File
<?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];
|
||
}
|
||
}
|