Jan 18, 2016
Table of contents:
In the modern world of cloud computing, users have a number of different options for storing their files. Services such as Dropbox, Box, and Google Drive offer an easy to use solution for storing files in the cloud.
These services also offer APIs that make it very easy to integrate their service with your application.
If your application allows it’s users to upload files, you should allow the user to upload from multiple locations. These types of integrations are usually fairly simple to set up, but offer flexibility and ease of use for your users.
In today’s tutorial we’re going to be looking at how to set up the functionality to allow uploads from multiple locations.
Before we jump into the code, first I will explain how this is going to work.
Lets imagine we are building a project management application that allows the user to upload attachments for a given project or task.
When the user wants to upload a file, we will give them a number of options.
Firstly, the user can choose to upload a new file, or select a file that has already been uploaded.
Secondly, we will allow the user to authenticate with a third-party service such as Dropbox, Box, or Google Drive and select a file that they want to add to the project or task.
When we accept this request, we need to deal with these difference types of file upload.
We will grab a copy of the file, save it to our own cloud storage, and then attach the file to the project or the task.
The important bit that we will be focusing on today will be the part we we deal with the different upload strategies.
We are going to be dealing with many different implementations of something, but we shouldn’t need to know what particular implementation we are currently dealing with.
This is the perfect scenario to define an interface:
interface Type
{
/**
* Handle the Type and return an Upload
*
* @return Upload
*/
public function handle();
}
Here I’ve defined a single handle()
method on the interface. This means that is doesn’t matter which implementation we have, we will always be able to call the handle()
method.
Next we need to define each implementation we are going to be offering to the user:
class FileId implements Type
{
}
class File implements Type
{
}
class Dropbox implements Type
{
}
class Box implements Type
{
}
class GoogleDrive implements Type
{
}
Each of these classes has it’s own responsibility for accepting the value from the request and returning the Upload
object.
For example, the FileId
implementation will accept a file id, search the database of existing uploads and return the Upload
object.
The File
implementation will accept the file from the request, save it to the cloud storage, save the path to the database, and then return the Upload
object.
The Dropbox
, Box
, and GoogleDrive
implementations will make an API request to the respective service, download the file, upload the file to the cloud storage, save the path to the database, and then return the Upload
object.
Using this pattern means we can deal with each different type of file upload in isolation.
This makes it easy to add or remove implementations in the future, as well as making it easy to test.
Now that we have the implementations created, we can inject them into the class that will be dealing with the request:
/**
* @param array $types
* @return void
*/
public function __construct(array $types)
{
$this->types = $types;
}
This will be a simple associative array that will match the key of the request to the implementation that should deal with it.
Next we can iterate through the array of uploaded files and deal with them individually:
foreach ($data["files"] as $file) {
$type = new $this->types[$file["type"]]($file["value"]);
$upload = $type->handle();
$project->uploads()->save($upload);
}
In today’s tutorial we looked at how to deal with different types of uploads. By defining a class for each different type, we make it easy to deal with the requirements of that type in isolation.
This pattern is known as The Strategy Pattern.
This is particularly important for services such as Dropbox, Box, and Google Drive that require us to make an API request.
Hopefully this was a good illustration of how to deal with many different types of something in a generic way.
You basically just have to find a way to treat them all the same, then it makes it easy to add or remove implementations, and it’s easy to test each one in isolation.