2013-04-02 195 views
3

我需要將一個CSV文件寫入PHP輸出緩衝區,然後在完成寫入後將該文件下載到客戶端計算機。 (我想寫在服務器上並下載它正在工作,但事實證明,我將無法在生產服務器上進行寫訪問)。寫入PHP輸出緩衝區,然後從緩衝區下載CSV

我有以下的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()命令的排序有關,但我不確定排序這些東西的最佳方式是什麼。

任何幫助表示讚賞。

+0

如果您寫入輸出緩衝區,您將立即將其發送到客戶端。在這種情況下,首先需要發送任何頭文件。 – datasage

+1

ob_clean()是沒有意義的,除非你在某處完成了ob_start()。並考慮讀取文件WRITES輸出,但你正在閱讀該輸入。除非你在所有這些代碼之前完成了ob_start(),否則你的fputcsv()已經寫入輸出並且在任何頭文件代碼被執行之前都轉到瀏覽器。 –

+0

只要使用「@」可以證明正確,我就可以從字面上命名PHP中的一種方法。它不在你的代碼中,所以你可以在開始時正確地編寫你的代碼嗎? –

回答

6

你做得太多了。創建腳本的唯一目的是輸出CSV。只需將其直接打印到屏幕即可。不要擔心標題或緩衝區或php://輸出或類似的東西呢。

一旦你確認你打印出到屏幕上的數據正確,只是在開始添加這些標題:

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

...確認該適當地下載文件。然後你可以添加其他的頭文件(如果你喜歡的話)(上面包含的頭文件是我自己使用的頭文件,沒有任何額外的瑕疵來實現它的工作,其他頭文件基本上是爲了效率和緩存控制,其中一些可能已經適當地處理通過你的服務器,可能對你的特定應用程序很重要)。

如果需要,請使用輸出緩衝區ob_start()ob_get_clean()將輸出內容轉換爲字符串,然後用該字符串填充Content-Length

+5

感謝實際幫助我,不像其他人只是抱怨我做錯了 – jrubins

0

我做了一些調整你的代碼。

  • 在任何輸出之前移動標題,如PHP's doc所示;

記住header()函數是 發送的任何實際輸出之前,必須被調用,或者通過正常的HTML標記,空行在文件中,或從PHP

  • 去掉了一些頭這並沒有太大的改變;
  • 評論了使用select列名寫入csv頭的另一個選項;
  • 現在內容長度有效;
  • 沒有必要echo $ basic_info,因爲它已經在輸出緩衝區,我們通過頭文件將它重定向到一個文件中;
  • 刪除@(PHP錯誤控制操作員),因爲它可能會導致開銷,沒有一個鏈接現在顯示你,但你可能會發現它,如果你搜索。在沉默錯誤之前,你應該三思,大多數次它應該被修復而不是沉默。

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); 
+0

不是頭已發送在標題('Content-Length:'。$ streamSize);既然你之前寫過php://輸出? –

+0

@dnFer是的,但我只能在寫完文件後才知道文件大小,而且這個工作正常,瀏覽器會在下載時彈出文件大小。 –

+0

我明白了。但我有點想知道爲什麼「標題已發送」警告不會彈出。 –

1

正如我在埃德森的答案的評論中提到,我預期「已經發送了頭」在代碼的最後一行警告:

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

因爲輸出在此之前寫頭被髮送,但他的例子工作正常。

一些調查使我對以下結論:

在您使用的輸出緩衝器(weither用戶的一個或 默認PHP),你可以發送HTTP標頭和內容的方式時你想要 。您知道任何協議都需要在主體 (因此術語「標題」)之前發送標題,但是當您使用輸出緩衝層時,PHP 將爲您處理此問題。任何使用輸出 頭(header(),setcookie(),session_start())的PHP函數實際上都將使用 內部的sapi_header_op()函數,該函數僅填充緩衝區的頭部 。當你使用printf()寫入輸出時,它將輸出緩衝區寫入 (假設一個)。 當發送輸出緩衝區爲 時,PHP開始首先發送標題,然後開始發送標題。 PHP 照顧你的一切。如果你不喜歡這種行爲,你可以使用 來禁用任何輸出緩衝層。

下大多數配置

PHP的緩衝區的默認大小爲4096個字節 (4KB),這意味着PHP緩衝器可以保持數據高達4KB。 一旦超過此限制或PHP代碼執行完成,緩衝內容 自動發送到到任何後端PHP正在使用(CGI, mod_php,FastCGI)。輸出緩衝在PHP-CLI中始終爲Off。

Edson的代碼工作,因爲輸出緩衝區沒有自動刷新,因爲它沒有超過緩衝區大小(並且在發送最後一個報頭之前腳本未明顯終止)。

只要輸出緩衝區中的數據超過緩衝區大小,就會引發警告。或者在他的例子中,當數據爲

$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(); 

不過,你仍然受到其他限制。