PHP libxml_clear_errors()函数

PHP libxml_clear_errors() 函数

libxml_clear_errors() 函数用于清除 libxml 错误缓冲区。当使用 libxml 库处理 XML 文档时(如通过 DOMDocument、SimpleXML 或 XMLReader),解析过程中产生的错误会存储在错误缓冲区中。此函数用于清空该缓冲区。

提示: libxml 是 PHP 处理 XML 的核心库。在解析 XML 时,如果遇到格式错误、编码问题或结构问题,libxml 会记录这些错误。使用此函数可以清空错误列表,避免后续操作受到之前错误的影响。

语法

libxml_clear_errors ( ) : void

该函数没有参数。

返回值

该函数没有返回值(返回类型为 void)。

功能说明:
  • 清空 libxml 错误缓冲区
  • 释放错误缓冲区占用的内存
  • 重置 libxml 错误状态

示例 1:基本用法 - 清除XML解析错误

下面的示例演示在解析XML时如何使用 libxml_clear_errors() 清除错误缓冲区。

<?php
// 启用用户自定义错误处理
libxml_use_internal_errors(true);

// 创建一个格式错误的XML字符串
$malformed_xml = '<root><item>value1</item><item>value2<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>解析错误:</h5>";
    echo "<table class='table table-bordered table-sm'>";
    echo "<thead><tr><th>级别</th><th>错误代码</th><th>消息</th><th>行号</th></tr></thead>";
    echo "<tbody>";

    foreach ($errors as $error) {
        $level = '';
        switch ($error->level) {
            case LIBXML_ERR_WARNING: $level = '警告'; break;
            case LIBXML_ERR_ERROR: $level = '错误'; break;
            case LIBXML_ERR_FATAL: $level = '致命错误'; break;
            default: $level = '未知'; break;
        }

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

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

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

    echo "<div class='alert alert-success'>已清除错误缓冲区</div>";

    // 验证错误是否已被清除
    $remaining_errors = libxml_get_errors();
    echo "<p>剩余错误数量: " . count($remaining_errors) . "</p>";
}

// 尝试解析另一个XML(之前的错误已清除)
echo "<hr><h4>尝试解析另一个XML:</h4>";
$another_xml = '<root><item>value1</item></root>';

$doc2 = new DOMDocument();
$result2 = $doc2->loadXML($another_xml);

if ($result2) {
    echo "<div class='alert alert-success'>XML解析成功!</div>";
    echo "<p>XML内容: " . htmlspecialchars($doc2->saveXML()) . "</p>";
}
?>

示例 2:批量处理多个XML文件

下面的示例演示在处理多个XML文件时如何及时清除错误,避免错误累积。

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

// 模拟多个XML文件数据
$xml_files = [
    'file1' => '<root><item>正确XML</item></root>',
    'file2' => '<root><item>value<item></root>', // 格式错误:缺少结束标签
    'file3' => '<root><item attribute="value">内容</item></root>',
    'file4' => '<root><item>特殊字符 & < > </item></root>',
    'file5' => '<root>未闭合标签', // 严重的格式错误
];

echo "<h4>批量处理XML文件:</h4>";
echo "<table class='table table-bordered'>";
echo "<thead><tr><th>文件</th><th>状态</th><th>错误信息</th></tr></thead>";
echo "<tbody>";

foreach ($xml_files as $filename => $xml_content) {
    // 每次解析前清除之前的错误
    libxml_clear_errors();

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

    if ($result) {
        echo "<tr class='table-success'>";
        echo "<td>$filename</td>";
        echo "<td><span class='badge bg-success'>成功</span></td>";
        echo "<td>无错误</td>";
        echo "</tr>";
    } else {
        echo "<tr class='table-danger'>";
        echo "<td>$filename</td>";
        echo "<td><span class='badge bg-danger'>失败</span></td>";

        // 获取并显示错误
        $errors = libxml_get_errors();
        $error_messages = [];

        foreach ($errors as $error) {
            // 截断过长的错误信息
            $message = htmlspecialchars($error->message);
            if (strlen($message) > 100) {
                $message = substr($message, 0, 100) . '...';
            }
            $error_messages[] = "行 {$error->line}: $message";
        }

        echo "<td>" . implode('<br>', $error_messages) . "</td>";
        echo "</tr>";
    }
}

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

