PHP libxml_get_last_error()函数

PHP libxml_get_last_error() 函数

libxml_get_last_error() 函数用于获取最后一个 libxml 错误。与 libxml_get_errors() 返回所有错误数组不同,此函数只返回最近发生的单个错误。

提示: 这个函数非常适合在只需要知道是否有错误发生,而不需要全部错误细节的场景下使用。例如,在日志记录、快速错误检查或只需要最后错误信息的调试场景中。
注意: 要使用此函数,必须先调用 libxml_use_internal_errors(true) 启用内部错误处理模式。

语法

libxml_get_last_error ( ) : LibXMLError|false

该函数没有参数。

返回值

函数返回一个 LibXMLError 对象,如果没有错误发生则返回 FALSE

LibXMLError 对象包含以下属性:

属性 类型 说明
level int 错误级别:LIBXML_ERR_WARNING、LIBXML_ERR_ERROR、LIBXML_ERR_FATAL
code int 错误代码
column int 发生错误的列号(如果可用)
message string 错误消息文本
file string 文件名(如果解析的是文件)
line int 发生错误的行号
错误级别说明:
  • LIBXML_ERR_WARNING (1) - 警告,不影响解析但可能有潜在问题
  • LIBXML_ERR_ERROR (2) - 错误,文档不符合规范但可能被解析
  • LIBXML_ERR_FATAL (3) - 致命错误,文档无法解析

示例 1:基本用法 - 获取最后一个XML解析错误

下面的示例演示如何使用 libxml_get_last_error() 获取最后一个XML解析错误。

<?php
// 启用内部错误处理
libxml_use_internal_errors(true);

// 清除之前的错误
libxml_clear_errors();

// 创建一个格式错误的XML
$malformed_xml = '<root>
    <item>第一个项目</item>
    <item>第二个项目<item>  <!-- 缺少结束标签 -->
    <item attribute="value">第三个项目</item>
</root>';

echo "<h4>解析格式错误的XML:</h4>";
echo "<pre>" . htmlspecialchars($malformed_xml) . "</pre>";

// 尝试解析XML
$doc = new DOMDocument();
$result = $doc->loadXML($malformed_xml);

if (!$result) {
    echo "<div class='alert alert-danger'>XML解析失败!</div>";

    // 获取最后一个错误
    $last_error = libxml_get_last_error();

    if ($last_error !== false) {
        echo "<h5>最后一个错误信息:</h5>";

        // 错误级别
        $level = '';
        switch ($last_error->level) {
            case LIBXML_ERR_WARNING: $level = '警告'; break;
            case LIBXML_ERR_ERROR: $level = '错误'; break;
            case LIBXML_ERR_FATAL: $level = '致命错误'; break;
            default: $level = '未知级别'; break;
        }

        echo "<div class='card" . ($last_error->level === LIBXML_ERR_FATAL ? ' border-danger' : ' border-warning') . "'>";
        echo "<div class='card-header" . ($last_error->level === LIBXML_ERR_FATAL ? ' bg-danger text-white' : ' bg-warning') . "'>";
        echo "最后错误 - $level (代码: {$last_error->code})";
        echo "</div>";
        echo "<div class='card-body'>";
        echo "<p class='card-text'><strong>错误消息:</strong> " . htmlspecialchars(trim($last_error->message)) . "</p>";
        echo "<p class='card-text'><strong>位置:</strong> 行 {$last_error->line}, 列 " . ($last_error->column ?: '未知') . "</p>";
        echo "</div></div>";

        // 显示所有错误数量作为对比
        $all_errors = libxml_get_errors();
        echo "<p class='mt-3'>总错误数: " . count($all_errors) . " (最后一个错误如上)</p>";
    } else {
        echo "<div class='alert alert-info'>没有获取到错误信息</div>";
    }

    // 清除错误缓冲区
    libxml_clear_errors();
} else {
    echo "<div class='alert alert-success'>XML解析成功!</div>";
}
?>

示例 2:与 libxml_get_errors() 的比较

