PHP: Буферизация вывода

PHP: Буферизация вывода

Индекс материала
PHP: Буферизация вывода
Повторное использование буферов
Чтение буферов вывода
Сжатие выходных данных
Все страницы

Буферизация вывода в PHP это довольно полезная штука, если уметь ею пользоваться. Скажите сколько раз вы видели ошибки типа:

Warning: Cannot modify header information - headers already sent by (output started at ...)

Как правило подобное случается, если вы хотите отправить куки, или выполнить операции, которые способствуют их отправке, или иные операции способствующие отправке заголовков, например запуск сессии или применение функции header или подобных. Или ваш файл в кодировке юникод и в самом его начале притаилась BOM - метка порядка байтов. Это подлющая штука есть неразрывный пробел с нулевой шириной. Смотрим под заголовком "Порядок байтов".

Всё очень просто: По - умолчанию никакой буферизации вывода нет ( если, конечно вы не нашаманили в файле php.ini и не присвоили директиве output_buffering значение On ) и PHP отсылает данные юзеру, сразу, как только они готовы. Согласно протоколу HTTP, сервер в ответ на запрос пользователя, должен сначала отправить ему служебные заголовки, а уже после, тело страницы. А тут внезапно, в коде вы опять пытаетесь заставить его отправить HTTP - заголовки, вызывая, скажем функцию session_start() - после удачной авторизации... Ясен пень - повторная отправка заголовков запрещена, HTTP - протокол так не работает! Но что ж делать то? Если после вывода на странице, нужно ещё и сессию стартануть и кУку поставить? - Вспоминаем про буферизацию вывода.

Возможности при буферизации

Используя буферизацию вывода мы можем:

  • Посылать cookie из любого места в скрипте.
  • Стартовать сессию в любой момент.
  • Сжимать данные, перед отправкой их пользователю.

Сжатие дополнительно нагружает процессор, тем не менее объем трафика на одну страницу уменьшится примерно процентов на сорок, а это значит, что сервер потратит меньше времени на пересылку данных по сети. Величина сжатия зависит от многих факторов, например картинки сжимаются хуже чем текст.

Что происходит при буферизации?

При буферизации вывода, механизм PHP складывает в стопку весь вывод скрипта, паралельно формируя пакет HTTP - заголовков, в том числе, добавляя туда и заголовки "cookie" и любые другие, которые получатся в результате работы вашего скрипта. А потом, когда скрипт отработал он берёт и отправляет всё это клиенту в правильном порядке: сначала заголовки, а потом страницу - результат работы скрипта.

Как включить буферизацию вывода?

Первый способ это если сервак ваш, или у вас просто есть доступ к файлу php.ini ( как я писал выше ) ищем в нём директиву output_buffering и присваиваем ей значение On. Это включит буферизацию для всех скриптов.

Второй способ это использовать функцию ob_start() в скрипте, вывод которого нам нужно буферизовать. Этот способ предпочтительней - вы получите большую гибкость/контроль в работе, а так же лучшую переносимость.

На мой взгляд довольно удобно рассматривать буферизацию вывода, в виде неких контейнеров - буферов. Так проще понять их работу.

Итак, открыть такой контейнер - буфер можно лишь одной функцией ob_start(), а вот закрыть этот самый буфер можно двумя функциями: ob_end_flush() и ob_end_clean()

ob_end_flush()

Закрывает буфер и отправляет данные.

ob_end_clean()

Закрывает буфер без отправки данных.

Все содержимое, которое выводиться в тот момент, когда буфер открыт попадает в буфер и никуда не отсылается. Например:

ob_start();
echo '<p>первый буфер </p>';
ob_end_flush();

ob_start();
echo '<p>второй буфер </p>';
ob_end_сlean();

ob_start();
echo '<p>третий буфер </p>';

Этот скрипт выведет результат:

<p>первый буфер </p>
<p>третий буфер </p>

Разберемся что произошло. Мы создали три буфера. Строки 1-3 - это первый. Он закрыт функцией ob_end_flush() - его вывод мы можем наблюдать в результате.

Второй буфер - это строки 5-7. Он закрыт с помощью функции ob_end_clean() - поэтому его вывод пропал безследно.

И третий буфер - это строки 9-10. Он у нас не закрыт никак! - Шозанах? - спросите вы ..? Всё просто - по окончанию сценария, PHP автоматом закрывает все буферы так, как будто они были закрыты функцией ob_end_flush().


Повторное использование буферов

Функции ob_end_flush() и ob_end_clean() дополняются функциями: ob_flush() и ob_clean() - они делают то же самое, но не закрывают буфер вывода. Верхний пример с их применением может выглядеть так:

