Fork me on GitHub

The Blog > Propel Gets Class Table Inheritance, With A Twist

The Propel Team – 22 August 2011

Propel 1.6 already supports Single Table Inheritance and Concrete Table Inheritance, two powerful ways to map an object inheritance to a relational persistence. However, every once in a while, a Propel user pops in and asks for a Propel implementation of Class Table Inheritance. This type of inheritance uses one table per class in the inheritance structure ; each table stores only the columns it doesn't inherits from its parent.

For example, a sports news website displays statistics about various sports player. The Class Table Inheritance patterns translates that to a player table storing the identity, and two "children" tables, footballer and basketballer, with distinct statistics columns.

player
-------
first_name
last_name

footballer
------------
goals_scored
fouls_committed

basketballer
------------
points
field_goals

Implementing Class Table Inheritance via Joins

I have always thought that Class Table Inheritance isn't really inheritance. Actually, it is usually achieved using joins, by defining a foreign key in the children tables to the parent table, as follows:

player
-------
id
first_name
last_name

footballer
------------
id
goals_scored
fouls_committed
player_id       // foreign key to player.id

basketballer
------------
id
points
field_goals
three_points_field_goals
player_id       // foreign key to player.id

So to create a basketballer with an identity, relate a Basketballer to a Player the usual Propel way:

// create a Basketballer
basketballer = new Basketballer();
$basketballer->setPoints(101);
$basketballer->setFieldGoals(47);
$basketballer->setThreePointsFieldGoals(7);
// create a Player
$player = new Player();
$player->setFirstName('Michael');
$player->setLastName('Giordano');
// relate the two objects
$basketballer->setPlayer($player);
// save the two objects
$basketballer->save();

The Delegation Pattern

But this isn't inheritance. What the user expects, with the inheritance concept in mind, is to deal only with a Basketballer instance to manage both the identity and the stats, as follows:

$basketballer = new Basketballer();
$basketballer->setPoints(101);
$basketballer->setFieldGoals(47);
$basketballer->setThreePointsFieldGoals(7);
// use inheritance to hide join
$basketballer->setFirstName('Michael');
$basketballer->setLastName('Giordano');
// save basketballer and player
$basketballer->save();

Even if the two pieces of code would produce the same result (one basketballer record and one player record), the second one is more object-oriented.

But is it possible to achieve that using the PHP inheritance system? Not really, because the user wants the name information to be store in the player table, not in the basketballer table (otherwise Concrete Table Inheritance would be a better fit). As a matter of fact, the Basketballer object needs the Player object to handle the first name and last name for him. In object-oriented design, this is called "delegation". It's a very common design pattern, for example in Objective-C, where it is used extensively.

In PHP, a usual implementation of the delegation pattern is via the __call() magic method. So in order to make the previous code snippet work, all that's needed is the following code:

class Basketballer extends BaseBasketballer
{
  /**
   * Delegating not found methods to the related Player
   */
  public function __call($method, $params)
  {
    if (is_callable(array('Player', $method))) {
      if (!$delegate = $this->getPlayer()) {
        $delegate = new Player();
        $this->setPlayer($delegate);
      }
      return call_user_func_array(array($delegate, $method), $params);
    }
    return parent::__call($method, $params);
  }
}

And here you go, a Basketballer can reply to the Player method calls, and hide the join used to implement class table inheritance. For the end user, everything happens as if Basketballer actually extended Player, but the Player data is stored in a separate table.

Introducing the delegate behavior

Instead of providing yet another extension system in the Propel ActiveRecord classes, I implemented a behavior, called delegate, which allows to delegate method calls to another model. This behavior generates exactly the __call() code shown above, provided you set up your schema in the following way:

<table name="player">
  <column name="id" type="INTEGER" primaryKey="true" autoIncrement="true"/>
  <column name="first_name" type="VARCHAR" size="100"/>
  <column name="last_name" type="VARCHAR" size="100"/>