下面的示例演示 libxml_get_last_error()libxml_get_errors() 的区别。

<?php
// 启用内部错误处理
libxml_use_internal_errors(true);

// 清除之前的错误
libxml_clear_errors();

// 创建一个包含多个错误的XML
$multi_error_xml = '<?xml version="1.0"?>
<root>
    <item>项目1</item>
    <item>项目2<item>  <!-- 错误1:缺少结束标签 -->
    <item attribute=value>项目3</item>  <!-- 错误2:属性值缺少引号 -->
    <item>项目4</item>
</root>';

echo "<h4>解析包含多个错误的XML:</h4>";
echo "<pre>" . htmlspecialchars($multi_error_xml) . "</pre>";

// 尝试解析XML
$doc = new DOMDocument();
$result = $doc->loadXML($multi_error_xml);

if (!$result) {
    echo "<div class='alert alert-danger'>XML解析失败!</div>";

    echo "<div class='row'>";

    // 左边:显示所有错误
    echo "<div class='col-md-6'>";
    echo "<h5>libxml_get_errors() - 所有错误</h5>";

    $all_errors = libxml_get_errors();
    echo "<p>错误总数: " . count($all_errors) . "</p>";

    if (!empty($all_errors)) {
        echo "<table class='table table-sm table-bordered'>";
        echo "<thead><tr><th>#</th><th>级别</th><th>错误消息</th><th>行号</th></tr></thead>";
        echo "<tbody>";

        foreach ($all_errors as $index => $error) {
            $level = $error->level === LIBXML_ERR_WARNING ? '警告' :
                    ($error->level === LIBXML_ERR_ERROR ? '错误' : '致命错误');

            echo "<tr" . ($index === count($all_errors) - 1 ? " class='table-warning'" : "") . ">";
            echo "<td>" . ($index + 1) . "</td>";
            echo "<td><span class='badge " . ($level === '致命错误' ? 'bg-danger' : ($level === '错误' ? 'bg-warning' : 'bg-info')) . "'>$level</span></td>";

            $message = trim($error->message);
            if (strlen($message) > 50) {
                $message = substr($message, 0, 50) . '...';
            }

            echo "<td>" . htmlspecialchars($message) . "</td>";
            echo "<td>{$error->line}</td>";
            echo "</tr>";
        }

        echo "</tbody></table>";
    }
    echo "</div>";

    // 右边:显示最后一个错误
    echo "<div class='col-md-6'>";
    echo "<h5>libxml_get_last_error() - 最后一个错误</h5>";

    $last_error = libxml_get_last_error();

    if ($last_error !== false) {
        $level = $last_error->level === LIBXML_ERR_WARNING ? '警告' :
                ($last_error->level === LIBXML_ERR_ERROR ? '错误' : '致命错误');

        echo "<div class='card border-warning'>";
        echo "<div class='card-header bg-warning'>";
        echo "最后一个错误 - $level (代码: {$last_error->code})";
        echo "</div>";
        echo "<div class='card-body'>";
        echo "<p class='card-text'><strong>错误消息:</strong> " . htmlspecialchars(trim($last_error->message)) . "</p>";
        echo "<p class='card-text'><strong>位置:</strong> 行 {$last_error->line}, 列 " . ($last_error->column ?: '未知') . "</p>";
        echo "<p class='card-text'><strong>错误顺序:</strong> 这是第 " . count($all_errors) . " 个(最后一个)错误</p>";
        echo "</div></div>";
    } else {
        echo "<div class='alert alert-info'>没有获取到错误信息</div>";
    }
    echo "</div>";

    echo "</div>"; // 结束row

    // 比较说明
    echo "<div class='alert alert-info mt-3'>";
    echo "<h6>两个函数的区别:</h6>";
    echo "<table class='table table-sm'>";
    echo "<tr><th>函数</th><th>返回值</th><th>适用场景</th></tr>";
    echo "<tr><td>libxml_get_errors()</td><td>错误数组</td><td>需要分析所有错误、统计错误类型、显示错误列表</td></tr>";
    echo "<tr><td>libxml_get_last_error()</td><td>单个错误对象</td><td>快速检查是否有错误、记录最后错误、简单错误处理</td></tr>";
    echo "</table>";
    echo "</div>";

    // 清除错误缓冲区
    libxml_clear_errors();
}
?>

