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