PHP-Parser日志系统:调试信息记录

【免费下载链接】PHP-Parser 一个用PHP编写的PHP解析器 【免费下载链接】PHP-Parser 项目地址: https://gitcode.com/GitHub_Trending/ph/PHP-Parser

概述

PHP-Parser作为PHP代码解析和静态分析的核心工具,其强大的错误处理和调试信息记录能力是开发者进行代码分析和转换的关键支撑。本文将深入探讨PHP-Parser的日志系统架构,展示如何有效捕获、记录和利用调试信息来提升开发效率。

错误处理核心组件

Error类:异常信息封装

PHP-Parser通过PhpParser\Error类封装所有解析过程中产生的错误信息。该类继承自RuntimeException,提供了丰富的错误元数据:

<?php
use PhpParser\Error;

try {
    $ast = $parser->parse($code);
} catch (Error $error) {
    // 获取基础错误信息
    echo "错误消息: " . $error->getRawMessage() . "\n";
    echo "发生行号: " . $error->getStartLine() . "\n";
    
    // 检查是否包含列信息
    if ($error->hasColumnInfo()) {
        echo "开始列: " . $error->getStartColumn($code) . "\n";
        echo "结束列: " . $error->getEndColumn($code) . "\n";
    }
    
    // 获取完整格式化消息
    echo "完整错误: " . $error->getMessageWithColumnInfo($code) . "\n";
}

ErrorHandler接口:错误处理策略

PHP-Parser提供了灵活的ErrorHandler机制,支持不同的错误处理策略:

mermaid

错误收集器实战应用

基础错误收集模式

<?php
use PhpParser\ParserFactory;
use PhpParser\ErrorHandler\Collecting;

$parser = (new ParserFactory())->createForHostVersion();
$errorHandler = new Collecting();

$code = '<?php function test($foo { return $foo; }'; // 缺少右括号

$stmts = $parser->parse($code, $errorHandler);

if ($errorHandler->hasErrors()) {
    echo "发现 " . count($errorHandler->getErrors()) . " 个错误:\n";
    
    foreach ($errorHandler->getErrors() as $index => $error) {
        echo sprintf(
            "错误 %d: %s (行%d)\n",
            $index + 1,
            $error->getRawMessage(),
            $error->getStartLine()
        );
    }
}

// 即使有错误,仍可能获得部分AST
if ($stmts !== null) {
    echo "成功解析部分AST,包含 " . count($stmts) . " 个语句\n";
}

高级错误分析与日志记录

<?php
class DetailedErrorLogger {
    private $logFile;
    private $errorHandler;
    
    public function __construct(string $logFile = 'php_parser_errors.log') {
        $this->logFile = $logFile;
        $this->errorHandler = new Collecting();
    }
    
    public function parseWithDetailedLogging(string $code, string $filename = 'unknown'): ?array {
        $parser = (new ParserFactory())->createForHostVersion();
        $stmts = $parser->parse($code, $this->errorHandler);
        
        $this->logErrors($code, $filename);
        return $stmts;
    }
    
    private function logErrors(string $code, string $filename): void {
        if (!$this->errorHandler->hasErrors()) {
            return;
        }
        
        $logEntry = [
            'timestamp' => date('Y-m-d H:i:s'),
            'filename' => $filename,
            'error_count' => count($this->errorHandler->getErrors()),
            'errors' => []
        ];
        
        foreach ($this->errorHandler->getErrors() as $error) {
            $errorInfo = [
                'message' => $error->getRawMessage(),
                'start_line' => $error->getStartLine(),
                'end_line' => $error->getEndLine(),
            ];
            
            if ($error->hasColumnInfo()) {
                $errorInfo['start_column'] = $error->getStartColumn($code);
                $errorInfo['end_column'] = $error->getEndColumn($code);
            }
            
            $logEntry['errors'][] = $errorInfo;
        }
        
        file_put_contents(
            $this->logFile,
            json_encode($logEntry, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE) . "\n",
            FILE_APPEND
        );
        
        $this->errorHandler->clearErrors();
    }
    
