Search code examples
phplaravelperformancelaravel-5

How should I serve an image with Laravel?


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?


Solution

  • 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.