About Jamie on Software

Jamie on Software is the online journal of web developer and writer Jamie Rumbelow.

Jamie likes books, guitars, programming, open source and food. He writes about these things too. This is where he puts the things he writes.

My Books

Tags
Tweets
Feeds
We Love
Powered by Squarespace
Friday
Jun072013

Rails-like HTML-friendly resourceful routing in Laravel

While Laravel 4's routing engine is certainly powerful, it didn't quite fulfil my desires with resourceful, RESTful routing. By default Laravel's resource routing is geared toward writing APIs - making use of the full range of HTTP verbs - and this isn't very helpful when working with a web browser.

Rather than replicate Rails' resource routing, where the HTTP verb is faked with a _method parameter, I decided to write a small helper function to manually route the basic set of resource routes to a controller.

The function looks like this:

// Rails-like routes
function htmlResource($resources, $controller)
{
    $resource = str_singular($resources);

    Route::get($resources,
                array( 'as' => $resources, 'uses' => $controller . '@getIndex' ));
    Route::get($resources . '/{id}',
                array( 'as' => $resource, 'uses' => $controller . '@getShow' ));
    Route::get($resources . '/{id}/new',
                array( 'as' => 'new_' . $resource, 'uses' => $controller . '@getNew' ));
    Route::post($resources,
                array( 'as' => 'create_' . $resource, 'uses' => $controller . '@postCreate'));
    Route::get($resources . '/{id}/edit',
                array( 'as' => 'edit_' . $resource, 'uses' => $controller . '@getEdit' ));
    Route::post($resources . '/{id}',
                array( 'as' => 'update_' . $resource, 'uses' => $controller . '@postUpdate' ));
    Route::post($resources . '/{id}/delete',
                array( 'as' => 'delete_' . $resource, 'uses' => $controller . '@postDelete' ));
}

...and should be called in your routes file, where the first parameter is the name of the resource and the second parameter the controller. This will route the CRUD actions and name each route too.

One call to this function:

htmlResource('users', 'UserController');

Gives me a php artisan routes output of this:

+-------------------------+-------------+---------------------------------+
| URI                     | Name        | Action                          |
+-------------------------+-------------+---------------------------------+
| GET /users              | users       | UsersController@getIndex        |
| GET /users/{id}         | user        | UsersController@getShow         |
| GET /users/{id}/new     | new_user    | UsersController@getNew          |
| POST /users             | create_user | UsersController@postCreate      |
| GET /users/{id}/edit    | edit_user   | UsersController@getEdit         |
| POST /users/{id}        | update_user | UsersController@postUpdate      |
| POST /users/{id}/delete | delete_user | UsersController@postDelete      |
+-------------------------+-------------+---------------------------------+
Sunday
Jun022013

GROUP_CONCAT for happier, more productive JOINs

I frequently find myself JOINing two tables together for a simple many-to-many relationship. This is usually a relationship between users and groups, or posts and tags, or books and categories: the kind of relationship that you rarely access both ways but need to store as normalised tables nonetheless.

Today, I was writing a small role-based authentication system:

users:
    id
    email
    blah blah blah

roles:
    id
    name
    description

users_roles:
    user_id
    role_id

I want my user objects to have various booleans for their various roles - is_admin, is_staff, things like that - so I'd usually do something like this:

$user = $this->db->where('id', 1)->get('users')->row();
$roles = array( 'admin', 'staff', 'client' );
$user_roles = $this->db->select('roles.name')
                       ->where('roles.id = users_roles.role_id')
                       ->where('users_roles.user_id', $user->id)
                       ->get('roles, users_roles')->result();

foreach ($roles as $role)
{
    $user->{"is_" . $role} = (bool)(isset($user_roles->{$role}));
}

It's not that hideous a solution, but I don't like that extra query and I'm sure I can do better.

Turns out MySQL has a pretty nifty function for things like this where I can include my roles as a JOIN and then concatenate them into a single column. It's called GROUP_CONCAT.

Instead, I can do this:

$user = $this->db->select('users.*, GROUP_CONCAT(groups.name) AS roles')
                 ->where('users.id', 1)
                 ->join('users_groups', 'users.id = users_groups.user_id', 'left')
                 ->join('groups', 'users_groups.group_id = groups.id')
                 ->get('users')
                 ->row();

$user->roles = explode(',', $user->roles);

foreach ($this->roles as $role)
{
    $user->{"is_" . $role} = (bool)(in_array($role, $user->roles));
}

...and now there's no need for the extra query.

With some simple callbacks in MY_Model I can tidy it up even further:

class User_model extends MY_Model
{
    protected $primary_key = "users.id";
    protected $roles = array( 'admin', 'client', 'staff' );

    protected $before_get = array( 'with_roles' );
    protected $after_get = array( 'unpack_roles' );

    protected function with_roles()
    {
        $this->db->select('users.*, GROUP_CONCAT(groups.name) AS roles');

        $this->db->join('users_groups', 'users.id = users_groups.user_id', 'left');
        $this->db->join('groups', 'users_groups.group_id = groups.id');
        $this->db->group_by('users.id');
    }

