16 min read
Next in trending

Laravel 4 API

A Comprehensive Tutorial

Laravel 4 API

A Comprehensive Tutorial


Laravel 4 is a huge step forward for the PHP community. It’s beautifully written, full of features and the community is presently exploding. So today we’re going to look at various aspects of API implementation.

I have spent two full hours getting the code out of a Markdown document and into Medium. Medium really isn’t designed for tutorials such as this, and while much effort has been spent in the pursuit of accuracy; there’s a good chance you could stumble across a curly quote in a code listing. Please make a note and I will fix where needed.
I have also uploaded this code to Github. You need simply follow the configuration instructions in this tutorial, after downloading the source code, and the application should run fine. This assumes, of course, that you know how to do that sort of thing. If not; this shouldn’t be the first place you learn about making PHP applications.
https://github.com/formativ/tutorial-laravel-4-api
If you spot differences between this tutorial and that source code, please raise it here or as a GitHub issue. Your help is greatly appreciated.

Installing Laravel 4

Laravel 4 uses Composer to manage its dependencies. You can install Composer by following the instructions at http://getcomposer.org/
doc/00-intro.md#installation-nix
.

Once you have Composer working, make a new directory or navigation to an existing directory and install Laravel 4 with the following command:

composer create-project laravel/laravel ./ --prefer-dist

If you chose not to install Composer globally (though you really should), then the command you use should resemble the following:

php composer.phar create-project laravel/laravel ./ --prefer-dist

Both of these commands will start the process of installing Laravel 4. There are many dependencies to be sourced and downloaded; so this process may take some time to finish.

Installing Other Dependencies

One of the goals, of this tutorial, is to speed up our prototyping. To that end; we’ll be installing Jeffrey Way’s Laravel 4 Generators. This can be done by amending the composer.json file to including the following dependency:

"way/generators" : "dev-master"
This was extracted from composer.json for brevity.

We’ll also need to add the GeneratorsServiceProvider to our app config:

"Way\Generators\GeneratorsServiceProvider"
This was extracted from app/config/app.php for brevity.

You should now see the generate methods when you run artisan.

Creating Resources With Artisan

Artisan has a few tasks which are helpful in setting up resourceful API endpoints. New controllers can be created with the command:

php artisan controller:make EventController

New migrations can also be created with the command:

php artisan migrate:make CreateEventTable

These are neat shortcuts but they’re a little limited considering our workflow. What would make us even more efficient is if we had a way to also generate models and seeders. Enter Jeffrey Way’s Laravel 4 Generators…

You can learn more about Laravel 4's built-in commands by running php artisan in your console, or at: http://laravel.com/docs.

Creating Resources With Generators

With the generate methods installed; we can now generate controllers, migrations, seeders and models.

Generating Migrations

Let’s begin with the migrations:

php artisan generate:migration create_event_table

This simple command will generate the following migration code:

<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
class CreateEventTable extends Migration {
    /**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::create('event', function(Blueprint $table) {
$table->increments('id');

$table->timestamps();
});
}

/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::drop('event');
}
}
This file should be saved as app/database/migrations/0000_00_00_
000000_create_event_table.php
.

We’ve seen these kinds of migrations before, so there’s not much to say about this one. The generators allow us to take it a step further by providing field names and types:

php artisan generate:migration --fields="name:string, description:text, started_at:timestamp, ended_at:timestamp" create_event_table

This command alters the up() method previously generated:

public function up()
{
Schema::create('event', function(Blueprint $table) {
$table->increments('id');
$table->string('name');
$table->text('description');
$table->timestamp('started_at');
$table->timestamp('ended_at');
$table->timestamps();
});
}
This was extracted from app/database/migrations/0000_000_000_
0000000_create_event_table.php
for brevity.

Similarly, we can create tables for sponsors and event categories:

php artisan generate:migration --fields="name:string, description:text" create_category_table
php artisan generate:migration --fields="name:string, url:string, description:text" create_sponsor_table

These commands generate the following migrations:

<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
class CreateCategoryTable extends Migration {
    /**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::create('category', function(Blueprint $table) {
$table->increments('id');
$table->string('name');
$table->text('description');
$table->timestamps();
});
}

/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::drop('category');
}
}
This file should be saved as app/database/migrations/0000_00_00_
000000_create_category_table.php
.

<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
class CreateSponsorTable extends Migration {
    /**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::create('sponsor', function(Blueprint $table) {
$table->increments('id');
$table->string('name');
$table->string('url');
$table->text('description');
$table->timestamps();
});
}

/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::drop('sponsor');
}
}
This file should be saved as app/database/migrations/0000_00_00_
000000_create_sponsor_table.php
.

The last couple of migrations we need to create are for pivot tables to connect sponsors and categories to events. Pivot tables are common in HABTM (Has And Belongs To Many) relationships, between database entities.

The command for these is just as easy:

php artisan generate:pivot event category
php artisan generate:pivot event sponsor

These commands generate the following migrations:


<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
class PivotCategoryEventTable extends Migration {
    /**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::create('category_event', function(Blueprint $table) {
$table->increments('id');
$table->integer('category_id')->unsigned()->index();
$table->integer('event_id')->unsigned()->index();
$table->foreign('category_id')->references('id')->on('category')->onDelete('cascade');
$table->foreign('event_id')->references('id')->on('event')->onDelete('cascade');
});
}
    /**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::drop('category_event');
}
}
This file should be saved as app/database/migrations/0000_00_00_
000000_pivot_category_event_table.php
.
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
class PivotEventSponsorTable extends Migration {
    /**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::create('event_sponsor', function(Blueprint $table) {
$table->increments('id');
$table->integer('event_id')->unsigned()->index();
$table->integer('sponsor_id')->unsigned()->index();
$table->foreign('event_id')->references('id')->on('event')->onDelete('cascade');
$table->foreign('sponsor_id')->references('id')->on('sponsor')->onDelete('cascade');
});
}
    /**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::drop('event_sponsor');
}
}
This file should be saved as app/database/migrations/0000_00_00_
000000_pivot_event_sponsor_table.php
.
Pay special attention to the names of the tables in the calls to the on() method. I have changed them from plural tables names to singular table names. The templates used to construct these constraints don’t seem to take into account the name of the table on which the constrains are being created.

Apart from the integer fields (which we’ve seen before); these pivot tables also have foreign keys, with constraints. These are common features of relational databases, as they help to maintain referential integrity among database entities.

With all these migration files created; we have only to migrate them to the database with:

php artisan migrate

(This assumes you have already configured the database connection details, in the configuration files.)

Generating Seeders

Seeders are next on our list. These will provide us with starting data; so our API responses don’t look so empty.

php artisan generate:seed Category
php artisan generate:seed Sponsor

These commands will generate stub seeders, and add them to the DatabaseSeeder class. I have gone ahead and customised them to include some data (and better formatting):

<?php
class CategoryTableSeeder
extends Seeder
{
public function run()
{
DB::table(“category”)->truncate();
        $categories = [
[
"name" => "Concert",
"description" => "Music for the masses.",
"created_at" => date("Y-m-d H:i:s"),
"updated_at" => date("Y-m-d H:i:s")
],
[
"name" => "Competition",
"description" => "Prizes galore.",
"created_at" => date("Y-m-d H:i:s"),
"updated_at" => date("Y-m-d H:i:s")
],
[
"name" => "General",
"description" => "Things of interest.",
"created_at" => date("Y-m-d H:i:s"),
"updated_at" => date("Y-m-d H:i:s")
]
];
        DB::table("category")->insert($categories);
}
}
This file should be saved as app/database/seeds/
CategoryTableSeeder.php
.
<?php
class SponsorTableSeeder
extends Seeder
{
public function run()
{
DB::table("sponsor")->truncate();
        $sponsors = [
[
"name" => "ACME",
"description" => "Makers of quality dynomite.",
"url" => "http://www.kersplode.com",
"created_at" => date("Y-m-d H:i:s"),
"updated_at" => date("Y-m-d H:i:s")
],
[
"name" => "Cola Company",
"description" => "Making cola like no other.",
"url" => "http://www.cheerioteeth.com",
"created_at" => date("Y-m-d H:i:s"),
"updated_at" => date("Y-m-d H:i:s")
],
[
"name" => "MacDougles",
"description" => "Super sandwiches.",
"url" => "http://www.imenjoyingit.com",
"created_at" => date("Y-m-d H:i:s"),
"updated_at" => date("Y-m-d H:i:s")
]
];
        DB::table("sponsor")->insert($sponsors);
}
}
This file should be saved as app/database/seeds/SponsorTableSeeder.php.

To get this data into the database, we need to run the seed command:

php artisan db:seed

At this point; we should have the database tables set up, and some test data should be in the category and sponsor tables.

Generating Models

Before we can output data, we need a way to interface with the database. We’re going to use models for this purpose, so we should generate some:

php artisan generate:model Event
php artisan generate:model Category
php artisan generate:model Sponsor

These commands will generate stub models which resemble the following:

<?php
class Event extends Eloquent {
protected $guarded = array();
    public static $rules = array();
}
This file should be saved as app/models/Event.php.

We need to clean these up a bit, and add the relationship data in…


<?php
class Event
extends Eloquent
{
protected $table = "event";

protected $guarded = [
"id",
"created_at",
"updated_at"
];
    public function categories()
{
return $this->belongsToMany("Category", "category_event", "event_id", "category_id");
}
    public function sponsors()
{
return $this->belongsToMany("Sponsor", "event_sponsor", "event_id", "sponsor_id");
}
}
This file should be saved as app/models/Event.php.
<?php
class Category
extends Eloquent
{
protected $table = "category";

protected $guarded = [
"id",
"created_at",
"updated_at"
];
    public function events()
{
return $this->belongsToMany("Event", "category_event", "category_id", "event_id");
}
}
This file should be saved as app/models/Category.php.
<?php
class Sponsor
extends Eloquent
{
protected $table = "sponsor";

protected $guarded = [
"id",
"created_at",
"updated_at"
];
    public function events()
{
return $this->belongsToMany("Event", "event_sponsor", "sponsor_id", "event_id");
}
}
This file should be saved as app/models/Sponsor.php.

As I mentioned before; we’ve gone with a belongsToMany() relationship to connect the entities together. The arguments for each of these is (1) the model name, (2) the pivot table name, (3) the local key and (4) the foreign key.

I refer to them as the local and foreign keys but they are actually both foreign keys on the pivot table. Think of local as the key closest to the model in which the relationship is defined and the foreign as the furthest.
Our models’ $table property should match what’s specified in the migrations and seeders.

The last step in creating our API is to create the client-facing controllers.

Generating Controllers

The API controllers are different from those you might typically see, in a Laravel 4 application. They don’t load views; rather they respond to the requested content type. They don’t typically cater for multiple request types within the same action. They are not concerned with interface; but rather translating and formatting model data.

Creating them is a bit more tricky than the other classes we’ve done so far:

php artisan generate:controller EventController

The command isn’t much different, but the generated file is far from ready:

<?php
class EventController extends BaseController {
    /**
* Display a listing of the resource.
*
* @return Response
*/
public function index()
{
return View::make('events.index');
}
    /**
* Show the form for creating a new resource.
*
* @return Response
*/
public function create()
{
return View::make('events.create');
}
    /**
* Store a newly created resource in storage.
*
* @return Response
*/
public function store()
{
//
}
    /**
* Display the specified resource.
*
* @param int $id
* @return Response
*/
public function show($id)
{
return View::make('events.show');
}
    /**
* Show the form for editing the specified resource.
*
* @param int $id
* @return Response
*/
public function edit($id)
{
return View::make('events.edit');
}
    /**
* Update the specified resource in storage.
*
* @param int $id
* @return Response
*/
public function update($id)
{
//
}
    /**
* Remove the specified resource from storage.
*
* @param int $id
* @return Response
*/
public function destroy($id)
{
//
}
}
This file should be saved as app/controllers/EventController.php.

