Перейти к содержимому
Поиск
Php Разработка

PHP: полный мануал с примерами, функциями и ООП

Практический мануал PHP: от переменных и читаемого кода до безопасных форм, интеграций, базы данных и ООП на примере магазина. Учебный маршрут, поиск функций, подробные примеры, разбор ошибок и исходники для скачивания.

Александр НекрасовИзменено:
Практический мануал PHP: от переменных и функций к объектной модели интернет-магазина
PHP 8.5.11149 глав4458 карточек справочника
Дерево руководства

Глава 37 · Практика

ob_start: шаблоны, вложенные буферы и отправка ответа

Зачем перехватывать echo, как вернуть HTML строкой и почему flush не обещает немедленный показ в браузере.

ob_start: как получить напечатанный HTML в переменную

Шаблон страницы уже выводит HTML через echo, а теперь тот же результат нужен как строка для письма. Буфер вывода временно собирает напечатанное: вы сможете забрать строку, не отправляя её сразу посетителю.

Что уже пригодится. return и echo, соединение строк

Вывод меняет получателя
  1. ob_start(): открыть буфер
  2. echo печатает в буфер
  3. ob_get_clean(): получить строку и закрыть буфер
  4. $html содержит результат
  5. Использовать строку там, где она нужна

ob_start() начинает сбор вывода. ob_get_clean() возвращает накопленный текст и удаляет этот буфер. Присваивание сохраняет возвращённую строку.

Сохраняем вывод двух echoСамостоятельный пример
<?php
declare(strict_types=1);

ob_start();
echo 'Заказ ';
echo '№42';
$message = ob_get_clean();
echo 'Собрано: ' . $message . "\n";
Ожидаемый результат
Собрано: Заказ №42

Первые два echo не появляются отдельно: их текст попал в буфер. Последний echo выполняется после закрытия буфера и показывает собранную строку. Для обычной склейки строк буфер не нужен; он полезен, когда существующий шаблон уже печатает HTML.

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

Буфер превращает вывод шаблона в значение

Наглядная модель

Как echo шаблона превращается в строку HTML

Одну карточку товара нужно использовать на странице и в письме. Сначала получим её разметку как значение.

  1. ob_start()

    PHP открывает свой уровень буфера: следующий вывод пока накапливается в нём.

  2. Подключить шаблон

    Шаблон получает подготовленные данные и печатает разметку через echo и HTML-вставки.

  3. Байты попали в буфер

    Это тело будущего HTML. Буфер сам не экранирует данные и не хранит HTTP-заголовки.

  4. ob_get_clean()

    Возвращает накопленный HTML строкой и закрывает текущий буфер.

  5. Использовать строку

    Передать HTML в письмо или вернуть в ответе. Сам ob_start письмо не отправляет.

При исключении рендерер убирает открытые им уровни буфера. flush не гарантирует немедленный показ на экране браузера.

Представьте карточку товара, которую нужно показать на странице, положить в письмо и сохранить в краткий кеш. Старый PHP-шаблон печатает HTML через echo и участки разметки. Без буфера include сразу отправляет вывод дальше, а вызывающий код не получает строку. ob_start создаёт буфер: последующий вывод накапливается, затем ob_get_clean возвращает его содержимое и закрывает буфер. Это позволяет рассматривать шаблон как функцию, которая возвращает представление.

Такой приём полезен в небольшом серверном рендерере, при адаптации старой библиотеки с echo и в тесте HTML-компонента. Он не обязателен для каждого include: файл с объявлениями классов вообще не должен выводить данные. Современный шаблонизатор может уже возвращать строку или управлять собственным буфером. Прежде чем добавлять ещё один, узнайте контракт существующего слоя.

Буфер содержит тело, а не HTTP-заголовки. Пока тело удерживается и ничего не отправлено ниже, ещё можно установить header или cookie. Но использовать ob_start как универсальное лечение «headers already sent» опасно: источник раннего вывода остаётся, а ограничение размера или вложенный flush могут проявить проблему снова. Правильный порядок — сначала решение и заголовки, затем отправка представления.

Получение HTML строкой
  1. ob_start: создать уровень
  2. Шаблон печатает HTML
  3. Буфер хранит байты тела
  4. ob_get_clean: получить и закрыть
  5. Контроллер выбирает статус/заголовки
  6. echo готового тела

Рендерер должен убрать свой буфер при исключении

Шаблон как строка и очистка после сбояСамостоятельный пример
<?php
declare(strict_types=1);