示例 3:简单错误检查和日志记录

下面的示例演示如何使用 libxml_get_last_error() 进行简单的错误检查和日志记录。

<?php
/**
 * 简单的XML解析器,使用libxml_get_last_error()进行错误处理
 */
class SimpleXmlParser {

    /**
     * 解析XML字符串,返回解析结果和错误信息
     */
    public function parseXml($xml_string, $source_name = '') {
        // 启用内部错误处理
        libxml_use_internal_errors(true);

        // 清除之前的错误
        libxml_clear_errors();

        $doc = new DOMDocument();
        $result = $doc->loadXML($xml_string);

        $error_info = null;

        // 获取最后一个错误(如果有)
        $last_error = libxml_get_last_error();

        if ($last_error !== false) {
            $error_info = $this->formatError($last_error, $source_name);
        }

        // 清除错误缓冲区
        libxml_clear_errors();

        return [
            'success' => $result,
            'document' => $result ? $doc : null,
            'error' => $error_info
        ];
    }

    /**
     * 格式化错误信息
     */
    private function formatError($error, $source_name) {
        $level = '';
        switch ($error->level) {
            case LIBXML_ERR_WARNING: $level = 'WARNING'; break;
            case LIBXML_ERR_ERROR: $level = 'ERROR'; break;
            case LIBXML_ERR_FATAL: $level = 'FATAL'; break;
            default: $level = 'UNKNOWN'; break;
        }

        return [
            'level' => $level,
            'code' => $error->code,
            'message' => trim($error->message),
            'line' => $error->line,
            'column' => $error->column,
            'source' => $source_name,
            'timestamp' => date('Y-m-d H:i:s')
        ];
    }

    /**
     * 批量处理XML,只记录最后一个错误
     */
    public function batchParse($xml_array) {
        $results = [];
        $error_count = 0;

        foreach ($xml_array as $name => $xml) {
            $result = $this->parseXml($xml, $name);

            $results[$name] = [
                'success' => $result['success'],
                'error' => $result['error']
            ];

            if (!$result['success']) {
                $error_count++;

                // 记录错误到日志(只记录最后一个错误)
                if ($result['error']) {
                    $this->logError($result['error']);
                }
            }
        }

        return [
            'results' => $results,
            'total' => count($xml_array),
            'success' => count($xml_array) - $error_count,
            'errors' => $error_count
        ];
    }

    /**
     * 记录错误到日志
     */
    private function logError($error_info) {
        $log_message = sprintf(
            "[%s] [%s] XML解析错误: %s (代码: %d, 位置: 行%d) - 来源: %s\n",
            $error_info['timestamp'],
            $error_info['level'],
            $error_info['message'],
            $error_info['code'],
            $error_info['line'],
            $error_info['source']
        );

        // 写入错误日志(这里简单输出,实际应用中可以写入文件或数据库)
        error_log($log_message, 3, '/tmp/xml_errors.log');
    }
}

// 测试数据
$test_data = [
    '正确XML' => '<?xml version="1.0"?><root><item>内容</item></root>',
    '格式错误XML' => '<root><item>未闭合标签</root>',
    '属性错误XML' => '<root><item id=123>属性值缺少引号</item></root>',
    '空XML' => '',
    '编码问题XML' => '<root><item>特殊字符 & < ></item></root>'
];

echo "<h4>XML批量处理演示:</h4>";
echo "<p>使用 libxml_get_last_error() 记录最后一个错误</p>";

$parser = new SimpleXmlParser();
$batch_result = $parser->batchParse($test_data);

// 显示结果
echo "<table class='table table-bordered table-hover'>";
echo "<thead class='table-dark'>";
echo "<tr><th>XML名称</th><th>状态</th><th>错误级别</th><th>错误消息</th><th>位置</th></tr>";
echo "</thead><tbody>";

