Recently I watched Misko Hevery's talk "The Clean Code Talks -- Inheritance, Polymorphism, & Testing" and was particulary struck by his polymorphism example. It matched what I had wanted to do on more than one occasion, but had always been in a hurry to "just get it done" and had never taken the time to setup.

We've all had that horrible switch() code where we loop over a result set and route the row into a different action or behavior based on some values in the row, such as the record status. Doing it wrong! is what "they" always say. But how do we do it right?

Enter "Single Table Inheritence"

The code I'm going to show you is similar Mark Smith's http://www.colorfultyping.com/single-table-inheritance-in-laravel-4/, which I'd recommend as a slightly different take on the solution, but hopefully this article will help explain it a in way that will help you understand the basic idea more clearly.

Here is what we are trying to achieve: We would like to cycle through the result set, and for each "row" return a different object that matches specifically the needs of that row, based on some criteria in the record (such as status). In many frameworks the result row that is returned is a simple array, but in Laravel an instance of the model is returned. This actually makes things much easier for us because all we need to do is swap out the default return object with our own.

Backing up a little bit, let's look inside the Laravel Illuminate\Database\Eloquent\Model.php to see what's going on. The function we are most interested in is newFromBuilder($attributes = array()). This is where the return object is created. If you call for a single row, such as using Post::find(1), this is called once. If you are getting a result set with more than one row, you are actually just creating a Collection object which in turn calls this many times, one for each row, and creates an array of objects (models). So this is where we'd like to break in and override it with our own class.

First, time for a bit of code. A simple table and some really terrible sample data.

CREATE TABLE `posts` (
  `id` int(10) unsigned NOT NULL AUTO_INCREMENT,
  `title` varchar(200) NOT NULL,
  `body` text NOT NULL,
  `status` varchar(20) NOT NULL DEFAULT 'unread',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB  DEFAULT CHARSET=utf8;

INSERT INTO `posts` (`id`, `title`, `body`, `status`) VALUES
(1, 'Wow this is great', 'Most amazing thing ever', 'unread'),
(2, 'This is read', 'I read this', 'read');

Also, a couple of simple models. Post is our main model, but we'll also make empty PostRead and PostUnread.

class Post extends Eloquent {
}

class PostRead extends Post {
}

class PostUnread extends Post {
}

In the vein of keeping things simple, we'll just use our routes file instead of a controller and view. I'm going to put the bad code we don't like in below, and we can refactor it.

Route::get('/', function()
{
  $collection = \Post::all();
    $new = $collection->map(function($post)
    {
      switch($post->status)
      {
      case 'read':
         return 'For post title: '.$post->title . ' we would do a read action';
        break;
      case 'unread':
      default:
         return 'For post title: '.$post->title . ' we would do an unread action';
        break;
      }

    });

    var_dump($new);
});

All we are doing is appending a string to the title, based on the status. In real life, we might want to build a out the html differently, or do some internal work, or just about anything. That's precisely where the problem lies - what if we want to do ALL those things? Then we would need to have switch statements all over our code, with each one perhaps doing a different job - or the same code duplicated. The typical solution to this has been to make a helper class and pass each row into it. But what if we could make each row already know how it is supposed to behave, through polymorphism?

Head back the newFromBuilder() function on Model.php

public function newFromBuilder($attributes = array())
{
  $instance = $this->newInstance(array(), true);

  $instance->setRawAttributes((array) $attributes, true);

  return $instance;
}

This is where Laravel builds the instance of the model for each row. newInstance() - called directly above this - does the actual work of creating the instance whihc we then apply the attributes. If you put a var_dump($attributes) in this function, you'll see a stdClass object with each of the fields we are requesting, which just gets "snapped onto" the Post model instance. So this is where we want to do our work. We want to intercept this creation of a new Post model instance and instead create an instance of our inherited class (ie, PostRead or PostUnread).

// Post.php

// override the base Model function
public function newFromBuilder($attributes = array())
{
    $class = 'Post'. ucwords($attributes->status);
    $instance = new $class;
    $instance->exists = true;
    $instance->setRawAttributes((array) $attributes, true);
    return $instance;
}

That's very quick and sloppy (you'll need to handle a default instance, for example, for when the class is not found), but you can see what we are doing - we read the row's status field, and instead of creating a Post instance, we make either a PostRead or PostUnread instance.

So now we are in position to use polymorphism. Recall the earlier code where we used a switch to dynamically change the title string. Instead of that, we will have each class change the string according to its own rule. So change our models to be:

class Post extends Eloquent {
  function change_title()
  {
    return ' we would do nothing';
  }
}

class PostRead extends Post {
  function change_title()
  {
    return ' we would do a read action';
  }
}

class PostUnread extends Post {
  function change_title()
  {
    return ' we would do an unread action';
  }
}

Change our main controller code (in the route for this example) to:

    $new = $collection->map(function($post)
    {
      return 'For post title: '.$post->title . $post->change_title();
    });

Now each row will return either an instance of PostRead or PostUnread, and logic specific to either of those statuses can be added and fired off where needed. It gives us both the advantage of more easily testable classes and nice packages for each bit of business logic.

How might you use this? Technically, anywhere there is a switch statement of complicated if conditions (I agree with Misko that replacing every single if clause is a bit over-doing it). One place I'm considering is in the case of dynamic forms, where each row represents a question for a survey or form, which might be a text field, dropdown, checkbox, etc. Taking all that messy out of your presenter or view, and wrap it in a class of its own. I'm sure you can thing of cases in your projects.

Hope that helped! Definitely take some time to watch Misko's presentation to understand why we are doing this.

Read it Monday, Use it by Friday!

Laravel Quick Tips Weekly Newsletter. Short, immediately helpful bits you'll use in your own codebase before the next one arrives.

Join us now and get a FREE PDF of the first fix months of Laravel Quick Tips!

No Spam, Unsubscribe Anytime

Contact me

Do you freelance?

Avoid the stressful ups and downs!
What Mom Forgot to Tell You about Remote and Freelance Work