The Laravel PHP Framework: A Walkthrough

5th August 2011

Laravel is the new kid on the block of PHP frameworks, and whilst in its early stages of evolution (the author wrote it, he says, in the early part of 2011) it does promise - and delivers, from what I can tell - an expressive syntax and an elegant (indeed, you might say eloquent) ORM. Laravel is a fully Object Oriented MVC framework which makes extensive use of features new or recent to PHP - you can forget about running it on PHP4. With PHP5.3 compliant namespacing mechanisms as opposed to, for example, Zend Framework's pseudo-namespacing, it's certainly what you might call a modern framework.

Routes

In Laravel you define routes - which, staying true to the RESTful paradigm, are defined not just as URI's, but according to the HTTP operation they are to react to. Thus, instead of defining a route such as /about-us you would define a function to respond to a GET request on that URI, which is done using a closure which looks reassuringly like JavaScript:

'GET /about-us' => function()
{       
    return 'some output';
}

Wild-card parameters allow parameters to be passed via the URI, and indeed you can even restrict these to numeric ID's. So for example, your route definition could look like this: `

'GET /person/(:num)' => function($id)
{       
    $user = User::find($id);
    $view = View::make('user');
    $view->bind('user', $user);
    return $view;
}

Or, you can create a slug using the built-in URL class:

$article->slug = URL::slug($article->title, "-");

Then switch from :num to :any in your route definition:

'GET /article/(:any)' => function($slug)
{       
    // .. find the article identified by slug ...
}

If you think of the routes definitions as being lightweight controllers, you'll quickly see from the examples above where the other components of a fully-fledged MVC architecture come in; the ORM (called "Eloquent") is in action, finding a User entity with the ID provided from the route, then being binded (well, bound) to a View. Moving on from processing GET requests, a process to handle the submission of a form might be defined thus:

'POST /form-submit' => function()
{       
    // do some processing
    Redirect::to('/')->with('status', 'Your form submission has been received');
}

Note the fluent interface in the redirection function, allowing a flash message to be passed along the request chain. Nice syntax. Keeping RESTful, a function could be defined to delete a resource using something like this:

'DELETE /person/(:num)/delete' => function($id)
{       
    // .. delete person resource identified by $id ...
    Redirect::to(/);
}

I keep talking about the RESTfulness because the first thing that struck me is that it looks a great little framework for quickly scaffolding a web service. While there is not currently any content negotiation built in which would make this even easier, it's not difficult to see how this could be extended.

Filters

One of the things I particularly like about Laravel is that your route definitions can also apply filters to your input (for example CSRF protection or input sanitisation) or on their output. There are a couple of filters built in (for CSRF protection and for authentication) and they can be shared amongst routes. Thus, you could restrict access to a route from unauthorised users thus:

'GET /secure' => array('before' => 'auth', 'do' => function() {

In the example above, the filter auth is to be executed before execution of the route logic (i.e., the closure defined in the 'do' element). (The format of the route declaration has changed slightly from the previous, simply because the definition is getting slightly more complex.) The auth filter is defined for you in another closure, this time in the file filters.php:

'auth' => function()
{
    return ( ! Auth::check()) ? Redirect::to_login() : null;
},

This callback highlights another geature of Laravel; the built-in authentication mechanism, which I'll cover later - for now, what about that Redirect::to_login() syntax? This leads nicely onto named routes.

Named Routes

One of the other features of the routes system designed for developer convenience is the option to create a definition like this:

'GET /user/login' => array('name' => 'login', 'do' => function()
{       
    return View::make('user/login');
}), 

Now, with the route to user_login assigned the name login, you can start doing this:

return Redirect::to_login();

However, there is more to Laravel than routing; here are some other features that stood out for me.

The ORM

Laravel has a built-in Object Relational Mapper (ORM) for data persistance, and provides the Model to the router's Controller and the Views. Defining a new model using the Eloquent ORM is as simple as this:

class Post extends Eloquent { }

In this example a new model is defined which, unless specified otherwise, expects to find a table named posts (a pluralised, lowercase version of the model's name) and a primary key named id It's common to include timestamps on creation and updating; Laravel takes care of this out of the box - all you have to do is add columns called created_at and updated_at (both of the MySQL type TIMESTAMP) to your base table, and the following line in the class definition: `

class Post extends Eloquent {
    
    public static $timestamps = true;

Relationships can be modelled easily and intuitively; as is common across Laravel, if you keep to certain naming conventions then configuration is simple:

class Post extends Eloquent {
    
    public function author()
    {
        return $this->has_one('Author');
    }

    public function tags()
    {
        return $this->has_and_belongs_to_many('Tag');
    }

The assumptions here are: * Posts are defined in a table called posts (lower case, pluralised form of the classname) * Authors are defined in a table called authors * Tags are defined in a table called tags * The posts table contains a column called author_id * All tables' primary key columns are called id * A foreign key in the tags table is called post_id

Database Support

In additon to the ORM there is a nice fluent query builder (not dissimilar to Zend, or Drupal 7's new database abstraction layer), and it's database-agnostic; there is support currently for SQLite (the Laravel default), the ubiquitous MySQL or Postgres. Configuration for this, as with other areas, is simple - in fact if you're using SQLite and place your database in the /application/storage/db directory and call it application.sqlite, then it requires no configuration whatsoever. So, for example, you can do this for insertion:

$hits = Db::table('hits');

$hits->insert(
    array(
        'url' => $url,
        'ts' => time(),
        'ip_address' => Request::ip()
    )
);

Or create queries such as:

DB::table('posts')
     ->where('author_id', '=', '123')
     ->where_in('status', array('draft', 'unpublished'))
     ->get();

You can even use what Laravel calls dynamic where methods:

$user = DB::table('users')->where_email_and_password('hello@lukaswhite.com', 'secret');

Simple Configuration

Configuration is in the form of a collection of files in the /application/config directory, each returning an array of named configuration values. Extending the configuration options is as simple as adding a new file. Add a file called facebook.php, for example: `

return array(
    'app_id'    => 'YOUR_APP_ID_HERE',
    'secret'    => 'YOUR_SECRET_HERE',
);

...and you can do something like this from anywhere in your code:

$facebook_config = Config::get('facebook');  // returns an array of options

Or you can be more specific:

$api_key = Config::get('facebook.api_key');

In keeping with Laravel's intention to require the minimum possible configuration, the pre-existing configuration files come pre-loaded with sensible default values, but changing any of them is simple.

An Auth Component

Authentication is largely taken care of for you by the Auth class; provided you adhere to certain assumptions; users are represented by an "Eloquent" model, there is a password and a column called salt (which is generated for you by the framework's built-in hashing utility). The identifying column is configurable however, should you wish to allow people to log in using an email address, for example. There is not, at time of writing, an authorisation / ACL model, but the authentication system provided is probably adequate for most small web applications.

A Data Validation Library

Data validation is neatly done, with a basic set of generic validators - lengths, inclusion (e.g. checkboxes or radio buttons), regular expression matching or acceptance of (like a checkbox being ticked, e.g. acceptance of terms and conditions) - all with the minimum of configuration. If you call a set of validators without telling it what to validate, it will automatically validate the input (e.g. GET / POST parameters). Again, simplistic, but cover most simple cases - and there's nothing to stop you from defining your own.

Text and Inflection Utilities

There are some fairly standard text-related helper functions for operations such as:

  • (English) language processing, such as pluralisation
  • Character or word-based trimming
  • There is even a basic bad-word ("censor") filter There is also some rudimentary internationalisation support, primarily for translatable strings.

Caching and Logging

While developing an application with the framework, you can opt to get an error message and a full stack trace in a nicely presented error page. Additionally, logging to a file is simple - change a configuration value or two and set the log files' directory to be writeable.

Extensibility

The true test of any framework, arguably, is its extensibility - and overriding or extending framework classes is easy. Everything is namespaced with framework classes aliased, thus:

$view = View::make('user/login');

...would, were it not for aliasing, be:

$view = System\View::make('user/login');

The alias.php configuration file defines these aliases as a name/value array, e.g.:

return array(
    'Auth' => 'System\\Auth',
    ...
    'View' => 'System\\View',
);

Therefore should you wish, for example, to start using a custom View across your code, a quick re-jig of your aliases and it's done:

return array(
    'View' => 'My\\View',
);

Conclusions

Even after looking closely at Laravel and building a few little applications with it, I stick by my view that it looks like a tool for building web services - but this is no bad thing. In part it's because some of the things I've become accustomed to for web applications aren't there; managing dependencies (e.g. of Javascript files), layouts, decorators and so on. There are things that are either limited or simple aren't particularly evolved - the form-building functions (which I've not even touched on) are a shadow of those offered by Zend_Form, or Drupal with its Form API, for example - but this is a very new framework and one which will surely evolve. One of the great things about the framework is its learning curve - indeed, I've covered a lot of the basics here and if you work through this post you wouldn't be far off being able to develop a simple application. Configuration is beautifully simple, and whilst certain assumptions can be frustrating at first, if you're building something from scratch then you'll soon appreciate how little you actually have to do to get up and running. The main thing that's not clear nor well documented is the process for extending the functionality; extending the system classes is easy but the packages mechanism is largely undocumented - but packages are starting to appear nonetheless. My view is that the framework would be an excellent introduction to a simple web framework from an O-O minded PHP developer, a promising way to start building a web service and a good way of building a quick prototype for a website or application. Very promising indeed.