Upload Files with PHP

Over 80% of the web runs on PHP, so there must be some pretty solid solutions for handling common tasks like uploading files to a server, right? Yes, then no. Let me explain:

Uploading a file is easy. Just present the user with an HTML form that allows them to use AJAX file upload to submit a POST to your PHP file. You’ll have access to $_FILES, a global array of whatever they ship your way. Of course, this assumes you have Apache or Nginx and PHP configured properly, but that’s a given to get anything done.

Actually building a secure, sane, working project that handles those files once they’ve been sent your way is another story entirely. In the real world, we don’t just upload a file and call it a day. We need to at least check that the uploaded file is an expected file format, within a certain range of file sizes and perhaps dimensions, and many more requirements that seem to grow like the heads of the mythical hydra.

I’ve worked with PHP since 1998, so I’ve solved content management problems countless times with different solutions over the years. For many of those years, and across numerous major version releases of PHP, the de facto standard for processing images has been a PHP module called Imagick. Uploading files quickly shifts from “how do I get these files?” to “what do I do with these files?”, and Imagick becomes a depressingly dominant presence in our lives.

Let’s look at some of the challenges in working with files in PHP, and then I’ll end with a simple recipe for integrating Filestack’s PHP SDK to solve these problems immediately today.

The Problem: DIY File Uploads in PHP are Chaotic

You know about the snowball effect, right? As a snowball rolls down a hill, more and more snow sticks to it until it’s a massive boulder of frozen water barreling downwards at increasingly terrifying speeds. Most PHP projects that attempt to handle user generated content are like that.

A line drawing in 3 panels joking about a fully automated data pipeline being a house of cards
Image source: https://imgs.xkcd.com/comics/data_pipeline.png

So you’ve got somebody on the team that knows how to use the imagick PHP module, and you’re all set to implement file uploading and some basic processing. You know, “just the basics”, right?

Uploading Files Means Handling Files

“Just the basics” for a simple file upload process might look something like this: We receive a file from a user, and we save it to the same hard drive as our web server. Great! We have the file. It’s on our server’s hard drive. But that file is lost forever now. We realize we need to do something with the reference to that file in order to use it again. We could just list the contents of the directory where we stored the file, and grab the results; but which file is it?

As soon as we have more than one file in that directory, we have a decision to make about what belongs where and why. Ok, so let’s add a database to the mix: we’ll store the name of the file with an association to the ID of the user that uploaded it. Now we’re talkin’! A user uploads a file, and we can retrieve that file by name in the future when the user wants to use it. We run things like this for a while and then our server administrator sends us an email Saturday night at 1am with the dreaded all-caps “DISK IS FULL!!!” subject, and we realize several things all at once.

We shouldn’t be storing our users files on our server. We learned that lesson the hard way, and we get an Amazon S3 bucket so we never have to think about storage again. We move all of the files over there, and keeping the same file names our database is still a valid way to retrieve them. By now we have several users uploading files, and we’re starting to get complaints…

“My photos are filling up the whole screen and take forever to load!”, many of our users write. Ok, we decide to change the dimensions we use to limit the size of the photo. Still takes forever for the photos to load, so we set a limit on the size of the files that can even be uploaded. Now users complain that they can’t upload the pictures from their digital cameras, and we’re hearing complaints that many users need to rotate their photos to be properly oriented. We’re still getting complaints about the time it takes to download or view images.

So, it’s time for our project to start processing these user images. We need to keep the original files, but we also want to create resized, optimized images for when we display them on the web. We decide to install Imagick on our server so it can process these images for us, but wait… the images aren’t on that server anymore! The team starts working on a short term fix – all uploads land on the server first, run through processing, and then get shipped over to the S3 bucket. We’ve bumped the RAM and storage on our server several times, increasing our costs and trying to catch up with the extra work by working longer hours.

Uploading images from other parts of the world is getting to be a burden for our users, as the farther away they are from our S3 bucket’s region, the more time it takes to move their files over the many hops in between. We add another bucket in a different region, and have to build the logic to determine where the user is and therefore where the files should be stored. But wait! All files have to go through the one web server that processes the images first, and we’re seeing it fill up faster and faster…