We’ve not going to be rendering views, so we can remove those statements/actions. We’re also not going to deal just with integer ID values (we’ll get to the alternative shortly).

For now; what we want to do is list events, create them, update them and delete them. Our controller should look similar to the following:

<?php
class EventController
extends BaseController
{
public function index()
{
return Event::all();
}
    public function store()
{
return Event::create([
"name" => Input::get("name"),
"description" => Input::get("description"),
"started_at" => Input::get("started_at"),
"ended_at" => Input::get("ended_at")
]);
}
    public function show($event)
{
return $event;
}
    public function update($event)
{
$event->name = Input::get("name");
$event->description = Input::get("description");
$event->started_at = Input::get("started_at");
$event->ended_at = Input::get("ended_at");
$event->save();
        return $event;
}
    public function destroy($event)
{
$event->delete();
return Response::json(true);
}
}
This file should be saved as app/controllers/EventController.php.

We’ve deleted a bunch of actions and added some simple logic in others. Our controller will list (index) events, show them individually, create (store) them, update them and delete (destroy) them.

We need to return a JSON response differently, for the destroy() action, as boolean return values do not automatically map to JSON responses in the same way as collections and models do.
You can optionally specify the template to be used in the creation of seeders, models and controllers. you do this by providing the --template option with the path to a Mustache template file. Unfortunately migrations do not have this option. If you find yourself using these generators often enough, and wanting to customise the way in which new class are created; you will probably want to take a look at this option (and the template files in vendor/way/generators/src/Way/Generators/Generators/templates).
You can find out more about Jeffrey Way’s Laravel 4 Generators at: https://github.com/JeffreyWay/Laravel-4-Generators.

