PHP libxml_get_errors()函数

PHP libxml_get_errors() 函数

libxml_get_errors() 函数用于获取 libxml 错误缓冲区中的所有错误信息。当使用 libxml 库处理 XML 或 HTML 文档时(如通过 DOMDocument、SimpleXML、XMLReader),解析过程中产生的错误会存储在错误缓冲区中。此函数可以获取这些错误的详细信息。

提示: 在使用此函数前,通常需要先调用 libxml_use_internal_errors(true) 来启用内部错误处理模式,否则 libxml 会直接输出错误或触发 PHP 错误。
注意: 错误缓冲区是全局的,所有使用 libxml 的操作(DOM、SimpleXML、XMLReader 等)产生的错误都会累积在其中。

语法

libxml_get_errors ( ) : array

该函数没有参数。

返回值

函数返回一个包含 LibXMLError 对象的数组。每个 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解析错误

下面的示例演示如何获取XML解析过程中的错误信息。

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

// 创建一个格式错误的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>";

    // 获取所有错误
    $errors = libxml_get_errors();

    echo "<h5>发现 " . count($errors) . " 个错误:</h5>";

    // 显示错误详情
    foreach ($errors as $index => $error) {
        echo "<div class='card mb-3" . ($error->level === LIBXML_ERR_FATAL ? ' border-danger' : '') . "'>";
        echo "<div class='card-header" . ($error->level === LIBXML_ERR_FATAL ? ' bg-danger text-white' : ' bg-warning') . "'>";

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

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

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

示例 2:错误分类、统计和格式化输出

下面的示例演示如何对错误进行分类、统计并生成格式化的报告。

<?php
/**
 * 获取并格式化libxml错误
 */
function getFormattedXmlErrors($xml_string) {
    // 启用内部错误处理
    libxml_use_internal_errors(true);

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

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

    // 获取错误
    $errors = libxml_get_errors();

    // 分类统计
    $stats = [
        'total' => 0,
        'warnings' => 0,
        'errors' => 0,
        'fatal' => 0,
        'by_code' => []
    ];

    // 格式化错误信息
    $formatted_errors = [];

    foreach ($errors as $error) {
        $stats['total']++;

        // 按级别统计
        switch ($error->level) {
            case LIBXML_ERR_WARNING: $stats['warnings']++; break;
            case LIBXML_ERR_ERROR: $stats['errors']++; break;
            case LIBXML_ERR_FATAL: $stats['fatal']++; break;
        }

        // 按错误代码统计
        $code = $error->code;
        if (!isset($stats['by_code'][$code])) {
            $stats['by_code'][$code] = 0;
        }
        $stats['by_code'][$code]++;

        // 格式化错误信息
        $level_name = '';
        switch ($error->level) {
            case LIBXML_ERR_WARNING: $level_name = '警告'; break;
            case LIBXML_ERR_ERROR: $level_name = '错误'; break;
            case LIBXML_ERR_FATAL: $level_name = '致命错误'; break;
        }

        $formatted_errors[] = [
            'level' => $level_name,
            'code' => $code,
            'message' => trim($error->message),
            'line' => $error->line,
            'column' => $error->column,
            'severity' => $error->level
        ];
    }

    return [
        'success' => $result,
        'stats' => $stats,
        'errors' => $formatted_errors,
        'document' => $result ? $doc : null
    ];
}

// 测试XML
$test_xml = '<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<root xmlns="http://www.w3.org/1999/xhtml">
    <item>项目1</item>
    <item>项目2<!-- 未闭合的注释 -->
    <item invalid-attr="值">项目3</item>
    <unknown-tag>未知标签</unknown-tag>
    <item>特殊字符: & < ></item>
</root>';

echo "<h4>XML错误分析和统计:</h4>";

$result = getFormattedXmlErrors($test_xml);

// 显示统计信息
echo "<div class='row mb-4'>";
echo "<div class='col-md-3'>";
echo "<div class='card text-white bg-primary'>";
echo "<div class='card-body text-center'>";
echo "<h5 class='card-title'>总错误数</h5>";
echo "<p class='card-text display-6'>" . $result['stats']['total'] . "</p>";
echo "</div></div></div>";

echo "<div class='col-md-3'>";
echo "<div class='card text-white bg-warning'>";
echo "<div class='card-body text-center'>";
echo "<h5 class='card-title'>警告</h5>";
echo "<p class='card-text display-6'>" . $result['stats']['warnings'] . "</p>";
echo "</div></div></div>";

echo "<div class='col-md-3'>";
echo "<div class='card text-white bg-danger'>";
echo "<div class='card-body text-center'>";
echo "<h5 class='card-title'>错误</h5>";
echo "<p class='card-text display-6'>" . $result['stats']['errors'] . "</p>";
echo "</div></div></div>";

echo "<div class='col-md-3'>";
echo "<div class='card text-white bg-dark'>";
echo "<div class='card-body text-center'>";
echo "<h5 class='card-title'>致命错误</h5>";
echo "<p class='card-text display-6'>" . $result['stats']['fatal'] . "</p>";
echo "</div></div></div>";
echo "</div>";

// 显示详细错误
if (!empty($result['errors'])) {
    echo "<h5>详细错误列表:</h5>";
    echo "<table class='table table-bordered table-hover'>";
    echo "<thead class='table-dark'>";
    echo "<tr><th>#</th><th>级别</th><th>错误代码</th><th>消息</th><th>位置</th></tr>";
    echo "</thead><tbody>";

    foreach ($result['errors'] as $index => $error) {
        $row_class = '';
        if ($error['severity'] === LIBXML_ERR_FATAL) {
            $row_class = 'table-danger';
        } elseif ($error['severity'] === LIBXML_ERR_ERROR) {
            $row_class = 'table-warning';
        }

        echo "<tr class='$row_class'>";
        echo "<td>" . ($index + 1) . "</td>";

        // 级别标签
        $badge_class = 'bg-secondary';
        if ($error['severity'] === LIBXML_ERR_FATAL) {
            $badge_class = 'bg-danger';
        } elseif ($error['severity'] === LIBXML_ERR_ERROR) {
            $badge_class = 'bg-warning';
        } elseif ($error['severity'] === LIBXML_ERR_WARNING) {
            $badge_class = 'bg-info';
        }

        echo "<td><span class='badge $badge_class'>{$error['level']}</span></td>";
        echo "<td><code>{$error['code']}</code></td>";

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

        echo "<td>$message</td>";
        echo "<td>行: {$error['line']}, 列: " . ($error['column'] ?: '未知') . "</td>";
        echo "</tr>";
    }

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

    // 显示错误代码统计
    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($result['stats']['by_code']);
    foreach ($result['stats']['by_code'] as $code => $count) {
        echo "<tr><td><code>$code</code></td><td>$count</td></tr>";
    }

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

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

示例 3:XML Schema验证和错误处理

下面的示例演示如何使用Schema验证XML,并获取详细的验证错误。

<?php
/**
 * 验证XML是否符合Schema
 */
function validateXmlWithSchema($xml_string, $schema_string) {
    // 启用内部错误处理
    libxml_use_internal_errors(true);
    libxml_clear_errors();

    // 加载XML
    $doc = new DOMDocument();
    $doc->loadXML($xml_string);

    // 加载Schema
    $schema_doc = new DOMDocument();
    $schema_doc->loadXML($schema_string);

    // 验证
    $is_valid = @$doc->schemaValidateSource($schema_string);

    // 获取验证错误
    $errors = libxml_get_errors();

    return [
        'valid' => $is_valid,
        'errors' => $errors,
        'document' => $doc
    ];
}

// XML Schema定义
$schema_xml = '<?xml version="1.0" encoding="UTF-8"?>
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
    <xs:element name="employees">
        <xs:complexType>
            <xs:sequence>
                <xs:element name="employee" maxOccurs="unbounded">
                    <xs:complexType>
                        <xs:sequence>
                            <xs:element name="name" type="xs:string"/>
                            <xs:element name="age" type="xs:integer"/>
                            <xs:element name="department" type="xs:string"/>
                            <xs:element name="salary" type="xs:decimal" minOccurs="0"/>
                        </xs:sequence>
                        <xs:attribute name="id" type="xs:integer" use="required"/>
                    </xs:complexType>
                </xs:element>
            </xs:sequence>
        </xs:complexType>
    </xs:element>
</xs:schema>';

// 测试XML(包含多个错误)
$test_xml = '<?xml version="1.0" encoding="UTF-8"?>
<employees>
    <employee id="101">
        <name>张三</name>
        <age>30</age>
        <department>技术部</department>
        <salary>5000.50</salary>
    </employee>
    <employee id="102">
        <name>李四</name>
        <age>二十五</age>  <!-- 错误:年龄应该是整数 -->
        <department>销售部</department>
        <!-- 缺少salary元素 -->
    </employee>
    <employee>  <!-- 错误:缺少id属性 -->
        <name>王五</name>
        <age>40</age>
        <department>市场部</department>
        <salary>invalid</salary>  <!-- 错误:工资应该是数字 -->
    </employee>
    <unknown>未知元素</unknown>  <!-- 错误:未在schema中定义 -->
</employees>';

echo "<h4>XML Schema验证:</h4>";

$result = validateXmlWithSchema($test_xml, $schema_xml);

if ($result['valid']) {
    echo "<div class='alert alert-success'>XML符合Schema定义!</div>";
} else {
    echo "<div class='alert alert-danger'>XML不符合Schema定义,发现 " . count($result['errors']) . " 个错误:</div>";

    // 显示验证错误
    echo "<table class='table table-bordered'>";
    echo "<thead><tr><th>#</th><th>级别</th><th>错误代码</th><th>消息</th><th>位置</th></tr></thead>";
    echo "<tbody>";

    foreach ($result['errors'] as $index => $error) {
        $level = '';
        $row_class = '';

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

        echo "<tr class='$row_class'>";
        echo "<td>" . ($index + 1) . "</td>";
        echo "<td>$level</td>";
        echo "<td>{$error->code}</td>";

        // 简化错误消息
        $message = trim($error->message);
        $message = preg_replace('/Element \{.*?\}:/', 'Element ', $message);
        $message = preg_replace('/The value \'.*?\' of element .*? is not valid\./', '值不符合要求', $message);

        echo "<td>" . htmlspecialchars($message) . "</td>";
        echo "<td>行: {$error->line}, 列: " . ($error->column ?: '未知') . "</td>";
        echo "</tr>";
    }

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

    // 错误分类
    $error_types = [
        '数据类型错误' => 0,
        '缺少必需元素' => 0,
        '结构错误' => 0,
        '其他错误' => 0
    ];

    foreach ($result['errors'] as $error) {
        $message = $error->message;

        if (strpos($message, 'is not a valid value') !== false ||
            strpos($message, 'is not valid') !== false) {
            $error_types['数据类型错误']++;
        } elseif (strpos($message, 'missing') !== false ||
                 strpos($message, 'required') !== false) {
            $error_types['缺少必需元素']++;
        } elseif (strpos($message, 'element') !== false &&
                 strpos($message, 'not expected') !== false) {
            $error_types['结构错误']++;
        } else {
            $error_types['其他错误']++;
        }
    }

    echo "<h5>错误类型统计:</h5>";
    echo "<div class='row'>";

    $colors = ['primary', 'danger', 'warning', 'info'];
    $color_index = 0;

    foreach ($error_types as $type => $count) {
        if ($count > 0) {
            echo "<div class='col-md-3 mb-3'>";
            echo "<div class='card bg-" . $colors[$color_index % count($colors)] . " text-white'>";
            echo "<div class='card-body text-center'>";
            echo "<h6 class='card-title'>$type</h6>";
            echo "<p class='card-text h4'>$count</p>";
            echo "</div></div></div>";
            $color_index++;
        }
    }

    echo "</div>";
}

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

示例 4:HTML解析错误处理和恢复

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

<?php
/**
 * 解析HTML并处理错误
 */
function parseHtmlWithErrorHandling($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);

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

    // 设置解析选项
    if ($options['recover']) {
        $doc->recover = true;
    }

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

    // 获取错误
    $errors = libxml_get_errors();

    // 格式化错误信息
    $formatted_errors = [];
    $has_fatal_error = false;

    foreach ($errors as $error) {
        // 检查是否为致命错误
        if ($error->level === LIBXML_ERR_FATAL) {
            $has_fatal_error = true;
        }

        // 简化HTML解析错误消息
        $message = trim($error->message);
        $message = str_replace('Tag ', '标签 ', $message);
        $message = str_replace('invalid', '无效', $message);

        $formatted_errors[] = [
            'level' => $error->level,
            'code' => $error->code,
            'message' => $message,
            'line' => $error->line,
            'column' => $error->column,
            'is_fatal' => ($error->level === LIBXML_ERR_FATAL)
        ];
    }

    return [
        'success' => $load_result && !$has_fatal_error,
        'document' => $doc,
        'errors' => $formatted_errors,
        'has_fatal_error' => $has_fatal_error,
        'error_count' => count($errors)
    ];
}

// 测试HTML(包含多个错误)
$test_html = '<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <title>测试页面</title>
</head>
<body>
    <h1>HTML解析测试</h1>

    <!-- 常见的HTML错误 -->
    <div>
        <p>段落1
        <p>段落2</p>  <!-- 第一个p标签未闭合 -->
    </div>

    <!-- 无效的嵌套 -->
    <p><div>无效的div在p中</div></p>

    <!-- 重复的属性 -->
    <div class="container" class="main">重复的class属性</div>

    <!-- 特殊字符 -->
    <p>特殊字符: & < > " </p>

    <!-- 未闭合的标签 -->
    <ul>
        <li>项目1
        <li>项目2</li>
    </ul>

    <!-- 未知标签 -->
    <custom-tag>自定义标签</custom-tag>
</body>
</html>';

echo "<h4>HTML解析错误处理:</h4>";

$result = parseHtmlWithErrorHandling($test_html);

if ($result['success']) {
    echo "<div class='alert alert-success'>HTML解析成功(尽管有" . $result['error_count'] . "个错误)</div>";
} else {
    echo "<div class='alert alert-danger'>HTML解析失败,发现" . $result['error_count'] . "个错误</div>";
}

// 显示错误统计
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>{$result['error_count']}</td></tr>";
echo "<tr><td>致命错误</td><td>" . ($result['has_fatal_error'] ? '有' : '无') . "</td></tr>";
echo "<tr><td>解析结果</td><td>" . ($result['success'] ? '成功' : '失败') . "</td></tr>";
echo "</tbody></table>";

// 显示错误详情
if (!empty($result['errors'])) {
    echo "<h5>HTML解析错误详情:</h5>";
    echo "<div style='max-height: 300px; overflow-y: auto;'>";
    echo "<table class='table table-sm table-bordered'>";
    echo "<thead><tr><th>#</th><th>级别</th><th>消息</th><th>位置</th><th>建议</th></tr></thead>";
    echo "<tbody>";

    $common_errors = [
        'Tag p invalid' => '<p>标签必须正确闭合',
        'div not allowed' => '<div>不能嵌套在<p>中',
        'attribute class redefined' => '重复的属性,请删除一个',
        'Tag li invalid' => '<li>标签必须正确闭合',
        'Tag custom-tag invalid' => '使用有效的HTML5标签'
    ];

    foreach ($result['errors'] as $index => $error) {
        $level = '';
        $row_class = '';

        switch ($error['level']) {
            case LIBXML_ERR_WARNING:
                $level = '警告';
                $row_class = '';
                break;
            case LIBXML_ERR_ERROR:
                $level = '错误';
                $row_class = 'table-warning';
                break;
            case LIBXML_ERR_FATAL:
                $level = '致命错误';
                $row_class = 'table-danger';
                break;
        }

        echo "<tr class='$row_class'>";
        echo "<td>" . ($index + 1) . "</td>";
        echo "<td><span class='badge " . ($error['is_fatal'] ? 'bg-danger' : 'bg-warning') . "'>$level</span></td>";
        echo "<td>" . htmlspecialchars($error['message']) . "</td>";
        echo "<td>行: {$error['line']}</td>";

        // 提供修复建议
        $suggestion = '检查语法';
        foreach ($common_errors as $pattern => $advice) {
            if (strpos($error['message'], $pattern) !== false) {
                $suggestion = $advice;
                break;
            }
        }

        echo "<td><small>$suggestion</small></td>";
        echo "</tr>";
    }

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

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

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

示例 5:调试和生产环境中的错误处理策略

下面的示例演示如何在调试和生产环境中使用不同的错误处理策略。

<?php
/**
 * XML处理器的生产环境配置
 */
class XmlProcessor {
    private $debug_mode;
    private $error_log_path;
    private $max_errors_per_file;

    public function __construct($debug_mode = false, $error_log_path = null) {
        $this->debug_mode = $debug_mode;
        $this->error_log_path = $error_log_path ?: sys_get_temp_dir() . '/xml_errors.log';
        $this->max_errors_per_file = 100;

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

    /**
     * 安全解析XML(生产环境使用)
     */
    public function safeParse($xml_content, $source = 'unknown') {
        // 清除之前的错误
        libxml_clear_errors();

        try {
            $doc = new DOMDocument();

            // 安全设置:禁用外部实体加载
            $old_entity_loader = libxml_disable_entity_loader(true);

            // 尝试解析
            $result = @$doc->loadXML($xml_content);

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

            if (!$result) {
                $this->handleParseErrors($source);
                return null;
            }

            return $doc;

        } catch (Exception $e) {
            $this->logError("解析异常: " . $e->getMessage(), $source);
            return null;
        }
    }

    /**
     * 处理解析错误
     */
    private function handleParseErrors($source) {
        $errors = libxml_get_errors();

        if (empty($errors)) {
            return;
        }

        $error_count = count($errors);

        // 调试模式:显示详细错误
        if ($this->debug_mode) {
            $this->displayDebugErrors($errors, $source);
        }

        // 生产环境:记录错误日志
        $this->logErrors($errors, $source, $error_count);

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

    /**
     * 调试模式显示错误
     */
    private function displayDebugErrors($errors, $source) {
        echo "<div class='alert alert-danger'>";
        echo "<h5>XML解析错误(来源: $source)</h5>";
        echo "<p>发现 " . count($errors) . " 个错误:</p>";

        echo "<table class='table table-sm table-bordered'>";
        echo "<thead><tr><th>级别</th><th>代码</th><th>消息</th><th>位置</th></tr></thead>";
        echo "<tbody>";

        foreach ($errors as $error) {
            $level = $this->getErrorLevelName($error->level);

            echo "<tr>";
            echo "<td>$level</td>";
            echo "<td>{$error->code}</td>";

            // 截断过长的错误消息
            $message = trim($error->message);
            if (strlen($message) > 100) {
                $message = substr($message, 0, 100) . '...';
            }

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

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

    /**
     * 生产环境记录错误日志
     */
    private function logErrors($errors, $source, $error_count) {
        $log_entry = date('Y-m-d H:i:s') . " - 来源: $source - 错误数: $error_count\n";

        // 只记录前N个错误,避免日志过大
        $max_log_errors = min($error_count, $this->max_errors_per_file);

        for ($i = 0; $i < $max_log_errors; $i++) {
            $error = $errors[$i];
            $level = $this->getErrorLevelName($error->level);
            $message = trim($error->message);

            $log_entry .= "  错误 #" . ($i + 1) . " [$level] [代码: {$error->code}] 行 {$error->line}: $message\n";
        }

        if ($error_count > $max_log_errors) {
            $log_entry .= "  还有 " . ($error_count - $max_log_errors) . " 个错误未显示...\n";
        }

        $log_entry .= str_repeat('-', 80) . "\n";

        // 写入日志文件
        error_log($log_entry, 3, $this->error_log_path);

        // 同时记录到系统日志(可选)
        if ($error_count > 5) { // 只有错误较多时才记录到系统日志
            $this->logError("XML解析失败: $source ($error_count 个错误)", $source);
        }
    }

    /**
     * 记录单个错误
     */
    private function logError($message, $source) {
        $log_message = date('Y-m-d H:i:s') . " [$source] $message\n";
        error_log($log_message, 3, $this->error_log_path);
    }

    /**
     * 获取错误级别名称
     */
    private function getErrorLevelName($level) {
        static $level_names = [
            LIBXML_ERR_WARNING => '警告',
            LIBXML_ERR_ERROR => '错误',
            LIBXML_ERR_FATAL => '致命错误'
        ];

        return $level_names[$level] ?? '未知级别';
    }

    /**
     * 获取错误统计信息
     */
    public function getErrorStats() {
        $log_content = @file_get_contents($this->error_log_path);

        if (!$log_content) {
            return ['total_entries' => 0, 'last_error' => null];
        }

        $lines = explode("\n", $log_content);
        $entry_count = 0;

        foreach ($lines as $line) {
            if (strpos($line, '来源:') !== false) {
                $entry_count++;
            }
        }

        return [
            'total_entries' => $entry_count,
            'last_error' => end($lines),
            'log_size' => round(strlen($log_content) / 1024, 2) . ' KB'
        ];
    }
}

// 演示不同环境下的处理
echo "<h4>XML错误处理策略演示:</h4>";

// 模拟不同环境
$environments = [
    'debug' => true,
    'production' => false
];

foreach ($environments as $env_name => $is_debug) {
    echo "<h5>环境: " . ($is_debug ? '调试模式' : '生产模式') . "</h5>";

    $processor = new XmlProcessor($is_debug);

    // 测试XML(包含错误)
    $test_xml = '<?xml version="1.0"?>
<data>
    <item id="1">正常数据</item>
    <item>缺少id属性</item>
    <item id="invalid">无效ID<item>  <!-- 未闭合标签 -->
    <unknown>未知元素</unknown>
</data>';

    echo "<p>解析测试XML...</p>";

    $result = $processor->safeParse($test_xml, "test_$env_name");

    if ($result) {
        echo "<div class='alert alert-success'>解析成功</div>";
    } else {
        echo "<div class='alert alert-danger'>解析失败" . ($is_debug ? '' : '(错误已记录到日志)') . "</div>";
    }

    // 显示错误统计(生产环境)
    if (!$is_debug) {
        $stats = $processor->getErrorStats();
        echo "<div class='alert alert-info'>";
        echo "错误日志统计: ";
        echo "总条目: {$stats['total_entries']}, ";
        echo "日志大小: {$stats['log_size']}";
        echo "</div>";
    }

    echo "<hr>";
}

// 最佳实践建议
echo "<h4>libxml错误处理最佳实践:</h4>";
echo "<div class='alert alert-info'>";
echo "<ul>";
echo "<li><strong>始终启用内部错误处理</strong>:使用 <code>libxml_use_internal_errors(true)</code> 避免错误直接输出</li>";
echo "<li><strong>及时清除错误缓冲区</strong>:使用 <code>libxml_clear_errors()</code> 防止错误累积</li>";
echo "<li><strong>分类处理错误</strong>:根据错误级别(警告、错误、致命错误)采取不同策略</li>";
echo "<li><strong>环境适配</strong>:调试环境显示详细错误,生产环境记录到日志</li>";
echo "<li><strong>安全性考虑</strong>:使用 <code>libxml_disable_entity_loader(true)</code> 防止XXE攻击</li>";
echo "<li><strong>性能优化</strong>:批量处理时定期清除错误缓冲区,避免内存泄漏</li>";
echo "</ul>";
echo "</div>";

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

注意事项和常见问题

  • 内存使用:错误缓冲区会累积错误直到被清除,在处理大量XML时可能占用大量内存。
  • 并发安全:libxml错误缓冲区是全局的,在多线程或多进程环境下需要特别注意。
  • 错误级别理解:LIBXML_ERR_FATAL通常表示文档无法解析,而LIBXML_ERR_WARNING可能只是格式警告。
  • 性能影响:获取大量错误信息可能影响性能,建议在生产环境中限制记录的错误数量。
  • 错误消息格式:错误消息可能包含技术性语言,可能需要处理后才能展示给最终用户。
  • 兼容性:该函数自PHP 5.1.0起可用,但某些错误属性可能在不同PHP版本中有所不同。
  • 与libxml_clear_errors()配合:通常在使用libxml_get_errors()获取错误后,应使用libxml_clear_errors()清除缓冲区。
  • 错误代码解读:libxml错误代码可能需要查阅libxml文档才能完全理解其含义。
  • HTML vs XML错误:解析HTML时产生的错误可能与XML不同,HTML解析器通常更宽容。

相关函数