Traits for ActiveRecords next TopModel

In this post, two possible implementations of the ActiveRecord design pattern will be discussed. With the raise of Ruby on Rails, ActiveRecord became very popular to the IT crowd. by Sebastian

To put the concepts of this pattern in a nutshell:

ActiceRecord = Data + Business Logic.

An ActiveRecord object maps a row in a database table to an object in your web application (Data). This record is flavoured with object logic and even some domain logic (Business Logic). I won’t repeat too much details as the ActiveRecord pattern is exhaustive discussed elsewhere.

As introduced in Rails, all the object relational mapping code is encapsulated in a very powerful ActiveRecord base class. Following the convention over configuration paradigm, you derive your model from this base class - having all the power of a database at your fingertips.

Implementing the ActiveRecord design pattern in PHP is not very easy. There have been many attempts by various frameworks but none of it has an elegant implementation like in Rails. Flinn Mueller pointed out that because of a lack of PHPs language core features it is impossible to create a such an elegant implementation.

Using the AR pattern typically looks like this:

phpclass Blogentry extends ActiveRecord {

}

$blogentry = new Blogentry;
$blogentry->title = "Pac Man eats my Panties!";
$blogentry->save();
$otherentry = Blogentry::find_by_id(3);
var_dump($otherentry);

Usually the save or find_by_id method is implemented in the ActiveRecord base class. Before we had Late Static Binding (arrived in PHP 5.3) it was almost impossible to have a nice implementation of this pattern. E.g. when you call Blogentry::find_by_id(3), which is implemented in the ActiveRecord base class, it is impossible to derive that the method is statically invoked by the derived Blog entry class.

First I’m going to show you the LSB based implementation:

The magic lies in the get_called_class method - this function returns the class where the function was statically called from.

This is quite elegant but it brings up some problems. As mentioned in Kores rant “Why Active Record Sucks”, deriving your model from an ActiveRecord base class will disturb your code semantics.

To solve this problem I used the very promising Traits patch from Stefan Marr. Traits are like interfaces with code - by using them, you can simulate multiple inheritance (Ruby has a similar concept called mixins). And here comes the beef:

phptrait ActiveRecord {
  static function find_by_id($id) {
    $class = get_class();
    $sql = 'SELECT FROM `' . $class . '` WHERE `id`=\'' . $id . '\';';

    echo "Generate sql for select by id\n';
    echo $sql . "\n';

    return(new $class);
  }

  function save() {
    $class = get_class();

    if(!isset($this->id)) {
      echo "Generate sql for new Record:\n';

      $sql = 'INSERT INTO `' . $class . '` ';

      $sql_fields = '(';
      $sql_values = '(';

      foreach($this->fields as $field => $type) {
        if($field != 'id') {
          $sql_fields .= '\`' . $field . '\`,';
          $sql_values .= "'' . $this->{$field} . "',';
        }
      }

      $sql = $sql . substr($sql\_fields, 0, -1) . ') VALUES ' . substr($sql\_values, 0, -1) . ');';

      echo 'sql: ' . $sql . "\n';

      $this->id = 1;
    } else {
      echo "Generate sql for Update:\n';

      $sql = 'UPDATE \`' . $class . '\` SET ';

      foreach($this->fields as $field => $type) {
        if($field != 'id') {
          $sql .= '\`' . $field . '\`=\' . $this->{$field} . '\',';
        }
      }

      $sql = substr($sql, 0, -1) . ';';
      echo 'sql: ' . $sql . "\n';
    }
  }
}
?>

You simply add your functionality to your class by using the trait:

phpclass Blogentry uses ActiveRecord {
  public $fields = array(
    'id' => 'int',
    'title' => 'varchar',
    'text' => 'text',
    'created_on' => 'datetime'
  );
}

I think the implementation with traits is much better than with late static binding because you don’t have to worry about your code semantics and (almost) no need for magic functions (if you dig deeper into AR - say hello to __callStatic).

I hope that the trait feature will soon be added to the PHP Core.

Disclaimer: These examples are just Proof-Of-Concepts there is no database driver implemented yet. Currently we are working with PHP bleeding edge to have a tight implementation of a state of the art web framework. You can check it out at google code - but the current state is far from being stable.

Cofounder of 9elements. Software alchemist since he was a kid. Knee deep in tech with building things in React, Rails or Solidity.