Binding Models To Routes

Before we can access these; we need to add routes for them:

Route::model("event", "Event");
Route::get("event", [
"as" => "event/index",
"uses" => "EventController@index"
]);
Route::post("event", [
"as" => "event/store",
"uses" => "EventController@store"
]);
Route::get("event/{event}", [
"as" => "event/show",
"uses" => "EventController@show"
]);
Route::put("event/{event}", [
"as" => "event/update",
"uses" => "EventController@update"
]);
Route::delete("event/{event}", [
"as" => "event/destroy",
"uses" => "EventController@destroy"
]);
This was extracted from app/routes.php for brevity.

There are two important things here:

  1. We’re binding a model parameter to the routes. What that means is that we are telling Laravel to look for a specific parameter (in this case event) and treat the value found as an ID. Laravel will attempt to return a model instance if it finds that parameter in a route.
  2. We’re using different request methods to do perform different actions on our events. GET requests retrieve data, POST requests store data. PUT requests update data and DELETE requests delete data. This is called REST (Representational State Transfer).
Laravel 4 does support the PATCH method, though it’s undocumented. Many API’s support/use the PUT method when what they implement is the closer to the PATCH method. That’s a discussion for elsewhere; but just know you could use both without modification to Laravel 4.
You can find out more about Route Model Binding at: http://laravel.com/docs/routing#route-model-binding.

Troubleshooting Aliases

If you go to the index route; you will probably see an error. It might say something like “Call to undefined method Illuminate\Events\
Dispatcher::all()”. This is because there is already a class (or rather an alias) called Event. Event is the name of our model, but instead of calling the all() method on our model; it’s trying to call it on the event disputer class baked into Laravel 4.

I’ve lead us to this error intentionally, to demonstrate how to overcome it if you ever have collisions in your applications. Most everything in Laravel 4 is in a namespace. However, to avoid lots of extra keystrokes; Laravel 4 also offers a configurable list of aliases (in app/config/
app.php
).

In the list of aliases; you will see an entry which looks like this:

'Event' => 'Illuminate\Support\Facades\Event',
This was extracted from app/config/app.php for brevity.

I changed the key of that entry to Events but you can really call it anything you like.

It’s often just easier to change the name of the classes you create than it is to alter the aliases array. There are many Laravel 4 tutorials that will refer to these classes by their alias, so you’ll need to keep a mental log-book of the aliases you’ve changed and where they might be referred to.

Testing Endpoints

It’s not always easy to test REST API’s simply by using the browser. Often you will need to use an application to perform the different request methods. Thankfully modern *nix systems already have the Curl library, which makes these sorts of tests easier.

You can test the index endpoint with the console command:

curl http://dev.tutorial-laravel-4-api/event
Your host (domain) name will differ based on your own setup.

Unless you’ve manually populated the event table, or set up a seeder for it; you should see an empty JSON array. This is a good thing, and also telling of some Laravel 4 magic. Our index() action returns a collection, but it’s converted to JSON output when passed in a place where a Response is expected.

Let’s add a new event:

curl -X POST -d "name=foo&description=a+day+of+foo&started_at=2013-10-03+09:00&ended_at=2013-10-03+12:00" http://dev.tutorial-laravel-4-api:2080/event

..now, when we request the index() action, we should see the new event has been added. We can retrieve this event individually with a request similar to this:

curl http://dev.tutorial-laravel-4-api:2080/event/1

There’s a lot going on here. Remember how we bound the model to a specific parameter name (in app/routes.php)? Well Laravel 4 sees that ID value, matches it to the bound model parameter and fetches the record from the database. If the ID does not match any of the records in the database; Laravel will respond with a 404 error message.

If the record is found; Laravel returns the model representation to the action we specified, and we get a model to work with.

Let’s update this event:

curl -X PUT -d "name=best+foo&description=a+day+of+the+best+foo&started_at=2013-10-03+10:00&ended_at=2013-10-03+13:00" http://dev.tutorial-laravel-4-api:2080/event/1

Notice how all that’s changed is the data and the request type — even though we’re doing something completely different behind the scenes.

Lastly, let’s delete the event:

curl -X DELETE http://dev.tutorial-laravel-4-api:2080/event/1
Feel free to set the same routes and actions up for categories and sponsors. I’ve not covered them here, for the sake of time, but they only differ in terms of the fields.

Authenticating Requests

