Когда вообще есть смысл отдавать файлы через PHP? Ну, если ты хочешь считать, кто сколько раз качал файл, или тебе надо ограничить доступ: один пользователь — одна попытка, остальным — фиг. В остальных случаях — не страдай фигнёй, отдай это nginx'у и живи спокойно. Но если выбора нет — читай дальше, как сделать это без боли.
Допустим, чтобы скачать какой-то файл, мы отправляем пользователя не на прямую ссылку с расположением файла:
/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. Все работает отлично, без нареканий.