Как правильно реализовать контроллируемое скачивание файлов?

Добрый день.

Продолжаю изучать Laravel, и у меня возникала необходимость реализовать контроллируемое скачивание файлов (возможно, это называется как-то по-другому).

Задача: файлы не должны быть доступны по прямой ссылке вида
http://domain.com/storage/public/sdfjkshfjkhsdf.doc

а должны скачивать по ссылке вида
http://domain.com/file/123
где 123 - это id файла в таблице файлов. Чтобы я мог проверить права доступа пользователя к данному файлу.

Что сделал я? Создал таблицу:
$table->increments('id');
$table->string('fileable_type', 255);
$table->integer('fileable_id');
$table->unsignedInteger('size'); // размер файла
$table->string('content_type', 50); // content-type
$table->string('path', 255); // путь к файлу
$table->string('original_name', 255); // оригинальное имя файла
$table->timestamps();


Т.к. файлы могут загружаться к разным сущностям, то использую полиморфные связи в моделях.
Модель File:
class File extends Model
{
	protected $table = 'files';

	public function fileable()
	{
		return $this->morphTo();
	}
}


Например, модель Custom (это таможня, если что:) ):
class Custom extends Model
{
	protected $table = 'customs';

	public function files()
	{
		return $this->morphMany('App\File', 'fileable');
	}
}


Теперь в контроллере CustomController сохраняю поля и файл приблизительно так:

public function update(UpdateCustomRequest $request, $id)
    {		
		// Save files
		if ($request->hasFile('contract')) {
			$folder = 'private/'.str_random(4);
			$contract = $request->file('contract');
			$path = $contract->store($folder);
			DB::table('files')->insert([
				'fileable_type' => 'App\Custom',
				'fileable_id' => $id,
				'size' => $contract->getClientSize(),
				'content_type' => $contract->getClientMimeType(),
				'path' => $path,
				'original_name' => $contract->getClientOriginalName(),
			]);
		}
    }


Я понимаю, что работу с БД нужно вынести в модель. Но тогда в модель нужно будет передавать:
- и значение fileable_type (тип сущности, к которой загружается файл)
- и значение fileable_id (id сущности, к которой загружается файл)
- и сам файл $contract

Как-то много всего и не красиво. Мне такое решение не нравится.

Как можно реализовать это лучше? С точки зрения laravel, правильности и красоты кода.

Возможно, есть готовые решения для такой задачи и в них можно подглядеть реализацию?
  • Вопрос задан
  • 1247 просмотров
Пригласить эксперта
Ответы на вопрос 2
Приведённые примеры будут работать, но всё таки отдача файлов это не задача Laravel и PHP.

В laravel можно отдавать ещё так:
return response()->download(storage_path("name.doc"));


Намного эффективней будет отдавать, через nginx.
Для этого нужно отдавать файлы только через с специальный заголовок.

Например со статьи хабра:

Nginx умеет отправлять файлы из коробки через специальный заголовок.
Для корректной работы нужно запретить доступ к папку напрямую через конфигурационный файл:
location /protected/ {
  internal;
  root   /some/path;
}

Пример отправки файла (файл должен находиться в директории /some/path/protected):

function file_force_download($file) {
  if (file_exists($file)) {
    header('X-Accel-Redirect: ' . $file);
    header('Content-Type: application/octet-stream');
    header('Content-Disposition: attachment; filename=' . basename($file));
    exit;
  }
}


Больше информации на странице официальной документации

Особенности:
  • Скрипт завершается сразу после выполнения всех инструкций
  • Физически файл отправляется модулем самого веб сервера, а не PHP
  • Минимальное потребление памяти и ресурсов сервера
  • Максимальное быстродействие
Ответ написан
eXcNightRider
@eXcNightRider
FullStack Web Developer | DevOps
Суть сводится к примерно следующей функции:
public function getFile($file_id)
    {
        $file_data = Files::where('fileable_id', $file_id)->first();
        $file = "/var/project/{$file_data['path']}/{$file_data['original_name']}";
        header('Content-type:'.$file_data['content_type']);
        header('Content-Disposition: attachment;filename=' . md5($file_data['original_name']));
        header('Cache-Control: must-revalidate');
        header('Content-Length: ' . filesize($file));
        readfile($file);
    }
Ответ написан
Ваш ответ на вопрос

Войдите, чтобы написать ответ

Похожие вопросы