How to implement controlled downloads of files?

Good afternoon.

Continue to learn Laravel, and I have a need to implement a controlled file download (it might be called something different).

Problem: the files should not be available via direct link like:
http://domain.com/storage/public/sdfjkshfjkhsdf.doc

a should download via the link
http://domain.com/file/123
where 123 is the file id in table files. So I can check the user's access rights to this file.

What have I done? Created a table:
$table->increments('id');
$table->string('fileable_type', 255);
$table->integer('fileable_id');
$table->unsignedInteger('size'); // file size
$table->string('content_type', 50); // the content-type
$table->string('path', 255); // file path
$table->string('original_name', 255); // original file name
$table->timestamps();


Because files can be loaded to different entities, then use polymorphic relationships in the models.
Model File:
class File extends Model
{
 protected $table = 'files';

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


For example, the model Custom (this custom, if anything:) ):
class Custom extends Model
{
 protected $table = 'customs';

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


Now in the controller CustomController fields and save the file like this:

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(),
]);
}
 }


I understand that the database should be put into the model. But then the model will need to be passed:
and the value fileable_type (type of the entity to which the file is loaded)
and fileable_id value (id of the entity to which the file is loaded)
- the file $contract

Somehow a lot of things and is not pretty. I do not like the decision.

How do you implement it better? From the perspective of laravel, the correctness and beauty of code.

Maybe there are ready solutions for such a task and you can peep the implementation?
June 10th 19 at 15:22
2 answers
June 10th 19 at 15:24
The examples will work, but still returns the files is not the task of Laravel and PHP.

In laravel you can give this:
return response()->download(storage_path("name.doc"));


Much more effective to give through nginx.
For this you need to give files only via a special header.

For example article with Habra:

Nginx is able to send files from the box through a special header.
To work correctly you need to deny access to the folder directly via the configuration file:
location /protected/ {
internal;
 root /some/path;
}

Example of sending the file (the file must reside in the directory /some/path/protected):

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


More information visit the official documentation

Features:
  • The script terminates right after execution of all instructions
  • Physically the file is sent from a module of the web server, not PHP
  • Minimal memory consumption and server resources
  • Maximum performance
For the remark about nginx - thank you.

Maybe I'm not quite correctly formulated the question. I have no problem with the implementation of the file delivery.

The question is, how competently from the point of view of architecture to implement all this functionality in General. Given me the code to save a file - is quite imperfect. But how to improve it - I don't really understand. - Kamron12 commented on June 10th 19 at 15:27
June 10th 19 at 15:26
The essence is reduced to approximately following functions:
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);
 }
Maybe I'm not quite correctly formulated the question. I have no problem with the implementation of this function.

The question is, how competently from the point of view of architecture to implement all this functionality in General. Given me the code to save a file - is quite imperfect. But how to improve it - I don't really understand. - Kamron12 commented on June 10th 19 at 15:29
Classic save in database in Laravel looks like this:
$post = Post::create([
 'title' => 'Laravel - wonderful!',
 'author' => 'Jason',
 'body' => 'Laravel is very convenient - use it if you don't do it!'
]);

The route for the download link
Route::get('resource/download',['as' => 'admin.resource.download','uses' => 'ResourceController@download']);

The corresponding function in the controller as mentioned above @tatu
public function download(Request $request){
 return response()->download(public_path('uploads/resource/'.$request['file'])); 
 }
- Kamron12 commented on June 10th 19 at 15:32

Find more questions by tags Laravel