Существует 2 вида буферов в php:
Cистемный буфер
Пользовательский буфер
Так могут возникнуть проблемы с работой с буфером.
Для работы с системным буфером есть одна единственная функция - flush(). Для работ
с пользовательским буфером остальные функции с приставкой ob_(Функция ob_implicit_flush() - не относится к данному списку, она больше подходит к системному буферу).
Код для демонстрации:
Внимание использована инструкция в файле .htaccess
php_value output_buffering off
";
for ($i = 1; $i <= 10; $i++) {
echo $i.":(".mt_rand(0,1000).") ";
flush();
sleep(1);
}
echo "
LVL of buffer: ".ob_get_level()."
";
header("Hello");
session_start();
header("Support title");
echo "Thx!
";
?>
Этим примером я хотел показать что при использовании только системного буфера м
можем выводить кусочками сообщение на экран. Минусом является то что если захотим отправить заголовки или сессию то будет ошибка, т.к после отправки первого сообщения на экран всевозможные заголовки были отправлены и повторная передача заголовков невозможна.
Код для демонстрации - 2:
Внимание использована инструкция в файле .htaccess
php_value output_buffering 4096
";
for ($i = 1; $i <= 10; $i++) {
echo $i.":(".mt_rand(0,1000).") ";
flush();
sleep(1);
}
echo "
LVL of buffer: ".ob_get_level()."
";
header("Hello");
session_start();
header("Support title");
echo "Thx!
";
?>
В этом примере мы не увидим кусочный вывод текста мы увидем его только когда исполнитс
весь код. Даже flush() здесь не поможет. Здесь между прочим используется не только системный
но и пользовательский буфер. Кстати вылезут ошибочки по поводу заголовков...тут конечно я не могу объяснить ситуацию из-за чего. Я в конце выскажу свои предположения и вопросы...stay here.
Код для демонстрации - 3:
Внимание использована инструкция в файле .htaccess
php_value output_buffering 4096
";
for ($i = 1; $i <= 10; $i++) {
echo $i.":(".mt_rand(0,1000).") ";
ob_flush();
flush();
sleep(1);
}
echo "
LVL of buffer: ".ob_get_level()."
";
header("Hello");
session_start();
header("Support title");
echo "Thx!
";
?>
В этом примере информация будет выводиться кусками. Этот пример как вы могли заметит
отличается только дополнительной функцией ob_flush(). Она таки высвобождает накопленны
данные в системный буфер. Следом вызывается flush() который информацию выводит прямо в браузер. Первый пример кода тому явное подтверждение. В конце будут отправлены ошибки о заголовках, оно и понятно данные были уже отправлены.
Код для демонстрации - 4:
Внимание использована инструкция в файле .htaccess
php_value output_buffering off
";
for ($i = 1; $i <= 10; $i++) {
echo $i.":(".mt_rand(0,1000).") ";
sleep(1);
}
echo "
LVL of buffer: ".ob_get_level()."
";
header("Hello");
session_start();
header("Support title");
echo "Thx!
";
?>
Интересно, а для чего я этот код добавил? Никаких флюшей здесь нет, даже функций
которые работают с буфером вывода. Все гораздо проще чем может показаться на первы
взгляд. Дело в том что если мы запустим этот код он отработает как надо и выведет ошибки об уже отправленных заголовках...what? Выходит системный буфер не такой и буфер если он вызывает ошибку о заголовках, однако данные выведутся только тогда когда закончится скрипт. Вот так вот.
Эпилог:
Мои суждения:
Т.к существует 2 буфера в php то получается один зависит от другого: чтобы вывест
информацию со свежевыпеченного echo, мы должны сначала высвободить информацию с "нашего" буфера в "казенный" php буфер, после этого мы должны вызвать операцию для "казенного" php буфера на вывод.
Системный буфер нельзя удалить, а пользовательский можно.
Информация всегда будет попадать в системный буфер хочешь ты того или нет!
Пользовательский буфер - это станция где люди ожидают свой самолет. Системный буфе
- это самолет, который вмещает в себя людей. Заголовки - это купленные билеты (Вы ж
не можете купить билет уже в самолете (Ну по крайней мере я ни разу такого не видел))
если самолет имеет время ожидания вы можете купить билет прямо на станции. Одно без другого жить не может, нет пользовательского буфера - нет времени для покупки билета, нет системного буфера - нет самолета. Выводом буфера будет являться то, когда самолет прибудет на станцию и высадит пассажиров
Вопросы:
Мой второй пример, мне не понятно по какой причине мне в конце работы скрипта ph
пишет ошибку, по факту flush() не срабатывает ввиду того что мы находимся в пользовательском буфере. (Хотя возможно то что в системном буфере ничего нет и этим вызовом мы уже отправляем заголовки)
Если опять же во втором примере мы заменим flush() => ob_flush(), мы получим ошибк
об отправленных заголовках...why? Как я писал есть 2 буфера системный и пользовательский
по логике в системном буфере лежит пользовательский и как вы могли видеть при наличии пользовательского буфера необходимо вызвать 2 флюша, а если у нас имелся только системный то хватало системного флюша.
Если мы в файле .htaccess напишем php_value output_buffering on то размер буфер
станет unlimited как сообщает php.ini, отнюдь если мы в скрипте вызовем функцию ob_get_level(
то получим 0, если вы запускали код, который я писал выше то могли заметить если у нас буфер был заданного размера то мы получали 1, если мы его удаляли или писали off то он у нас был 0 (хочу заметить я знаю что могут быть вложенные буферы и значение может быть куда выше)...собственно почему у нас 0 если размер буфера on?
Переосмысление (ответы на первый, второй и третий вопросы):
Если данные попадают в системный буфер то заголовки будут отправлены, не важно вызвали вы flush() или нет,
как следствие - варн об от отправленных заголовках*
Если вы вызовете flush(), но в системный буфер до этого ничего не попадало, это будет означать что данные
нужно вывести на экран немедленно из системного буфера, как следствие - отправк
заголовков и варн*
Если вы пишите ob_flush() в пользовательском буфере с уровнем < 2 (это равносильно тому, что если вы пишите echo
в системном буфере), то вы получите варн* так как вы пробрасываете содержимое в системный буфер, а как я писал выше
системный буфер если видит какие-то выводимые данные он начинает отправлять заголовки, в этом случае происходит
то же самое.
Если мы в файле .htaccess напишем php_value output_buffering on при вызове phpinfo(
в php файле мы увидим в колонке Local Value - on. В итоге у нас буфер 0! Как так? Вс
просто. Эта директива (output_buffering) не поддерживает такое значение. У этой директив
есть несколько состояний: 0 - выключенный буфер, 1 - безлимитный буфер и любое число. Кроме того если мы напишем текст вроде: on, off, true, false, sampletext - то в Local Value мы увидим подобные значения, но они будут интерпетироваться как 0. Если мы будем писать текст через пробел то получим ошибку 500, например php_value output_buffering hello world.
* Если вы отправляете заголовки после.
** php_value, php_flag, output_buffering - директивы.
P.S Возможно я что-то не правильно понял, не стоит воспринимать это как истину,
любом случае я смог понять только так, по-другому объяснить свои вопросы не удалось. В любом случае я жду объяснения от людей, которые соображают
Ответы
Ответ 1
Отвечу только на часть вашего вопроса, по крайней мере на текущий момент.
Ваш код:
";
for ($i = 1; $i <= 10; $i++) {
echo $i.":(".mt_rand(0,1000).") ";
sleep(1);
}
echo "
LVL of buffer: ".ob_get_level()."
";
header("Hello");
session_start();
header("Support title");
echo "Thx!
";
?>
Ваш комментарий к нему:
Интересно, а для чего я этот код добавил? Никаких флюшей здесь нет,
даже функций, которые работают с буфером вывода. Все гораздо проще чем
может показаться на первый взгляд. Дело в том что если мы запустим
этот код он отработает как надо и выведет ошибки об уже отправленных
заголовках...what? Выходит системный буфер не такой и буфер если он
вызывает ошибку о заголовках, однако данные выведутся только тогда
когда закончится скрипт. Вот так вот.
Так вот. Не совсем понятно, почему вы в этом, четвертом примере решили (если я ва
верно понял), почему не должно быть предупреждений насчет заголовков. Так или иначе
вы в любом случае должны послать их самыми первыми, до посыла всяких там текстовых сообщений через echo - банально потому, что именно такого поведения от веб-сервера ожидают браузеры. И php тут просто уведомляет вас, что вы неверно пытаетесь сообщить браузеру что-либо через заголовки.
Для понимания этого стоит обратиться к собственно протоколу HTTP, а именно к ег
структуре. Ответ веб-сервера в общем случае состоит из трех частей: Starting line, Headers и Message Body. Первое отвечает за тип сообщения, второе за заголовки и третье собственно за то, что получит браузер в качестве ответа.
Соответственно, когда приведенный код выше начинает выполняться, вне зависимост
от того, играетесь ли вы с буфером или нет, веб-сервер, который работает с вашим интерпретаторо
php, будь он apache'ем или чем угодно еще, при получении от php первого же текстовог
сообщения в третьей строке вашего кода (а оно будет первым, данный код же будет выполняться последовательно), сформирует ответ браузеру, который будет состоять из трех перечисленных выше частей. И далее веб-браузер может всего лишь досылать что-либо в Message Body, но заголовки уже будут отправлены.
Другими словами, если вы хотите что-то вывести в заголовках, то вам нужно сформироват
их до первого любого вывода в браузер, или же выстроить логику своего приложения каким-либо иным образом, где функция header() не будет использоваться после любой функции, реализующей вывод чего бы то ни было браузеру посредством HTTP.
Ответ 2
Чтобы немного прояснить для себя и других, как работает буферизация в PHP, давайте заглянем в исходники. У меня исходники php-7.1.3.
Заходим в корневую папку исходников и пытаемся найти в них реализацию функции ob_get_contents:
$ fgrep -r 'PHP_FUNCTION(ob_get_contents)' .
Находится такое:
./main/output.c:PHP_FUNCTION(ob_get_contents)
Вот эта функция:
PHP_FUNCTION(ob_get_contents)
{
if (zend_parse_parameters_none() == FAILURE) {
return;
}
if (php_output_get_contents(return_value) == FAILURE) {
RETURN_FALSE;
}
}
(Попутно было замечено, что PHP-еретики в своих кодах используют табулятор вместо 4-х пробелов.)
В том же файле находим функцию php_output_get_contents:
PHPAPI int php_output_get_contents(zval *p)
{
if (OG(active)) {
ZVAL_STRINGL(p, OG(active)->buffer.data, OG(active)->buffer.used);
return SUCCESS;
} else {
ZVAL_NULL(p);
return FAILURE;
}
}
Что происходит? Выглядит так, как будто что-то (видимо, возвращаемая строка) формируетс
на основе некоторого адреса в памяти и длины данных. То есть пользовательский буфер это просто кусок памяти. Вероятно, там еще есть стек меток для каждого вызова ob_start. Это уже сами ищите реализацию ob_start, кому интересно.
Аналогичным образом можно найти реализацию функции flush и увидеть, что она работает через функцию конкретного применяемого SAPI. Я посмотрел SAPI для PHP как модуля Apache:
static void
php_apache_sapi_flush(void *server_context)
{
php_struct *ctx;
request_rec *r;
ctx = server_context;
/* If we haven't registered a server_context yet,
* then don't bother flushing. */
if (!server_context) {
return;
}
r = ctx->r;
sapi_send_headers();
r->status = SG(sapi_headers).http_response_code;
SG(headers_sent) = 1;
if (ap_rflush(r) < 0 || r->connection->aborted) {
php_handle_aborted_connection();
}
}
Здесь используются структуры C API самого Apache, то есть и вывод предварительн
должен делаться таким же образом. Как Apache буферизует выводимые данные - это уже его личное дело и к PHP не имеет никакого отношения.
Результат таков, что "системная" и "пользовательская" буферизация в PHP - это абсолютн
разные вещи, настолько разные, что их не стоило бы называть в одном списке, чтобы не вносить сумятицу, и, возможно, с той же целью, пользовательскую буферизацию вообще стоило бы назвать как-то по-другому.
Комментариев нет:
Отправить комментарий