foreach ($batch_result['results'] as $name => $result) {
    if ($result['success']) {
        echo "<tr class='table-success'>";
        echo "<td>$name</td>";
        echo "<td><span class='badge bg-success'>成功</span></td>";
        echo "<td>-</td>";
        echo "<td>-</td>";
        echo "<td>-</td>";
        echo "</tr>";
    } else {
        $error = $result['error'];

        $row_class = '';
        if ($error['level'] === 'FATAL') {
            $row_class = 'table-danger';
        } elseif ($error['level'] === 'ERROR') {
            $row_class = 'table-warning';
        }

        echo "<tr class='$row_class'>";
        echo "<td>$name</td>";
        echo "<td><span class='badge bg-danger'>失败</span></td>";
        echo "<td><span class='badge " . ($error['level'] === 'FATAL' ? 'bg-danger' : ($error['level'] === 'ERROR' ? 'bg-warning' : 'bg-info')) . "'>{$error['level']}</span></td>";

        // 截断过长的错误消息
        $message = htmlspecialchars($error['message']);
        if (strlen($message) > 60) {
            $message = substr($message, 0, 60) . '...';
        }

        echo "<td>$message</td>";
        echo "<td>行: {$error['line']}</td>";
        echo "</tr>";
    }
}

echo "</tbody></table>";

// 显示统计信息
echo "<div class='alert alert-info'>";
echo "<strong>处理统计:</strong> ";
echo "总数: {$batch_result['total']}, ";
echo "成功: {$batch_result['success']}, ";
echo "失败: {$batch_result['errors']}";
echo "</div>";

// 显示日志示例
echo "<h5>错误日志示例(最后一条):</h5>";
echo "<div class='alert alert-light' style='font-family: monospace;'>";
echo "[" . date('Y-m-d H:i:s') . "] [ERROR] XML解析错误: Opening and ending tag mismatch: item line 1 and root (代码: 76, 位置: 行1) - 来源: 格式错误XML";
echo "</div>";

// 使用建议
echo "<div class='alert alert-success mt-3'>";
echo "<strong>使用 libxml_get_last_error() 的优势:</strong>";
echo "<ul class='mb-0'>";
echo "<li>性能更好,只处理一个错误对象</li>";
echo "<li>代码更简洁,适合简单错误检查</li>";
echo "<li>内存占用更少,不存储所有错误</li>";
echo "<li>适合日志记录,只记录最后/最重要的错误</li>";
echo "</ul>";
echo "</div>";
?>

示例 4:HTML解析错误处理

下面的示例演示在解析HTML时如何使用 libxml_get_last_error() 处理错误。

<?php
/**
 * HTML解析器,使用最后错误进行快速诊断
 */
class HtmlParser {

    /**
     * 解析HTML并返回诊断信息
     */
    public function parseAndDiagnose($html, $options = []) {
        // 启用内部错误处理
        libxml_use_internal_errors(true);
        libxml_clear_errors();

        $default_options = [
            'encoding' => 'UTF-8',
            'recover' => true,
            'options' => LIBXML_HTML_NOIMPLIED | LIBXML_HTML_NODEFDTD
        ];

        $options = array_merge($default_options, $options);

        $doc = new DOMDocument('1.0', $options['encoding']);

        if ($options['recover']) {
            $doc->recover = true;
        }

        // 加载HTML
        $load_result = @$doc->loadHTML($html, $options['options']);

        // 获取最后一个错误
        $last_error = libxml_get_last_error();

        // 诊断信息
        $diagnosis = [
            'parse_success' => $load_result,
            'has_error' => ($last_error !== false),
            'last_error' => $last_error,
            'document_size' => $load_result ? strlen($doc->saveHTML()) : 0,
            'element_count' => $load_result ? $doc->getElementsByTagName('*')->length : 0
        ];

        // 清除错误缓冲区
        libxml_clear_errors();

        return [
            'document' => $load_result ? $doc : null,
            'diagnosis' => $diagnosis
        ];
    }

