31

I'm storing user profile pictures in the Laravel storage folder instead of the public folder because I would like to keep the public folder clean from user clutter.

To serve an image from that folder, I created a simple Controller Action as follows:

public function profilePicture($person, $size = 40){
    $profile_picture_url = storage_path().'/profile_pictures/'.$person['id'].'/profile_'.$size.'.jpg';

    if(!File::exists( $profile_picture_url ))
        App::abort(404);

    return Image::make($profile_picture_url)->response('jpg');
}

Can this be considered a good practice, or should I simply save pictures in the public folder?

Will I run into performance issues by doing so?

1
  • The best way is to use a mutator. I'll provide you a sample now. Mar 17, 2016 at 16:20

5 Answers 5

57
+50

The Short Answer to Your Question

Can this be considered a good practice, or should I simply save pictures in the public folder? Will I run into performance issues by doing so?

It is not a suggested practice, because you read the file and re-generate it, which will take process time and load the server, but that said it all depends on how many requests, image size, etc. I used this practice to secure/protect images/files from public access, so only Authenticated members can access the images/files as it is in this answer. Again depending on file size, number of requests, and server specification, I have used it for a while and I have had no issues with performance, it has worked fine (my server is 512MBMemory, 1 CoreProcessor, 20GBSSD Disk VPS solution). You might give it a try for a while and see.

Symbolic link solution

It is also possible, to create symbolic links like

ln -s /pathof/laravel/storage/profile_pictures /pathof/laravel/public/profile

This solution won't affect performance, but you need to document the solution in your internal documentation, in case you move your setup to a new provider or in case you need to re-link to the storage folder.

But if you still wish to have the full solution for returning images from the storage folder, first of all, we need to install Intervention Image for Laravel, I am not sure if this is done already or not. if you have installed it continue here, but if not follow the last part of this answer and then continue with the Laravel solution.

Laravel solution

As said we assume your intervention works, first of all, you need to create a Route. The Route will forward all image request access to our Controller.

Create Route

Route::get('profile/{person}', 'ImagesController@profilePicture');

After creating a route, we need to create a controller to take care of image requests from our route.

Create ImagesController

From command

php artisan make:controller ImagesController

And your controller should look like this.

class ImagesController extends Controller {

    public function profilePicture($person, $size = 40)
    {
        $storagePath = storage_path('/profile_pictures/' . $person . '/profile_' . $size . '.jpg');

        return Image::make($storagePath)->response();
    }
}

**EDIT**

For those who use Laravel 5.2 and newer. Laravel introduces new and better way to serve files that has less overhead (This way does not regenerate the file as mentioned in the answer):

File Responses

The file method can be used to display a file, such as an image or PDF, directly in the user's browser instead of initiating a download. This method accepts the path to the file as its first argument and an array of headers as its second argument:

return response()->file($pathToFile);

return response()->file($pathToFile, $headers);

And remember to add

use Intervention\Image\Facades\Image;

in your ImagesController class

Finally, be sure you have created a folder structure with a test image.

storage/profile_pictures/person/profile_40.jpg

Now if you write in your browser

http://laravelLocalhostUrl/profile/person

It will show your image, I have made it myself and tested it. screenshot of image served from localhost shown in browser window

Note: I have tried best possible to make the folder reflect your question, but you can easily modify it to fit the way you want.


Install Intervention (skip this part if you already installed it)

Follow this guideline for it: http://image.intervention.io/getting_started/installation

Briefly: php composer require intervention/image

In your config/app the $providers array adds the service providers for this package.

Intervention\Image\ImageServiceProvider::class

Add the facade of this package to the $aliases array.

'Image' => Intervention\Image\Facades\Image::class

The solution inspired by this answer is to protect the image with authentication in general and this answer.

9
  • 1
    Finally a great answer! At the moment I have it set up exactly as you wrote it, but I was interested in the least-intensive method (i.e. without using a route). Are you suggesting that it is best to change the .htaccess and handle the request from there?
    – clod986
    Apr 8, 2016 at 10:26
  • @clod986 if you do not want to protect your files and if you have a lot of files with a lot of requests than .htaccess OR symbolic link is way to go. But the route solution works fine as well. Since i do not know your configuration, strategy, and how big your server request on images. I personally use nginx but it should be pretty easy creating new access via htaccess to storage file, just make sure to create a sub folder under storage call it like public2, other wise it is not good give access permission all storage folder. Apr 8, 2016 at 12:46
  • @maytham-ɯɐɥʇʎɐɯ I saw pages that serve a GIF file when it is used in <img src=""> but when the same link is put on the browser, it shows a page with the GIF and additional information. This can be achieved with the method that you described?
    – JCarlosR
    May 26, 2017 at 16:49
  • @maytham-ɯɐɥʇʎɐɯ is it possible to restrict access of files using .htaccess and still able to access it from code? Jul 2, 2019 at 13:32
  • 2
    very thorough answer, even working on laravel 9 here. Sep 16, 2022 at 23:24
20

Using Laravel 5.2+ Image::make($pathToFile)->response() can be considered bad practise. Anyone looking for a solution to serve images from Laravel should use return response() -> file($pathToFile, $headers);. This will produce much less overhead, as it just serves the file instead of "opening it in Photoshop" - at least that's what my CPU monitor says.

Here's the link to documentation

2
  • Thanks for updating with this answer with new info
    – clod986
    Jul 24, 2017 at 16:14
  • 5
    +1. If you remove the 5.4 from your docs link it will redirect to the latest version. Also you might like to add some hints for those variables... I'm using $headers = ['Content-Type' => 'image/png']; and $path = storage_path('app/.../myImage.png'). Dec 15, 2017 at 8:10
3

In your User.php model:

protected $appends = ['avatar'];

public function getAvatarAttribute($size=null)
{
   return storage_path().'/profile_pictures/'.$this->id.'/profile_'.$size.'.jpg';
}

So whenever you call a get a User instance, you'll have his avatar along with it.

5
  • 3
    I already have this implemented, thank you for the tip, but my question is something else entirely. I was wondering if I should serve images over a specific route or not
    – clod986
    Mar 17, 2016 at 16:30
  • Nope, you shouldn't be making additional requests for getting a user profile. The information is already appended to your user instance. You can get it from there. No need for the additional route. Mar 17, 2016 at 16:32
  • 1
    sorry @Jilson but I guess I was not clear enough. Your getAvatarAttribute() returns a url; I already know that. What I don't know, is what is best to do once the user connects to that url: should I create a route to handle it, or not?
    – clod986
    Mar 17, 2016 at 17:03
  • Are you trying to check if the user profile exists or not? Mar 17, 2016 at 17:04
  • No. I'm just trying to find out the best way to return once <img src="url-of-image" /> is requested.
    – clod986
    Mar 17, 2016 at 17:05
3
Response::stream(function() use($fileContent) {
    echo $fileContent;
}, 200, $headers);

https://github.com/laravel/framework/issues/2079#issuecomment-22590551

0

The @zhwei's answer worked fine for me, just changed to work with base64.

return response()->stream(function() use ($data) {
    $im = imagecreatefromstring(base64_decode($data));
    try {
        imagejpeg($im);
    } finally {
        $im && imagedestroy($im);
        $im = null;
    }
}, 200, ['Content-type' => 'image/jpeg']);

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.