    public function getErrorHandler(): Collecting {
        return $this->errorHandler;
    }
}

// 使用示例
$logger = new DetailedErrorLogger();
$code = '<?php class Test { public function method() }'; // 方法体缺失

$ast = $logger->parseWithDetailedLogging($code, 'test.php');

错误恢复与部分AST解析

PHP-Parser的强大之处在于其错误恢复能力,即使在存在语法错误的情况下,仍能生成部分AST:

<?php
use PhpParser\NodeDumper;

$problematicCode = <<<'CODE'
<?php
class User {
    public $name;
    public function getName() {
        return $this->name
    } // 缺少分号
    public function setName($name) {
        $this->name = $name;
    }
}
CODE;

$parser = (new ParserFactory())->createForHostVersion();
$errorHandler = new Collecting();

$ast = $parser->parse($problematicCode, $errorHandler);

if ($ast !== null) {
    $dumper = new NodeDumper();
    echo "部分AST结构:\n";
    echo $dumper->dump($ast) . "\n";
    
    // 检查AST中是否包含错误节点
    $errorNodes = [];
    foreach ($ast as $node) {
        if ($node instanceof \PhpParser\Node\Expr\Error) {
            $errorNodes[] = $node;
        }
    }
    
    echo "发现 " . count($errorNodes) . " 个错误表达式节点\n";
}

if ($errorHandler->hasErrors()) {
    echo "解析错误详情:\n";
    foreach ($errorHandler->getErrors() as $error) {
        echo "- " . $error->getMessageWithColumnInfo($problematicCode) . "\n";
    }
}

自定义错误处理器开发

实现高级日志策略

<?php
use PhpParser\Error;
use PhpParser\ErrorHandler;

class AdvancedLoggingErrorHandler implements ErrorHandler {
    private $errors = [];
    private $logCallback;
    
    public function __construct(callable $logCallback = null) {
        $this->logCallback = $logCallback ?? function(Error $error) {
            error_log('PHP-Parser Error: ' . $error->getMessage());
        };
    }
    
    public function handleError(Error $error): void {
        $this->errors[] = $error;
        
        // 执行自定义日志逻辑
        ($this->logCallback)($error);
    }
    
    public function getErrors(): array {
        return $this->errors;
    }
    
    public function hasErrors(): bool {
        return !empty($this->errors);
    }
    
    public function clearErrors(): void {
        $this->errors = [];
    }
    
    public function getErrorSummary(): array {
        $summary = [];
        foreach ($this->errors as $error) {
            $type = $this->classifyError($error->getRawMessage());
            if (!isset($summary[$type])) {
                $summary[$type] = 0;
            }
            $summary[$type]++;
        }
        return $summary;
    }
    
    private function classifyError(string $message): string {
        if (strpos($message, 'unexpected') !== false) {
            return 'syntax_unexpected';
        } elseif (strpos($message, 'expecting') !== false) {
            return 'syntax_expecting';
        } elseif (strpos($message, 'missing') !== false) {
            return 'syntax_missing';
        } else {
            return 'other';
        }
    }
}

// 使用自定义错误处理器
$customHandler = new AdvancedLoggingErrorHandler(function(Error $error) {
    // 集成到现有日志系统
    \MyApp\Logger::error('PHP解析错误', [
        'message' => $error->getRawMessage(),
        'line' => $error->getStartLine(),
    ]);
});

$parser = (new ParserFactory())->createForHostVersion();
$ast = $parser->parse($code, $customHandler);

if ($customHandler->hasErrors()) {
    $summary = $customHandler->getErrorSummary();
    echo "错误类型统计:\n";
    print_r($summary);
}

错误信息分析与报告生成

生成详细的错误分析报告