    /**
     * 根据错误提供修复建议
     */
    public function getFixSuggestions($error) {
        if ($error === false) {
            return '没有发现错误';
        }

        $message = trim($error->message);
        $suggestions = [];

        // 常见HTML错误和建议
        $common_issues = [
            'Tag' => '检查标签是否正确闭合',
            'attribute' => '检查属性是否正确格式(属性值应该用引号括起来)',
            'Entity' => 'HTML实体可能需要转义,如 &amp; 代表 &',
            'parser error' => 'HTML可能有严重的语法错误',
            'expected' => '缺少预期的标签或属性',
            'mismatch' => '开始和结束标签不匹配'
        ];

        foreach ($common_issues as $keyword => $suggestion) {
            if (stripos($message, $keyword) !== false) {
                $suggestions[] = $suggestion;
            }
        }

        // 如果没有匹配的建议,提供通用建议
        if (empty($suggestions)) {
            $suggestions[] = '检查HTML语法是否正确';
            $suggestions[] = '验证HTML是否符合规范';
        }

        return array_unique($suggestions);
    }
}

// 测试HTML(包含错误)
$test_html = '<!DOCTYPE html>
<html>
<head>
    <title>测试页面</title>
</head>
<body>
    <h1>HTML测试</h1>

    <div class=container>  <!-- 错误:属性值缺少引号 -->
        <p>段落文本
        <img src="image.jpg">  <!-- 错误:img标签没有alt属性 -->
    </div>

    <ul>
        <li>项目1
        <li>项目2</li>
    </ul>

    <script>
        // JavaScript代码
        console.log("Hello");
    </script>
</body>
</html>';

echo "<h4>HTML解析错误诊断:</h4>";

$parser = new HtmlParser();
$result = $parser->parseAndDiagnose($test_html);

// 显示诊断信息
echo "<table class='table table-bordered' style='width: auto;'>";
echo "<thead><tr><th>诊断项</th><th>结果</th></tr></thead>";
echo "<tbody>";
echo "<tr><td>解析是否成功</td><td>" . ($result['diagnosis']['parse_success'] ? '是' : '否') . "</td></tr>";
echo "<tr><td>是否有错误</td><td>" . ($result['diagnosis']['has_error'] ? '是' : '否') . "</td></tr>";
echo "<tr><td>文档大小</td><td>" . $result['diagnosis']['document_size'] . " 字节</td></tr>";
echo "<tr><td>元素数量</td><td>" . $result['diagnosis']['element_count'] . " 个</td></tr>";
echo "</tbody></table>";

// 显示最后一个错误(如果有)
if ($result['diagnosis']['has_error']) {
    $last_error = $result['diagnosis']['last_error'];

    echo "<h5>最后一个HTML解析错误:</h5>";
    echo "<div class='card border-warning'>";
    echo "<div class='card-header bg-warning'>";

    $level = '';
    switch ($last_error->level) {
        case LIBXML_ERR_WARNING: $level = '警告'; break;
        case LIBXML_ERR_ERROR: $level = '错误'; break;
        case LIBXML_ERR_FATAL: $level = '致命错误'; break;
    }

    echo "HTML解析错误 - $level (代码: {$last_error->code})";
    echo "</div>";
    echo "<div class='card-body'>";
    echo "<p><strong>错误消息:</strong> " . htmlspecialchars(trim($last_error->message)) . "</p>";
    echo "<p><strong>位置:</strong> 行 {$last_error->line}, 列 " . ($last_error->column ?: '未知') . "</p>";

    // 提供修复建议
    $suggestions = $parser->getFixSuggestions($last_error);
    if (!empty($suggestions)) {
        echo "<p><strong>修复建议:</strong></p>";
        echo "<ul>";
        foreach ($suggestions as $suggestion) {
            echo "<li>$suggestion</li>";
        }
        echo "</ul>";
    }

    echo "</div></div>";
}

