Обработка real-time вывода

Иногда может возникнуть задача на лету обрабатывать какой-либо пользовательский вывод, направляемый в браузер неявно (например, при работе функций readfile() или passthru()). При этом необходимо сохранять вывод в браузер в реальном времени. Для примера рассмотрим реально возникавшую задачу из моей практики.

PHP-скрипт должен запустить консольный сценарий, который в реальном времени выводит на экран проведённые операции, причём делает это небыстро. Пользователь должен так же в реальном времени наблюдать выполнение этого сценария в своём браузере, вдобавок весь вывод этого сценария нужно сохранить. Необходимо также помнить, что консольный сценарий использует символ \\n для переноса строки, тогда как в браузере необходимо применять тэг br.

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

Вот так всё это выглядит в контексте класса (показаны только ключевые методы):

/**
 * Выполнение команды, обработка вывода на лету и возврат лога работы.
 *
 * @param string Консольная команда.
 * @return string Полный лог работы команды.
 */
public function runCommand($command) {
    // Сброс предыдущего лога.
    $this->log = '';

    // Настройка немедленного и автоматического вывода.
    ini_set('implicit_flush', true);
    ob_implicit_flush(true);

    // Старт перехвата выходного потока. Минимальный размер буфера 2 байта (смотри описание функции ob_start()).
    ob_start(array(
        $this,
        'handleLogPart'
    ), 2);

    // Запуск команды с автоматическим выводом результата в браузер.
    passthru($command);

    // Освобождение буфера вывода и завершение буферизации.
    ob_end_flush();

    // Возврат сохранённого вывода.
    return $this->log;
}


/**
 * Callback для обработки лога.
 *
 * @param string Часть лога, получаемая при перехвате буфером.
 * @return string Обработанная часть лога.
 */
public function handleLogPart($logPart) {
    // Сохранение части лога.
    $this->log .= $logPart;

    // Обработка части лога и возврат.
    return nl2br($logPart);
}

Два байта это наименьший допустимый размер буфера, по крайней мере, до тех пор, пока не появится PHP 6. Поскольку в данной задаче необходимо отслеживать только символы переноса строки (1 байт), можно использовать минимальный размер буфера, чтобы сделать вывод максимально "чутким". Отслеживание и обработка блоков текста длиннее 1 байта потребует большего размера буфера и усложнённой логики для поиска этих блоков на стыках буферизованных частей.

Бывает, что некоторые браузеры не всегда хотят выводить текст сразу, как только получат. Помогает правильная разметка страницы как XHTML 1.0 Strict. Если вывод всё ещё не происходит мгновенно, проверьте, что отключена директива конфигурации output_buffering. И, пожалуй, последнее, что может помешать real-time выводу — другие вызовы ob_start() до запуска процесса.

Записи