Введение: "Вы что-то уже отправили на экран, а я не могу"
Если вы когда-нибудь видели на своём сайте вместо красивой страницы белый экран с пугающим сообщением Warning: Cannot modify header information - headers already sent — вы не одиноки. Это, пожалуй, одна из самых популярных ошибок среди начинающих (и не только) PHP-разработчиков. На Stack Overflow этот вопрос набрал сотни тысяч просмотров, а количество копий неправильного кода в мире измеряется терабайтами.
Но хорошая новость в том, что ошибка «Headers already sent» — это не проклятие, а просто строгое напоминание от PHP: «Эй, ты уже начал отправлять HTML-код, а теперь пытаешься изменить HTTP-заголовки. Так нельзя!».
В этой статье мы разберём, почему возникает ошибка headers already sent в PHP, как её найти и, самое главное, как исправить раз и навсегда. Поехали!
1. Что такое HTTP-заголовки и почему они "уже отправлены"?
Прежде чем лезть в код, давайте на пальцах разберёмся, что происходит «под капотом». Когда PHP-скрипт выполняется, он сначала отправляет браузеру HTTP-заголовки (header). В них содержится служебная информация: тип контента (Content-Type), куки (Set-Cookie), редиректы (Location), статус ответа (200, 404, 301) и так далее.
И только после всех заголовков начинается отправка тела документа — того самого HTML, который вы видите на странице. PHP позволяет изменять заголовки только до того момента, как был отправлен первый байт контента.
Как только вы выводите что-то на экран (через echo, print, var_dump или даже просто пробел до <?php), PHP считает, что заголовки уже отправлены. Любая попытка вызвать header(), setcookie() или session_start() после этого приводит к фатальной ошибке.
1.1. Классический пример ошибки
<?php
echo "Привет, мир!";
// Ой! Мы уже что-то вывели.
header("Location: https://example.com");
// Warning: Cannot modify header information - headers already sent
?>
В этом примере echo отправляет текст в тело документа. После этого PHP не может перезаписать заголовки, чтобы сделать редирект. Всё логично.
2. Топ-5 причин возникновения "Headers already sent"
Ошибка может быть вызвана не только явным echo. Вот самые частые сценарии, которые ловят даже опытных разработчиков:
- Пробел или пустая строка перед
<?php— самый популярный "баг". Если в файле до открывающего тега есть хотя бы один пробел, он будет отправлен в вывод. - Невидимые символы BOM (Byte Order Mark) — файл сохранён в кодировке UTF-8 with BOM. Первые 3 байта (EF BB BF) считаются выводом.
- Вывод из подключаемых файлов — в файле
config.phpесть лишний пробел после?>. - Ошибки и предупреждения — если PHP выводит предупреждение (например, об использовании неопределённой переменной), это тоже считается отправкой контента.
- Функции
print,printf,var_dumpдо header() — любая функция вывода ломает заголовки.
3. Как найти источник проблемы: 3 метода
Прежде чем исправлять, нужно найти, откуда берётся «лишний» вывод. Используйте один из этих способов.
3.1. Включаем отображение всех ошибок
Добавьте в начало скрипта (до любого вывода):
<?php
error_reporting(E_ALL);
ini_set('display_errors', 1);
?>
PHP часто показывает точную строку, где произошёл вывод. Ищите сообщение вроде Output started at /path/to/file.php:12.
3.2. Используем функцию error_get_last()
Можно перехватить последнюю ошибку:
<?php
header("Location: /");
$error = error_get_last();
if ($error) {
echo "Ошибка: " . $error['message'] . " в строке " . $error['line'];
}
?>
3.3. Проверяем файлы на BOM и лишние пробелы
Откройте все подключаемые файлы (include, require) в вашем редакторе кода. Включите отображение непечатаемых символов. Если видите точки или знаки до <?php или после ?> — удалите их.
Совет: Многие современные редакторы (VS Code, PhpStorm) подсвечивают лишние пробелы. В VS Code включите настройку editor.renderWhitespace: "all".
4. 4 проверенных способа исправить ошибку
Теперь перейдём к практике. Выберите подходящий метод в зависимости от ситуации.
4.1. Удаляем лишний вывод (чистый код)
Это самый правильный способ. Проверьте все файлы на наличие:
- Пробелов или пустых строк до
<?php. - Закрывающего тега
?>в конце файла (если после него нет HTML). PHP-файлы могут быть без закрывающего тега — это хорошая практика. - Лишних пробелов после
?>.
Пример правильного файла:
<?php
// Нет пробелов до этой строки
$title = "Мой сайт";
// Закрывающий тег не ставим
Если после PHP-кода идёт чистый HTML, то закрывающий тег нужен, но убедитесь, что нет лишних пробелов после него.
4.2. Включаем буферизацию вывода (ob_start)
Если вы не можете найти источник вывода (например, легаси-код или сторонняя библиотека), используйте буферизацию. Она перехватывает весь вывод и отправляет его только после того, как скрипт завершится.
<?php
ob_start(); // Включаем буферизацию в самом начале
// ... ваш код с header() и setcookie() ...
header("Location: https://example.com");
ob_end_flush(); // Отправляем буфер и отключаем буферизацию
?>
Важно: ob_start() должен быть вызван до любого вывода. Лучше всего разместить его в самом начале главного файла (index.php).