// 显示解析后的HTML(如果成功)
if ($result['diagnosis']['parse_success'] && $result['document']) {
    echo "<h5>解析后的HTML(已修复):</h5>";
    echo "<div style='max-height: 200px; overflow-y: auto; border: 1px solid #ddd; padding: 10px; background: #f8f9fa; font-size: 0.9em;'>";
    $result['document']->formatOutput = true;
    $html = $result['document']->saveHTML();

    // 高亮显示修改的部分(简单示例)
    $html = str_replace('class=container', '<span style="background-color: #ffeb3b;">class="container"</span>', $html);
    $html = str_replace('<img src="image.jpg">', '<span style="background-color: #ffeb3b;"><img src="image.jpg" alt=""></span>', $html);

    echo "<pre>" . htmlspecialchars($html) . "</pre>";
    echo "</div>";
    echo "<p class='text-muted'><small>注意:黄色高亮显示的是解析器自动修复的部分</small></p>";
}

// HTML解析特点说明
echo "<div class='alert alert-info mt-3'>";
echo "<strong>HTML解析的特点:</strong>";
echo "<ul class='mb-0'>";
echo "<li>HTML解析器比XML解析器更宽容</li>";
echo "<li>许多HTML错误会被自动修复</li>";
echo "<li>libxml_get_last_error() 通常返回最严重的错误</li>";
echo "<li>即使有错误,HTML文档通常也能被解析</li>";
echo "</ul>";
echo "</div>";
?>

示例 5:性能优化和错误处理策略

下面的示例演示在大规模XML处理场景中,如何使用 libxml_get_last_error() 进行性能优化。

<?php
/**
 * 高性能XML处理器,使用最后错误进行优化
 */
class HighPerformanceXmlProcessor {
    private $stats = [
        'processed' => 0,
        'success' => 0,
        'errors' => 0,
        'fatal_errors' => 0,
        'last_error_time' => null,
        'error_codes' => []
    ];

    /**
     * 处理大量XML数据(性能优化版)
     */
    public function processBatchOptimized($xml_items, $options = []) {
        $default_options = [
            'chunk_size' => 100,
            'log_errors' => true,
            'stop_on_fatal' => false,
            'memory_check' => true
        ];

        $options = array_merge($default_options, $options);

        // 启用内部错误处理
        libxml_use_internal_errors(true);

        $results = [];
        $chunk_counter = 0;

        foreach ($xml_items as $item_id => $xml_data) {
            // 定期清理错误缓冲区(优化内存)
            if ($chunk_counter % $options['chunk_size'] === 0) {
                libxml_clear_errors();

                if ($options['memory_check']) {
                    $this->checkMemoryUsage($chunk_counter);
                }
            }

            // 处理单个XML
            $result = $this->processSingleOptimized($xml_data, $item_id, $options);
            $results[$item_id] = $result;

            $this->stats['processed']++;

            if ($result['success']) {
                $this->stats['success']++;
            } else {
                $this->stats['errors']++;

                if ($result['is_fatal']) {
                    $this->stats['fatal_errors']++;

                    // 如果遇到致命错误且设置了停止,则中断处理
                    if ($options['stop_on_fatal']) {
                        $this->stats['last_error_time'] = date('Y-m-d H:i:s');
                        break;
                    }
                }

                // 记录错误代码统计
                if ($result['error_code']) {
                    $code = $result['error_code'];
                    if (!isset($this->stats['error_codes'][$code])) {
                        $this->stats['error_codes'][$code] = 0;
                    }
                    $this->stats['error_codes'][$code]++;
                }

                // 记录错误日志
                if ($options['log_errors']) {
                    $this->logOptimizedError($result, $item_id);
                }
            }

            $chunk_counter++;
        }

        // 最后清理一次
        libxml_clear_errors();

        return [
            'results' => $results,
            'stats' => $this->stats
        ];
    }