So far we’ve left the API endpoints unauthenticated. That’s ok for internal use but it would be far more secure if we were to add an authentication layer.

We do this by securing the routes in filtered groups, and checking for valid credentials within the filter:

Route::group(["before" => "auth"], function()
{
// ...routes go here
});
This was extracted from app/routes.php for brevity.
Route::filter("auth", function()
{
// ...get database user
    if (Input::server("token") !== $user->token)
{
App::abort(400, "Invalid token");
}
});
This was extracted from app/filters.php for brevity.

Your choice for authentication mechanisms will greatly affect the logic in your filters. I’ve opted not to go into great detail with regards to how the tokens are generated and users are stored. Ultimately; you can check for token headers, username/password combos or even IP addresses.

What’s important to note here is that we check for this thing (tokens in this case) and if they do not match those stored in user records, we abort the application execution cycle with a 400 error (and message).

You can find out more about filters at: http://laravel.com/docs/routing#route-filters.

Using Accessors And Mutators

There are times when we need to customise how model attributes are stored and retrieved. Laravel 4 lets us do that by providing specially named methods for accessors and mutators:

public function setNameAttribute($value)
{
$clean = preg_replace("/\W/", "", $value);
$this->attributes["name"] = $clean;
}
public function getDescriptionAttribute()
{
return trim($this->attributes["description"]);
}
This was extracted from app/models/Event.php for brevity.

You can catch values, before they hit the database, by creating public set*Attribute() methods. These should transform the $value in some way and commit the change to the internal $attributes array.

You can also catch values, before they are returned, by creating get*Attribute() methods.

In the case of these methods; I am removing all non-word characters from the name value, before it hits the database; and trimming the description before it’s returned by the property accessor. Getters are also called by the toArray() and toJson() methods which transform model instances into either arrays or JSON strings.

You can also add attributes to models by creating accessors and mutators for them, and mentioning them in the $appends property:

protected $appends = ["hasCategories", "hasSponsors"];
public function getHasCategoriesAttribute()
{
$hasCategories = $this->categories()->count() > 0;
return $this->attributes["hasCategories"] = $hasCategories;
}
public function getHasSponsorsAttribute()
{
$hasSponsors = $this->sponsors()->count() > 0;
return $this->attributes["hasSponsors"] = $hasSponsors;
}
This was extracted from app/models/Event.php for brevity.

Here we’ve created two new accessors which check the count for categories and sponsors. We’ve also added those two attributes to the $appends array so they are returned when we list (index) all events or specific (show) events.

You can find out more about attribute accessor and mutators at: http://laravel.com/docs/eloquent#accessors-and-mutators.

Using Cache

Laravel 4 provides a great cache mechanism. It’s configured in the same was as the database:

<?php
return [
"driver" => "memcached",
"memcached" => [
[
"host" => "127.0.0.1",
"port" => 11211,
"weight" => 100
]
],
"prefix" => "laravel"
];
This file should be saved as app/config/cache.php.

I’ve configured my cache to use the Memcached provider. This needs to be running on the specified host (at the specified port) in order for it to work.

Installing and running Memcached are outside the scope of this tutorial. It’s complicated.

No matter the provider you choose to use; the cache methods work the same way:

public function index()
{
return Cache::remember("events", 15, function()
{
return Event::all();
});
}
This was extracted from app/controllers/EventController.php for brevity.

The Cache::remember() method will store the callback return value in cache if it’s not already there. We’ve set it to store the events for 15 minutes.

The primary use for cache is in key/value storage:

public function total()
{
if (($total = Cache::get("events.total")) == null)
{
$total = Event::count();
Cache::put("events.total", $total, 15);
}
    return Response::json((int) $total);
}
This was extracted from app/controllers/EventController.php for brevity.

You can also invoke this cache on Query Builder queries or Eloquent queries:

public function today()
{
return Event::where(DB::raw("DAY(started_at)"), date("d"))
->remember(15)
->get();
}
This was extracted from app/controllers/EventController.php for brevity.

…we just need to remember to add the remember() method before we call the get() or first() methods.

You can find out more about cache at: http://laravel.com/docs/cache.

Conclusion

Laravel 4 is packed with excellent tools to create all manner of API’s. In addition, it also has an excellent ORM, template language, input validator and filter system. And that’s just the tip of the iceberg!

If you found this tutorial helpful, please tell me about it @followchrisp and be sure to recommend it to PHP developers looking to Laravel 4!


This tutorial comes from a book I’m writing. If you like it and want to support future tutorials; please consider buying it. Half of all sales go to Laravel.