Streaming Excel-Compatible Tables in PHP Using HTTP Headers
When you don’t need a heavy library like PHPExcel/PhpSpreadsheet, you can generate a tab‑separated file (TSV) and instruct the browser to download it as an Excel‑readable worksheet using standard HTTP headers.
Reusable exporter
<?php
/**
* Stream an Excel-readable TSV file to the browser.
*
* @param array $rows Array of rows (associative or numeric arrays)
* @param array $header Optional header: either list of keys or [key => label]
* @param string $filename Base filename without extension
*/
function sendExcelTsv(array $rows = [], array $header = [], string $filename = 'report'): void
{
// Ensure a safe filename and append extension Excel recognizes
$name = preg_replace('/[^\w\d\-_.]+/', '_', $filename) . '.xls';
// Clear any active output buffers to avoid corrupting the file or breaking headers
while (ob_get_level() > 0) {
ob_end_clean();
}
// Headers to force download and hint Excel
header('Content-Type: application/vnd.ms-excel; charset=UTF-8');
header('Content-Disposition: attachment; filename="' . $name . '"');
header('Cache-Control: no-store, no-cache, must-revalidate');
header('Pragma: no-cache');
header('Expires: 0');
// UTF-8 BOM helps some Excel versions interpret encoding correctly
echo "\xEF\xBB\xBF";
// Resolve column order (keys) and labels (header row)
$keys = [];
$labels = [];
if ($header) {
$isAssoc = array_keys($header) !== range(0, count($header) - 1);
if ($isAssoc) {
$keys = array_keys($header);
$labels = array_values($header);
} else {
$keys = $header; // keys match passed column names
$labels = $header; // labels are identical to keys
}
} elseif ($rows) {
$first = reset($rows);
$keys = array_keys((array) $first);
$labels = $keys;
}
if ($labels) {
echo implode("\t", array_map('tsvEscape', $labels)) . "\r\n";
}
foreach ($rows as $row) {
$line = [];
if ($keys) {
foreach ($keys as $k) {
$line[] = tsvEscape($row[$k] ?? '');
}
} else {
foreach ((array) $row as $v) {
$line[] = tsvEscape($v);
}
}
echo implode("\t", $line) . "\r\n";
}
}
/**
* Escape a value for TSV: remove tabs/newlines and mitigate formula injection.
*/
function tsvEscape($value): string
{
if ($value === null) return '';
$s = (string) $value;
// If content starts with characters that Excel treats as a formula, prefix with a single quote
if (preg_match('/^[=\-+@]/', $s)) {
$s = "'" . $s;
}
// Replace control characters that would break TSV
$s = str_replace(["\t", "\r", "\n"], ' ', $s);
return $s;
}
Example usage
<?php
// Fetch rows (e.g., from a database abstraction layer)
$rows = M('User')->field('id,account,password,nickname')->select();
// Define column order and labels
$header = [
'id' => 'ID',
'account' => 'Account',
'password' => 'Password',
'nickname' => 'Nickname',
];
sendExcelTsv($rows, $header, 'users');
Notes
- Do not echo or print anything before calling the function; any prior output can break headers or corrupt the file.
- The function uses UTF‑8 with a BOM. If you must target legacy Windows Excel expecting GBK/GB2312, switch the header charset and transcode values via iconv/mb_convert_encoding before outputting.
- TSV with a .xls extension prompts Excel to open the data directly, even though it’s a plain text format.
- If you need comma-separated files instead, replace the tab delimiter ("\t") with commas and adjust the contant type accordingly.