Building a Laravel 4 RESTful API - Part 3
In the last article in this series we discussed using the Laravel response model, and creating a code library in Laravel.
In this article I want to talk about Depedency Injection and how this will help us create version control with out creating a ton of Controllers.
In this article we'll cover:
- IoC Containers
- Depedency Injection
- Version Control
The first thing we need to understand is how Inversion of Control (IoC) Containers work in Laravel 4. There are a few places where you can put your IoC Code, but I've found the app->bootstrap->start.php file as good of place as any.
In Laravel 4, the entire framework is basically designed around IoC, and dependency injection. So bolting on new depedency injection is very simple. In the start.php file that I mentioned in the previous paragraph, we add some simple code:
App::bind('NameSpace\Location\NameOfInterface', function(){
});
You'll notice the above code is a closure, which means we can wrap some closure specific code inside. For simple Depedency Injection we can simply return the NameSpace location for the Class that implements the Interface that we want to use.
So in our Snacks example we could have something like:
App:bind('API\Snacks\SnackInterface',function(){
retrn API\Snacks\v1\SnacksAPI;
});
Basically what we have created is anywhere we create an object based on the SnackInterface we will recieve the v1 snacksAPI class.
The obvious reason why this is handy is that you could have multiple SnacksAPI's that implement the same interface. You can now write your code to work against this interface, and easily swap the class in and out, and the code in the controller will continue to work as expected. This is the premise behind the version control aspect.
Now in your controller, you can do the following code:
public function __construct(SnackInterface $api)
{
$this->api = $api;
}
Now in the rest of your code you can access $this->api to access your v1 SnacksAPI class. If you were to swap it out to v2 at a later date, the controller wouldn't need to modified, only the binding as long as v2 matched the same interface as v1.
Now I know what you're thinking. Great, but I have to update this manually everytime I make a change to the API?
Well, you could, or you could do something a bit more complex.
In my API I have a lot of resources that need to be accessed but I've designed them all to use the same basic CRUD interface. While each Resources API class needs some specific functionality to make it work with that resource, and it's relationships etc, they all use the same APIInterface. Ok so if all my resources are using the same API Interface, couldn't we some how make the App:bind
statement against one Interface, and write some code to detect which resource is actually being called and which version?
Well, I'm glad you asked, and the answer is yes, yes you can. And here's how I did it.
I created an API factory. It's basically a class that's sole purpose is to return a new instance of the class we want to inject into our controller.
But before we write that, we need to also create a config file.
Our config file needs to basically consist of all of the resource API's we have and their current version. It's important that the Configfile matches the naming convention of how you named the actual API classes.
//api configuration
return array(
'Snacks' => array(
'currentVersion' => 'v1'
)
);
Now when we build our API Factory, we will check this config file to determine what the current version is and use that to determine if the requested version exists, if not return the current version. This way if someone requests a v3 of the API, instead of getting a whole bunch of errors, you instead give them what ever your current version is, like v1.
The factory then will accept two variables, one the name of the API you want, again this needs to match the Class name, and the version you want.
You might end up with something like this
Class Factory {
const NS = "API\\";
Public static function getInstance($api, $version ='current'){
if($version == 'current'){
$apiConfig = \Config::get('api');
$version = $apiConfig[$api]['currentVersion'];
}else {
$api= \Config::get('api');
$arr1 = str_split($version);
$arr2 = str_split($apiConfig[$api]]['currentVersion']);
if((int) $arr1[1] > (int) $arr2[1]){
$version = $apiConfig[$api]['currentVersion'];
}
}
$apiClass = self::NS.$api."\\".$version."\\".$api.'API';
return new $apiClass();
}
}
As we described above this is simply taking the name fo the API we want to inject, and the version we're looking for, and returns a new instance of the class. So what does this look like in our App::bind
?
App::bind('API\Resource\v1\SnackInterface', function(){
return API\Factory::getInstance('Snacks', 'v1');
});
This binding will pass in the name of the API we want, and the version, and return a new instance of that API class that will now be available to the controller with out changing any controller code.
Ok so we have this working in a very simplistic setup. We have one App:bind
for each resource, and each version. That seems like a lot of code. Let's see if we can simplify this code a bit more.
Let's pop open our Routes file and make sure we have some routes setup for catching the version of the API in the url.
I have this setup as a route group.
Route::group(array('prefix'=>'api/{v1}'), function()
{
Route::resource('snacks', 'SnacksController');
});
This will capture any v1 number regardless of it's 1 or 20 and pass it to the below controller. This reduces the amount of work we have to do at the controller level. We have one Controller to rule them all!
Ok so now we have the controller being routed correctly. But what about that App::bind
section? Let's go back and look at that.
The simplist way I was able to get this to work was basically by parsing the URL. Not the most eloquent way, but wasn't sure of a better way, if someone has some suggestions, let me know in the comments below.
Here's an example of my App:bind
method in the start.php file.
if(isset($_SERVER['REQUEST_URI'])){
$uri = explode('/',$_SERVER['REQUEST_URI']);
}
App::bind('API\Resource\\'.$uri[2].'\\ResourceAPIInterface', function() use ($uri){
$resource = ucfirst($uri[3]);
$version = $uri[2];
return API\Factory::getInstance($resource, $version);
});
Ok so let's recap real quick. We have created a Factory Class that we use to retrieve a new instance of an API Class. We created a config file that defines which version each Resource is currently at to avoid getting versioning errors.
We have created an App::bind
so we can do dependency injection of our API class in our controller. This way our code doesn't need to change at the controller level as we load different versions of our API Class into it.
We then created a Route that captures v#'s and passes it to the same controller regardless of what version was requested.
We then use our Factory in our App::bind
in conjunction with parsing the $url to determine which resource is being requested, and which version. We pass that information into our Factory, and out pops the correct API class to inject into our controller.
So we have version control with one controller, using our API Classes we created in the last article when we setup our code library.
Now we can have a url that looks like https://yourdomain.com/api/v1/snacks/
https://yourdomain.com/api/v2/snacks/
and your snacks resource controller will get called, and injected with the correct version of your SnacksAPI class that does all the heavy lifting.
Hopefully that was clear! If not feel free to ask questions in the comments below.