Polymorphic Model Relations in Laravel

Originally published at WillWorkForBanjos.com

The Laravel PHP framework is highly customizable but comes with some really well-built components. One such component is the Eloquent ORM and it's model system. Out of the box it provides simple yet powerful ways to interact with your data in an object oriented way. In fact, the Eloquent ORM is so clever that the only thing necessary in your models are relationship definitions (ie foreign keys for your tables). In Laravel 4 Eloquent added a way to handle polymorphic relationships out of the box, but Laravel 3 didn't have that benefit. Let's have a look at how things have changed by first creating a simple data structure to work with. Say we want to keep track of jobs and each job has various types of costs associated with it for example labor costs, equipment costs, etc. But we want to build things in a way that we can easily add/remove/edit these different types of costs at any time. If we build our application correctly in this way, we shouldn't have to change our existing code if we want to add or remove a type of cost. So let's say we have the following database tables:

Jobs
Job IDContactTitle
1John DoeMain Job
Costs
IDTotal CostCost Type IDCost ID Job
240131
Cost Types
IDType
1labor
2equipment
Labor Costs
IDHoursHourly Rate
3410
Equipment Costs
IDPrice

The most important thing to note about the above database schema is how the entry in the Costs table references entries on the Cost Types and Labor Costs tables. Setting up the relationships in this way enables our application and database to be dynamic and easily modifiable.

Each one of the above tables has a model file in which its relationships (ie foreign keys) would be defined. We're just going to look at the few that change between Laravel 3 and 4 and are involved in the polymorphic capabilities of our models. First we'll look at the Labor Cost model in Laravel 3.

public function cost() {
  return $this->has_many('Costs', 'Cost ID');
}

The above function tells Eloquent that there are many entries on the Costs table which reference Labor Cost entries. The 'Cost ID' tells Eloquent which column is referencing the ID field in the Labor Cost table. Model functions are accessed when you attempt to access an object variable that doesn't exist on the table. Eloquent will check the model functions for one that matches the requested object variable and if found will use it to return a result.

Next up is the Costs model which is where the polymorphic magic happens:

public function cost() {
  $typeID = $this->get_attribute('Cost Type ID');
  if(!Empty($typeID)) {
    $type = CostType::find($typeID);
    $specificCostTable = ucwords($type->title).' Costs';
    return $this->belongs_to($specificCostTable, 'Cost ID');
  } else {
    return NULL;
  }
}

The above function first retrieves the "Cost Type ID" value of the current object then checks to make sure that value is valid. If that value exists then it is used to find retrieve the related Cost Type object. Once the Cost Type object is retrieved, it's title is then used to return the model relationship indicating the correct cost type. While the above code is fairly straightforward and simple, it certainly wasn't obvious and in fact took a good deal of thought and testing to get ironed out. But luckily Laravel 4 has addressed this issue and there is now an easier way to accomplish the same thing.

In Laravel 4 the Cost model now looks like this:

public function cost() {
  return $this->morphTo();
}

As you can see, the difference is immense. Along with this change, the models for our specific costs will change as well. Here's how the Labor Cost model changes:

public function cost() {
  return $this->morphMany('Costs', 'Cost ID');
}

And that's it! As you can see, not only has the newest version of Laravel brought many welcome updates and changes, but even without them the included Eloquent ORM makes even complex relationships a breeze to setup and utilize.

comments powered by Disqus