Все о буферизации вывода в PHP

Что вы не узнаете из документации, момент с дырой в безопасности + советы о том, как ускорить отклик сервера.

Буферизация вывода позволяет вам сохранять выходные данные PHP (в основном генерируемые echo, var_dump, print_r) в памяти (т.е. в буфере) вместо немедленной передачи в браузер или терминал. Что полезно для самых разных задач:

Предотвращение вывода «на экран»:

ob_start();  // включает буферизацию
$foo->bar();  // весь вывод этой строки идет только в буфер
ob_end_clean();  // очищает буфер и закрывает буферизацию

Захватить вывод и записать в переменную: (Таким образом можно создавать кэш)

ob_start();  // включает буферизацию
$foo->render();  // весь вывод этой строки идет только в буфер
$output = ob_get_contents();  // сохраняет содержимое буфера в переменную
ob_end_clean();  // очищает буфер и закрывает буферизацию

Функции ob_get_contents() и ob_end_clean() могут быть заменены одной функцией: ob_get_clean(), в ее имени больше нет «end» хотя фактически она отключает буферизацию вывода:

$output = ob_get_clean();  // сохраняет содержимое буфера в переменную и отключает буферизацию

В приведенных выше примерах полученный буфер не был отправлен на выход. Если вы хотите отправить его, используйте ob_end_flush() вместо ob_end_clean().

Чтобы получить содержимое буфера, отправить его на выход и отключить буферизацию, есть ещё одна функция (включая отсутствующий end в имени): ob_get_flush().

Буфер так же может быть очищен в любое время не выключая его, используя функцию ob_clean() (очищает буфер) или ob_flush() (отправляет буфер на выход):

ob_start();  // включает буферизацию
$foo->bar();  // весь вывод этой строки идет только в буфер
ob_clean();  // удаляет содержимое буфера, при этом не выключая буферизацию
$foo->render(); // Весь этот вывод опять идет в буфер
ob_flush(); // Отправить вывод буфера на выход
$none = ob_get_contents();  // Содержимое буфера здесь пустая строка
ob_end_clean();  // очищает буфер и закрывает буферизацию

В буфер также отправляются выходные данные, записанные в выход php://output, буферизацию можно избежать, записав в php://stdout (или STDOUT), который доступен только в CLI, т.е. при запуске скриптов из командной строки.

Гнездование (вложенные буферы)

Буферы могут быть вложенными, поэтому, когда один буфер активен, другой ob_start() активирует новый буфер. Таким образом, ob_end_flush() и ob_flush() на самом деле отправляют буфер не на выход, а в родительский буфер. И только при отсутствии родительского буфера содержимое отправляется в браузер или терминал.

Поэтому важно отключить буферизацию, даже если происходит исключение:

ob_start();
try {
    $foo->render();
} finally {  finally доступен с PHP 5.5
    ob_end_clean(); // или ob_end_flush()
}

Размер буфера (chunk_size)

Буферизация также может улучшить производительность сервера, когда PHP не будет отправлять каждое echo в браузер, а вместо этого будет отправлять большие куски данных, например, по 4 кб. Просто вызовите в начале скрипта:

ob_start(null, 4096);

Когда размер буфера превышает 4096 байт, PHP автоматически выполняет flush, т.е. буфер очищается и отправляется. То же самое может быть достигнуто установкой директивы output_buffering, которая игнорируется в CLI.

Будьте осторожны , если вы начнете буферизацию без указания размера (то есть просто ob_start()), это приведет к тому, что страница будет отправляться не непрерывно, а один раз в конце скрипта, поэтому сервер будет реагировать очень медленно!

HTTP заголовки

Буферизация вывода не влияет на заголовки HTTP, они обрабатываются по-разному. Однако из-за буферизации вы можете отправлять заголовки даже после того, как выходные данные были отправлены, потому что они все еще находятся в буфере. Тем не менее, вы не должны полагаться на этот побочный эффект, потому что нет уверенности в том, что вывод не превышает размер буфера.

Дыра в безопасности

Когда скрипт PHP завершится, все ожидающие буферы запишут его содержимое в выход. Это можно вполне себе считать раздражающей дырой в безопасности. Если вы готовите в буфере конфиденциальные данные, которые не предназначены для вывода, и например, возникает ошибка, PHP записывает их в вывод. Решение состоит в том, чтобы использовать пользовательский обработчик:

 ob_start ( function () { return '' ; }); 

Пользовательские обработчики

Вы можете установить свой собственный обработчик, т.е. функцию, которая будет обрабатывать содержимое буфера перед отправкой:

ob_start(
    function ($buffer, $phase) { return mb_strtoupper($buffer); }
);
echo 'Привет';
ob_end_flush(); // выведет ПРИВЕТ

Также ob_clean() и ob_end_clean() вызывают обработчик, но отбрасывают вывод. Обработчик может определить, какая функция вызывается, и ответить на нее с помощью второго параметра $phase, который является битовой маской (начиная с PHP 5.4)

  • PHP_OUTPUT_HANDLER_START когда буфер активирован
  • PHP_OUTPUT_HANDLER_FINAL когда буфер отключен
  • PHP_OUTPUT_HANDLER_FLUSH при вызове ob_flush() (но не ob_end_flush() или ob_get_flush())
  • PHP_OUTPUT_HANDLER_CLEAN при вызове ob_clean(), ob_end_clean() или ob_get_clean()
  • PHP_OUTPUT_HANDLER_WRITE при автоматическом flush

Этапы start, final и flush (так же clean) могут происходить одновременно. Это можно различить с помощью бинарного оператора &:

if ($phase & PHP_OUTPUT_HANDLER_START) { ... }
if ($phase & PHP_OUTPUT_HANDLER_FLUSH) { ... }
elseif ($phase & PHP_OUTPUT_HANDLER_CLEAN) { ... }
if ($phase & PHP_OUTPUT_HANDLER_FINAL) { ... }

Является переводом статьи с phpfashion, здесь очень хорошо описана буферизация в PHP на английском языке. 🙂