</table>
<table name="basketballer">
  <column name="id" required="true" primaryKey="true" autoIncrement="true" type="INTEGER" />
  <column name="points" type="INTEGER" />
  <column name="field_goals" type="INTEGER" />
  <column name="three_points_field_goals" type="INTEGER" />
  <column name="player_id" type="INTEGER" />
  <foreign-key foreignTable="player">
    <reference local="player_id" foreign="id" />
  </foreign-key>
  <behavior name="delegate">
    <parameter name="to" value="player" />
  </behavior>
</table>

Rebuild the model, and the Basketballer can now delegate all the method calls it can't manage on its own to his related Player, whether such a player already exists or not.

The delegate behavior, together with complete documentation and unit tests, has landed in the Propel master yesterday, and will be part of the upcoming 1.6.2 release.

You may think: Why should I be enthusiast about a behavior generating six lines of code in a __call() method? First of all, the delegate behavior has more features than than just simulating Class Table Inheritance. Second of all, it allows you to design your object model with delegation in mind, and that opens a lot of new possibilities.

Multiple Delegation

In PHP, an object can only inherit from one parent. However, delegation isn't restricted to a single class. So the Basketballer class can delegate to both a Player and an Employee class:

<table name="player">
  <column name="id" type="INTEGER" primaryKey="true" autoIncrement="true"/>
  <column name="first_name" type="VARCHAR" size="100"/>
  <column name="last_name" type="VARCHAR" size="100"/>
</table>
<table name="employee">
  <column name="id" type="INTEGER" primaryKey="true" autoIncrement="true"/>
  <column name="salary" type="INTEGER"/>
</table>
<table name="basketballer">
  <column name="id" required="true" primaryKey="true" autoIncrement="true" type="INTEGER" />
  <column name="points" type="INTEGER" />
  <column name="field_goals" type="INTEGER" />
  <column name="three_points_field_goals" type="INTEGER" />
  <column name="player_id" type="INTEGER" />
  <foreign-key foreignTable="player">
    <reference local="player_id" foreign="id" />
  </foreign-key>
  <column name="employee_id" type="INTEGER" />
  <foreign-key foreignTable="employee">
    <reference local="employee_id" foreign="id" />
  </foreign-key>
  <behavior name="delegate">
    <parameter name="to" value="player, employee" />
  </behavior>
</table>

Using only a Basketballer instance, a developer can now populate three records in three different tables:

$basketballer = new Basketballer();
$basketballer->setPoints(101);
$basketballer->setFieldGoals(47);
$basketballer->setThreePointsFieldGoals(7);
// delegate to player
$basketballer->setFirstName('Michael');
$basketballer->setLastName('Giordano');
// delegate to employee
$basketballer->setSalary(2000000);
// save basketballer and player and employee
$basketballer->save();

The liberty to use multiple inheritance might scare you, for it breaks one of the constraints that prevent many developers from designing horrible conceptual data models. However, it makes it possible to support Class Table Inheritance for several levels. For instance, if you modify the class hierarchy to have a ProBasketballer extend Basketballer extend Player, simple delegation doesn't work there. Even if ProBasketballer delegates to Basketballer, the generated ProBasketballer::__call() code won't be able to manage delegating all the way up to Player. The solution is to use multiple delegation to explicitly delegate to all ancestors, as follows:

<table name="player">
  <column name="id" type="INTEGER" primaryKey="true" autoIncrement="true"/>
  <column name="first_name" type="VARCHAR" size="100"/>
  <column name="last_name" type="VARCHAR" size="100"/>
</table>
<table name="basketballer">
  <column name="id" required="true" primaryKey="true" autoIncrement="true" type="INTEGER" />
  <column name="points" type="INTEGER" />
  <column name="field_goals" type="INTEGER" />
  <column name="three_points_field_goals" type="INTEGER" />
  <column name="player_id" type="INTEGER" />
  <foreign-key foreignTable="player">
    <reference local="player_id" foreign="id" />
  </foreign-key>
  <behavior name="delegate">
    <parameter name="to" value="player" />
  </behavior>
