Как вернуть файл в PHP: основные методы и их применение
Основные подходы к возврату файлов
Как эффективно отдать файл браузеру для скачивания?
Самый производительный способ отдать файл клиенту — использовать функцию readfile() в сочетании с правильными HTTP-заголовками. Этот метод не загружает содержимое файла в память целиком, а передаёт его напрямую из файловой системы в выходной буфер. Подходит для файлов любого размера.
function downloadFile(string $filePath, string $fileName = null): bool {
if (!file_exists($filePath)) {
return false;
}
if ($fileName === null) {
$fileName = basename($filePath);
}
header('Content-Description: File Transfer');
header('Content-Type: application/octet-stream');
header('Content-Disposition: attachment; filename="' . $fileName . '"');
header('Content-Length: ' . filesize($filePath));
readfile($filePath);
exit;
}
Php форматы данных (форматы данных в php (json, xml, serialize))
После вызова readfile() управление не возвращается, поэтому функция завершает скрипт через exit. Это предотвращает вывод лишнего содержимого после файла.
Типичные ошибки:
- Ошибка 404 при неверном пути — проверять file_exists().
- Заголовки не отправляются, если до вывода есть пробелы или echo. Использовать ob_clean() перед заголовками.
- Большой файл может превысить время выполнения — увеличить max_execution_time.
Как получить содержимое файла строкой для обработки?
Если нужно не отдать файл, а использовать его данные в скрипте, подходит file_get_contents(). Функция возвращает строку с содержимым файла.
function getFileContent(string $path): ?string {
if (!is_readable($path)) {
return null;
}
return file_get_contents($path);
}
Php null false (null и false в php)
Используется для чтения конфигураций, шаблонов, небольших файлов данных.
Проблемы:
- При больших файлах (более десятков мегабайт) может произойти переполнение памяти. Лучше применять потоковые методы.
- Ошибка возвращает false, поэтому нужно проверять строгим оператором ===.
Как вернуть большой файл частями, не загружая его в память?
Для чтения огромных файлов (например, видео или дампов) используют fopen() + fread() в цикле. Каждый фрагмент данных отправляется сразу в вывод.
function streamFileChunks(string $path, int $chunkSize = 8192): bool {
$handle = fopen($path, 'rb');
if (!$handle) {
return false;
}
header('Content-Type: application/octet-stream');
while (!feof($handle)) {
$chunk = fread($handle, $chunkSize);
if ($chunk === false) {
break;
}
echo $chunk;
flush();
}
fclose($handle);
return true;
}
Php get started (начало работы с php)
Этот вариант полезен, когда readfile() недоступен или требуется дополнительная логика перед каждым фрагментом.
Ошибки:
- Не закрыт файловый дескриптор — использовать fclose() в конце.
- Чтение до конца файла может генерировать warnings, если файл повреждён. Проверять возвращаемое значение fread().
Как отправить уже открытый поток напрямую клиенту?
Функция fpassthru() выводит оставшуюся часть открытого файлового потока, начиная с текущей позиции.
function outputStream($handle): void {
fpassthru($handle);
fclose($handle);
}
Custom index php (создание собственного index.php)
Применяется, когда файл уже открыт для чтения (например, после работы с ZIP-архивом).
Особенность: если поток уже был частично прочитан, будут отправлены только оставшиеся байты. Нужно убедиться, что текущая позиция в начале файла, или перемотать rewind().
Как скопировать поток с минимальным потреблением памяти?
stream_copy_to_stream() копирует данные из одного потока в другой, например из файла в php://output.
function copyFileToOutput(string $path): bool {
$in = fopen($path, 'rb');
if (!$in) {
return false;
}
$out = fopen('php://output', 'wb');
if (!$out) {
fclose($in);
return false;
}
stream_copy_to_stream($in, $out);
fclose($in);
fclose($out);
return true;
}
Метод полезен при работе с удалёнными файлами после fopen() с обёртками (например, ftp://).
Ошибки: не забудьте закрыть оба потока. При неудаче fopen() возвращает false — необходима проверка.
Расширенные примеры возврата файлов
Функция для скачивания с определением MIME-типа
Используется mime_content_type() для автоматического определения типа файла.
function downloadWithMime(string $file): void {
if (!file_exists($file)) {
http_response_code(404);
exit('Файл не найден');
}
$mime = mime_content_type($file);
$name = basename($file);
header('Content-Type: ' . $mime);
header('Content-Disposition: attachment; filename="' . $name . '"');
header('Content-Length: ' . filesize($file));
readfile($file);
exit;
}
// Вызов: downloadWithMime('document.pdf')
// Браузер получит заголовок Content-Type: application/pdf
Поддержка частичной загрузки (Range headers)
Для возобновления скачивания и видео-потоков обрабатывают заголовок HTTP_RANGE.
function rangeDownload(string $file): void {
$size = filesize($file);
$fp = fopen($file, 'rb');
$start = 0;
$end = $size - 1;
header('Accept-Ranges: bytes');
if (isset($_SERVER['HTTP_RANGE'])) {
preg_match('/bytes=(\d+)-(\d*)/', $_SERVER['HTTP_RANGE'], $matches);
$start = intval($matches[1]);
if (!empty($matches[2])) {
$end = intval($matches[2]);
}
header('HTTP/1.1 206 Partial Content');
header("Content-Range: bytes $start-$end/$size");
}
header('Content-Length: ' . ($end - $start + 1));
header('Content-Type: application/octet-stream');
fseek($fp, $start);
$chunk = 8192;
while ($start <= $end && !feof($fp)) {
$readLen = min($chunk, $end - $start + 1);
$data = fread($fp, $readLen);
if ($data === false) break;
echo $data;
$start += strlen($data);
flush();
}
fclose($fp);
}
// При запросе с заголовком Range: bytes=0-1023 вернёт первые 1024 байта.
Возврат файла с использованием output buffering
Иногда необходимо очистить буфер перед отправкой двоичных данных.
function cleanEchoReadfile(string $path): void {
ob_clean(); // очищаем предыдущий вывод
flush();
readfile($path);
exit;
}
// Функция гарантирует, что никакой текст до вызова не повлияет на скачивание.
Возврат сжатого файла (Gzip)
Если клиент поддерживает gzip, можно сжать файл на лету.
function gzipReturn(string $file): void {
if (strpos($_SERVER['HTTP_ACCEPT_ENCODING'], 'gzip') !== false) {
header('Content-Encoding: gzip');
echo gzencode(file_get_contents($file));
} else {
readfile($file);
}
}
// Для текстовых файлов размер может уменьшиться в 3-5 раз.
Возврат файла с проверкой на пустой вывод
Если файл пустой, можно вернуть сообщение об ошибке вместо скачивания пустого файла.
function safeReadfile(string $path): void {
if (!file_exists($path) || filesize($path) == 0) {
http_response_code(204);
exit('No content');
}
readfile($path);
}
// Для пустого файла клиент получает HTTP 204, что интерпретируется как отсутствие данных.