PHP headers_sent() 函数

headers_sent() 函数用于检查 HTTP 头是否已经发送给客户端。

一旦 HTTP 头已经发送,就无法再通过 header() 函数发送新的头信息。这个函数对于调试和避免 "headers already sent" 错误非常有用。

注意:如果头信息已经发送,任何后续使用 header() 函数的尝试都会失败,并产生一个警告。使用 headers_sent() 可以在尝试发送头信息之前进行检查。

语法

headers_sent ([ string &$file [, int &$line ]] ) : bool

参数

参数 类型 描述
$file string 可选的(通过引用传递)。如果提供了此参数,并且头信息已经发送,该变量将被设置为输出开始的文件名。
$line int 可选的(通过引用传递)。如果提供了此参数,并且头信息已经发送,该变量将被设置为输出开始的行号。

返回值

返回值 描述
true 如果 HTTP 头已经发送给客户端。
false 如果 HTTP 头尚未发送。

示例

示例 1:基本使用 - 检查头信息是否已发送

以下示例展示了如何检查 HTTP 头是否已经发送。

<?php
// 检查头信息是否已发送
if (headers_sent()) {
    echo "HTTP 头已经发送,无法再发送新的头信息。\n";
} else {
    echo "HTTP 头尚未发送,可以发送新的头信息。\n";
    // 这里可以安全地使用 header() 函数
    header("X-Custom-Header: MyValue");
}
?>

示例 2:获取头信息开始发送的位置

使用可选参数获取头信息开始发送的文件和行号。

<?php
// 假设在发送一些输出后检查
echo "Some output before checking headers.\n";

// 检查头信息是否已发送,并获取位置
if (headers_sent($file, $line)) {
    echo "头信息已经开始发送。\n";
    echo "发送开始的文件: " . $file . "\n";
    echo "发送开始的行号: " . $line . "\n";

    // 这对于调试 "headers already sent" 错误非常有用
} else {
    echo "头信息尚未发送。\n";
    header("Content-Type: text/plain");
}
?>

示例 3:安全地发送头信息

在尝试发送头信息之前进行检查,避免错误。

<?php
/**
 * 安全地发送 HTTP 头信息
 * @param string $header 要发送的头信息字符串
 * @param bool $replace 是否替换相同类型的头信息
 * @param int $response_code 强制指定的 HTTP 响应码
 * @return bool 如果头信息发送成功返回 true,否则返回 false
 */
function safe_header($header, $replace = true, $response_code = null) {
    if (headers_sent()) {
        // 记录错误到日志,而不是显示给用户
        error_log("无法发送头信息 '$header',头信息已经发送。");
        return false;
    }

    if ($response_code !== null) {
        header($header, $replace, $response_code);
    } else {
        header($header, $replace);
    }

    return true;
}

// 使用安全函数发送头信息
if (safe_header("Content-Type: application/json")) {
    echo json_encode(['status' => 'success']);
} else {
    // 备选方案:可能需要使用其他方式发送数据
    echo '{"status": "error", "message": "无法设置头信息"}';
}
?>

示例 4:调试 "headers already sent" 错误

当遇到 "headers already sent" 错误时,使用 headers_sent() 进行调试。

<?php
// 模拟一个常见的错误场景:在输出之后尝试发送头信息

// 一些输出(可能来自包含文件或错误的空格)
echo "Some output before header.\n";

// 现在尝试发送头信息
if (!headers_sent()) {
    header("Location: /newpage.php");
    exit;
} else {
    // 获取头信息开始发送的位置
    headers_sent($file, $line);

    echo "<h1>调试信息</h1>\n";
    echo "<p>无法重定向,因为头信息已经发送。</p>\n";
    echo "<p>头信息开始发送的文件: <code>" . htmlspecialchars($file) . "</code></p>\n";
    echo "<p>头信息开始发送的行号: <code>" . $line . "</code></p>\n";

    // 提供更多调试信息
    echo "<h2>可能的解决方案:</h2>\n";
    echo "<ul>\n";
    echo "<li>检查文件 <code>" . htmlspecialchars($file) . "</code> 的第 " . $line . " 行之前是否有输出</li>\n";
    echo "<li>检查文件开头是否有空格或空行</li>\n";
    echo "<li>确保文件保存为无 BOM 的 UTF-8 格式</li>\n";
    echo "<li>使用输出缓冲(ob_start())来延迟输出</li>\n";
    echo "</ul>\n";
}
?>

示例 5:与输出缓冲结合使用

使用输出缓冲来避免头信息发送过早的问题。

<?php
// 开始输出缓冲
ob_start();

// 在缓冲区内进行输出
echo "This output is buffered.\n";
echo "It won't cause headers to be sent yet.\n";

// 检查头信息是否已发送(应该返回 false)
if (headers_sent()) {
    echo "ERROR: Headers already sent!\n";
} else {
    echo "Headers not sent yet - we can still set them.\n";

    // 安全地设置头信息
    header("Content-Type: text/plain");
    header("X-Custom-Header: BufferedExample");
}

// 更多输出
echo "More buffered output.\n";

// 结束缓冲并发送所有内容
ob_end_flush();

// 现在头信息已经发送
if (headers_sent()) {
    echo "\nNow headers have been sent.\n";
}

// 尝试在头信息发送后设置新头信息(会失败)
if (!headers_sent()) {
    header("Another-Header: This will fail");
} else {
    echo "Cannot set Another-Header because headers already sent.\n";
}
?>