ob_start();
echo '<p>первый</p>';
ob_flush();
echo '<p>второй</p>';
ob_сlean();
echo '<p>третий</p>';

Этот скрипт выведет результат:

<p>первый буфер </p>
<p>третий буфер </p>

Смотрим сверху вниз. На этот раз содержимое выводится так же строка 1-3, но буфер не закрывается, затем буфер просто чистится (строа 5) без вывода содержимого, оставаясь открытым, и наконец буфер автоматом закрывается, а содержимое выводится, так как подошёл конец скрипта. Такой подход даст 60% прирост производительности, по сравнению с предыдущим, за счёт того, что мы избегаем лишних открытий/закрытий буферов.

Стек буферов

Если рассматривать буферы вывода как контейнеры, то вполне можно понять тот факт, что один контейнер - буфер, можно поместить внутрь другого, создав некий стек буферов, который в свою очередь подчиняется определённым правилам. Стек буферов приблизительно можно представить так:

Стек буферов вывода

Подпись "уровень" - это фактический уровень вложенности буфера. Когда в коде следует инструкция очистить буфер и вывести его содержимое ( например вызов функции ob_end_flush() ), то содержимое буфера выводится внутрь его родительского контейнера. Если родительского контейнера нет, то содержимое выводится в основной поток, т.е. (условно) на экран:

// Буфер верхнего уровня:
ob_start();

ob_start();
echo '<p>вывод второго буфера</p>';
ob_end_flush();

echo '<p>вывод первого буфера</p>';

ob_start();
echo '<p>вывод третьего буфера</p>';
ob_end_flush();

ob_end_flush();

Выведет следующее:

<p>вывод второго буфера</p>
<p>вывод первого буфера</p>
<p>вывод третьего буфера</p>

Обратите внимание: очерёдность вывода сохраняется. Теперь проведём такой эксперимент - "зарэжэм"основной буфер, т.е. тот который находиться на самом верху стека:

// Буфер верхнего уровня:
ob_start();

ob_start();
echo '<p>вывод второго буфера</p>';
ob_end_flush();

echo '<p>вывод первого буфера</p>';

ob_start();
echo '<p>вывод третьего буфера</p>';
ob_end_flush();

// Вот тут мы убили весь вывод.
ob_end_clean();

Теперь мы с вами ничего не увидим, потому что вложенные буферы извлекли своё содержимое в буфер верхнего уровня, а буфер верхнего уровня у нас закрылся функцией ob_end_clean() - которая накрыла весь вывод медным тазом.

Главное помнить, что буферы вывода работают в стеке, и всё будет ок.


Чтение буферов вывода

Буфер вывода - штука двунаправленная, т.е. в него можно как писать (ударение на втором слоге! ), так и читать из него. Читать можно в переменную. Сделать это позволяет функция ob_get_contents()

ob_get_contents()

Эта функция не принимает параметров, и возвращает всё содержимое последнего открытого буфера:

ob_start();

echo 'Это какой то текст... много текста...';
// Получаем содержимое буфера в переменную:
$content = ob_get_contents();
// Чистим бфер
ob_end_clean();
// Пишем данные в файл:
file_put_contents('somefile.txt', $content);

Этот скрипт рассматривает выводимые данные, как сверх оперативную память и сохраняет их в файл, а не выводит пользователю.

Другие ob - функции

У механизма буферизации вывода есть ещё несколько полезных функций:

ob_get_length()

Не принимает никаких параметров, возвращает число - количество байт информации, хранимой в буфере. Бывает полезна, например при передаче HTTP - заголовка Contetnt-length.

ob_get_level()

Не принимает никаких параметров, возвращает число - уровень вложенности буфера. Вернёт 0, если вызвана вне какого - либо буфера. Бывает полезна, при реализации рекурсивного закрытия буферов, с передачей или без передачи данных в случае возникновения ошибки.

ob_get_handlers()

Не принимает никаких параметров, возвращает массив, содержащий дескрипторы вывода, которые работают на данный момент. Если буферизация включена - вернёт массив, в котором будет дескриптор по-умолчанию, если используется gzip сжатие буфера (об этом позже) то получим дескриптор "ob_gzhandler", а если используется замена URL то получим "URL-Rewriter".

Отправка выходных данных

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

