Как исправить ошибку отправки заголовков в PHP: буферизация и проверки

Раздел: Работа с HTTP заголовками -> Обработка ошибок заголовков

Причины и способы устранения ошибки 'Cannot modify header information'

Эта ошибка возникает, когда PHP пытается изменить HTTP-заголовки после того, как часть ответа уже была отправлена. Вывод любого текста (включая пробелы до <?php) делает заголовки неизменяемыми. Рассмотрим наиболее эффективное решение и альтернативные подходы.

Как гарантированно избежать ошибки при любом выводе?

Включение буферизации вывода с помощью ob_start() в начале скрипта. Весь вывод накапливается в буфере, и заголовки можно изменять в любой момент до вызова ob_end_flush() или автоматического сброса при завершении скрипта.


<?php
ob_start(); // начинаем буферизацию
// любой код, в том числе заголовки
echo "Текст до header()";
header('Content-Type: text/html; charset=utf-8');
header('X-Custom: value');
ob_end_flush(); // отправляем заголовки и содержимое

Php warning cannot modify header information (ошибка 'cannot modify header information' в php)

Этот метод подходит для любых скриптов, включая legacy-код, где сложно убрать вывод до заголовков.

Проблема: Если буферизация включена глобально в php.ini (output_buffering = On), то вызов ob_start() может создать вложенные буферы. Это не ошибка, но избыточно. Убедитесь, что вы не забыли вызвать ob_end_flush(), иначе буфер может не сброситься до конца скрипта (хотя при завершении PHP сбрасывает всё).

Как проверить, отправлены ли заголовки, перед их установкой?

Используйте функцию headers_sent(), которая возвращает true, если вывод уже начался. Тогда можно принять решение: не устанавливать заголовок или очистить буфер.


<?php
if (!headers_sent()) {
    header('Location: /new-page');
} else {
    echo "Заголовки уже отправлены, редирект невозможен";
}

Вариант полезен при разработке модулей или плагинов, когда нельзя контролировать весь вывод.

Проблема: Вывод, сделанный до проверки, уже попал в буфер вывода или на клиента. Поэтому проверка не отменяет факт вывода. Лучше использовать в паре с буферизацией.

Как настроить буферизацию глобально через php.ini?

Установка директивы output_buffering = On в файле конфигурации PHP. Тогда все скрипты будут работать с включённой буферизацией, и ошибка не возникнет.


; php.ini
output_buffering = On

Это решение для всего сервера или виртуального хоста. Подходит, если у вас есть доступ к настройкам PHP и нужно быстро исправить проблему на всех сайтах.

Проблема: Включение глобальной буферизации может повлиять на работу приложений, которые полагаются на немедленную отправку данных (например, потоковая передача). Также увеличивается потребление памяти при большом объёме вывода.

Как перехватить вывод до отправки заголовков с помощью ob_clean?

Если вывод уже начался, но заголовки ещё не отправлены (буфер не сброшен), можно очистить буфер функцией ob_clean() и затем установить заголовки.


<?php
ob_start();
echo "Временный вывод";
ob_clean(); // очищаем буфер
header('Content-Type: application/json');
echo json_encode(['status' => 'ok']);
ob_end_flush();

Этот приём применяется, когда нужно динамически изменить тип контента после того, как часть данных уже записана в буфер.

Проблема: Если между ob_clean() и ob_end_flush() произошёл вывод с ошибкой, весь контент потеряется. Стоит оборачивать такие участки в try-catch.

Как исправить ошибку при использовании setcookie?

setcookie() также отправляет заголовки Set-Cookie. Применяйте те же методы: буферизация или проверка headers_sent().


<?php
ob_start();
// до любого вывода
setcookie('user', 'John', time()+3600);
echo 'Куки установлена';
ob_end_flush();

Этот пример демонстрирует стандартный способ работы с куками в контролируемом окружении.

Проблема: Если кука устанавливается после вывода, браузер может её проигнорировать. Всегда используйте буферизацию или помещайте вызов setcookie() до любого вывода.

Расширенные примеры работы с буферизацией и заголовками

Пример 1: Буферизация в большом скрипте с множественными заголовками

Пример

<?php
ob_start();

// Имитация загрузки данных
$data = ['name' => 'Alice', 'role' => 'admin'];

// Устанавливаем заголовки в произвольном порядке
header('Content-Type: application/json');
header('X-Request-Id: 12345');

// Вывод данных (может быть много строк)
echo json_encode($data, JSON_PRETTY_PRINT);

// Дополнительные заголовки после вывода? Нет, но они могут быть внутри буфера
header('X-Process-Time: ' . (microtime(true) - $_SERVER['REQUEST_TIME_FLOAT']));

ob_end_flush();
Результат: HTTP-ответ содержит все три заголовка и тело JSON. Ошибки нет, так как весь вывод буферизирован до момента вызова ob_end_flush().

Пример 2: Проверка headers_sent в окружении с включённой буферизацией

Пример

<?php
// Буферизация не включена явно, но может быть в php.ini
if (!headers_sent()) {
    header('Location: /login');
    exit;
} else {
    echo 'Редирект невозможен, выводим ссылку: <a href="/login">Войти</a>';
}

Если headers_sent() вернула true, значит, до этого был какой-то вывод. Вывод может быть в буфере, но он уже зафиксирован как начало отправки. Для гарантии лучше в начале скрипта вызывать ob_start().

Пример 3: Использование ob_start с callback для модификации всего вывода

Пример

<?php
ob_start(function($buffer) {
    // Заменяем все ссылки на https
    return str_replace('http://', 'https://', $buffer);
});
echo 'Ссылка: http://example.com';
header('Content-Type: text/html; charset=utf-8');
ob_end_flush();
Результат: В теле ответа все http:// заменены на https://. Заголовок Content-Type установлен внутри буфера и передан вместе с телом.

Пример 4: Работа с сессиями и заголовками (session_start)

Сессия также использует заголовки и может вызывать ошибку, если вывод начат до session_start().

Пример

<?php
ob_start();
session_start(); // Устанавливает заголовки Set-Cookie
$_SESSION['user'] = 'Bob';
echo 'Сессия стартовала';
ob_end_flush();

Буферизация решает проблему, так как заголовки сессии будут отправлены до фактического вывода.

Пример 5: Принудительная отправка заголовков и очистка буфера

Пример

<?php
ob_start();
// Вывод контента
echo 'Старый контент';
// Внезапно понадобилось сменить тип ответа
ob_clean(); // очищаем буфер
header('Content-Type: text/plain');
echo 'Новый контент';
ob_end_flush();
Результат: Клиент получит только "Новый контент" с заголовком text/plain. Старый контент удалён.

Важно: После ob_clean() нужно повторно установить все необходимые заголовки, так как ob_start() сохраняет их в буфере, но ob_clean() удаляет только тело, заголовки остаются в буфере (зависит от версии PHP, лучше переустановить).

Пример 6: Обработка ошибок в сценарии с буферизацией

Пример

<?php
ob_start();
try {
    // Код, который может выдать ошибку
    if ($someCondition) {
        throw new Exception('Ошибка обработки');
    }
    header('HTTP/1.1 200 OK');
    echo 'Успех';
} catch (Exception $e) {
    header('HTTP/1.1 500 Internal Server Error');
    ob_clean(); // очищаем предыдущий вывод
    echo 'Ошибка: ' . $e->getMessage();
}
ob_end_flush();

Этот шаблон гарантирует, что даже при ошибке заголовки будут корректно изменены.

Ошибка 'Cannot modify header information' в PHP - comments

En
Php warning cannot modify header information (php)