close

Day 4: Mixing and Matching PHP Components with Composer

With the rise of Composer over the last year the PHP ecosystem is changing, to a world where well-tested, framework agnostic packages are taking over from framework specific code.

Why not just use a single framework that does everything? Well, what if you find a new ORM that you prefer, or you decide the templating system is too complicated? It would be much easier to rewrite only that one aspect and not the entire application right?

Another example could be if you’re building an API and want the most lightweight, bare-bones system possible. To prototype the architecture and get output working you can use a lightweight framework combined with an ORM, then go and replace some of the slower sections with raw SQL to speed it up down the road. After-all, nobody has a million users on their first day and management always want more code quicker.

Really whatever it is you are doing, managing your dependencies with Composer is much easier. You can add SimplePie, the Facebook SDK, AWS SDK, etc and have them update minor patches and security fixes without going to check the websites all the time.

Hello World

We’re going to start with a totally empty folder directory, run a few commands and output a hello world response with Slim, then we’re going to jam the Laravel 4 ORM called “Eloquent” in to help us prototype some outputs.

First we need to install Composer:

$ curl -s https://getcomposer.org/installer | php

This will create composer.phar in the directory. To check it is installed and executable run:

$ ./composer.phar about

You can install this tool system-wide by running an extra command:

$ sudo mv composer.phar /usr/bin/composer

Now create a composer.json which for now is as simple as this:

{
    "require": {
        "slim/slim": "2.*"
    }
}

To install the Slim framework now all we have to do is run $ composer install and watch as it goes to work:

Loading composer repositories with package information
Installing dependencies
- Installing slim/slim (2.1.0)
    Downloading: 100%         

Writing lock file
Generating autoload files

The reason somebody might use Slim over a more full-featured framework is that the name is very accurate, Slim is incredibly small:

$ cloc .
    56 text files.
    55 unique files.                              
    9 files ignored.

http://cloc.sourceforge.net v 1.56  T=0.5 s (94.0 files/s, 23812.0 lines/s)
-------------------------------------------------------------------------------
Language                     files          blank        comment           code
-------------------------------------------------------------------------------
PHP                             46            920           4752           6227
YAML                             1              2              0              5
-------------------------------------------------------------------------------
SUM:                            47            922           4752           6232
-------------------------------------------------------------------------------

With this tiny framework in place, we can make the obligatory “Hello World” application. With Slim this can be done in an index.php without the need to set up full MVC.


require 'vendor/autoload.php';

$app = new SlimSlim();

$app->get('/hello/:name', function ($name) {
    echo "Ohai {$name}!";
});

$app->run();

This simply includes the Composer autoloader so that all code is available, fires up a Slim instance then sets a new GET route with a named parameter.

If we’re feeling a little too lazy to upload this, set up a vagrant box or use (X/M/W/L)AMP then we can use PHP 5.4’s built in server to get things started:

$ php -S localhost:8000

Note: If PHP 5.4 is not available on your machine then give phpbrew a try. Think of it like RVM for PHP version installations._

Now you can navigate to http://localhost:8000/hello/timmy and you should see the welcome screen. Winner.

Up until this point we’ve mainly just been walking through the Slim getting started guide. This was not the point. Most of you will either be starting from scratch, or using a pre-existing framework. These steps get everyone onto the same page as we have a “sorta” framework environment going, but it’s close enough to proceedural PHP for the “from scratch” types to know what is going on too. Now we are ready for the next step.

Using IlluminateDatabase

You will see me use Illuminate and Laravel interchangably in this article. Laravel is the framework project in general, Illuminate is the namespace being used to build Laravel 4. This may change before launch, or may not. Who knows – but they are not two different things.

In previous versions of Laravel the Query Builder (Fluent) and ORM (Eloquent) were only available to Laravel itself. Now if you’re not worried of a few lines of boilerplate to set things up then you can use them in any project.

Setting up a connection ourselves is as easy as doing this:

// Connect using the Laravel Database component
$cf = new IlluminateDatabaseConnectorsConnectionFactory;
 
// Make a new connection
$app->db = $cf->make(array(
    'driver'    => 'mysql',
    'host'      => 'localhost',
    'port'      => 3306,
    'database'  => 'example',
    'username'  => 'root',
    'password'  => 'password',
    'prefix'    => '',
    'charset'   => "utf8",
    'collation' => "utf8_unicode_ci",
));

This approach makes it really easy to get access to the Query Builder and Schema system, so you can use the do things like this:

$app->db->table('users')->where('activated', '=', true)->get();

Or even:

$schema = $app->db->getSchemaBuilder();

$schema->dropIfExists('blog');

$schema->create('blog', function($table) { 
    $table->increments('id');
    $table->string('slug', 200)->unique();
    $table->string('title', 200)->unique();
    $table->integer('category_id');
    $table->text('intro');
    $table->text('body');

    $table->index('slug');
    $table->index('category_id');
});

This is one of the biggest benefits for me. That Schema Builder is the nicest I have yet to come across in PHP and being able to use it anywhere is a huge win.

