Как исправить ошибку отправки заголовков в PHP: буферизация и проверки
Причины и способы устранения ошибки '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();
Этот шаблон гарантирует, что даже при ошибке заголовки будут корректно изменены.