I could go on, but it gets really scary from that point. (I’ll bet you have some horror stories of your own that you could share with me!) I’ve walked into the middle of projects where teams are leaving the office at 1am trying to catch up with the snowball effect as they take “just the basics” live and then fall increasingly far behind the needs of their users as they scramble to achieve a usable, maintainable, sustainable service.

Handling Files Means Configuring and Maintaining Environments

So, point made, right? That’s the wrong way to do things. What’s the right way?

Planning ahead. If you’re expecting your site to accept user generated content (UGC), you need to be approaching the solution with a sustainable, pragmatic strategy. Your users need solid, maintained environments for uploads, downloads, transformations, optimizations. These need to be scalable, reliable, secure, and distributed all around the planet. They need the ingest process to be intelligent, so they don’t have to start uploads over from scratch if there’s a hiccup in the network. They need you to take good care of them.

The Solution: Use Filestack’s PHP SDK

When a project can separate business requirements into a discrete set of goals for developers to prioritize, everyone wins. The heroic struggle to invent, create, and ship value on a regular basis becomes a successful endeavor for the dev team. The more we spend our valuable dev time on business logic, and the less we waste that time on solving lower-layer problems like networking, storage, delivery, and logistics, the more we have something to show for our hard work. Filestack’s SDKs don’t replace the development process; they enable it. The PHP SDK allows you to write just enough code to leverage Filestack’s API, and spend the rest of your dev time on the things that matter to your business. Here, I’ll show you:

Obtain the SDK

Composer dominates the PHP package landscape these days. We’ve been through several iterations over the years, from everything being a PHP Module to systems like PECL and PEAR, but really anything from recent years in the world of PHP will be `composer install`-able.

This is true across many languages today. Package managers are the first step in most development projects, and hey — it’s 2018. TIme to embrace the future! So if you’re not already using Composer, now’s the time to start. Go to https://getcomposer.org and follow the instructions to install. It is possible to keep Composer anywhere on your system, including only within the project folder where you’re working with PHP, but I recommend following the instructions to install it globally for effective frequent use.

Once you have Composer installed, you’re ready to use it to obtain any package created to work with its package management ecosystem. These packages can be found online at https://packagist.org by searching at the top of the page. For example, the first result for “Filestack” is https://packagist.org/packages/filestack/filestack-php and by confirming the GitHub.com source link to be the official SDK from Filestack, you know you’ve found the right one. No downloading is necessary; just a good way to read more.

The right way to get a Composer package is to run the following command on your terminal:

$ composer require --prefer-dist filestack/filestack-php

Now, this doesn’t actually pull anything down to your computer. What it does is either generate a composer.json file (if one does not yet exist), or update the existing composer.json file, with a reference to the packagist package you’re requiring to run your project. Note that we did not specify a version number for the SDK, so it will simply specify whatever is the latest version at that time. Right now, that looks like this:

{
   "require": {
       "filestack/filestack-php": "^1.1"
   }
}

That’s the only thing added by the composer require command recommended above, but you can really go to town adding additional information like authors, license info, etc. If you’re really getting serious about creating a PHP project from scratch, your first step may be to run `composer init` in the empty project directory to kick things off. This will give you an interactive series of questions that allow you to simply type out your answers and generate a more complete composer.json file from the start. After that, you can run the composer require command above. Heck, you could even just edit the text of composer.json to add “filestack/filestack-php”: “^1.1” under the “require” section. What matters is that the file is in the same directory as your project’s root, that it contains correct information to run properly, and that the instructions it contains don’t specify conflicting requirements (though don’t worry–it will tell you with error messages when that happens).

All set with your composer.json file? Good. Now you just run `composer install` within that directory, and watch the magic happen! Composer reads the composer.json file, reaches out to packagist.org for the matching package name and version, and downloads it into a folder simply called vendor.

Use the SDK

Once you have the Filestack PHP SDK, you need your code to be able to use it. If you just create a file like, for example, myTestFile.php, and try to call the API, you’re gonna have a bad time. Unlike some other, more modern language implementations that magically know where to find imported packages (node, for example, will find packages in the node_modules folder if you just require the package by name), PHP needs you to explicitly import the autoload.php file created within its vendor directory by Composer.