function renderCard(string $title, bool $fail = false): string {
    $level = ob_get_level();
    ob_start();
    try {
        echo '<h2>'.htmlspecialchars($title,ENT_QUOTES|ENT_SUBSTITUTE,'UTF-8').'</h2>';
        if ($fail) throw new RuntimeException('template-failed');
        $html = ob_get_clean();
        if ($html === false) throw new RuntimeException('buffer-missing');
        return $html;
    } finally {
        while (ob_get_level() > $level) ob_end_clean();
    }
}
echo renderCard('Tea & Coffee')."
";
$before = ob_get_level();
try { renderCard('Broken',true); } catch (RuntimeException $e) { echo $e->getMessage()."
"; }
echo ob_get_level() === $before ? "buffer-restored
" : "leaked
";
Ожидаемый результат
<h2>Tea &amp; Coffee</h2>
template-failed
buffer-restored

finally удаляет только уровни, созданные внутри функции; чужой внешний буфер не уничтожается. Контракт шаблона запрещает закрывать чужие уровни или создавать неудаляемые буферы. Частичный HTML ошибочного шаблона не попадает в ответ.

Буферы образуют стек, а не один глобальный мешок

Внутренний компонент сохраняется отдельноСамостоятельный пример
<?php
declare(strict_types=1);

ob_start();
echo 'page:';
ob_start();
echo 'badge';
$badge = ob_get_clean();
echo strtoupper($badge);
$page = ob_get_clean();
echo $page."
";
echo 'saved-component:'.$badge."
";
Ожидаемый результат
page:BADGE
saved-component:badge

Внутренний вывод не потерян и не отправлен немедленно: он получен строкой, преобразован и помещён во внешний уровень. ob_end_flush вместо ob_get_clean отправил бы содержимое во внешний буфер, а не вернул бы его как строку.

ob_get_contents копирует содержимое, но не закрывает уровень. ob_clean очищает текущий уровень, оставляя его активным. ob_end_clean выбрасывает содержимое и закрывает уровень. ob_flush передаёт накопленное ниже, но продолжает буферизацию; ob_end_flush передаёт и завершает. Выбор зависит от того, нужен ли результат как значение и кто владеет уровнем.

В callback обработчика буфера нельзя бездумно вызывать функции управления тем же стеком: PHP запрещает ряд таких операций и может завершить выполнение ошибкой. Для обычного шаблона callback не требуется. Большой буфер удерживает всё тело в памяти, а получение строки и последующие преобразования могут увеличить пиковое потребление. Для гигабайтного экспорта лучше потоковое чтение и выдача, а не сборка одной строки.

От PHP до экрана есть ещё несколько буферов

После PHP данные могут буферизовать веб-сервер, FastCGI, сжатие, обратный прокси и браузер. Поэтому echo, ob_flush и flush не гарантируют немедленное появление текста на экране. Для потокового ответа нужно согласовать всю цепочку, отключить несовместимую буферизацию и протестировать реальный сервер. Локальный CLI показывает другой путь и не доказывает поведение браузера.

Если клиенту нужен прогресс долгой задачи, часто проще вернуть ID задания и опрашивать отдельный endpoint. SSE подходит для однонаправленных событий, но требует правильного Content-Type, таймаутов, освобождения сессии и контроля количества соединений. Удерживать множество PHP-FPM процессов открытыми ради прогресса может быть дорого. Выбор транспорта связан с инфраструктурой, а не только с наличием flush.

Не кладите персонализированную карточку в общий кеш под ключом одного товара, если в ней есть имя покупателя или его скидка. ob_start лишь помогает получить строку; он не делает кеш безопасным. Ключи, срок жизни и инвалидирование остаются отдельными решениями. Тестируйте экранирование, отсутствие раннего вывода, очистку при исключении и независимость вложенных компонентов.

Шаблон не должен получать весь контекст приложения

Если рендерер подключает PHP-файл, тот исполняется в области видимости подключения. Не передавайте ему автоматически все глобальные переменные или произвольный массив через extract с перезаписью имён. Лучше задать явные данные представления и ограничить доступные зависимости. Имя шаблона выбирают из кода или разрешённого списка, а не превращают параметр URL в путь include.

Ошибка PHP warning не всегда становится исключением. Поэтому пример с finally гарантирует очистку при Throwable, но не обещает автоматически распознать каждое предупреждение как неуспех. Политика error handler задаётся приложением и тестируется отдельно. В production display_errors не должен добавлять технический текст в HTML или JSON.

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

Практика: проверьте решение, а не только успешный путь

Библиотека печатает отчёт и иногда бросает исключение. Как не допустить утечки половины отчёта в JSON API?

Разбор решения

Обернуть вызов собственным буфером, запомнить исходный уровень, получить строку только после успеха, в finally убрать свои уровни. Заголовки JSON устанавливать после успешного формирования тела. Если библиотека сама flush-ит данные наружу, её контракт нужно изменить: внешний буфер не гарантирует перехват произвольного поведения.

Функции и методы этой темы

Откройте карточку, чтобы увидеть договор функции, примеры, ограничения и связанные инструменты.

ob_startПерехватить вывод печатающего шаблона, чтобы получить его как строку или обработать на одной границе.

PHP 4+ · Core

ob_start(?callable $callback = null, int $chunk_size = 0, int $flags = PHP_OUTPUT_HANDLER_STDFLAGS): bool

Перехватить вывод печатающего шаблона, чтобы получить его как строку или обработать на одной границе.

Параметр / результатКонтракт
callbacknull — неизменяющий обработчик; callable — обработчик (string buffer,int phase). Для отдельных внутренних alias действуют специальные правила.
chunk_sizeПорог автоматической обработки в байтах; 0 — без автоматического порога, 1 — после каждого непустого блока. Не hard memory cap.
flagsМаска CLEANABLE/FLUSHABLE/REMOVABLE; по умолчанию STDFLAGS. С 8.4 status flags через неё не задаются.
Возврат / отказtrue при запуске; false и warning/notice при невозможности создать обработчик. Некорректные типы параметров могут дать TypeError.
PHP 8.5Вывод из пользовательского callback deprecated; предупреждение обходит handler.
Печать становится данными
  1. ob_start открывает приёмник
  2. Шаблон echo печатает HTML в него
  3. ob_get_clean возвращает HTML и закрывает приёмник
  4. Далее строка передаётся по нужному маршруту

Старый шаблон печатает карточку через echo, а её HTML нужен как строка для письма. ob_start создаёт временный приёмник вывода. Далее шаблон исполняется обычным образом; меняется место, куда попадает печать. Затем ob_get_clean забирает содержимое и закрывает именно этот буфер. Вычисления, запросы в БД и другие побочные действия шаблона при этом выполняются по-настоящему.

Сохранить напечатанную карточку в строкуСамостоятельный пример
<?php
declare(strict_types=1);

ob_start();
echo '<p>Заказ готов</p>';
$html = ob_get_clean();
echo 'Получено: ', $html, "\n";
Ожидаемый результат
Получено: <p>Заказ готов</p>

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

Подключить доверенный шаблон и вернуть его HTMLСамостоятельный пример
<?php
declare(strict_types=1);

function renderReceipt(string $product): string
{
    ob_start();
    try {
        require 'receipt.php';
    } catch (Throwable $error) {
        ob_end_clean();
        throw $error;
    }
    $html = ob_get_clean();
    if ($html === false) { throw new RuntimeException('Буфер потерян'); }
    return $html;
}
echo renderReceipt('Чай <мятный>'), "\n";
Ожидаемый результат
<p>Чай &lt;мятный&gt;</p>

Файл fixture известен разработчику, не приходит из запроса. htmlspecialchars защищает вставку в HTML; сам буфер ничего не экранирует. В примере договор шаблона запрещает ему открывать/закрывать чужие буферы. Обработка ошибки очищает принадлежащий этой функции буфер; общий случай нарушения уровней разобран отдельно.

callback=null/пропуск использует встроенный default output handler, который пропускает содержимое без изменения. Пользовательский callback получает строку buffer и битовую маску phase; возвращает новый текст или false по описанному контракту. Его вызывают при обработке/очистке/завершении, а не сразу от одного ob_start. Проще сначала освоить захват без callback, затем обработчики. Некоторые расширения регистрируют специальные имена alias, например ob_iconv_handler; проверка is_callable сама по себе не описывает этот особый механизм.

chunk_size=0 — автоматического порога нет, остаётся ограничение доступной памяти. Положительное значение — порог в байтах: после очередной операции печати буфер обрабатывается, если достиг или превысил его. Это не жёсткий предел памяти и не разбиение большого echo ровно по N байт. chunk_size=1 запускает обработку после каждого непустого блока вывода; отрицательное значение реализация PHP 8.5 приводит к 0, но прикладная конфигурация должна требовать число ≥0.

Увидеть, что порог не разрезает один большой echoСамостоятельный пример
<?php
declare(strict_types=1);

$sizes = [];
ob_start(static function (string $buffer, int $phase) use (&$sizes): string {
    $sizes[] = strlen($buffer);
    return '';
}, 3);
echo 'ab';
echo 'cdef';
echo 'g';
ob_end_flush();
echo implode(', ', $sizes), "\n";
Ожидаемый результат
6, 1

Первые два echo вместе накопили шесть байт, хотя порог равен трём. Последний один байт обработан при завершении. Возврат пустой строки сознательно не передаёт исходный текст наружу; callback только записывает длины в локальный массив.

flags — отдельная маска разрешённых операций: CLEANABLE для ob_clean, FLUSHABLE для ob_flush, REMOVABLE для функций закрытия. Стандартная STDFLAGS объединяет все три. Начиная с PHP 8.4 этим аргументом нельзя заранее выставить runtime status STARTED/DISABLED/PROCESSED. flags=0 оставляет чтение содержимого, но запрещает ручную очистку/flush/удаление; окончательное завершение скрипта всё равно обработает буфер.

Успешный запуск возвращает true; при отказе создать буфер — false и диагностику. Например, несуществующее имя callback в PHP 8.5.11 даёт warning о callable, затем notice об отказе, а не универсальный TypeError для этого случая. Типы chunk_size/flags проверяются как int. Внутри output handler запрещено менять стек через ob_start/clean/flush/end/get_clean/get_flush: это fatal error. С PHP 8.5 печать echo внутри пользовательского handler deprecated; возвращайте строку, диагностируйте вне обработки.

Соседние операции: ob_get_clean, ob_get_contents, ob_get_status.

Начать с захвата текста в переменную · Сравнить clean, flush и закрытие.

Создаёт новый уровень буфера, возвращает успешность. Вывод echo/HTML удерживается до извлечения или передачи ниже. callback может преобразовать данные; chunk_size задаёт порог обработки, а flags — доступные операции.

HTML-компонент как строкаСамостоятельный пример
<?php
declare(strict_types=1);

ob_start();
echo '<strong>Sale</strong>';
$html=ob_get_clean();
echo strlen($html)." bytes\n";
Ожидаемый результат
21 bytes

Компонент не отправлен непосредственно, его можно включить в письмо или проверить тестом.

Перехват старой функции с echoСамостоятельный пример
<?php
declare(strict_types=1);

function legacyBadge(): void { echo 'stock:3'; }
ob_start();
legacyBadge();
$badge=ob_get_clean();
echo strtoupper($badge)."\n";
Ожидаемый результат
STOCK:3

Адаптер превращает печатающую функцию в значение. Сама legacy-функция не должна управлять внешними буферами.

Рядом по задаче. ob_get_clean возвращает строку и закрывает уровень; в главе есть очистка через finally.

ob_get_cleanЗавершить захват шаблона: забрать исходный текст в переменную и закрыть буфер.

PHP 4.3.0+ · Core

ob_get_clean(): string|false

Завершить захват шаблона: забрать исходный текст в переменную и закрыть буфер.

Параметр / результатКонтракт
АргументыНет.
РазрешениеREMOVABLE; CLEANABLE не требуется.
ВозвратИсходная строка буфера; false без notice при отсутствии. Пустая строка означает пустой буфер.
PHP8.5 edge caseПри запрете удаления может вернуть снимок строки вместе с E_NOTICE, сохранив буфер; подробный опыт ниже.
HandlerCLEAN|FINAL, возвращённое преобразование отбрасывается. Исключения handler возможны.
Захват завершён
  1. echo заполнил буфер HTML
  2. get_clean копирует исходный текст в результат
  3. Удаляет этот уровень без отправки наружу
  4. Переменная html используется дальше

Шаблон закончил печать. ob_get_clean возвращает накопленный исходный текст, стирает буфер и закрывает его: строка теперь принадлежит переменной. Это обычный завершающий шаг превращения echo-шаблона в функцию render. Передачи этих байтов наружу при успешном clean не происходит.

Получить HTML для повторного использованияСамостоятельный пример
<?php
declare(strict_types=1);

ob_start();
echo '<p>Чай</p>';
$html = ob_get_clean();
echo $html, ' | ', $html, "\n";
Ожидаемый результат
<p>Чай</p> | <p>Чай</p>

Один раз сформировали строку и дважды использовали её. Повторной работы генератора не было. Печатаем после закрытия, поэтому случайно не дописываем копию в исходный буфер.

Проверить, что callback не заменяет возвращаемую строкуСамостоятельный пример
<?php
declare(strict_types=1);

$phases = [];
ob_start(static function (string $buffer, int $phase) use (&$phases): string {
    $phases[] = $phase;
    return strtoupper($buffer);
});
echo 'tea';
$raw = ob_get_clean();
echo $raw, "\n";
var_dump(($phases[0] & PHP_OUTPUT_HANDLER_CLEAN) !== 0,
         ($phases[0] & PHP_OUTPUT_HANDLER_FINAL) !== 0);
Ожидаемый результат
tea
bool(true)
bool(true)

Callback действительно исполнился, но его TEA отброшено. Возвращается снимок исходного tea. Фраза «handler не работает с get_clean» неверна: он работает, но результат его преобразования не является результатом функции.

Распознать отказ закрытия, даже если вернулась строкаСамостоятельный пример
<?php
declare(strict_types=1);

$notices = 0;
set_error_handler(static function (int $level, string $message) use (&$notices): bool {
    if ($level === E_NOTICE) { ++$notices; return true; }
    return false;
});
$base = ob_get_level();
ob_start(static fn(string $buffer, int $phase): string => '', 0, 0);
echo 'kept';
$result = ob_get_clean();
$stillActive = ob_get_level() === $base + 1;
restore_error_handler();
fwrite(STDOUT, ($result === 'kept' && $stillActive && $notices > 0)
    ? "Строка получена, но буфер не закрыт\n" : "Иное поведение\n");
Ожидаемый результат
Строка получена, но буфер не закрыт

Узкий опыт PHP8.5.11: функция сначала получает текст, затем не может удалить non-removable буфер. fwrite(STDOUT) намеренно обходит его, чтобы показать результат; при shutdown handler возвращает пустую строку и ничего лишнего не печатает. Так защищённые буферы не создают в обычном render: это опыт границы контракта.

Операция требует REMOVABLE, но не CLEANABLE. Пустой успешный результат — "", не false. Callback получает CLEAN|FINAL (и START при первом вызове); его side effects/исключения не исчезают вместе с отбрасываемой строкой. Если нужен обработанный результат, заверните внутренний handler во внешний обычный буфер и завершите внутренний через flush, либо явно преобразуйте уже захваченную строку.

Соседние операции: ob_start, ob_get_contents, ob_end_clean, ob_get_flush.

Начать с захвата текста в переменную · Сравнить clean, flush и закрытие.

Возвращает содержимое активного удаляемого буфера, затем закрывает его. При отсутствии подходящего буфера возвращает false.

Пустой компонентСамостоятельный пример
<?php
declare(strict_types=1);

ob_start();
$html=ob_get_clean();
echo $html === '' ? "valid empty component\n" : "unexpected\n";
Ожидаемый результат
valid empty component

Отсутствие разметки может быть правильным результатом условного блока.

Накопить строку CSVСамостоятельный пример
<?php
declare(strict_types=1);

ob_start();
echo "sku,quantity\nbook,2\n";
$csv=ob_get_clean();
echo substr_count($csv,"\n")." rows\n";
Ожидаемый результат
2 rows

Подходит только небольшому ограниченному экспорту. Для больших файлов выбирайте поток.

Рядом по задаче. ob_get_contents читает без закрытия; ob_end_flush отправляет ниже без возврата строки.

ob_get_contentsПолучить снимок исходного активного буфера, сохранив сам буфер и его содержимое.

PHP 4+ · Core

ob_get_contents(): string|false

Получить снимок исходного активного буфера, сохранив сам буфер и его содержимое.

Параметр / результатКонтракт
АргументыНет. Работает только с верхним активным уровнем.
ВозвратСтрока содержимого, включая пустую; false, если буфера нет.
ЭффектыНе очищает/не закрывает/не выполняет преобразование callback; не возвращает всю вложенную цепочку.
Чтение не опустошает приёмник
  1. В буфере Заказ
  2. Снимок=Заказ
  3. После echo буфер=Заказ готов
  4. Первый снимок по-прежнему Заказ

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

Прочитать два снимка растущего буфераСамостоятельный пример
<?php
declare(strict_types=1);

ob_start();
echo 'Заказ';
$first = ob_get_contents();
echo ' готов';
$second = ob_get_contents();
ob_end_clean();
echo $first, ' | ', $second, "\n";
Ожидаемый результат
Заказ | Заказ готов

Сначала сохранены обе строки, потом буфер закрыт без передачи наружу. Снимок не заменяет живой буфер и не обновляется после следующего echo.

Не спутать исходный снимок с результатом callbackСамостоятельный пример
<?php
declare(strict_types=1);

$calls = 0;
ob_start(static function (string $buffer, int $phase) use (&$calls): string {
    ++$calls;
    return strtoupper($buffer);
});
echo 'tea';
$snapshot = ob_get_contents();
$callsBeforeClean = $calls;
ob_end_clean();
echo $snapshot, '/', $callsBeforeClean, '/', $calls, "\n";
Ожидаемый результат
tea/0/1

Чтение не вызвало callback; очистка при закрытии вызвала, но отбросила его результат. Поэтому снимок содержит tea, а не TEA. Для получения обработанного результата используйте внешний буфер и flush внутреннего, либо явную функцию преобразования строки.

Без активного буфера возвращается false, пустой активный буфер даёт "". Функция не возвращает совокупность всех уровней; внешний буфер может хранить другие данные. Чтение длинного содержимого и удержание снимков увеличивает расход памяти; для размера используйте ob_get_length, не копию ради strlen. Это не механизм безопасного просмотра секрета: содержимое может включать токен, персональные данные или диагностический HTML.

Соседние операции: ob_get_clean, ob_get_length, ob_get_status.

Начать с захвата текста в переменную · Сравнить clean, flush и закрытие.

Возвращает текущие байты или false без активного буфера. Содержимое не очищается и уровень не закрывается.

Посмотреть промежуточный результатСамостоятельный пример
<?php
declare(strict_types=1);

ob_start();
echo 'A';
$first=ob_get_contents();
echo 'B';
$all=ob_get_clean();
echo $first.'|'.$all."\n";
Ожидаемый результат
A|AB

Чтение снимка не остановило дальнейшее накопление.

Проверить размер до продолженияСамостоятельный пример
<?php
declare(strict_types=1);

ob_start();
echo 'catalog';
$length=strlen(ob_get_contents());
ob_end_clean();
echo $length." bytes\n";
Ожидаемый результат
7 bytes

Буфер отброшен после измерения. Для большого потока лучше не получать полную копию ради подсчёта.

Рядом по задаче. ob_get_clean завершает сборку; ob_clean стирает текущие байты.

ob_get_levelУзнать число активных уровней, чтобы сохранить границу владения буферами.

PHP 4.2.0+ · Core

ob_get_level(): int

Узнать число активных уровней, чтобы сохранить границу владения буферами.

Параметр / результатКонтракт
АргументыНет.
ВозвратКоличество активных уровней; 0 означает отсутствие. Первый активный уровень даёт 1.
Сравнениеob_get_status()["level"] нумерует с 0, поэтому для активного буфера на единицу меньше.
ОграничениеКоличество не является идентификатором/владельцем буфера.
Вложенные приёмники
  1. Внешний буфер: уровень1
  2. Внутренний: уровень2
  3. Закрываем только внутренний → снова1
  4. Внешнее содержимое сохранено

Функция библиотеки может быть вызвана, когда приложение уже открыло свой буфер. ob_get_level показывает число активных уровней; оно позволяет сохранить точку входа и проверить, что библиотека вернула управление с тем же стеком. Закрывать все буферы сайта ради очистки одного шаблона нельзя: это вмешается в сжатие, middleware и другой код.

Создать два уровня и вернуть исходную глубинуСамостоятельный пример
<?php
declare(strict_types=1);

$base = ob_get_level();
ob_start(); $outer = ob_get_level();
ob_start(); $inner = ob_get_level();
$statusLevel = ob_get_status()['level'];
ob_end_clean(); ob_end_clean();
echo $outer - $base, '/', $inner - $base, "\n";
var_dump($statusLevel === $inner - 1, ob_get_level() === $base);
Ожидаемый результат
1/2
bool(true)
bool(true)

ob_get_level считает первый уровень как 1, status[level] — как 0. Разность с base не зависит от того, был ли внешний буфер до вызова.

Не закрыть чужой внешний буфер при ошибке шаблонаСамостоятельный пример
<?php
declare(strict_types=1);

ob_start();
echo 'Внешняя часть; ';
$base = ob_get_level();
ob_start();
try {
    echo 'Незавершённая карточка';
    throw new RuntimeException('Учебный отказ');
} catch (RuntimeException) {
    if (ob_get_level() !== $base + 1) { throw new LogicException('Шаблон нарушил договор буферов'); }
    ob_end_clean();
    echo 'Карточка недоступна';
}
$page = ob_get_clean();
echo $page, "\n";
Ожидаемый результат
Внешняя часть; Карточка недоступна

Мы точно знаем: шаблон не управляет буферами и наш уровень removable. Проверка обнаруживает нарушение числа уровней, но не доказывает identity буфера: чужой код мог закрыть один и создать другой. Не используйте это как sandbox для недоверенного PHP.

Соседние операции: ob_get_status, ob_list_handlers, ob_end_clean.

Начать с захвата текста в переменную · Сравнить clean, flush и закрытие.

Возвращает глубину стека буферизации. Уровень, созданный настройкой output_buffering, тоже может влиять на начальное значение.

Измерить собственную вложенностьСамостоятельный пример
<?php
declare(strict_types=1);

$base=ob_get_level();
ob_start();
ob_start();
$depth=ob_get_level()-$base;
ob_end_clean();
ob_end_clean();
echo $depth." own levels\n";
Ожидаемый результат
2 own levels

Сравнение с исходным уровнем работает и внутри чужого рендерера.

Вернуть исходный стек после сбояСамостоятельный пример
<?php
declare(strict_types=1);

$base=ob_get_level();
try { ob_start(); echo 'partial'; throw new RuntimeException(); }
catch (RuntimeException) {}
finally { while (ob_get_level()>$base) ob_end_clean(); }
echo "clean response\n";
Ожидаемый результат
clean response

Частичный вывод не просочился в ответ. Контракт запрещает неудаляемые или чужие буферы внутри блока.

Рядом по задаче. ob_get_status даёт сведения о буферах; ob_end_clean закрывает один уровень.

ob_cleanСтереть ещё накопленный фрагмент и продолжить печатать в тот же буфер.

PHP 4.2.0+ · Core

ob_clean(): bool

Стереть ещё накопленный фрагмент и продолжить печатать в тот же буфер.

Параметр / результатКонтракт
АргументыНет.
Нужное разрешениеАктивный буфер с CLEANABLE.
Возврат / ошибкаtrue при очистке; false и E_NOTICE при отсутствии/запрете. Исключение пользовательского handler может выйти наружу.
ЭффектВызывает handler с CLEAN, отбрасывает его результат и содержимое; сам буфер остаётся.
Исправление ещё не отправленного текста
  1. В активном буфере Черновик
  2. ob_clean стирает его
  3. Буфер остаётся открытым
  4. Следующий echo записывает Готово

Предпросмотр ещё не отправлен, но его первая попытка больше не нужна. ob_clean очищает активный буфер и оставляет его открытым: следующая печать попадёт туда же. Это подходит для замены предварительного фрагмента в принадлежащем вам буфере, но не отменяет выполненные SQL-запросы, отправленные письма или данные, уже прошедшие во внешний уровень.

Заменить черновую надпись окончательнойСамостоятельный пример
<?php
declare(strict_types=1);

ob_start();
echo 'Черновик';
$cleaned = ob_clean();
echo 'Готово';
$text = ob_get_clean();
var_dump($cleaned);
echo $text, "\n";
Ожидаемый результат
bool(true)
Готово

Буфер после ob_clean продолжает существовать. Мы забрали новую надпись отдельным ob_get_clean; старая не попала в результат.

Отличить разрешение очистки от разрешения закрытияСамостоятельный пример
<?php
declare(strict_types=1);

$notices = 0;
set_error_handler(static function (int $level, string $message) use (&$notices): bool {
    if ($level === E_NOTICE) { ++$notices; return true; }
    return false;
});
ob_start(null, 0, PHP_OUTPUT_HANDLER_REMOVABLE);
echo 'Содержимое';
$cleaned = ob_clean();
$saved = ob_get_contents();
$ended = ob_end_clean();
restore_error_handler();
var_dump($cleaned, $ended);
echo $saved, '/', $notices > 0 ? 'Был notice' : 'Без notice', "\n";
Ожидаемый результат
bool(false)
bool(true)
Содержимое/Был notice

CLEANABLE отсутствует, поэтому ob_clean отказал и не стёр данные. REMOVABLE присутствует: ob_end_clean затем закрывает и очищает буфер. Это два разных разрешения, а не общий флаг «можно удалить текст».

Успех — true, отсутствие активного буфера или CLEANABLE — false и E_NOTICE. При очистке вызывается handler с CLEAN (и START, если это первый вызов), но возвращённая им строка отбрасывается. Побочные действия callback всё же происходят; handler может бросить исключение. Не используйте callback для оплаты/другого необратимого действия, которое «должно случиться лишь при успешном показе».

Соседние операции: ob_end_clean, ob_get_clean, ob_get_contents.

Начать с захвата текста в переменную · Сравнить clean, flush и закрытие.

Очищает текущий очищаемый уровень и возвращает bool. Без подходящего активного буфера операция неуспешна и может выдать notice.

Заменить черновой фрагментСамостоятельный пример
<?php
declare(strict_types=1);

ob_start();
echo 'draft';
ob_clean();
echo 'final';
$result=ob_get_clean();
echo $result."\n";
Ожидаемый результат
final

После очистки тот же уровень продолжает собирать данные.

Убрать вывод проверяемой legacy-функцииСамостоятельный пример
<?php
declare(strict_types=1);

ob_start();
echo 'debug noise';
ob_clean();
$stillActive=ob_get_level()>0;
ob_end_clean();
echo $stillActive ? "buffer remained active\n" : "closed\n";
Ожидаемый результат
buffer remained active

Показано отличие очистки от завершения. Лучше удалить отладочный echo из самой функции.

Рядом по задаче. ob_end_clean очищает и закрывает, ob_get_clean возвращает накопленное.

ob_end_cleanОтменить ещё не переданный фрагмент и закрыть принадлежащий вам активный буфер.

PHP 4+ · Core

ob_end_clean(): bool

Отменить ещё не переданный фрагмент и закрыть принадлежащий вам активный буфер.

Параметр / результатКонтракт
АргументыНет.
Нужное разрешениеАктивный буфер с REMOVABLE; CLEANABLE для этой операции не требуется.
Возврат / ошибкаtrue при закрытии; false и E_NOTICE при отсутствии/запрете; callback может бросить исключение.
ЭффектCLEAN|FINAL, результат handler и исходный текст отбрасываются, активный уровень удаляется.
Отмена одного уровня
  1. Внешняя страница содержит шапку
  2. Внутренний буфер содержит плохую карточку
  3. ob_end_clean удаляет только внутренний
  4. Внешняя страница продолжает собираться

Генератор карточки начал печатать HTML, затем обнаружил ошибку. Если этот фрагмент не нужно ни возвращать, ни показывать, ob_end_clean очищает и закрывает активный буфер. Вы снова работаете с тем внешним уровнем, который был до его открытия. Возвращаемое значение — bool, не удалённый HTML.

Отбросить незавершённую карточкуСамостоятельный пример
<?php
declare(strict_types=1);

ob_start();
echo '<p>Незавершённая карточка';
$discarded = ob_end_clean();
var_dump($discarded);
echo "Показать запасной блок\n";
Ожидаемый результат
bool(true)
Показать запасной блок

Внешнему потребителю не достался незакрытый тег. Это локальная подготовка текста; уже выполненные действия генератора буфер не отменяет.

Отменить внутренний фрагмент, сохранив внешнюю страницуСамостоятельный пример
<?php
declare(strict_types=1);

ob_start();
echo 'Шапка | ';
ob_start();
echo 'Ошибочная карточка';
ob_end_clean();
echo 'Запасная карточка';
$page = ob_get_clean();
echo $page, "\n";
Ожидаемый результат
Шапка | Запасная карточка

Закрыт только последний буфер. Внешняя шапка не была стёрта; последующий echo попал во внешний уровень. Поэтому while (ob_get_level()) без знания владельцев — неверный универсальный cleanup библиотеки.

Требуется REMOVABLE; отсутствие CLEANABLE не мешает этой операции. При нормальном вызове handler получает CLEAN|FINAL и дополнительно START, если ещё ни разу не вызывался; результат handler отбрасывается. false/E_NOTICE сообщают о невозможности закрыть буфер. Если callback кидает исключение, не считайте очистку успешно завершённой: разберите оставшееся состояние и политику ошибки. Невозможность удалить защищённый уровень не решается бесконечным циклом повторных ob_end_clean.

Соседние операции: ob_clean, ob_get_clean, ob_end_flush, ob_get_level.

Начать с захвата текста в переменную · Сравнить clean, flush и закрытие.

Закрывает удаляемый уровень без отправки накопленных байтов. Возвращает bool; отсутствие/неподходящие флаги уровня приводят к неуспеху.

Выбросить неудачный шаблонСамостоятельный пример
<?php
declare(strict_types=1);

ob_start();
echo '<p>unfinished';
ob_end_clean();
echo "fallback\n";
Ожидаемый результат
fallback

Клиент получает только альтернативный ответ.

Отменить внутренний компонентСамостоятельный пример
<?php
declare(strict_types=1);

ob_start(); echo 'page:';
ob_start(); echo 'private debug';
ob_end_clean();
echo 'ok';
$html=ob_get_clean();
echo $html."\n";
Ожидаемый результат
page:ok

Внешний буфер сохранён, внутренний отброшен.

Рядом по задаче. ob_get_clean нужен, если результат надо сохранить; ob_end_flush — если отправить ниже.

ob_flushПередать подготовленную порцию дальше и оставить активный буфер для следующей.

PHP 4.2.0+ · Core

ob_flush(): bool

Передать подготовленную порцию дальше и оставить активный буфер для следующей.

Параметр / результатКонтракт
АргументыНет.
РазрешениеАктивный буфер с FLUSHABLE.
Возврат / ошибкаtrue при flush; false и E_NOTICE при отсутствии/запрете; callback может бросить исключение.
ЭффектFLUSH, результат handler идёт следующему уровню, исходное содержимое очищается, уровень остаётся.
ОграничениеНе закрывает буфер и не гарантирует показ/доставку в браузере.
Порция идёт изнутри наружу
  1. Внутренний буфер: Часть1
  2. ob_flush → внешнему буферу
  3. Внутренний теперь пуст, но открыт
  4. Следующий echo собирает Часть2

Генератор уже подготовил одну часть ответа, а затем будет печатать следующую в тот же буфер. ob_flush вызывает handler, передаёт его результат следующему внешнему уровню и опустошает текущий буфер. Он продолжает существовать. Слово «передаёт» не означает «человек уже увидел в браузере»: впереди могут быть ещё буферы PHP, сервера и клиента.

Передать две порции из внутреннего буфера во внешнийСамостоятельный пример
<?php
declare(strict_types=1);

ob_start();
ob_start();
echo 'Часть1; ';
$flushed = ob_flush();
$remaining = ob_get_length();
echo 'Часть2';
ob_end_flush();
$whole = ob_get_clean();
var_dump($flushed, $remaining);
echo $whole, "\n";
Ожидаемый результат
bool(true)
int(0)
Часть1; Часть2

После первого flush данные уже во внешнем буфере, а внутренний пуст. Следующий echo остаётся в том же внутреннем уровне. Внешний буфер позволил увидеть весь результат без HTTP.

Отправить преобразованную порцию и продолжить сборСамостоятельный пример
<?php
declare(strict_types=1);

ob_start();
ob_start(static fn(string $buffer, int $phase): string => strtoupper($buffer));
echo 'tea'; ob_flush();
echo 'mint'; ob_end_flush();
echo ob_get_clean(), "\n";
Ожидаемый результат
TEAMINT

Во внешний буфер поступили TEA и MINT, а не исходные строки. Это показывает отличие flush от get_clean. Многословное преобразование может сломаться на границе порции: например, str_replace полного слова не найдёт слово, разрезанное между двумя echo.

Нужен активный буфер с FLUSHABLE. Успех true, отсутствие/запрет false+E_NOTICE. Handler получает FLUSH и при первом вызове также START; FINAL здесь нет. Обработчик может вернуть false/бросить исключение и отключиться: исходный текст тогда может пройти наружу. Не используйте flush как транзакционное подтверждение безопасной фильтрации.

Соседние операции: ob_end_flush, ob_get_flush, flush, ob_clean.

Начать с захвата текста в переменную · Сравнить clean, flush и закрытие.

Очищает содержимое текущего flushable-буфера после передачи ниже и сохраняет уровень. Возвращает bool; требует подходящего активного буфера.

Передать часть во внешний буферСамостоятельный пример
<?php
declare(strict_types=1);

ob_start();
ob_start(); echo 'A'; ob_flush(); echo 'B'; ob_end_flush();
$result=ob_get_clean();
echo $result."\n";
Ожидаемый результат
AB

Обе части попали в внешний уровень, который ещё можно получить строкой.

После flush буфер остаётся открытымСамостоятельный пример
<?php
declare(strict_types=1);

$base=ob_get_level();
ob_start(); ob_flush();
$active=ob_get_level()===$base+1;
ob_end_clean();
echo $active ? "still active\n" : "closed\n";
Ожидаемый результат
still active

Пустой flush показывает жизненный цикл без сетевых эффектов.

Рядом по задаче. ob_end_flush завершает уровень; flush просит нижележащую систему вывода отправить данные.

ob_end_flushПередать окончательный обработанный фрагмент дальше и закрыть активный уровень.

PHP 4+ · Core

ob_end_flush(): bool

Передать окончательный обработанный фрагмент дальше и закрыть активный уровень.

Параметр / результатКонтракт
АргументыНет.
РазрешениеREMOVABLE; FLUSHABLE отдельно не требуется.
Возврат / ошибкаtrue при завершении, false+E_NOTICE при отсутствии/запрете; возможны исключения callback.
ЭффектFINAL (не FLUSH), результат handler передаётся дальше, буфер удаляется.
Завершение генератора
  1. Внутренний буфер готов
  2. Handler получает FINAL
  3. Результат передан внешнему уровню
  4. Внутренний уровень удалён

Генератор закончил работу, и готовый фрагмент должен пройти дальше по цепочке. ob_end_flush вызывает handler, отправляет его результат и удаляет активный уровень. Теперь следующий echo попадёт в прежний внешний буфер либо в системный вывод PHP. Возвращается bool, а не отправленный текст.

Завершить внутренний фрагмент и продолжить внешнюю страницуСамостоятельный пример
<?php
declare(strict_types=1);

ob_start();
echo 'Шапка; ';
ob_start();
echo 'Карточка; ';
$ended = ob_end_flush();
echo 'Подвал';
$page = ob_get_clean();
var_dump($ended);
echo $page, "\n";
Ожидаемый результат
bool(true)
Шапка; Карточка; Подвал

Карточка добавлена к внешнему буферу, внутренний закрыт. Подвал уже печатается прямо во внешний уровень. Очистить внутреннюю карточку после этого можно только изменив внешнюю строку, не ob_clean старого уровня.

Закрыть с отправкой без отдельного разрешения ob_flushСамостоятельный пример
<?php
declare(strict_types=1);

ob_start();
ob_start(null, 0, PHP_OUTPUT_HANDLER_REMOVABLE);
echo 'Готово';
$ended = ob_end_flush();
$text = ob_get_clean();
var_dump($ended);
echo $text, "\n";
Ожидаемый результат
bool(true)
Готово

REMOVABLE разрешает завершающую операцию. FLUSHABLE управляет отдельным ob_flush, поэтому его отсутствие не запрещает ob_end_flush. Не путайте эти разрешения в проверке прав буфера.

При завершении handler получает FINAL, а не FLUSH; START добавляется, если handler вызван впервые. Нужен REMOVABLE; отсутствие/запрет даёт false+E_NOTICE. Handler может бросить исключение. После конца скрипта PHP сам последовательно завершает оставшиеся буферы изнутри наружу, даже если ручное удаление было запрещено; это не причина забывать о нормальном завершении принадлежащего функции уровня.

Соседние операции: ob_flush, ob_get_flush, ob_end_clean, ob_get_level.

Начать с захвата текста в переменную · Сравнить clean, flush и закрытие.

Завершает текущий удаляемый буфер с передачей его содержимого. Возвращает bool, а не строку HTML.

Включить компонент в страницуСамостоятельный пример
<?php
declare(strict_types=1);

ob_start(); echo 'page:';
ob_start(); echo 'badge'; ob_end_flush();
$html=ob_get_clean();
echo $html."\n";
Ожидаемый результат
page:badge

Внутренний компонент передан внешнему без отдельной переменной.

Проверить завершение своего уровняСамостоятельный пример
<?php
declare(strict_types=1);

$base=ob_get_level();
ob_start();
$ok=ob_end_flush();
echo $ok && ob_get_level()===$base ? "closed\n" : "failed\n";
Ожидаемый результат
closed

Пустой пример проверяет bool и восстановление глубины.

Рядом по задаче. ob_get_clean возвращает строку; ob_flush оставляет уровень активным.

flushПопросить PHP/SAPI передать системный вывод дальше, понимая границы этой просьбы.

PHP 4+ · Core

flush(): void

Попросить PHP/SAPI передать системный вывод дальше, понимая границы этой просьбы.

Параметр / результатКонтракт
АргументыНет.
Возвратvoid, при присваивании получится null; это не подтверждение доставки.
СлойНе влияет на пользовательские ob_start/URL-Rewriter буферы. Они должны передать данные наружу отдельно.
SAPI / версияВеб обычно headers→body; CLI — данные. FastCGI headers-only flush поддержан с PHP 8.4.
ОграниченияСервер, прокси и браузер могут буферизовать дальше; ранние headers мешают некоторым handler.
Попросить сброс на своей ступени
  1. Пользовательские буферы уже передали текст
  2. flush вызывает системный/SAPI сброс
  3. Дальше действуют собственные правила получателя
  4. Функция не возвращает статус показа

CLI-команда сообщает этапы импорта, а разработчик хочет передавать печать следующему слою вывода после каждого этапа. flush просит PHP/SAPI сбросить системный вывод. Он не исполняет обрабатывающий callback вашего ob_start и не опустошает пользовательский буфер. Сначала нужно понять, в каком именно слое лежит текст.

Убедиться, что flush не забирает текст из ob_startСамостоятельный пример
<?php
declare(strict_types=1);

ob_start();
echo 'Отчёт';
$before = ob_get_length();
$returned = flush();
$after = ob_get_length();
$text = ob_get_clean();
var_dump($returned, $before === $after);
echo $text, "\n";
Ожидаемый результат
NULL
bool(true)
Отчёт

Возврат void не сообщает об успешной доставке. Текст остался в пользовательском буфере; системному слою здесь ещё нечего отправлять из него. Не заменяйте ob_flush вызовом flush.

Передать сообщения о двух этапах CLI-работыСамостоятельный пример
<?php
declare(strict_types=1);

foreach (['Файл проверен', 'Отчёт подготовлен'] as $stage) {
    echo $stage, "\n";
    flush();
}
Ожидаемый результат
Файл проверен
Отчёт подготовлен

Вместо настоящей обработки здесь только два сообщения, без sleep и сети. Проверен итоговый CLI-вывод. Момент отображения в терминале/браузере или прохождения прокси этим опытом не измеряется.

Цепочка до человека
  1. echo → активный пользовательский буфер
  2. ob_flush/завершение → внешний пользовательский уровень
  3. После всех уровней → системный вывод PHP/SAPI
  4. flush → просьба backend отправить данные
  5. Веб-сервер, прокси, браузер решают свои буферизации

В CLI и embed сбрасываются данные; веб-SAPI обычно сначала отправляет заголовки, затем тело. С PHP 8.4 FastCGI поддерживает flush заголовков без тела. Нельзя распространять этот пункт на гарантированное поведение всех веб-серверов: действие реализует конкретный SAPI и цепочка инфраструктуры. Отправка заголовков заранее может помешать gzip/другому handler, которому ещё нужно добавить свои заголовки.

flush не управляет буфером браузера, рендерингом страницы или прокси/CDN. Дописывание произвольных 4096 пробелов — историческая попытка обойти порог отдельного клиента, а не переносимый контракт. Для прогресса веб-задачи сначала выбирают протокол и серверную конфигурацию (например, отдельный status endpoint или согласованный streaming), затем проверяют реальную цепочку. Как только заголовки/часть тела действительно ушли, прежний ответ уже нельзя полностью заменить новой ошибкой.

Соседние операции: ob_flush, ob_end_flush, ob_implicit_flush.

Начать с захвата текста в переменную · Сравнить clean, flush и закрытие.

Не возвращает значение и не очищает пользовательские ob-буферы автоматически. Поведение зависит от SAPI, веб-сервера, прокси и клиента.

Фрагмент SSE-потокаПример для указанного окружения
<?php
declare(strict_types=1);

header('Content-Type: text/event-stream');
header('Cache-Control: no-cache');
echo "event: progress\ndata: {\"done\":1}\n\n";
flush();

HTTP-фрагмент требует настройки буферизации всей цепочки и закрытой сессии. Не обещает мгновенную доставку.

CLI-вывод прогрессаСамостоятельный пример
<?php
declare(strict_types=1);

echo "batch 1 complete\n";
flush();
Ожидаемый результат
batch 1 complete

В CLI нет браузерного рендера; вывод в терминал или pipe имеет собственные условия буферизации.

Рядом по задаче. Для прогресса часто проще очередь и polling; ob_flush сначала передаёт пользовательский буфер ниже.

Источники и обсуждения

Код и руководство с собой

Скачайте самостоятельные примеры с исходными данными, инструкциями и ожидаемыми результатами. Примеры HTTP и фрагменты для проекта помечены отдельно: им нужно указанное окружение.

Код не исполняется на сайте. Скачайте нужный пример и запускайте его в своём локальном окружении.

Материалы сверены 2026-10-12. Примеры и объяснения написаны для этого руководства; источники и обсуждения доступны в каждой главе.