<?php
class ErrorAnalysisReport {
    public static function generateReport(array $errors, string $sourceCode): string {
        $report = "# PHP-Parser 错误分析报告\n\n";
        $report .= "生成时间: " . date('Y-m-d H:i:s') . "\n";
        $report .= "错误总数: " . count($errors) . "\n\n";
        
        $report .= "## 错误详情\n\n";
        
        foreach ($errors as $index => $error) {
            $report .= self::formatErrorDetail($error, $sourceCode, $index + 1);
        }
        
        $report .= "## 错误统计\n\n";
        $report .= self::generateErrorStatistics($errors);
        
        $report .= "## 建议修复\n\n";
        $report .= self::generateFixSuggestions($errors);
        
        return $report;
    }
    
    private static function formatErrorDetail(Error $error, string $code, int $number): string {
        $detail = "### 错误 {$number}\n\n";
        $detail .= "- **消息**: `" . $error->getRawMessage() . "`\n";
        $detail .= "- **位置**: 行 " . $error->getStartLine();
        
        if ($error->hasColumnInfo()) {
            $detail .= ", 列 " . $error->getStartColumn($code);
            $detail .= " - " . $error->getEndColumn($code) . "\n";
        } else {
            $detail .= "\n";
        }
        
        // 显示错误周围的代码上下文
        $context = self::getCodeContext($code, $error->getStartLine());
        if ($context) {
            $detail .= "- **代码上下文**:\n\n```php\n" . $context . "\n```\n\n";
        }
        
        return $detail;
    }
    
    private static function getCodeContext(string $code, int $line, int $contextLines = 3): string {
        $lines = explode("\n", $code);
        $start = max(0, $line - $contextLines - 1);
        $end = min(count($lines), $line + $contextLines);
        
        $context = [];
        for ($i = $start; $i < $end; $i++) {
            $lineNumber = $i + 1;
            $prefix = $lineNumber === $line ? '> ' : '  ';
            $context[] = $prefix . $lineNumber . ': ' . $lines[$i];
        }
        
        return implode("\n", $context);
    }
    
    private static function generateErrorStatistics(array $errors): string {
        $stats = [
            'syntax' => 0,
            'semantic' => 0,
            'other' => 0
        ];
        
        foreach ($errors as $error) {
            $message = strtolower($error->getRawMessage());
            if (strpos($message, 'unexpected') !== false || 
                strpos($message, 'expecting') !== false) {
                $stats['syntax']++;
            } elseif (strpos($message, 'cannot') !== false ||
                     strpos($message, 'invalid') !== false) {
                $stats['semantic']++;
            } else {
                $stats['other']++;
            }
        }
        
        return "| 错误类型 | 数量 | 百分比 |\n|----------|------|--------|\n" .
               "| 语法错误 | {$stats['syntax']} | " . round($stats['syntax']/count($errors)*100, 1) . "% |\n" .
               "| 语义错误 | {$stats['semantic']} | " . round($stats['semantic']/count($errors)*100, 1) . "% |\n" .
               "| 其他错误 | {$stats['other']} | " . round($stats['other']/count($errors)*100, 1) . "% |\n\n";
    }
    
    private static function generateFixSuggestions(array $errors): string {
        $suggestions = [];
        
        foreach ($errors as $error) {
            $message = $error->getRawMessage();
            
            if (strpos($message, 'unexpected \'{\'') !== false) {
                $suggestions[] = "- 检查是否缺少分号或括号不匹配";
            } elseif (strpos($message, 'expecting \';\'') !== false) {
                $suggestions[] = "- 在语句末尾添加分号";
            } elseif (strpos($message, 'unexpected $end') !== false) {
                $suggestions[] = "- 检查代码是否完整,可能缺少关闭括号或引号";
            }
        }
        
        return !empty($suggestions) ? implode("\n", array_unique($suggestions)) . "\n" : "无具体建议\n";
    }
}

// 使用错误分析报告
$errorHandler = new Collecting();
$parser->parse($code, $errorHandler);

if ($errorHandler->hasErrors()) {
    $report = ErrorAnalysisReport::generateReport(
        $errorHandler->getErrors(),
        $code
    );
    
    file_put_contents('error_analysis.md', $report);
    echo "已生成错误分析报告: error_analysis.md\n";
}

集成到CI/CD流水线