    /**
     * 处理单个XML(优化版)
     */
    private function processSingleOptimized($xml_data, $item_id, $options) {
        $result = [
            'success' => false,
            'is_fatal' => false,
            'error_code' => null,
            'error_message' => null,
            'processing_time' => 0,
            'memory_used' => 0
        ];

        $start_time = microtime(true);
        $start_memory = memory_get_usage();

        try {
            $doc = new DOMDocument();

            // 禁用外部实体(安全+性能)
            $old_entity_loader = libxml_disable_entity_loader(true);

            // 快速解析(不进行额外验证)
            $parse_result = @$doc->loadXML($xml_data, LIBXML_NOERROR | LIBXML_NOWARNING);

            // 恢复实体加载器
            libxml_disable_entity_loader($old_entity_loader);

            if ($parse_result) {
                $result['success'] = true;
                $result['data'] = $doc;
            } else {
                // 只获取最后一个错误(性能优化)
                $last_error = libxml_get_last_error();

                if ($last_error !== false) {
                    $result['error_code'] = $last_error->code;
                    $result['error_message'] = trim($last_error->message);
                    $result['is_fatal'] = ($last_error->level === LIBXML_ERR_FATAL);
                }
            }

        } catch (Exception $e) {
            $result['error_message'] = '异常: ' . $e->getMessage();
            $result['is_fatal'] = true;
        }

        $end_time = microtime(true);
        $end_memory = memory_get_usage();

        $result['processing_time'] = round(($end_time - $start_time) * 1000, 3); // 毫秒
        $result['memory_used'] = $end_memory - $start_memory; // 字节

        return $result;
    }

    /**
     * 检查内存使用
     */
    private function checkMemoryUsage($chunk_counter) {
        $memory = memory_get_usage(true);
        $memory_mb = round($memory / 1024 / 1024, 2);

        // 如果内存使用过高,强制清理
        if ($memory_mb > 100) { // 超过100MB
            libxml_clear_errors();
            gc_collect_cycles(); // 强制垃圾回收

            error_log("内存警告: 块 $chunk_counter 使用了 {$memory_mb}MB");
        }
    }

    /**
     * 记录优化后的错误日志
     */
    private function logOptimizedError($result, $item_id) {
        $log_entry = sprintf(
            "[%s] [%s] XML处理错误: %s (代码: %s, 耗时: %.3fms)\n",
            date('Y-m-d H:i:s'),
            $result['is_fatal'] ? 'FATAL' : 'ERROR',
            $result['error_message'] ? substr($result['error_message'], 0, 100) : '未知错误',
            $result['error_code'] ?: 'N/A',
            $result['processing_time']
        );

        // 简化的日志记录(实际应用中可写入文件)
        error_log($log_entry, 3, '/tmp/xml_perf_errors.log');
    }

    /**
     * 获取统计信息
     */
    public function getStats() {
        return $this->stats;
    }
}

// 生成大量测试数据
echo "<h4>高性能XML批处理演示:</h4>";
echo "<p>模拟处理大量XML数据,使用 libxml_get_last_error() 优化性能</p>";

// 创建测试数据(1000个XML,其中10%有错误)
$test_items = [];
for ($i = 0; $i < 1000; $i++) {
    if ($i % 10 === 0) {
        // 每10个中有一个格式错误的XML
        $test_items["item_$i"] = '<root><item>未闭合标签</root>';
    } else {
        $test_items["item_$i"] = "<?xml version=\"1.0\"?><root><item id=\"$i\">数据内容 $i</item></root>";
    }
}

echo "<p>生成测试数据: " . count($test_items) . " 个XML文档</p>";

// 启动处理器
$processor = new HighPerformanceXmlProcessor();

// 处理数据(性能优化模式)
$start_time = microtime(true);
$result = $processor->processBatchOptimized($test_items, [
    'chunk_size' => 50,
    'log_errors' => true,
    'stop_on_fatal' => false
]);
$end_time = microtime(true);

$stats = $result['stats'];

