Skip to content


Subversion checkout URL

You can clone with
Download ZIP
Tree: 4866580953
Fetching contributors…

Cannot retrieve contributors at this time

499 lines (417 sloc) 21.52 kB
layout title


Developers often need one model table to extend another model table. Inheritance being an object-oriented notion, it doesn't have a true equivalent in the database world, so this is something an ORM must emulate. Propel offers three types of table inheritance: Single Table Inheritance, which is the most efficient implementations from a SQL and query performance perspective, but is limited to a small number of inherited fields ; Class Table Inheritance, which separates data into several tables and uses joins to fetch complete children entities, and Concrete Table Inheritance, which provides the most features but adds a small overhead on write queries.

Single Table Inheritance

In this implementation, one table is used for all subclasses. This has the implication that your table must have all columns needed by the main class and subclasses. Propel will create stub subclasses.

Let's illustrate this idea with an example. Consider an object model with three classes, Book, Essay, and Comic - the first class being parent of the other two. With single table inheritance, the data of all three classes is stored in one table, named book.

Schema Definition

A table using Single Table Inheritance requires a column to identify which class should be used to represent the table row. Classically, this column is named class_key - but you can choose whatever name fits your taste. The column needs the inheritance="single" attribute to make Propel understand that it's the class key column. Note that this 'key' column must be a real column in the table.

{% highlight xml %}

{% endhighlight %}

Once you rebuild your model, Propel generated all three model classes (Book, Essay, and Comic) and three query classes (BookQuery, EssayQuery, and ComicQuery). The Essay and Comic classes extend the Book class, the EssayQuery and ComicQuery classes extend BookQuery.

An inherited class can extend another inherited class. That mean that you can add a Manga kind of book that extends Comic instead of Book.

If you define an <inheritance> tag with no extends attribute, as in the example above, it must use the table phpName as its class attribute.

Using Inherited Objects

Use inherited objects just like you use regular Propel model objects:

{% highlight php %} <?php $book = new Book(); $book->setTitle('War And Peace'); $book->save(); $essay = new Essay(); $essay->setTitle('On the Duty of Civil Disobedience'); $essay->save(); $comic = new Comic(); $comic->setTitle('Little Nemo In Slumberland'); $comic->save(); {% endhighlight %}

Inherited objects share the same properties and methods by default, but you can add your own logic to each of the generated classes.

Behind the curtain, Propel sets the class_key column based on the model class. So the previous code stores the following rows in the database:

{% highlight text %} id | title | class_key ---|-----------------------------------|---------- 1 | War And Peace | Book 2 | On the Duty of Civil Disobedience | Essay 3 | Little Nemo In Slumberland | Comic {% endhighlight %}

Incidentally, that means that you can add new classes manually, even if they are not defined as <inheritance> tags in the schema.xml:

{% highlight php %} <?php class Novel extends Book { public function __construct() { parent::__construct(); $this->setClassKey('Novel'); } } $novel = new Novel(); $novel->setTitle('Harry Potter'); $novel->save(); {% endhighlight %}

Retrieving Inherited objects

In order to retrieve books, use the Query object of the main class, as you would usually do. Propel will hydrate children objects instead of the parent object when necessary:

{% highlight php %} <?php $books = BookQuery::create()->find(); foreach ($books as $book) { echo get_class($book) . ': ' . $book->getTitle() . "\n"; } // Book: War And Peace // Essay: On the Duty of Civil Disobedience // Comic: Little Nemo In Slumberland // Novel: Harry Potter {% endhighlight %}

If you want to retrieve only objects of a certain class, use the inherited query classes:

{% highlight php %} <?php $comic = ComicQuery::create() ->findOne(); echo get_class($comic) . ': ' . $comic->getTitle() . "\n"; // Comic: Little Nemo In Slumberland {% endhighlight %}

You can override the base peer's getOMClass() to return the classname to use based on more complex logic (or query).

Abstract Entities

If you wish to enforce using subclasses of an entity, you may declare a table "abstract" in your XML data model:

{% highlight xml %}

... {% endhighlight %} That way users will only be able to instantiate `Essay` or `Comic` books, but not `Book`. ## Class Table Inheritance ## Class Table Inheritance uses one table per class in the inheritance structure ; each table stores only the columns it doesn't inherit from its parent. Propel doesn't offer class table inheritance per se, however it provides a behavior called `delegate`, which offers the same functionality. ### Schema Definition ### A sports news website displays stats about various sports player. The object oriented design therefore produces a `Player` model with an identity, and two children classes, `Footballer` and `Basketballer`, with distinct stats columns. Instead of having the children table inherit from the parent table, Propel lets you _delegate_ column handling from the child to the parent table. Here is how it looks like: {% highlight xml %}

{% endhighlight %}

The footballer table doesn't own the identity columns, which are in the player table. However, the Footballer class can delegate these columns to the Player class. In this case, the delegate behavior doesn't alter the schema, it just allows the Footballer and Basketballer classes to proxy method calls to their "parent" Player class.

Delegation only works for a single level. If you have a deep inheritance hierarchy, you will need to delegate to each ancestors in the hierarchy by simulating multiple inheritance (see below).

Delegation in Practice

Using the previous schema, here is how you create a Basketballer and set his stats and identity:

{% highlight php %} <?php $basketballer = new Basketballer(); $basketballer->setPoints(101); $basketballer->setFieldGoals(47); $basketballer->setThreePointsFieldGoals(7); // set player identity via delegation $basketballer->setFirstName('Michael'); $basketballer->setLastName('Giordano'); // same as $player = new Player(); $player->setFirstName('Michael'); $player->setLastName('Giordano'); $basketballer->setPlayer($player);