自动化代码质量检查

<?php
class CodeQualityChecker {
    private $parser;
    private $errorHandler;
    private $qualityThreshold;
    
    public function __construct(float $qualityThreshold = 0.9) {
        $this->parser = (new ParserFactory())->createForHostVersion();
        $this->errorHandler = new Collecting();
        $this->qualityThreshold = $qualityThreshold;
    }
    
    public function checkFile(string $filepath): array {
        if (!file_exists($filepath)) {
            throw new \InvalidArgumentException("文件不存在: {$filepath}");
        }
        
        $code = file_get_contents($filepath);
        $ast = $this->parser->parse($code, $this->errorHandler);
        
        $result = [
            'file' => $filepath,
            'parseable' => $ast !== null,
            'error_count' => count($this->errorHandler->getErrors()),
            'errors' => $this->errorHandler->getErrors(),
            'quality_score' => $this->calculateQualityScore($ast, $code),
            'passed' => false
        ];
        
        $result['passed'] = $result['quality_score'] >= $this->qualityThreshold;
        
        $this->errorHandler->clearErrors();
        return $result;
    }
    
    private function calculateQualityScore(?array $ast, string $code): float {
        if ($ast === null) {
            return 0.0;
        }
        
        $errorCount = count($this->errorHandler->getErrors());
        $lineCount = substr_count($code, "\n") + 1;
        
        if ($lineCount === 0) {
            return 1.0;
        }
        
        // 基于错误密度计算质量分数
        $errorDensity = $errorCount / $lineCount;
        $score = max(0, 1 - $errorDensity * 10); // 每行最多容忍0.1个错误
        
        return round($score, 2);
    }
    
    public function checkDirectory(string $directory, string $pattern = '*.php'): array {
        $files = glob($directory . '/' . $pattern);
        $results = [];
        
        foreach ($files as $file) {
            if (is_file($file)) {
                $results[] = $this->checkFile($file);
            }
        }
        
        return $results;
    }
    
    public function generateQualityReport(array $results): string {
        $totalFiles = count($results);
        $passedFiles = count(array_filter($results, fn($r) => $r['passed']));
        $totalErrors = array_sum(array_column($results, 'error_count'));
        
        $report = "# 代码质量检查报告\n\n";
        $report .= "检查时间: " . date('Y-m-d H:i:s') . "\n";
        $report .= "文件总数: {$totalFiles}\n";
        $report .= "通过检查: {$passedFiles}\n";
        $report .= "未通过: " . ($totalFiles - $passedFiles) . "\n";
        $report .= "总错误数: {$totalErrors}\n\n";
        
        $report .= "## 详细结果\n\n";
        foreach ($results as $result) {
            $status = $result['passed'] ? '✅' : '❌';
            $report .= "{$status} {$result['file']} - 分数: {$result['quality_score']}, 错误: {$result['error_count']}\n";
        }
        
        return $report;
    }
}

// CI/CD集成示例
$checker = new CodeQualityChecker(0.8); // 质量阈值80%
$results = $checker->checkDirectory('/path/to/src');
$report = $checker->generateQualityReport($results);

file_put_contents('quality_report.md', $report);

// 如果有未通过的文件,退出码非零
$failed = count(array_filter($results, fn($r) => !$r['passed']));
if ($failed > 0) {
    exit(1);
}

总结

PHP-Parser的日志系统提供了强大的错误处理和调试信息记录能力。通过合理的错误收集、分析和报告机制,开发者可以:

  1. 实时监控解析过程中的错误和异常
  2. 精准定位代码中的语法和语义问题
  3. 生成详细的错误分析报告和质量评估
  4. 集成到CI/CD流程中实现自动化代码检查

掌握这些技术后,您将能够构建更加健壮和可靠的代码分析工具,显著提升开发效率和代码质量。

【免费下载链接】PHP-Parser 一个用PHP编写的PHP解析器 【免费下载链接】PHP-Parser 项目地址: https://gitcode.com/GitHub_Trending/ph/PHP-Parser

更多推荐