</table>
<table name="pro_basketballer">
  <column name="id" required="true" primaryKey="true" autoIncrement="true" type="INTEGER" />
  <column name="salary" type="INTEGER" />
  <column name="basketballer_id" type="INTEGER" />
  <foreign-key foreignTable="basketballer">
    <reference local="basketballer_id" foreign="id" />
  </foreign-key>
  <column name="player_id" type="INTEGER" />
  <foreign-key foreignTable="player">
    <reference local="player_id" foreign="id" />
  </foreign-key>
  <behavior name="delegate">
    <parameter name="to" value="basketballer, player" />
  </behavior>
</table>

Now a ProBasketballer can have a salary, while a simple Basketballer can't.

Delegating The Other Way Around

In all the examples shown previously, the foreign key supporting the delegation relation was located in the table that was actually delegating. This is because the main table (basketballer in the example) must have only one delegate in the other table (player in the example). The model must show a many-to-one relationship, and that places the foreign key in the delegating table.

player
-------
id
first_name
last_name

basketballer    // delegates to player
------------
id
points
field_goals
three_points_field_goals
player_id       // foreign key to player.id

But there is another way to have only one related record. Instead of using a many-to-one relationship, one could use a one-to-one relationship. In Propel, this is achieved by setting a foreign key which is also a primary key. So the player_id column can be removed, and the foreign key be placed on the basketballer primary key.

player
-------
id
first_name
last_name

basketballer   // delegates to player
------------
id             // foreign key to player.id
points
field_goals
three_points_field_goals

Since this kind of model is also suitable for delegation, the delegate behavior has been designed to supports one-to-one relationships as well.

One-to-one relationships are reversible. That means that the foreign key could be placed in the other table. For the player/basketballer model, that would mean:

player
-------
id             // foreign key to basketballer.id
first_name
last_name

basketballer   // delegates to player
------------
id
points
field_goals
three_points_field_goals

This is still supported by the behavior. But such a setup creates one constraint: a player can't have both basketballer and footballer stats anymore. In this case, it's not such a good idea. But think about this other use case:

user_profile
------------
id             // foreign key to user.id
first_name
last_name
email
telephone

user           // delegates to user_profile
-------
id
login
password

This schema may sound familiar to users of the sfGuardPlugin for the symfony framework. In this plugin, the User class handles only the basic identification data for a user. All the other information, like email address or full identity, is "delegated" to another class, the UserProfile. It is not a use case for Single Table Inheritance, but it's a great one for delegation.

Using the delegate behavior, Propel can now give access to the profile information directly from the user class:

<table name="user">
  <column name="id" type="INTEGER" primaryKey="true" autoIncrement="true"/>
  <column name="login" type="VARCHAR" size="100"/>
  <column name="password" type="VARCHAR" size="100"/>
  <behavior name="delegate">
    <parameter name="to" value="user_profile" />
  </behavior>
</table>
<table name="user_profile">
  <column name="id" type="INTEGER" primaryKey="true"/>
  <column name="first_name" type="VARCHAR" size="100"/>
  <column name="last_name" type="VARCHAR" size="100"/>
  <column name="email" type="VARCHAR" size="100"/>
  <column name="telephone" type="VARCHAR" size="100"/>
  <foreign-key foreignTable="user">
    <reference local="id" foreign="id" />
  </foreign-key>
</table>

In PHP, the developer can now write:

$user = new User();
$user->setLogin('francois');
$user->setPassword('S€cr3t');
// Fill the profile via delegation
$user->setEmail('francois@example.com');
$user->setTelephone('202-555-9355');
// save the user and its profile
$user->save();

This is why the concept of delegation is more powerful than Class Table Inheritance. There are a lot of use cases that delegation solves, without even being designed to do so. And this is why the introduction of the delegate behavior in Propel is such a great news.

Found a typo ? Something is wrong in this documentation ? Just fork and edit it !