示例 6:在函数或类中使用 headers_sent()

在函数或类方法中安全地处理头信息。

<?php
/**
 * HTTP 响应类
 */
class HttpResponse {
    /**
     * 发送 JSON 响应
     * @param mixed $data 要编码为 JSON 的数据
     * @param int $status_code HTTP 状态码
     * @return bool 成功返回 true,失败返回 false
     */
    public static function json($data, $status_code = 200) {
        // 检查头信息是否已发送
        if (headers_sent()) {
            error_log("Cannot send JSON response: headers already sent");
            return false;
        }

        // 设置 JSON 头信息
        header("Content-Type: application/json; charset=UTF-8");
        http_response_code($status_code);

        // 输出 JSON 数据
        echo json_encode($data, JSON_PRETTY_PRINT);

        return true;
    }

    /**
     * 重定向到指定 URL
     * @param string $url 要重定向到的 URL
     * @param int $status_code 重定向状态码(301 或 302)
     * @return bool 成功返回 true,失败返回 false
     */
    public static function redirect($url, $status_code = 302) {
        if (headers_sent()) {
            error_log("Cannot redirect to $url: headers already sent");

            // 备选方案:使用 JavaScript 重定向
            echo '<script>window.location.href="' . htmlspecialchars($url) . '";</script>';
            echo '<noscript><meta http-equiv="refresh" content="0;url=' . htmlspecialchars($url) . '"></noscript>';
            return false;
        }

        // 使用适当的 HTTP 状态码进行重定向
        if ($status_code === 301) {
            header("Location: $url", true, 301);
        } else {
            header("Location: $url", true, 302);
        }

        return true;
    }
}

// 使用示例
HttpResponse::json(['message' => 'Hello, World!']);

// 或者在需要时重定向
if ($some_condition) {
    HttpResponse::redirect('/newpage.php', 301);
    exit;
}
?>

注意事项

  • 输出控制:任何输出,包括空格、换行符、HTML 标签或来自 echoprint 等的文本,都会导致头信息被发送。
  • 文件编码:确保 PHP 文件保存为无 BOM 的 UTF-8 格式。BOM(字节顺序标记)可能导致在文件开头发送不可见字符,从而使头信息过早发送。
  • 包含文件:被包含的文件中的输出也会影响头信息发送。确保在包含文件之前没有输出。
  • 错误报告:PHP 错误和警告可能包含输出,从而导致头信息发送。在生产环境中,应适当配置错误报告。
  • 输出缓冲:使用 ob_start()ob_end_flush() 可以控制输出时机,从而避免头信息过早发送的问题。
  • 调试:当遇到 "headers already sent" 错误时,使用 headers_sent($file, $line) 可以快速定位问题源头。
  • 性能:频繁调用 headers_sent() 可能会有轻微的性能影响,但通常可以忽略不计。

常见问题

问题 解决方案
总是收到 "headers already sent" 错误
  • 检查文件开头和结尾是否有空格或空行
  • 确保文件保存为无 BOM 的 UTF-8 格式
  • 检查所有包含文件是否有输出
  • 使用输出缓冲来延迟输出
headers_sent() 始终返回 true
  • 可能已经有输出被发送
  • 检查是否有错误消息被输出
  • 使用 headers_sent($file, $line) 定位输出开始的位置
无法在函数中发送头信息
  • 在调用函数之前检查头信息是否已发送
  • 使用 safe_header() 函数(如示例3)
  • 考虑使用输出缓冲
重定向不起作用
  • 检查头信息是否已发送
  • 确保在 header("Location: ...") 后调用 exitdie
  • 考虑使用 JavaScript 或 meta refresh 作为备选方案
输出缓冲导致性能问题
  • 只对需要延迟输出的部分使用输出缓冲
  • 避免缓冲大量数据
  • 考虑在适当的时机清空缓冲区

实用技巧

1. 创建安全的头信息发送函数
<?php
function send_header_if_possible($header, $value = null, $replace = true) {
    if (headers_sent()) {
        return false;
    }

    if ($value !== null) {
        header("$header: $value", $replace);
    } else {
        header($header, $replace);
    }

    return true;
}

// 使用示例
send_header_if_possible("Content-Type", "text/html");
send_header_if_possible("HTTP/1.1 404 Not Found");
?>
2. 调试输出位置
<?php
function debug_headers_sent() {
    if (headers_sent($file, $line)) {
        echo "<div style='background:#ffeeee;padding:10px;border:1px solid red;'>";
        echo "<strong>Warning:</strong> Headers already sent!<br>";
        echo "File: $file<br>";
        echo "Line: $line";
        echo "</div>";
        return true;
    }
    return false;
}

// 在脚本的关键位置调用
debug_headers_sent();
?>
3. 确保文件无 BOM
<?php
// 检查文件是否有 BOM
function has_bom($filename) {
    $handle = fopen($filename, 'r');
    $bom = fread($handle, 3);
    fclose($handle);

    return $bom === "\xEF\xBB\xBF";
}

// 移除 BOM
function remove_bom($filename) {
    $content = file_get_contents($filename);
    if (substr($content, 0, 3) === "\xEF\xBB\xBF") {
        $content = substr($content, 3);
        file_put_contents($filename, $content);
    }
}
?>

相关函数