    protected function unpack_roles($user)
    {
        $user->roles = explode(',', $user->roles);

        foreach ($this->roles as $role)
        {
            $user->{"is_" . $role} = (bool)(in_array($role, $user->roles));
        }

        return $user;
    }
}

Jasper Tandy has reminded me that its return value has a length limit (1024 characters) that sneaks up on you - so be careful, but for small amounts of grouped data like my role example, GROUP_CONCAT seems like a perfect solution.

Sunday
May262013

Memcached vs Redis

I have been debating with one of the developers at LogicPad about the pros and cons of various caching systems and we've got it narrowed down to Memcached and Redis. I've used Memcached a lot in the past, but am now firmly fighting the corner of Redis. I thought I'd summarise my thoughts on the matter here.

The conversation began when we were talking about message queues and using Resque, a Redis-backed message queue. Originally everyone agreed on Memcached for the caching, but after deliberating my opinion has changed.

Here's why:

  • Performance: Redis has amazing performance, and it often out-performs Memcached. There have been a number of tests to prove this, and many others where Memcached only slightly out-performs Redis, and many more where Redis is the clear victor (http://oldblog.antirez.com/post/redis-memcached-benchmark.html).
  • Feature-set: Redis's feature set is much wider than Memcached: you get a bunch of data types (hashes, lists), which Memcached could never support; persistence (very important, else you have to warm up the cache when you restart the server); transactions; complex operations (which means you can offload the processing to the redis server rather than make lots of IO calls to and from it) and pub/sub support. These are all features that will make our lives significantly easier down the road as well as improve our development speed immediately.
  • Reliability: Redis has been used for years and years as the main caching system for GitHub, Twitter, the Guardian, Instagram, Craigslist and Stack Overflow / Stack Exchange, among countless others. It's a tried and tested system. Memcached has obviously been around for longer, and is tried and tested even more so; this point is just to recognise that Redis's relative newness isn't something to worry about.
  • Development / support: Redis is being developed and worked on every day. A quick look at the GitHub repositories shows us the commit activity is vastly different - Redis vs Memcache - and while this isn't a poor reflection on memcached per se, it does show that Redis is a helluva lot more active. Since they're both open source projects this is actually quite a useful metric.
  • One less instance: LogicPad is going to need a message queue... that's inevitable. We can speed up the user experience substantially and save our servers a load of work through this technique and I've found it an indispensable weapon in my arsenal. If we use Resque we can use the same Redis instance (or set of instances) for both caching and queuing, which means less work and less opportunities for things to go wrong.
  • Clients: there are native C PHP clients available for Redis, and most modern caching libraries (including Laravel's standalone cache) support it. The PHP extension is really easy to use and we'll want to use it if we want to take advantage of Redis's more powerful features.
  • Admin system: because Redis gives us the ability to actually look at the datastore and collects lots of information about it all, it allows us to run an internal monitoring system for it (like the sexy Redmon) which can come in very handy when developing or debugging. Memcache is simply unable to do this.

In short, I think anyone would be foolish not to use Redis at this point. It's tried, tested and infinitely more powerful than Memcache, while being just as performant (or even more so in many cases). It's an awesome piece of software, and next to it, Memcached emerges as a trusted friend, but one that's getting old and tired. So I suggest we take Memcached out into the back yard and shoot it, and let Redis into our lives.

 

Thursday
May162013

Death in Samarkand

I just heard this story - relevant to my philsophical studies about free will - and decided it was interesting enough to blog. It's about fate and the will of what is beyond your control.

It's an old Islamic parable called Death in Samarkand:

The disciple of a Sufi of Baghdad was sitting in an inn one day when he heard two figures talking. He realised that one of them was the Angel of Death.

"I have several calls to make in this city," said the Angel to his companion.

The terrified disciple concealed himself until the two had left. To escape Death, he hired the fastest horse he could find and rode day and night to the far distant city of Samarkand.

Meanwhile, Death met the disciple's teacher, and they talked of this and that. "And where is your disciple, so-and-so?" asked Death.

"I suppose he is at home, where he should be, studying," said the Sufi.

"That is surprising," said Death, "for here he is on my list. And I have to collect him tomorrow, in Samarkand, of all places."

Tuesday
Feb122013

Catapult into PyroCMS

Yesterday, Efendi Books released our latest title - Catapult into PyroCMS. PHP guru Phil Sturgeon has written a rather brilliant book about the popular open-source CMS, PyroCMS, that he created and develops.

The content is superb - it's a great introductory guide to a very good CMS, with enough in there for developers and designers to get used to building and developing applications and websites using it. It also looks gorgeous, with some wonderful illustrations by the amazing Jenny Thorne.

Here's some text from the introduction:

In this book - from PyroCMS creator Phil Sturgeon - you’ll learn the core concepts underpinning Pyro, create malleable templates and themes, utilise Streams for powering content-heavy sites and examine the basics of writing addons for the Pyro platform. You’ll learn the theory behind the CMS, how to mould the system around your content and how to use the powerful, designer-friendly template layer.

It's a fantastic title and it's already getting some great reviews, so be sure to grab your copy now!