// 最后清除所有错误
libxml_clear_errors();

// 显示统计信息
$final_errors = libxml_get_errors();
echo "<div class='alert alert-info'>";
echo "处理完成。最终错误缓冲区大小: " . count($final_errors);
echo "</div>";
?>

示例 3:结合自定义错误处理函数

下面的示例演示如何创建自定义错误处理函数并与 libxml_clear_errors() 结合使用。

<?php
/**
 * 自定义XML错误处理类
 */
class XmlErrorHandler {
    private $errors = [];
    private $error_count = 0;

    /**
     * 处理XML解析错误
     */
    public function handleXmlError($errno, $errstr, $errfile, $errline) {
        $this->errors[] = [
            'level' => $errno,
            'message' => $errstr,
            'file' => $errfile,
            'line' => $errline,
            'time' => time()
        ];
        $this->error_count++;
    }

    /**
     * 获取所有错误
     */
    public function getErrors() {
        return $this->errors;
    }

    /**
     * 获取错误数量
     */
    public function getErrorCount() {
        return $this->error_count;
    }

    /**
     * 清除错误记录
     */
    public function clearErrors() {
        $this->errors = [];
        $this->error_count = 0;
    }

    /**
     * 安全解析XML
     */
    public function safeParseXml($xml_string, $filename = '') {
        // 启用内部错误处理
        libxml_use_internal_errors(true);

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

        $doc = new DOMDocument();

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

        if (!$result) {
            // 获取libxml错误
            $libxml_errors = libxml_get_errors();

            // 将libxml错误转换为自定义格式
            foreach ($libxml_errors as $error) {
                $this->errors[] = [
                    'level' => $error->level,
                    'code' => $error->code,
                    'message' => trim($error->message),
                    'line' => $error->line,
                    'column' => $error->column,
                    'file' => $filename,
                    'time' => time()
                ];
                $this->error_count++;
            }

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

        return $result ? $doc : false;
    }
}

// 使用自定义错误处理器
$xml_handler = new XmlErrorHandler();

// 测试XML数据
$test_xmls = [
    '正确XML' => '<?xml version="1.0" encoding="UTF-8"?><root><item id="1">测试内容</item></root>',
    '格式错误XML' => '<root><item>未闭合标签</root>',
    '编码问题XML' => '<root><item>特殊字符: & © €</item></root>'
];

echo "<h4>使用自定义错误处理器解析XML:</h4>";

foreach ($test_xmls as $name => $xml) {
    echo "<h5>测试: $name</h5>";

    // 清除之前的错误记录
    $xml_handler->clearErrors();

    $result = $xml_handler->safeParseXml($xml, $name);

    if ($result) {
        echo "<div class='alert alert-success mb-3'>解析成功</div>";
    } else {
        echo "<div class='alert alert-danger mb-3'>解析失败</div>";

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

            foreach ($errors as $error) {
                $level = '';
                switch ($error['level']) {
                    case LIBXML_ERR_WARNING: $level = '警告'; break;
                    case LIBXML_ERR_ERROR: $level = '错误'; break;
                    case LIBXML_ERR_FATAL: $level = '致命错误'; break;
                    default: $level = '未知'; break;
                }

                echo "<tr>";
                echo "<td>{$error['file']}</td>";
                echo "<td><span class='badge bg-danger'>$level</span></td>";
                echo "<td>" . htmlspecialchars($error['message']) . "</td>";
                echo "<td>{$error['line']}</td>";
                echo "</tr>";
            }

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

echo "<div class='alert alert-info mt-3'>";
echo "总错误数(所有测试): " . $xml_handler->getErrorCount();
echo "</div>";
?>

示例 4:性能优化和内存管理

下面的示例演示在处理大量XML数据时,如何通过清除错误缓冲区来优化性能和管理内存。

<?php
/**
 * 高性能XML批处理类
 */
class XmlBatchProcessor {
    private $processed_count = 0;
    private $error_count = 0;
    private $success_count = 0;

    /**
     * 处理大量XML数据
     */
    public function processBatch(array $xml_data_array, $options = []) {
        // 启用内部错误处理
        libxml_use_internal_errors(true);

        $default_options = [
            'validate' => false,
            'memory_check' => true,
            'chunk_size' => 100  // 每处理100个后清理错误缓冲区
        ];

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

        $results = [];
        $chunk_counter = 0;

        foreach ($xml_data_array as $id => $xml_data) {
            // 清除错误缓冲区(按块大小)
            if ($chunk_counter % $options['chunk_size'] === 0) {
                libxml_clear_errors();

                if ($options['memory_check']) {
                    $this->logMemoryUsage("处理块开始 {$chunk_counter}");
                }
            }

            $result = $this->processSingle($xml_data, $options);
            $results[$id] = $result;

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

            $this->processed_count++;
            $chunk_counter++;
        }

        // 最后清理一次错误缓冲区
        libxml_clear_errors();

        return $results;
    }

    /**
     * 处理单个XML
     */
    private function processSingle($xml_data, $options) {
        $result = [
            'success' => false,
            'errors' => [],
            'data' => null
        ];

        try {
            $doc = new DOMDocument();

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

            // 尝试解析
            $parse_result = @$doc->loadXML($xml_data);

            if ($parse_result) {
                $result['success'] = true;
                $result['data'] = $doc;
            } else {
                // 获取错误
                $errors = libxml_get_errors();
                foreach ($errors as $error) {
                    $result['errors'][] = [
                        'level' => $error->level,
                        'code' => $error->code,
                        'message' => trim($error->message),
                        'line' => $error->line
                    ];
                }
            }
        } catch (Exception $e) {
            $result['errors'][] = [
                'level' => 'EXCEPTION',
                'message' => $e->getMessage()
            ];
        }

        return $result;
    }

    /**
     * 记录内存使用情况
     */
    private function logMemoryUsage($context) {
        $memory = memory_get_usage(true);
        $memory_mb = round($memory / 1024 / 1024, 2);
        error_log("[$context] 内存使用: {$memory_mb} MB");
    }

    /**
     * 获取统计信息
     */
    public function getStats() {
        return [
            'processed' => $this->processed_count,
            'success' => $this->success_count,
            'errors' => $this->error_count
        ];
    }
}

// 生成测试数据
$test_data = [];
for ($i = 0; $i < 250; $i++) {
    if ($i % 10 === 0) {
        // 每10个中有一个格式错误的XML
        $test_data["xml_$i"] = '<root><item>未闭合标签</root>';
    } else {
        $test_data["xml_$i"] = "<?xml version=\"1.0\"?><root><item id=\"$i\">数据 $i</item></root>";
    }
}

echo "<h4>高性能XML批处理演示:</h4>";
echo "<p>处理数据量: " . count($test_data) . " 个XML文档</p>";

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

// 处理数据
$start_time = microtime(true);
$results = $processor->processBatch($test_data, ['chunk_size' => 50]);
$end_time = microtime(true);

$stats = $processor->getStats();

echo "<table class='table table-bordered'>";
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>" . round(($end_time - $start_time) * 1000, 2) . " 毫秒</td></tr>";
echo "<tr><td>平均每个XML</td><td>" . round(($end_time - $start_time) * 1000 / count($test_data), 3) . " 毫秒</td></tr>";
echo "</tbody></table>";

// 显示部分结果
echo "<h5>部分处理结果:</h5>";
echo "<div class='table-responsive' style='max-height: 300px; overflow-y: auto;'>";
echo "<table class='table table-sm table-bordered'>";
echo "<thead><tr><th>ID</th><th>状态</th><th>错误数</th><th>错误示例</th></tr></thead>";
echo "<tbody>";

$counter = 0;
foreach ($results as $id => $result) {
    if ($counter++ >= 15) break; // 只显示前15个结果

    if ($result['success']) {
        echo "<tr class='table-success'>";
        echo "<td>$id</td>";
        echo "<td><span class='badge bg-success'>成功</span></td>";
        echo "<td>0</td>";
        echo "<td>-</td>";
        echo "</tr>";
    } else {
        echo "<tr class='table-danger'>";
        echo "<td>$id</td>";
        echo "<td><span class='badge bg-danger'>失败</span></td>";
        echo "<td>" . count($result['errors']) . "</td>";

        $error_sample = '';
        if (!empty($result['errors'])) {
            $first_error = $result['errors'][0];
            $error_sample = htmlspecialchars(substr($first_error['message'], 0, 50)) . '...';
        }

        echo "<td>$error_sample</td>";
        echo "</tr>";
    }
}

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

echo "<div class='alert alert-info mt-3'>";
echo "<strong>性能优化说明:</strong>";
echo "<ul class='mb-0'>";
echo "<li>通过定期调用 <code>libxml_clear_errors()</code> 防止错误缓冲区无限增长</li>";
echo "<li>分块处理可以减少内存占用</li>";
echo "<li>及时清理错误可以避免内存泄漏</li>";
echo "</ul>";
echo "</div>";
?>

示例 5:与其他libxml函数配合使用

下面的示例演示 libxml_clear_errors() 与其他libxml函数如何配合工作。

<?php
/**
 * 演示libxml相关函数的配合使用
 */
class LibXmlDemo {

    /**
     * 安全解析XML并获取详细信息
     */
    public function safeParseWithDetails($xml_string) {
        // 1. 启用内部错误处理
        libxml_use_internal_errors(true);

        // 2. 清除之前的错误(确保干净状态)
        libxml_clear_errors();

        // 3. 获取libxml版本信息
        $version_info = $this->getLibXmlVersion();

        echo "<h5>libxml版本信息:</h5>";
        echo "<ul>";
        echo "<li>版本: {$version_info['version']}</li>";
        echo "<li>内部版本: {$version_info['internal_version']}</li>";
        echo "<li>实体加载: " . ($version_info['entity_loader'] ? '启用' : '禁用') . "</li>";
        echo "</ul>";

        // 4. 禁用实体加载(安全考虑)
        $old_entity_loader = libxml_disable_entity_loader(true);

        // 5. 设置解析选项
        $old_options = libxml_set_streams_context(stream_context_create([
            'http' => ['timeout' => 5]
        ]));

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

        // 7. 获取错误统计
        $error_stats = $this->getErrorStatistics();

        echo "<h5>解析结果:</h5>";
        if ($result) {
            echo "<div class='alert alert-success'>解析成功</div>";

            // 获取XML基本信息
            $xml_info = [
                'encoding' => $doc->encoding ?: '未指定',
                'version' => $doc->xmlVersion ?: '未指定',
                '根元素' => $doc->documentElement ? $doc->documentElement->tagName : '无'
            ];

            echo "<table class='table table-bordered table-sm'>";
            echo "<thead><tr><th>属性</th><th>值</th></tr></thead>";
            echo "<tbody>";
            foreach ($xml_info as $key => $value) {
                echo "<tr><td>$key</td><td>$value</td></tr>";
            }
            echo "</tbody></table>";
        } else {
            echo "<div class='alert alert-danger'>解析失败</div>";
        }

        echo "<h5>错误统计:</h5>";
        echo "<table class='table table-bordered table-sm'>";
        echo "<thead><tr><th>错误级别</th><th>数量</th></tr></thead>";
        echo "<tbody>";
        foreach ($error_stats as $level => $count) {
            echo "<tr><td>$level</td><td>$count</td></tr>";
        }
        echo "</tbody></table>";

        // 8. 显示详细错误信息(如果有)
        $errors = libxml_get_errors();
        if (!empty($errors)) {
            echo "<h5>详细错误信息:</h5>";
            echo "<div style='max-height: 200px; overflow-y: auto;'>";
            echo "<table class='table table-bordered table-sm'>";
            echo "<thead><tr><th>级别</th><th>代码</th><th>消息</th><th>位置</th></tr></thead>";
            echo "<tbody>";

            foreach ($errors as $error) {
                $level = $this->getErrorLevelName($error->level);
                $position = "行: {$error->line}, 列: " . ($error->column ?: '未知');

                echo "<tr>";
                echo "<td><span class='badge bg-danger'>$level</span></td>";
                echo "<td>{$error->code}</td>";
                echo "<td>" . htmlspecialchars($error->message) . "</td>";
                echo "<td>$position</td>";
                echo "</tr>";
            }

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

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

        // 10. 恢复原始设置
        libxml_disable_entity_loader($old_entity_loader);

        return $result ? $doc : false;
    }

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

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

    /**
     * 获取错误统计
     */
    private function getErrorStatistics() {
        $errors = libxml_get_errors();
        $stats = ['警告' => 0, '错误' => 0, '致命错误' => 0];

        foreach ($errors as $error) {
            $level_name = $this->getErrorLevelName($error->level);
            if (isset($stats[$level_name])) {
                $stats[$level_name]++;
            }
        }

        return $stats;
    }

    /**
     * 获取libxml版本信息
     */
    private function getLibXmlVersion() {
        return [
            'version' => LIBXML_VERSION,
            'internal_version' => LIBXML_DOTTED_VERSION,
            'entity_loader' => !libxml_disable_entity_loader()
        ];
    }
}

// 测试数据
$test_xml = '<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE root [
    <!ENTITY test "测试实体">
]>
<root>
    <item id="1">第一个项目</item>
    <item id="2">第二个项目</item>
    <item id="3">第三个项目 &test;</item>
    <item>未闭合标签
</root>';

echo "<h4>libxml函数综合演示:</h4>";

$demo = new LibXmlDemo();
$result = $demo->safeParseWithDetails($test_xml);

echo "<div class='alert alert-info mt-3'>";
echo "<strong>相关libxml函数:</strong>";
echo "<ul class='mb-0'>";
echo "<li><code>libxml_use_internal_errors()</code> - 启用/禁用内部错误处理</li>";
echo "<li><code>libxml_get_errors()</code> - 获取错误数组</li>";
echo "<li><code>libxml_get_last_error()</code> - 获取最后一个错误</li>";
echo "<li><code>libxml_disable_entity_loader()</code> - 禁用外部实体加载(安全)</li>";
echo "<li><code>libxml_set_streams_context()</code> - 设置流上下文</li>";
echo "</ul>";
echo "</div>";
?>

注意事项和最佳实践

  • 错误处理模式:在使用 libxml_clear_errors() 之前,通常需要先调用 libxml_use_internal_errors(true) 来启用内部错误处理模式。
  • 及时清理:在处理大量XML文档时,定期调用此函数可以防止错误缓冲区无限增长,避免内存泄漏。
  • 错误保存:如果需要保存错误信息用于日志记录或调试,请在调用 libxml_clear_errors() 之前使用 libxml_get_errors() 获取错误信息。
  • 并发环境:libxml错误缓冲区是全局的,在并发环境下使用时需要注意同步问题。
  • 与try-catch配合:XML解析异常(如内存不足)可能会抛出异常,而不仅仅是产生libxml错误,建议结合try-catch使用。
  • 性能影响:频繁调用此函数对性能影响很小,但在超高并发场景下仍需注意。
  • 兼容性:该函数自PHP 5.1.0起可用。
  • 重置状态:除了清除错误缓冲区,此函数还会重置libxml的内部错误状态。
  • 与自定义错误处理器配合:如果设置了自定义错误处理器,libxml错误仍会通过标准错误机制报告。

相关函数