Camron Cade

Artisan web development, mostly using Laravel.

Dealing with Image Uploads - Laravel 4

Often, you want to allow users of your web application to upload photos. Whether they are avatars, cover photos, background photos, there is not guarantee they will be in the dimensions you need. Plus, you have to deal with storing them on your server, saving the storage address, dimensions and other attributes of the photo to your database along with ensuring that the permissions are in place to access your file system. The good news is that there is an easier way.

Meet Laravel-Stapler

Meet Stapler. Well, actually Laravel-Stapler. The Laravel-optimized version of a popular package called Stapler. It includes the functionality that helps deal with images. With Stapler, you don't have to worry about where or how images are stored -- it does everything for you.

In this post, we're going to walk through adding the ability for our users to upload their organizations logos for one of my projects. To do this we are going to use Laravel-Stapler.

Let's Get Started

Before we can start, we need to install the Laravel-Stapler package. Follow the two-step installation process listed on the github repository.

Setup and CHMOD Folders

By default, Stapler will save images to the folder public/system. We need to create this folder, and give our server permissions to write to it. From the root top folder of your application, run the following in the terminal:

mkdir public/system
chmod 666 public/system

'Organization' Model

Now, we need to begin working on our model. For my project, while it is users that upload images for their organization, the logos belong to the organization. I have an organziation model, and that's what I want to associate the pictures with. The relevent code is below:

use Codesleeve\Stapler\ORM\StaplerableInterface;
use Codesleeve\Stapler\ORM\EloquentTrait;

class Organization extends Eloquent implements StaplerableInterface {
    use SoftDeletingTrait;
    use EloquentTrait;

    protected $table = 'organization';

    protected $guarded = array('id');

    public $timestamps = true;

    public function __construct(array $attributes = array()) {
        $this->hasAttachedFile('logo', [
            'styles' => [
            'prompt' => '380',
            'thumb' => '100'
             ]
        ]);

        parent::__construct($attributes);
    }
}

You'll notice that for each style (prompt, and thumb) I only assigned one dimension. Stapler accepts dimensions, '150x200' for example, but if you only include one of the dimensions, '159' or 'x200', for example, it will automatically resize the image to the specified width or height, whist keeping the aspect-ratio intact.

The values for prompt and thumb above tells Stapler to resize my images to a set width, and to auto-set the height to keep the aspect ratio.

I am using the $guarded attribute to deal with mass-assignment. If you are using $fillable you also need to add the column name for the attachment to that array, in my case it is called logo.

Add Columns to Database Table

Stapler comes with an artisan command helper that will automatically create a migration file to add the appropriate columns to your existing table. In case, I use MySQL and store organizations and their associated attributes in a table called organization. The format for the artisan command is

php artisan stapler:fasten TABLE ATTACHMENT_NAME

In my case, my table is organization and my attachment name is logo. I ran the following artisan command:

php artisan stapler:fasten organization logo

Checking app/database/migrations, I have a new migration file containing:

    public function up()
{   
    Schema::table('organization', function(Blueprint $table) {     

        $table->string("logo_file_name")->nullable();
        $table->integer("logo_file_size")->nullable();
        $table->string("logo_content_type")->nullable();
        $table->timestamp("logo_updated_at")->nullable();

    });

}

including the subsequent and opposite commands for the function down(). A quick php artisan migrate and my the columns are added to the table, and we're good to go.

Create the Upload Form

For my project, I already have an form that allows users to edit their organizations, stylized by Twitter Bootstrap. All I need to do is add the upload form to the organization.edit view. (Don't forget to pass through 'input' => true in Form::open())

The relevent code, using Blade, for the upload form is below:

{{ Form::open(array('files' => true)) }}
{{ Form::label('logo', 'Name') }}
{{ Form::file('logo') }}
{{ Form::submit('Save Changes') }}

Saving The Image to the Database

Now we just need to receive the image data and do something with it. If you read the "quickstart" section on the github repository for Laravel-Stapler, the example mass-assigns the input values.

$org = Organization::create(Input::all()); 

Edit: For applications used by others, I recommend manually setting the input values, but the above method will still perform just fine.

Now, I am only modifying an edit page -- I want users to be able to add the logo after an organization exits and not at time of creation. For my update() function in my controller, I don't want to write values to the my database if they haven't chagned. In regards to the logo that was updated, the following will get the job done.

if ($array['logo'] !== $org->logo)
    $org->logo = $array['logo'];
$org->save();

And, actually, that's it! That's all there is. Images are now automatically uploaded, converted, resized, and saved in the correct location. There's no need to even worry about where they are.

Returning Images

If I want to return the image from that particular organization, I could utilize the following code:

$org = Organization::find(1);
return "<img src='" . $org->logo->url('thumb') . "'/>";

This returns the following HTML:

<img src='/system/Organization/logos/000/000/001/prompt/Swipe Screen.jpg'/>

Remember, I had set two possible attributes in my organization model? Here, I could have put $org->logo->url('prompt') and the larger image would have outputted. Leaving the type blank ($org->logo->url()) would similarly output the unmodified image in its original dimensions.

We can also retreive the path to the image requested. By replacing the url() method with the path() method in the above example the following HTML is outputted:

<img src='/home/vagrant/Sites/telepsychiatry/public/system/Organization/logos/000/000/001/prompt/Swipe Screen.jpg'/>

Important Things to Note

Altering the Styles (Defined in the Model)

Images are resized and organized at the time of upload, not the time of request. This means that any at the time of upload, images are converted to the different dimensions defined in the controller. If you, at a later time, alter the dimensions or add additional image sizes, the current uploaded images will not reflect those changes.

To solve this problem, the artisan command refresh is provided:

php artisan stapler:refresh Organization

You, of course, would replace Organization with the name of the model the images are attached to.

Uploading Images by URL

You can also save remote images, from URLs in place of the upload:

$photo->logo = "http://camroncade.com/logo.jpg";
$photo->save();

Deleting the Uploaded Image

To delete the image, the delete() method is called. In my case, I would run:

$org->logo->delete();

This automatically removes all uploaded files, conversions, modifications, and styles of the image.

As always, if you find any errors, or have any questions, please let me know.

.gitignore

It's probably a good idea to add /public/system to your .gitignore file. You don't want the images and their associated sizings that get uploaded to be synced to github, nor your production server if you get to deploy.