Blog: New In Propel 1.5: Concrete Table Inheritance And A New Behavior. Wait, That's The Same Thing.
Inheritance is very common in the Object-Oriented world, much less in the database world. Yet, being able to extend a model allows for clean code organization and more complex logic. Some RDBMS, like PostgreSQL, offer native inheritance. Starting with Propel 1.5, Propel gives the same ability to every compatible storage (including MySQL, Oracle, SQLite, and MSSQL) through the new concrete_inheritance behavior.
Understanding Inheritance Design Patterns
Propel has offered Single Table Inheritance for a long time. This feature allows several model classes to extend one another, but all the records are persisted in a single table. For complex inheritance patterns, this leads to tables with a lot of columns - and a lot of NULL values in the records.
Another common strategy for implementing inheritance in an ORM is called Concrete Table Inheritance. In this case, each child class has its own table for storage, and the inheritance adds the columns of the parent class to each of the child classes. It’s this implementation that Propel 1.5 introduces - with a twist.
Tip: If you want to know more about table inheritance and other design patterns, I highly recommend the reading of “Patterns Of Enterprise Application Architecture”, by Martin Fowler.
The Concrete Table Inheritance Behavior
In the following example, the article
and video
tables use this behavior to inherit the columns and foreign keys of their parent table, content
:
<table name="content">
<column name="id" type="INTEGER" primaryKey="true" autoIncrement="true">
<column name="title" type="VARCHAR" size="100">
<column name="category_id" required="false" type="INTEGER">
<foreign-key foreignTable="category" onDelete="cascade">
<reference local="category_id" foreign="id" />
</foreign-key>
</table>
<table name="category">
<column name="id" required="true" primaryKey="true" autoIncrement="true" type="INTEGER" />
<column name="name" type="VARCHAR" size="100" primaryString="true" />
</table>
<table name="article">
<behavior name="concrete_inheritance">
<parameter name="extends" value="content" />
</behavior>
<column name="body" type="VARCHAR" size="100"/>
</table>
<table name="video">
<behavior name="concrete_inheritance">
<parameter name="extends" value="content" />
</behavior>
<column name="resource_link" type="VARCHAR" size="100"/>
</table>
The behavior copies the columns of the parent table to the child tables. That means that the generated Article
and Video
models have a Title
property and a Category
relationship:
<?php
// create a new Category
$cat = new Category();
$cat->setName('Movie');
$cat->save();
// create a new Article
$art = new Article();
$art->setTitle('Avatar Makes Best Opening Weekend in the History');
$art->setCategory($cat);
$art->setContent('With $232.2 million worldwide total, Avatar had one of the best-opening weekends in the history of cinema.');
$art->save();
// create a new Video
$vid = new Video();
$vid->setTitle('Avatar Trailer');
$vid->setCategory($cat);
$vid->setResourceLink('http://www.avatarmovie.com/index.html')
$vid->save();
Model Inheritance
If Propel stopped there, the concrete_inheritance
behavior would only provide a shorcut to avoid repeating tags in the schema. But the behaviors uses PHP’s inheritance system to make the models of the child tables extends the model of the parent table: the Article
and Video
classes actually extend the Content
class:
<?php
class Content extends BaseContent
{
public function getCategoryName()
{
return $this->getCategory()->getName();
}
}
echo $art->getCategoryName(); // 'Movie'
echo $vid->getCategoryName(); // 'Movie'
Imagine how convenient this class extension is to avoid repeating code across similar classes. And contrary to the Single Table Inheritance pattern, the Concrete Table Inheritance scales without problems to dozens of tables with very different columns.
The fact that the behavior system, introduced in Propel 1.4, provides the best implementation for the Concrete Table Inheritance, shows how powerful behaviors can be. Propel keeps getting new features without adding too much complexity to the core.
One More Thing
Usually, ORMs stop the Concrete Table Inheritance implementation there. But not Propel. The concrete_inheritance
behavior does not only copy the table structure, it also copies data.<p /> Every time you save an Article
or a Video
object, Propel saves a copy of the title
and category_id
columns in a Content
object. Consequently, retrieving objects regardless of their child type becomes very easy:
<?php
$conts = ContentQuery::create()->find();
foreach ($conts as $content) {
echo $content->getTitle() . "(". $content->getCategoryName() ")/n";
}
// Avatar Makes Best Opening Weekend in the History (Movie)
// Avatar Trailer (Movie)
The resulting relational model is denormalized - in other terms, data is copied across tables - but the behavior takes care of everything for you. That allows for very effective read queries on complex inheritance structures.
Check out the brand new Inheritance Documentation for more details on using and customizing this behavior.