for($i = 1; $i < 11; $i++ )
{	
	sleep(1);
	echo '

Это очередное обновление данных № '.$i.'

' ; flush(); }

результат:

<p>Это очередное обновление данных № 1</p>
<p>Это очередное обновление данных № 2</p>
...
<p>Это очередное обновление данных № 10</p>

При работе этого скрипта данные к клиенту будут уходить частями, с интервалом в 1 сек. И клиент в реальном времене будет видеть, как у него появляются новые данные.

В IE есть некий механизм "оптимизации" который отображает страницу только после получения первых 256 байт информации! Независимо от применения вами функции flush(). Так что имейте ввиду с "ишаком", как всегда могут быть проблемы, если ваш скрипт отправляет клиенту меньше 256 символов ( или примерно 128 символов кирилицы в юникоде ) .

Функция ob_implicit_flush() - избавляет вас от вызова каждый раз функции flush():

ob_implicit_flush();

for($i = 1; $i < 11; $i++ )
{	
	sleep(1);
	echo '

Это очередное обновление данных № '.$i.'

' ; }

Этот скрипт даст тот же эффект, что и скрипт выше.


Сжатие выходных данных

Для того чтобы сжать буфер вывода нужно передать функции ob_start() параметр - имя функции - компрессора, например ob_gzhandler.При этом будет выполнена проверка поддержки сжатия и если такая поддержка есть, то данные будут сжиматься:

// Здесь мы включили сжатие буфера:
ob_start('ob_gzhandler');
echo 'Contrary ... section';
ob_end_flush();

Результат обведён красным:

Сжатие буфера вывода

А вот результат без сжатия вывода:

Без сжатия вывода

Сжатие работает только применительно к HTML - коду, к картинкам и прочему это не относится. Учитывайте это. А ещё PHP позволяет сжимать только один буфер вывода, так как содержимое должно сжиматься всё и сразу. Имейте это ввиду, если вы используете стек буферов вывода.

Переписывание URL

Скажу сразу что к ЧПУ ссылкам и к mod_rewrite данные возможности не имеют никакого отношения, но тем не менее позволяют вытворятьинтересные штуки.

output_add_rewrite_var( 'name', 'value' )

Эта функция принимает два параметра, первый это имя переменной, а второй это значение этой переменной. Эта пара ключ-значение добавиться ко всем URL, формам и атрибутам SRC - элементов FRAME, в виде GET - переменных строки запроса. К формам добавятся скрытые поля с соответствующими значениями. В общем проще показать на примере:

ob_start();
output_add_rewrite_var('somevar', 'someval');

echo '<form action="anywere.html" method="get">
        <input name="..." value="..." />
      </form>';

echo '<a href="anywere.html">LINK</a>';

ob_end_flush();

Выдаст такой HTML - код:

<form action="anywere.html" method="get">
  <input type="hidden" name="somevar" value="someval" />
  <input name="..." value="..." />
</form>

<a href="anywere.html?somevar=someval">LINK</a>

Заметьте атрибуты action тега form - остались не тронуты! Пример с тегами FRAME:

ob_start();
output_add_rewrite_var('somevar', 'someval');

echo '<frameset cols="80,*">
        <frame src="left.html" name="leftFrame" scrolling="no" noresize>
        <frame src="main.html" name="mainFrame">
     </frameset>';

ob_end_flush();

Выдаст такой HTML - код:

<frameset cols="80,*">
  <frame src="left.html?somevar=someval" name="leftFrame" scrolling="no" noresize>
  <frame src="main.html?somevar=someval" name="mainFrame">
</frameset>

Функцию output_add_rewrite_var() можно вызывать несколько раз с разными параметрами, в этом случае пары будут добавляться согласно формату кодирования данных: "application/x-www-form-urlencoded" разделяясь знаками амперсанда а в формы будут добавляться новые скрытые поля. Вот так будет выглядеть пример:

ob_start();

output_add_rewrite_var('somevar', 'someval');
output_add_rewrite_var('somevar1', 'someval1');
output_add_rewrite_var('somevar2', 'someval2');

echo '<form action="anywere.html" method="get">
        <input name="..." value="..." />
			</form>';

echo '<a href="anywere.html">LINK</a>';

ob_end_flush();

Выдаст такой HTML - код:

<form action="anywere.html" method="get">
  <input type="hidden" name="somevar" value="someval" />
  <input type="hidden" name="somevar1" value="someval1" />
  <input type="hidden" name="somevar2" value="someval2" />
  <input name="..." value="..." />
</form>
<a href="anywere.html?somevar=someval&somevar1=someval1&somevar2=someval2">LINK</a>

А вот если в конце примера вызвать функцию output_reset_rewrite_vars() - то она отменит все изменения произведённые вызовами функций output_add_rewrite_var(...) т.е. все ссылки вернуться к исходному состоянию.

Ну, вот надеюсь я доступно объяснил что такое буферизация вывода в PHP и с чем её едят.

Добавить комментарий


Защитный код
Обновить






Кто на сайте
Сейчас 24 гостей онлайн