// save basketballer and player $basketballer->save();

// retrieve delegated data directly from the main object echo $basketballer->getFirstName(); // Michael {% endhighlight %}

The Basketballer object delegates Player methods to its related Player object. If no Player object exists, the delegate behavior creates one and relates it to the current basketballer, so that both objects can be persisted together.

The same happens for Footballer class, which also delegates to Player. And since a Basketballer and a Footballer instance can delegate to the same Player instance, a player can play both soccer and basketball.

Again, delegation isn't really inheritance. The Basketballer class doesn't inherit from Player, but proxies method calls to Player. And since Propel uses __call() magic to achieve this proxying, an IDE won't see the Player methods accessible to a Basketballer instance.

Multiple Inheritance

The delegate behavior allows to delegate to more than one class, effectively simulating multiple inheritance. Here is an example:

{% highlight xml %}

{% endhighlight %}

Now you can access the Person and Team properties directly from the Footballer class:

{% highlight php %} <?php $footballer = new Footballer(); $footballer->setGoalsScored(43); $footballer->setFoulsCommitted(4); $footballer->setThreePointsFieldGoals(7); // set player identity via delegation $footballer->setFirstName('Michael'); $footballer->setLastName('Platino'); // set team name via delegation $footballer->setName('Saint Etienne');

// save footballer and player and team $footballer->save();

// retrieve delegated data directly from the main object echo $footballer->getFirstName(); // Michael {% endhighlight %}

Multiple delegation also allows to implement a deep inheritance hirerarchy. For instance, if your object model contains a ProBasketballer inheriting from Basketballer inheriting from Player, the ProBasketballer should delegate to both Basketballer and Player; delegating to Basketballer only isn't enough.

{% highlight xml %}

{% endhighlight %}

That way, you can call setPoints() as well as setFirstName() directly on ProBasketballer.

The delegate behavior offers much more than just simulating Class Table Inheritance. Discover other use cases and complete reference in the delegate behavior documentation.

Concrete Table Inheritance

Concrete Table Inheritance uses one table for each class in the hierarchy. Each table contains columns for the class and all its ancestors, so any fields in a superclass are duplicated across the tables of the subclasses.

Propel implements Concrete Table Inheritance through a behavior.

Schema Definition

Once again, this is easier to understand through an example. In a Content Management System, content types are often organized in a hierarchy, each subclass adding more fields to the superclass. So let's consider the following schema, where the article and video tables use the same fields as the main content tables, plus additional fields:

{% highlight xml %}

{% endhighlight %}

Since the columns of the main table are copied to the child tables, this schema is a simple implementation of Concrete Table Inheritance. This is something that you can write by hand, but the repetition makes it tedious. Instead, you should let the concrete_inheritance behavior do it for you:

{% highlight xml %}

{% endhighlight %}

The concrete_inheritance behavior copies columns, foreign keys, indices and validators.

Using Inherited Model Classes

For each of the tables in the schema above, Propel generates a Model class:

{% highlight php %} <?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('') $vid->save(); {% endhighlight %}

And since the concrete_inheritance behavior tag defines a parent table, the Article and Video classes extend the Content class (same for the generated Query classes):

{% highlight php %} <?php // methods of the parent model are accessible to the child models class Content extends BaseContent { public function getCategoryName() { return $this->getCategory()->getName(); } } echo $art->getCategoryName(); // 'Movie' echo $vid->getCategoryName(); // 'Movie'

// methods of the parent query are accessible to the child query class ContentQuery extends BaseContentQuery { public function filterByCategoryName($name) { return $this ->useCategoryQuery() ->filterByName($name) ->endUse(); } } $articles = ArticleQuery::create() ->filterByCategoryName('Movie') ->find(); {% endhighlight %}

That makes of Concrete Table Inheritance a powerful way to organize your model logic and to avoid repetition, both in the schema and in the model code.

Data Replication

By default, 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:

{% highlight php %} <?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) {% endhighlight %}

Propel also creates a one-to-one relationship between a object and its parent copy. That's why the schema definition above doesn't define any primary key for the article and video tables: the concrete_inheritance behavior creates the id primary key which is also a foreign key to the parent id column. So once you have a parent object, getting the child object is just one method call away:

{% highlight php %} <?php class Article extends BaseArticle { public function getPreview() { return $this->getContent(); } } class Movie extends BaseMovie { public function getPreview() { return $this->getResourceLink(); } } $conts = ContentQuery::create()->find(); foreach ($conts as $content) { echo $content->getTitle() . "(". $content->getCategoryName() ")/n" if ($content->hasChildObject()) { echo ' ' . $content->getChildObject()->getPreview(), "\n"; } // Avatar Makes Best Opening Weekend in the History (Movie) // With $232.2 million worldwide total, Avatar had one of the best-opening // weekends in the history of cinema. // Avatar Trailer (Movie) // {% endhighlight %}

The hasChildObject() and getChildObject() methods are automatically added by the behavior to the parent class. Behind the curtain, the saved content row has an additional descendant_column field allowing it to use the right model for the job.

You can disable the data replication by setting the copy_data_to_parent parameter to "false". In that case, the concrete_inheritance behavior simply modifies the table at buildtime and does nothing at runtime. Also, with copy_data_to_parent disabled, any primary key copied from the parent table is not turned into a foreign key:

{% highlight xml %}

// results in

{% endhighlight %}

Jump to Line
Something went wrong with that request. Please try again.