Uploading Files to S3 in PHP

Last Updated: 15 July 2014

file upload php s3 static assets

Table of Contents

If your application needs to receive files uploaded by users, you need to make sure these uploads are stored in a central and durable location.

With Heroku’s ephemeral filesystem, any information written to a dyno’s filesystem will be lost when the dyno is restarted. Instead, Heroku recommends backing services. For file and media storage, Amazon’s Simple Storage Service (S3) is a great solution.

This article demonstrates how to set up a PHP application to use S3 for storing file uploads.

S3 setup

To follow this example, we’re assuming that you already have an Amazon Web Services (AWS) account set up, and you’re in possession of an AWS Access Key pair (access key ID and secret access key), see How do I Get Security Credentials? in the AWS documentation.

You’ll also need to create a new S3 bucket using your account, or re-use an existing one.

Read our Using AWS S3 to Store Static Assets and File Uploads guide for more information; it contains detailed background knowledge, step-by-step instructions for getting started with S3, and useful tips and tricks.

Application setup

If you haven’t created a Heroku application and Git repository yet, do so first:

$ mkdir heroku-s3-example
$ cd heroku-s3-example
$ git init
$ heroku create

Using the AWS SDK for PHP

The easiest way to install the AWS SDK for PHP is using Composer. The composer require command is the easiest way (alternatively, you can add the aws/aws-sdk-php package to composer.json by hand):

$ composer require aws/aws-sdk-php:~2.6

If you haven’t already done so, now is a good time to add the vendor/ directory to your .gitingore; you only want your composer.json and composer.lock, but not the vendor/ directory, under version control:

$ echo "vendor" >> .gitignore

Now you can commit everything:

$ git add composer.* .gitignore
$ git commit -m "use aws/aws-sdk-php"

Setting application config vars

Hard-coding database connection information, security credentials, or other runtime settings is not a good idea. Instead, you should store such information in an application’s environment and read it from there at runtime.

Any config var you set on your Heroku application will be available at runtime using getenv() or in $_ENV/$_SERVER. We will use this mechanism to dynamically read the AWS security keys (key ID and secret key) and the S3 bucket name in our code; all you need to do is set these three bits information (the code below uses “aaa”, “bbb” and “ccc” placeholders) as config vars using the Heroku toolbelt:

$ heroku config:set AWS_ACCESS_KEY_ID=aaa AWS_SECRET_ACCESS_KEY=bbb S3_BUCKET=ccc

All that’s missing now is some code to handle a file upload!

Handling file uploads

Next, we’ll build a very simple script that accepts a file to upload in the browser, and stores it on S3 under the same name it had on the client’s computer.

This is a very simple example without proper validation and using verbatim file names from the client. Make sure you always validate the file’s name and contents e.g. using fileinfo, and generate a custom filename or use another method of ensuring you’re not overwriting an existing file. You may also wish to put file type and size limits in place.

Put the following into a file named index.php:

<?php
require('vendor/autoload.php');
// this will simply read AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY from env vars
$s3 = Aws\S3\S3Client::factory();
$bucket = getenv('S3_BUCKET')?: die('No "S3_BUCKET" config var in found in env!');
?>
<html>
    <head><meta charset="UTF-8"></head>
    <body>
        <h1>S3 upload example</h1>
<?php
if($_SERVER['REQUEST_METHOD'] == 'POST' && isset($_FILES['userfile']) && $_FILES['userfile']['error'] == UPLOAD_ERR_OK && is_uploaded_file($_FILES['userfile']['tmp_name'])) {
    // FIXME: add more validation, e.g. using ext/fileinfo
    try {
        // FIXME: do not use 'name' for upload (that's the original filename from the user's computer)
        $upload = $s3->upload($bucket, $_FILES['userfile']['name'], fopen($_FILES['userfile']['tmp_name'], 'rb'), 'public-read');
?>
        <p>Upload <a href="<?=htmlspecialchars($upload->get('ObjectURL'))?>">successful</a> :)</p>
<?php } catch(Exception $e) { ?>
        <p>Upload error :(</p>
<?php } } ?>
        <h2>Upload a file</h2>
        <form enctype="multipart/form-data" action="<?=$_SERVER['PHP_SELF']?>" method="POST">
            <input name="userfile" type="file"><input type="submit" value="Upload">
        </form>
    </body>
</html>

The example sets the ACL for the uploaded file so it’s publicly readable. If you want different permissions, adjust the example accordingly. You can also use a bucket policy to define what the default permissions for uploaded files in a bucket should be.

You can now add, commit and push this to Heroku:

$ git add index.php
$ git commit -m "file upload test form"
$ git push heroku master

After the deploy has finished, you can run heroku open or manually point your browser to your application to test it. Select a file from your computer (e.g. an image or text file) and upload it. If all went well, a success message will appear with a link to the uploaded file.