KHAN's avatar
Level 4

How to downlod a stored file within a view.

My filesystem uses local storage atm so all files are stored within storage/app folders. The config will be changed to point to an amazon s3 server at a later date.

Im trying to allow a user to be able to download a file with a click of a button.

html

{{ link_to_route('event-attachment-download', 'Download', [$event->id, $attachment->path], ['class' => 'btn btn-primary btn-block']) }}

controller

    public function download($eventID, $attachmentPath)
    {

        $event = EventModel::find($eventID);
        $file = $event->attachments->where('path', $attachmentPath)->first();
        $path = $event->getDirectoryPath() . $file->path;

        return response()->download($path);

    }

However its saying that the file does not exist but ive double checked the path/names and everything seems right. Im going to assume now that the response()->download function only works for files stored in the public directory? Whats my solution here.

0 likes
20 replies
TheNodi's avatar

@KHAN

It should be able to serve files to any location has long as it can read them.

Have you tried add dd($path) before the return and check if the path is correct?

Does laravel has the permission to read the file?

KHAN's avatar
Level 4

@TheNodi

Permissions are right.

The dd returns "event_attachments/86_y01e6oqrl4n92mx/image.png"

This folder/file is within my storage folder i can see it there.

TheNodi's avatar

@KHAN

Try to use:

return response()->download(storage_path($path));

Relative paths may point to a different directory.

KHAN's avatar
Level 4

@TheNodi

Ty but i still get a error

The file "/var/www/ProjectNameHere/storage/event_attachments/86_y01e6oqrl4n92mx/image.png" does not exist

Actually i noticed its not adding the "storage/app" folder. Im not sure why when my filesystem config is

        'local' => [
            'driver' => 'local',
            'root' => storage_path('app'),
        ],

Why is storage_path not adding the "app" folder within storage to the url.

I changed it to return response()->download(storage_path('app/' . $path));

Im just wondering what im going to need to change when i switch to amazon s3 server. How is this going to affect it

TheNodi's avatar

@KHAN

You're not using the flysystem to handle your download, that code simply fetches the path from the database. If you want to use the advantages of flysystem you can return a stream response, take a look at this answer.

I would suggest you to use File URLs especially after the switch to S3, it saves a lot of load for your servers.

SaeedPrez's avatar
// This will get and return storage/app/test.png
return response(Storage::disk('local')->get('test.png'), 200)->header('Content-Type', 'image/png');
1 like
KHAN's avatar
Level 4

@SaeedPrez

Can i do something like Storage::disk('default') will that point to the default value in filesystem so i can easily switch it over?

and the header content type will be dynamic.

Also ur response doesent download it , it just opens the image in a new tab.

SaeedPrez's avatar

Yes, it will read the configuration file so you can change it to S3 later, and feel free to replace the strings with $variables, it's just example code to point you to the right direction.

TheNodi's avatar

@KHAN

In your config/filesystem.php you have a default option, you can use it without specifying any disk at all, like Storage::get('...').

KHAN's avatar
Level 4

@SaeedPrez

I understand now.

@TheNodi

Yeah i thought so.

So my final code looks like this.

        $event = EventModel::find($eventID);
        $file = $event->attachments->where('path', $attachmentPath)->first();
        $url = Storage::url($event->getDirectoryPath() . $file->path);
        return response()->download($url);

but when i dd $url it still ommits the "app" folder. Why is that? i dont want to hard code that in. Should it not know that if its local driver it should point to the app folder?

TheNodi's avatar

@KHAN

There's a note in the File URLs section of the docs:

Note: When using the local driver, be sure to create a symbolic link at public/storage which points to the storage/app/public directory.

So if you call http://example.com/storage/hello.jpg it goes to /public/storage/hello.jpg that point to /storage/app/hello.jpg.

KHAN's avatar
Level 4

@TheNodi

I don't understand that.

Im using the default local driver where i upload my files into storage/app, and not storage/app/public though that folder is there, i don't know the different use cases. Which should i be using for local storage?

And also there is no storage folder inside my /public folder just css/images/js etc.

SaeedPrez's avatar

To download you have two options, either use the response()->download() which will require file path and not file content, or you will have to manually send the correct headers with the code example I gave you.

$file = Storage::disk('local')->get('test.png');
$fileName = 'MyImage.png'; // the file name the downloader gets
$contentType = 'image/png';

return response($file, 200)
    ->header('Cache-Control', 'public')
    ->header('Content-Description', 'File Transfer')
    ->header('Content-disposition', "attachment; filename=$fileName")
    ->header('Content-Type', $contentType)
    ->header('Content-Transfer-Encoding', 'binary');

1 like
KHAN's avatar
Level 4

@SaeedPrez

Is that a late response? i know how to download it now. I just dont understand why Storage::url() ommits the "app" folder inside my storage/app.

And i can't create a symbolic link like mentioned above because there is no public/storage folder and im not using the public storage driver im using local because i dont know the different use cases i just want to give the user the ability to download files.

Update:

Ive made the storage folder inside of public.

Created the symblic link.

ls -s public/storage storage/app/public/

public/storage:
total 0

storage/app/public/:
total 0

But i get an error still when trying to download. "The file "/storage/event_attachments/86_y01e6oqrl4n92mx/image.png" does not exist"

TheNodi's avatar

@KHAN

You have to create a symbolic link in public/ with the name "storage" that points to storage/app.

From your project root you have to type (on unix): ln -s ../storage/app/ public/storage

If you check the public folder with ls -l public/ you'll see something like:

lrwxrwxrwx.  1 user group   12  9 ago 14.42 storage -> storage/app/

This will "create" a storage folder in your public directory which is a "mirror" of the storage/app folder. (See Symbolic Link ) If the user will go to http://localhost/storage/file.jpg the web server will find the file in storage/app/file.jpgand serve it.

KHAN's avatar
Level 4

@TheNodi @TheNodi

I understand the purpose of the symbolic link now. Its just referencing.

I created the symbolic link like you said and i saw it by running that command. But even still when i try to download the file it just returns me with "The file "/storage/event_attachments/86_y01e6oqrl4n92mx/image.png" does not exist"

but the user won't be going to http://localhost/storage/file.jpg. Because im calling a route to a function when they click the link to download. So just wondering if this was even the problem.

KHAN's avatar
Level 4

Please can someone try it by using the code below. I'ts not working for me. Even with below i still get file does not exist.


Route::get('test', function() {

    Storage::put('myfile.txt' , 'Test File');
    $url = Storage::url('myfile.txt');
    return response()->download($url);


});

TheNodi's avatar

@KHAN

The purpose of Storage:url() is to replace the need for your laravel app to handle the download, you should use it in your blade template:

<a href="{{ Storage::url('...') }}">Download</a>
KHAN's avatar
Level 4

@TheNodi

Oh, this was not explained before. I thought i was using Storage::url so i could access the file from the local drive and the S3 drive. This is what i want. Im not going to have a symbolic link with the S3 drive.

I need a solution where my app handles download of both Local and S3 Drive files

AND

<a href="{{ Storage::url('...') }}">Download</a>

This does not download the file it just allows the user to see the file in a separate tab. I want to be able to download.

TheNodi's avatar

@KHAN

In that case go with @SaeedPrez 's solution.

return response(Storage::get($path), 200)->header('Content-Type', 'image/png');

Note: You can get mimetype with:

$mimetype = Storage::mimeType($path);
1 like

Please or to participate in this conversation.