But, how do we work with the Eloquent ORM itself instead of these lower-level systems?

Eloquent Preperation

The connection has already been made above, but when you create an instance of a User model and extend the Eloquent model it will have no idea which connection to use, as the Eloquent Resolver has not been set up. Laravel would of course handle all of this for you in the background, but when we use these components outside of their original home a little more effort does sometime need to go into wrapping them up nicely.

Handily Dan Horrigan has built a nice little wrapper that does this for us, so we’re going to add another line to composer.json and run $ composer update when you’re done:

{
    "require": {
        "slim/slim": "2.*",
        "illuminate/database": "*",
        "dhorrigan/capsule": "*"
    }
}

This is literally one class which wraps the connection process:

// Make a new connection
$app->db = CapsuleDatabaseConnection::make('default', [
    'driver'    => 'mysql',
    'host'      => 'localhost',
    'port'      => 3306,
    'database'  => 'example',
    'username'  => 'root',
    'password'  => 'password',
    'prefix'    => '',
    'charset'   => "utf8",
    'collation' => "utf8_unicode_ci",
], true);

There we called it “default” but it could just have easily been called “dickens” and the last param of true just let’s Eloquent know this is the default connection.

Models

Right, we’re in a “kinda framework” environment but we don’t seem to have any models. Ahh?

Nope. Just make “app/models” and add update your composer.json again:

{
    "require": {
        "slim/slim": "2.*",
        "illuminate/database": "*",
        "dhorrigan/capsule": "*"
    },
    "autoload": {
        "classmap": [
            "app/models"
        ]
    }
}

In that folder create a simple User model:

class User extends IlluminateDatabaseEloquentModel
{
    protected $table = 'users';
}

Run $ composer update and Composer will generate a class map of every class it finds in there, so you can now call the user model in a new method. I’ll show you all of this put together in a new index.php:

require 'vendor/autoload.php';

$app = new SlimSlim();

// Make a new connection
$app->db = CapsuleDatabaseConnection::make('default', [
    'driver'    => 'mysql',
    'host'      => 'localhost',
    'port'      => 3306,
    'database'  => 'example',
    'username'  => 'root',
    'password'  => 'password',
    'prefix'    => '',
    'charset'   => "utf8",
    'collation' => "utf8_unicode_ci",
], true);

$app->get('/users', function () use ($app) {

    $users = User::all();

    $res = $app->response();
    $res['Content-Type'] = 'application/json';
    $res->body($users);
});

$app->run();

This is mostly Slim logic in here to make it spit out JSON but im sure you can see the easy part, where is simply asks for all the Users.

all-users-

Now this is a bit of an issue, as everything is being returned, so lets hide some fields:

class User extends IlluminateDatabaseEloquentModel
{
    protected $table = 'users';

    protected $hidden = ['password', 'password_reset_hash', 'temp_password'];
}

Refreshing the page will show those fields have been removed:

hidden-data-

Relationships

There is not much point using a ORM unless relationships are going to be utilized, so lets look at that text.

This sample database has 3 tables:

  • users
  • groups
  • users_groups

The users_groups join table means that instead of a simple belongsTo or hasMany we are using a belongsToMany, so crack open the User model and enter the following method:

class User extends IlluminateDatabaseEloquentModel
{
    protected $table = 'users';

    protected $hidden = ['password', 'password_reset_hash', 'temp_password'];

    public function groups()
    {
        return $this->belongsToMany('Group', 'users_groups', 'user_id', 'group_id');
    }
}

The first param there “Group” is a new model which needs to be made and again just contains something like:

class Group extends IlluminateDatabaseEloquentModel
{
    protected $table = 'groups';
}

To get this group data you can make a second endpoint in your Slim file which shows a little more information about the user:

$app->get('/users/:username', function ($username) use ($app) {

    $users = User::with('groups')->where('username', '=', $username)->get();

    $res = $app->response();
    $res['Content-Type'] = 'application/json';
    $res->body($users);
});

Again you’ve got 3 lines of “send back JSON please” but you can add some sort of function to handle all this, or extend the controller or whatever. This is not an article on how to build an entire API nicely, just how to get the basics of IlluminateDatabase interaction anywhere.

Using this new route you can see the groups data coming back:

details-page-

There you have it, multiple endpoints returning different data using an kick-ass ORM, that previously would not have been available to you unless you used ALL of Laravel.

Summary

This article has not been “How to build a proper API” or “You should use Slim to do everything”, and that code above is not ideal in many ways. You can make Slim use controllers, namespace your models, add more autoload folders in, etc but that is all beyond the scope of this article.

This article has got you up and running with Eloquent – a kick-ass PSR-2 compatible ORM inside a database agnostic framework.

Using a similar approach you could inject Eloquent into any other framework or codebase, and by adding more lines to that composer.json you easily inject more tools to use in a consistent manner (because they mostly use PSR-0/1/2 you’ll know how to interact with them).