Напишите в буфер вывода PHP, а затем загрузите CSV из буфера

Мне нужно написать CSV-файл в выходной буфер PHP, а затем загрузить этот файл на компьютер клиента после его написания. (Я хотел просто написать его на сервере и загрузить его, который работал, но, оказывается, у меня не будет доступа на запись на производственных серверах).

У меня есть следующий скрипт PHP:

$basic_info = fopen("php://output", 'w'); $basic_header = array(HEADER_ITEMS_IN_HERE); @fputcsv($basic_info, $basic_header); while($user_row = $get_users_stmt->fetch(PDO::FETCH_ASSOC)) { @fputcsv($basic_info, $user_row); } @fclose($basic_info); header('Content-Description: File Transfer'); header('Content-Type: application/csv'); header('Content-Disposition: attachment; filename=test.csv'); header('Expires: 0'); header('Cache-Control: must-revalidate'); header('Pragma: public'); header('Content-Length: ' . filesize("php://output")); ob_clean(); flush(); readfile("php://output"); 

Я не уверен, что делать. Файл CSV загружается, но ничего не отображается. Я предполагаю, что это имеет какое-то отношение к упорядочению моих команд ob_clean () и flush (), но я не уверен, что лучший способ заказать эти вещи.

Любая помощь приветствуется.

Вы слишком много делаете. Создайте сценарий с единственной целью вывода CSV. Просто распечатайте его непосредственно на экране. Не беспокойтесь о заголовках или буферах или php: // output или что-то еще.

Как только вы подтвердите, что вы печатаете данные на экране соответствующим образом, просто добавьте эти заголовки в начале:

 <?php header("Content-disposition: attachment; filename=test.csv"); header("Content-Type: text/csv"); ?> 

… подтвердите, что это загружает файл соответствующим образом. Затем вы можете добавить другие заголовки, если хотите (заголовки, которые я включил выше, это те, которые я использовал самостоятельно, без каких-либо дополнительных усилий, чтобы заставить это работать, другие в основном для эффективности и управления кешем, некоторые из которых могут быть обработаны надлежащим образом вашим сервером и может или не может быть важна для вашего конкретного приложения).

Если вы хотите, используйте буферизацию вывода с помощью ob_start() и ob_get_clean() чтобы получить содержимое вывода в строку, которую вы затем можете использовать для заполнения Content-Length .

Как упоминалось в моих комментариях к ответу Эдсона, я ожидал, что предупреждение «заголовки уже отправлено» в последней строке кода:

 header('Content-Length: '.$streamSize); 

так как вывод написан до отправки этого заголовка, но его пример работает нормально.

Некоторое расследование приводит меня к следующим выводам:

В то время, когда вы используете выходной буфер (как пользовательский, так и стандартный PHP), вы можете отправлять HTTP-заголовки и контент так, как вы хотите. Вы знаете, что для любого протокола требуется отправить заголовки перед телом (таким образом, термин «заголовок»), но когда вы используете буферный уровень ouput, PHP позаботится об этом для вас. Любая функция PHP, играющая с заголовками вывода (header (), setcookie (), session_start ()), фактически использует внутреннюю функцию sapi_header_op (), которая просто заполняет буфер заголовков. Когда вы затем записываете вывод, используя команду printf (), он записывает в выходной буфер (в предположении один). Когда выходной буфер должен быть отправлен, PHP начинает сначала отправлять заголовки, а затем тело. PHP заботится обо всем для вас. Если вам не нравится это поведение, у вас нет другого выбора, кроме как отключить любой буферный буфер.

а также

Размер по умолчанию для буфера PHP в большинстве конфигураций составляет 4096 байт (4 КБ), что означает, что буферы PHP могут хранить данные до 4 КБ. Как только этот предел будет превышен или выполнение PHP-кода будет завершено, буферизованный контент будет автоматически отправлен на любой конец PHP, который используется (CGI, mod_php, FastCGI). Буферизация вывода всегда отключена в PHP-CLI.

Код Эдсона работает, потому что выходной буфер не был автоматически очищен, потому что он не превышает размер буфера (и сценарий не заканчивается явно до того, как будет отправлен последний заголовок).

Как только данные в выходном буфере превысят размер буфера, предупреждение будет поднято. Или в его примере, когда данные

 $get_users_stmt->fetch(PDO::FETCH_ASSOC) 

слишком велико.

Чтобы этого избежать, вы должны управлять буферизацией вывода с помощью ob_start () и ob_end_flush (); как показано ниже:

 // Turn on output buffering ob_start(); // Define handle to output stream $basic_info = fopen("php://output", 'w'); // Define and write header row to csv output $basic_header = array('Header1', 'Header2'); fputcsv($basic_info, $basic_header); $count = 0; // Auxiliary variable to write csv header in a different way // Get data for remaining rows and write this rows to csv output while($user_row = $get_users_stmt->fetch(PDO::FETCH_ASSOC)) { if ($count == 0) { // Write select column's names as CSV header fputcsv($basic_info, array_keys($user_row)); } else { //Write data row fputcsv($basic_info, $user_row); } $count++; } // Get size of output after last output data sent $streamSize = ob_get_length(); //Close the filepointer fclose($basic_info); // Send the raw HTTP headers header('Content-Type: text/csv'); header('Content-Disposition: attachment; filename=test.csv'); header('Expires: 0'); header('Cache-Control: no-cache'); header('Content-Length: '. ob_get_length()); // Flush (send) the output buffer and turn off output buffering ob_end_flush(); 

Тем не менее, вы все еще связаны с другими ограничениями.

Я сделал некоторые изменения в вашем коде.

  • Перемещенные заголовки перед любым выходом, как это было предложено в PHP-документе ;

Помните, что header () должен быть вызван до отправки любого фактического вывода либо с помощью обычных тегов HTML, пустых строк в файле, либо из PHP

  • Удалены некоторые заголовки, которые не сильно изменились;
  • Прокомментировал другой вариант написания заголовка csv, используя имена столбца select;
  • Теперь работает длина контента;
  • Нет необходимости эхо $ basic_info, поскольку он уже находится в выходном буфере, и мы перенаправили его внутри файла через заголовки;
  • Удалено @ (PHP Error Control Operator), поскольку это может вызвать накладные расходы, у вас нет ссылки, чтобы показать вам прямо сейчас, но вы можете найти ее, если будете искать. Вы должны подумать дважды, прежде чем замалчивать ошибки, в большинстве случаев его следует исправлять, а не замолчать.

 header('Content-Type: text/csv'); header('Content-Disposition: attachment; filename=test.csv'); header('Expires: 0'); header('Cache-Control: no-cache'); $basic_info = fopen("php://output", 'w'); $basic_header = array(HEADER_ITEMS_IN_HERE); fputcsv($basic_info, $basic_header); $count = 0; // auxiliary variable to write csv header in a different way while($user_row = $get_users_stmt->fetch(PDO::FETCH_ASSOC)) { // Write select column's names as CSV header if ($count == 0) { fputcsv($basic_info, array_keys($user_row)); } fputcsv($basic_info, $user_row); $count++; } // get size of output after last output data sent $streamSize = ob_get_length(); fclose($basic_info); header('Content-Length: '.$streamSize);