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.

Чек-лист диагностики

  1. Найти output started at в сообщении ошибки.
  2. Проверить пробелы до <?php.
  3. Проверить BOM в указанном файле.
  4. Убрать закрывающий PHP-тег в чистых PHP-файлах.
  5. Найти echo, var_dump, print_r, die.
  6. Выключить display_errors на production.
  7. Проверить include-файлы и старые helpers.
  8. Повторить сценарий и посмотреть свежий лог.

Headers already sent почти всегда связан с тем, что вывод начался раньше времени. Ошибка может выглядеть странно, но лечится дисциплиной: не печатать отладку в ответ, не закрывать PHP-теги без необходимости и держать ошибки в логах.

Комментарии (0)

Пока нет комментариев. Будьте первым!