That import can be as simple as putting a line at the top of your PHP code like this:

<?php
  require_once 'vendor/autoload.php';

If you’re working in a framework like Zend or CodeIgniter, open up their files and you’ll find something similar. Also, because frameworks handle this sort of thing for you, you normally do not include your own require statement to get the autoload.php file into your script’s scope. See your framework’s documentation for more details; here we’ll just use a single PHP file for our simple example recipe:

A Simple Recipe for PHP File Uploads

Requirements:

  • Composer, PHP’s de facto package manager
  • PHP
  • A Filestack API Key

Create a file to test inside of the directory where you have used composer to obtain the SDK. I’ll call mine `upload.php`.

For our example, a single standalone file, we’ll open the PHP tag and immediately require the autoload.php file:

<?php
  require_once 'vendor/autoload.php';

Include the Filestack SDK by adding it at the top of your file next:

use Filestack\FilestackClient;
use Filestack\Filelink;
use Filestack\FilestackException;

That imports three classes we’ll take advantage of within our code. The “main” one we’re interested in is the FilestackClient, which is a class that expects us to pass in our Filestack API key as we instantiate:

$client = new FilestackClient("APIKEY");

Of course, you’ll substitute your actual API key above. You may be interested in viewing additional setup options in the source repository’s examples folder: https://github.com/filestack/filestack-php/tree/master/examples

We’ll keep things fairly simple and ask the client object to upload a file based on a path:

$filelink = null;
$filepath = './assets/images/elephant.jpg'; // 4000×3000 JPEG image

try {
   $filelink = $client->upload($filepath);
   var_dump($filelink);
} catch (FilestackException $e) {
   echo $e->getMessage();
   echo $e->getCode();
}

The first block just sets up a couple of variables to use quite soon. The path to the picture of the elephant is relative, and of course you must create the same structure and have a file there called elephant.jpg if you paste that in to your code editor. Any file will do, though; we’re just going to pass that path (a String) to the upload method of the client object, and the result will be assigned to the filelink variable.

We wrap our attempt inside of a try/catch block in case any exceptions are thrown, and if they are we have our special FilestackException ready and waiting to catch it and give us the error message and error code.

That’s it! Running this script should upload the file into your Filestack app associated with the APIKEY you’ve passed in. We have successfully “uploaded” a file from our local machine to our Filestack app using PHP, but we’ve kind of abandoned it after that. Let’s wrap things up by using that $filelink->handle to get the metadata from that recent upload:

fields = [];
$metadata = $client->getMetaData($filelink->handle, $fields);
var_dump($metadata);

TL;DR: Run the PHP SDK Inside a Docker Container with a Command

Ok, here’s an even shorter path to try this out on any machine: if you have Docker installed on your system, you can run the test above with the following command:

$ docker run -it –rm -e APIKEY=MYAPIKEYHERE filestack/php:example

This will spin up the example image and put you inside an interactive shell. Just substitute your Filestack API Key in the example command above, and then the example image (an elephant obtained from Google Image Search with the usage rights filter set to reuse with modification) will be uploaded to your very own app automatically. Expect to wait a moment as the container wakes up and runs, then it will automatically upload an elephant image and spit out the var_dump result of the MetaData, then promptly exit leaving you back on your original system command line.

Docker containers are easy to inspect. For example, you can get into that same image by adding `/bin/bash` to the end of the command above, and it will drop you in to a BASH shell inside the /app folder where you can inspect the files with ls, cat, etc.

Right now (using the example command above) it is just doing a var_dump of the object returned from the call you made to the API with the SDK. Of interest is the “handle” string, which is the unique identifier Filestack assigned to your upload when it was received. Try dropping that handle into one of our convenient transformation URLs to view it in your browser and, for example, change its width:

https://cdn.filestackcontent.com/resize=width:480/PASTEYOURFILEHANDLEHERE

This has just been a tiny peek at what you can do with Filestack’s PHP SDK. To learn more, check out https://www.filestack.com/products/file-upload/ and explore our docs for detailed features and implementation instructions.

Read More →