echo "<h5>处理结果统计:</h5>";
echo "<table class='table table-bordered' style='width: auto;'>";
echo "<thead><tr><th>统计项</th><th>值</th></tr></thead>";
echo "<tbody>";
echo "<tr><td>总处理数</td><td>{$stats['processed']}</td></tr>";
echo "<tr><td>成功数</td><td><span class='badge bg-success'>{$stats['success']}</span></td></tr>";
echo "<tr><td>错误数</td><td><span class='badge bg-danger'>{$stats['errors']}</span></td></tr>";
echo "<tr><td>致命错误数</td><td><span class='badge bg-dark'>{$stats['fatal_errors']}</span></td></tr>";
echo "<tr><td>总处理时间</td><td>" . round(($end_time - $start_time) * 1000, 2) . " 毫秒</td></tr>";
echo "<tr><td>平均每个XML</td><td>" . round(($end_time - $start_time) * 1000 / count($test_items), 3) . " 毫秒</td></tr>";
echo "</tbody></table>";

// 显示错误代码统计
if (!empty($stats['error_codes'])) {
    echo "<h5>错误代码统计:</h5>";
    echo "<table class='table table-sm table-bordered' style='width: auto;'>";
    echo "<thead><tr><th>错误代码</th><th>出现次数</th></tr></thead>";
    echo "<tbody>";

    arsort($stats['error_codes']);
    foreach ($stats['error_codes'] as $code => $count) {
        echo "<tr><td><code>$code</code></td><td>$count</td></tr>";
    }

    echo "</tbody></table>";
}

// 性能优化说明
echo "<div class='alert alert-success mt-3'>";
echo "<strong>使用 libxml_get_last_error() 的性能优化策略:</strong>";
echo "<ul class='mb-0'>";
echo "<li><strong>减少内存占用</strong>:只获取最后一个错误,不存储所有错误数组</li>";
echo "<li><strong>减少CPU开销</strong>:避免遍历和格式化大量错误对象</li>";
echo "<li><strong>定期清理</strong>:按块大小清理错误缓冲区,防止内存泄漏</li>";
echo "<li><strong>简化日志</strong>:只记录关键错误信息,减少I/O操作</li>";
echo "<li><strong>快速失败</strong>:遇到致命错误时快速中断,减少无效处理</li>";
echo "</ul>";
echo "</div>";

// 对比性能数据
echo "<h5>性能对比:</h5>";
echo "<table class='table table-bordered table-sm' style='width: auto;'>";
echo "<thead><tr><th>方法</th><th>内存占用</th><th>处理时间</th><th>适用场景</th></tr></thead>";
echo "<tbody>";
echo "<tr><td>libxml_get_errors()</td><td>较高</td><td>较慢</td><td>需要所有错误细节的调试场景</td></tr>";
echo "<tr><td>libxml_get_last_error()</td><td>较低</td><td>较快</td><td>批量处理、日志记录、生产环境</td></tr>";
echo "</tbody></table>";
?>

注意事项和最佳实践

  • 必须先启用内部错误处理:使用前必须调用 libxml_use_internal_errors(true),否则函数返回 FALSE。
  • 错误缓冲区是全局的:所有 libxml 操作共享同一个错误缓冲区,注意及时使用 libxml_clear_errors() 清理。
  • 只获取最后一个错误:如果解析过程中产生多个错误,此函数只返回最后一个(最新的)错误。
  • 错误级别的重要性:注意检查错误级别,致命错误(LIBXML_ERR_FATAL)表示文档无法解析。
  • 与 libxml_get_errors() 的选择:根据需求选择函数,需要所有错误细节时使用 libxml_get_errors(),只需要快速检查时使用 libxml_get_last_error()
  • 性能考虑:在批量处理大量 XML 时,使用 libxml_get_last_error() 性能更好,内存占用更少。
  • 错误信息可能不完整:由于只获取最后一个错误,可能无法了解所有问题,需要结合其他调试手段。
  • 及时清理错误缓冲区:在处理完错误后,应及时调用 libxml_clear_errors() 防止错误累积。
  • 并发环境注意事项:在多线程或多进程环境中,libxml 错误缓冲区是共享的,需要注意同步问题。

相关函数