Зачем использовать буферизацию вывода в PHP?
Я прочитал довольно много материала в Интернете, где разные авторы предлагают использовать буферизацию вывода. Самое смешное, что большинство авторов аргументируют его использование только потому, что оно позволяет смешивать заголовки ответов с реальным содержимым. Честно говоря, я думаю, что ответственные веб-приложения не должны смешивать выходные заголовки и контент, и веб-разработчики должны искать возможные логические недостатки в своих сценариях, которые приводят к отправке заголовков после генерации вывода. Это мой первый аргумент против ob_*
API буферизации вывода. Даже для такого небольшого удобства, которое вы получаете - смешивая заголовки с выводом, - это недостаточно веская причина для его использования, если только вам не нужно быстро взламывать сценарии, что обычно не является ни целью, ни способом серьезного веб-приложения.
Кроме того, я думаю, что большинство людей, имеющих дело с API буферизации вывода, не думают о том факте, что даже без явной буферизации вывода PHP в сочетании с веб-сервером, к которому он подключен, все равно выполняет некоторую внутреннюю буферизацию. Это легко проверить - сделайте эхо какой-нибудь короткой строки, поспите, скажем, 10 секунд, и сделайте другое эхо. Запросите ваш скрипт в браузере и наблюдайте за паузой пустой страницы в течение 10 секунд, после чего появятся обе строки. Прежде чем некоторые скажут, что это артефакт рендеринга, а не трафик, отслеживание фактического трафика между клиентом и сервером показывает, что сервер сгенерировал Content-Length
заголовок с соответствующим значением для всего вывода - предполагая, что вывод не был отправлен последовательно с каждым echo
вызов, но накапливается в некотором буфере, а затем отправляется при завершении сценария Это одна из моих проблем с явной буферизацией вывода - зачем нам две разные реализации буфера вывода друг на друга? Может ли это быть из-за того, что внутренняя (недоступная) буферизация вывода PHP/Web-сервера зависит от условий, которые разработчик PHP не может контролировать, и, таким образом, на самом деле не может использоваться?
В любом случае, я, например, начинаю думать, что следует избегать явной буферизации вывода (серия ob_*
функции) и полагаться на неявное, помогая ему с хорошим flush
функция, когда это необходимо. Может быть, если бы была какая-то гарантия от веб-сервера на фактическую отправку вывода клиенту при каждом вызове echo / print, тогда было бы полезно установить явную буферизацию - ведь никто не хочет отправлять ответ клиенту с некоторыми 100 байтовые куски. Но альтернатива с двумя буферами кажется несколько бесполезным уровнем абстракции.
Итак, в конечном счете, нужны ли серьезным веб-приложениям буферизацию вывода?
11 ответов
да
Серьезные веб-приложения нуждаются в буферизации вывода в одной конкретной ситуации:
Ваше приложение хочет контролировать то, что выводится каким-либо сторонним кодом, но нет API для управления тем, что этот код испускает.
В этом случае вы можете позвонить
ob_start()
непосредственно перед передачей управления этому коду, возитесь с тем, что написано (в идеале с обратным вызовом или проверкой содержимого буфера, если необходимо), а затем вызывайтеob_flush()
,
В конечном счете, PHP- функции ob_ представляют собой механизм для захвата того, что делает какой-то другой фрагмент кода, в буфер, с которым вы можете связываться.
Если вам не нужно проверять или изменять то, что записано в буфер, ничего не получится с помощью ob_start()
,
Вполне вероятно, что ваше "серьезное приложение" на самом деле является своего рода фреймворком.
В любом случае, у вас уже есть выходная буферизация
Вам не нужно ob_start()
для того, чтобы использовать буферизацию вывода. Ваш веб-сервер уже буферизирует ваш вывод.
С помощью ob_start()
не улучшит буферизацию вывода - фактически он может увеличить использование памяти вашего приложения и задержку за счет "накопления" данных, которые в противном случае веб-сервер уже отправил бы клиенту.
Может быть ob_start()
...
... для удобства при промывке
В некоторых случаях вы можете захотеть контролировать, когда веб-сервер очищает свой буфер, основываясь на некоторых критериях, которые ваше приложение знает лучше всего. В большинстве случаев вы знаете, что вы только что закончили написание логического "модуля", который может использовать клиент, и вы говорите веб-серверу о необходимости выполнить очистку, а не ждать заполнения буфера вывода. Для этого просто необходимо вывести ваш вывод как обычно, и пунктуировать его flush()
,
В более редких случаях вы захотите скрыть данные с веб-сервера, пока у вас не будет достаточно данных для отправки. Нет смысла мешать клиенту половиной новостей, особенно если остальные станут доступными. Просто ob_start
позже заключенный ob_end_flush()
действительно может быть самой простой и подходящей вещью.
... если вы несете ответственность за определенные заголовки
Если ваше приложение берет на себя ответственность за вычисление заголовков, которое может быть определено только после получения полного ответа, тогда это может быть приемлемым.
Тем не менее, даже здесь, если вы не можете сделать что-то лучше, чем получить заголовок путем проверки полного выходного буфера, вы можете также позволить веб-серверу сделать это (если так будет). Код веб-сервера написан, протестирован и скомпилирован - вряд ли вы улучшите его.
Например, было бы полезно установить Content-Length
заголовок, если ваше приложение знает длину тела ответа после того, как оно вычисляет тело ответа.
Нет панацеи от плохой практики
Вы не должны ob_start()
избегать дисциплин:
- открытие, использование и быстрое закрытие ресурсов, таких как память, потоки и соединения с базой данных
- излучающие заголовки первыми, а тело вторыми
- Выполняя все вычисления и обработку ошибок, вы можете, прежде чем начать ответ
Если вы сделаете это, они вызовут технический долг, который однажды заставит вас плакать.
Хорошо, вот настоящая причина: вывод не запускается, пока все не будет сделано. Представьте себе приложение, которое открывает соединение SQL и не закрывает его перед началом вывода. Что происходит, если ваш скрипт получает соединение, начинает выводить, ждет, пока клиент получит все, что ему нужно, и в конце закрывает соединение. Woot, соединение 2s, где достаточно 0.3s.
Теперь, если вы буферизуете, ваш скрипт подключается, помещает все в буфер, автоматически отключается в конце, а затем начинает отправку сгенерированного контента клиенту.
Если вы хотите вывести отчет на экран, а также отправить его по электронной почте, буферизация вывода позволяет вам не повторять обработку, чтобы вывести ваш отчет дважды.
Наиболее очевидные варианты использования:
- Выходной фильтр (например,
ob_gzhandler
или любое количество фильтров, которые вы можете разработать самостоятельно); Я сделал это с помощью API, которые поддерживают только вывод (а не возвращаемые значения), где я хотел выполнить последующий анализ с помощью библиотеки, такой как phpQuery. - Поддержка (а не переписывание) кода, написанного со всеми обсуждаемыми вами проблемами; это включает в себя такие вещи, как отправка заголовков после начала вывода (кредит Дон Дикинсон) или подавление определенного вывода, который уже был сгенерирован.
- Пораженный выход (кредит здесь для Тома и Лэнгдона); обратите внимание, что ваши тесты могут быть неудачными, потому что они конфликтуют с внутренним буфером PHP/Apache по умолчанию, но это возможно сделать, просто требуется определенное количество, чтобы очистить его перед тем, как PHP отправит что-либо - хотя PHP все равно будет держать соединение открытым.
Я использую буферизацию вывода по одной причине... она позволяет мне отправлять заголовок "location" после того, как я начал обрабатывать запрос.
Буферизация вывода критически важна для IIS, которая не имеет собственной внутренней буферизации. Если выходная буферизация отключена, PHP-скрипты работают намного медленнее, чем в Apache. Включите его, и они будут работать во много раз быстрее.
Это старый вопрос, но никто не сказал, что важной особенностью внешней буферизации является фильтрация. Возможно предварительно обработать буфер перед отправкой его клиенту.
Это очень мощная концепция и открывает много интересных возможностей. В проекте я использовал два фильтра одновременно:
- специальный перевод терминов (замена коротких текстов)
- обфускация HTML, CSS и Javascript (не спрашивайте меня, почему)
Чтобы включить фильтрацию выходных вызовов ob_start("callback")
где callback
это имя функции фильтрации. Для более подробной информации см. Руководство по PHP для ob_start
: http://php.net/manual/en/function.ob-start.php
Я использую буферизацию вывода, чтобы избежать генерации HTML путем конкатенации строк, когда мне нужно знать результат операции рендеринга, чтобы создать какой-либо вывод, прежде чем использовать рендеринг.
Раньше мы использовали его для страниц с огромными таблицами, заполненными данными из базы данных. Вы очищаете буфер каждые x строк, чтобы пользователь знал, что страница действительно работает. Затем кто-то услышал о юзабилити, и такие страницы получили пейджинг и поиск.
Используйте буферизацию вывода для кэширования данных в файле, для других подобных запросов, если вы выполняете много транзакций и обработки базы данных.
Это полезно, если вы пытаетесь отобразить индикатор выполнения на странице, обработка которой занимает некоторое время. Поскольку PHP-код не является многопоточным, вы не сможете сделать это, если обработка зависает с помощью 1 функции.