Headers already sent в PHP: почему не работает редирект или cookie
Ошибка headers already sent в PHP появляется, когда код пытается отправить HTTP-заголовок после того, как в ответ уже попал какой-то вывод. Из-за этого может не работать redirect, установка cookie, сессия, скачивание файла или изменение status code.
На практике причина часто оказывается очень простой: пробел до открывающего PHP-тега, BOM в файле, случайный echo, var_dump, закрывающий PHP-тег в include-файле или предупреждение, которое попало в вывод.
Как выглядит ошибка
В логе обычно есть две важные части: где заголовок пытались отправить и где начался вывод.
Cannot modify header information - headers already sent by
(output started at /var/www/site.ru/config/params.php:1)
Строка output started at важнее всего. Она показывает файл, с которого начался вывод. Часто именно там и нужно искать проблему.
Пробелы и BOM
Если PHP-файл начинается с невидимого BOM или пробела до <?php, вывод уже начался. В браузере это может быть незаметно, но для заголовков критично.
<?php
return [
'adminEmail' => 'admin@example.ru',
];
Перед открывающим тегом не должно быть ничего. Для конфигов и классов закрывающий PHP-тег в конце файла обычно не нужен. Это снижает риск случайных пробелов после него.
<?php
return [
'adminEmail' => 'admin@example.ru',
];
Случайный вывод при отладке
var_dump, print_r, echo и die часто остаются после быстрой проверки. Если они срабатывают до redirect или setcookie, заголовки уже не отправить.
var_dump($model->errors);
return $this->redirect(['index']);
Для диагностики на рабочем сайте лучше писать в лог:
Yii::info($model->errors, 'debug-form');
Предупреждения PHP тоже являются выводом
Если display_errors включён на production, warning или notice может попасть прямо в HTML. После этого cookie или redirect уже ломаются.
Warning: Undefined array key "phone"
На production ошибки лучше писать в лог, а не показывать пользователю.
display_errors = Off
log_errors = On
Сессии и cookie
Сессия тоже использует заголовки. Если вывод начался раньше session_start, PHP не сможет корректно отправить cookie сессии.
echo 'debug';
session_start();
В Yii2 сессия обычно запускается через компоненты приложения, но ошибка всё равно возможна, если до инициализации пошёл вывод из подключенного файла.
Редиректы в Yii2
В Yii2 редирект должен выполняться до вывода контента. Если в action уже что-то напечатали напрямую, redirect может не сработать.
public function actionSave()
{
echo 'saved';
return $this->redirect(['index']);
}
Правильнее вернуть response без прямого вывода:
public function actionSave()
{
Yii::$app->session->setFlash('success', 'Сохранено');
return $this->redirect(['index']);
}
Файлы для скачивания
При отдаче файла лишний вывод особенно заметен: файл повреждается или скачивается с лишними символами в начале.
return Yii::$app->response->sendFile($path);
Перед sendFile не должно быть echo, пробелов в include-файлах и предупреждений PHP.
Как искать причину
Если ошибка неочевидна, нужно открыть файл из output started at и проверить начало и конец. Также полезно искать закрывающие PHP-теги в проекте.
grep -R "?>" -n config components helpers
Для проверки BOM можно использовать:
xxd -g 1 config/params.php | head
Если файл начинается с ef bb bf, это BOM.
Чек-лист диагностики
- Найти output started at в сообщении ошибки.
- Проверить пробелы до <?php.
- Проверить BOM в указанном файле.
- Убрать закрывающий PHP-тег в чистых PHP-файлах.
- Найти echo, var_dump, print_r, die.
- Выключить display_errors на production.
- Проверить include-файлы и старые helpers.
- Повторить сценарий и посмотреть свежий лог.
Headers already sent почти всегда связан с тем, что вывод начался раньше времени. Ошибка может выглядеть странно, но лечится дисциплиной: не печатать отладку в ответ, не закрывать PHP-теги без необходимости и держать ошибки в логах.
Комментарии (0)
Пока нет комментариев. Будьте первым!