Когда вообще нужно отдавать файлы через PHP? Например, когда нужно собрать какую-то статистику скачиваний файла или осуществить запрет на скачивание файлов, позволив скачивать по ссылке только один раз и ограниченной группе пользователей.
Допустим, чтобы скачать какой-то файл, мы отправляем пользователя не на прямую ссылку с расположением файла:
/uploads/files/program.exe
а на адрес скрипта, который отдает пользователю файл через PHP:
/files/download.php?file=$file_id
Где, в параметр file, для скрипта можно передавать идентификатор файла, который требуется скачать, после чего можно выстраивать различные проверки и выдавать пользователю файл для скачивания:
Header('location: /uploads/files/' . $file);
Сделать это можно несколькими способами, о которых речь пойдет ниже.
Функция readfile()
Этот метод будем применять в специально созданной функции. Такая функция поможет отправлять даже большие файлы, PHP будет отдавать файл пользователю по частям. Функция ждет, когда файл будет прочтен и отдан.
/**
* Отдача файла
* Функция для отдачи файла через PHP
* @param string $file путь к файлу на сервере
* @return mixed
*/
function file_download($file) {
if (file_exists($file)) {
if (ob_get_level()) {
ob_end_clean();
}
header('Content-Description: File Transfer');
header('Content-Type: application/octet-stream');
header('Content-Disposition: attachment; filename=' . basename($file));
header('Content-Transfer-Encoding: binary');
header('Expires: 0');
header('Cache-Control: must-revalidate');
header('Pragma: public');
header('Content-Length: ' . filesize($file));
readfile($file);
return [
'status' => 'success',
'message' => 'Файл успешно отдан'
];
}else {
return [
'status' => 'error',
'message' => 'Файл не найден'
];
}
}
Чтение и отправка файла вручную
Эта функция аналогична той, которая описана выше, но для чтения и отдали файла используются: fopen, feof, fread, fclose. Функция ждет когда файл будет прочитан и отдан, также позволяет экономить память.
/**
* Отдача файла
* Функция для отдачи файла через PHP
* @param string $file путь к файлу на сервере
* @return mixed
*/
function file_download($file) {
if (file_exists($file)) {
if (ob_get_level()) {
ob_end_clean();
}
header('Content-Description: File Transfer');
header('Content-Type: application/octet-stream');
header('Content-Disposition: attachment; filename=' . basename($file));
header('Content-Transfer-Encoding: binary');
header('Expires: 0');
header('Cache-Control: must-revalidate');
header('Pragma: public');
header('Content-Length: ' . filesize($file));
if ($fd = fopen($file, 'rb')) {
while (!feof($fd)) {
print fread($fd, 1024);
}
fclose($fd);
}
return [
'status' => 'success',
'message' => 'Файл успешно отдан'
];
}else {
return [
'status' => 'error',
'message' => 'Файл не найден'
];
}
}
Отдаем файл через сервер
Отдать файл можем не скриптом, через PHP, а с помощью Apache или Nginx. Отдача файла средствами сервера дает максимальное быстродействие, минимум потребляет памяти и ресурсов сервера.
Для Apache есть модуль XSendFile, который поможет с помощью специального заголовка сделать отправку файла Apache. В настройках хоста включитте директиву перехвата заголовка:
XSendFile On
Функция для отправки файла будет следующей:
/**
* Отдача файла
* Функция для отдачи файла через PHP
* @param string $file путь к файлу на сервере
* @return mixed
*/
function file_download($file) {
if (file_exists($file)) {
header('X-SendFile: ' . realpath($file));
header('Content-Type: application/octet-stream');
header('Content-Disposition: attachment; filename=' . basename($file));
return [
'status' => 'success',
'message' => 'Файл успешно отдан'
];
}else {
return [
'status' => 'error',
'message' => 'Файл не найден'
];
}
}
Nginx умеет отправку файла из коробки, все что нужно, настроить конфиг, указав запрет на доступ к каталогу (my/path/protected/):
location /protected/ {
internal;
root /my/path;
}
Функция отправки файла выглядит так:
/**
* Отдача файла
* Функция для отдачи файла через PHP
* @param string $file путь к файлу на сервере
* @return mixed
*/
function file_download($file) {
if (file_exists($file)) {
header('X-Accel-Redirect: ' . $file);
header('Content-Type: application/octet-stream');
header('Content-Disposition: attachment; filename=' . basename($file));
return [
'status' => 'success',
'message' => 'Файл успешно отдан'
];
}else {
return [
'status' => 'error',
'message' => 'Файл не найден'
];
}
}
Все варианты хороши, зависит от задачи какой из них выбирать
Через nginx разруливаю отдачу файлов. Для Апач тут вариант все делать через htaccess
Через nginx все же предпочтительней отдавать файлы. Этим ведь и должен заниматься сервер. Поэтому я за этот вариант, т.к. он лучше.
Всегда отдавал описанным в статье способом через php. Все работает отлично, без нареканий.