Skip to content

Inheritance

Inheritance is an object-oriented concept without a direct database equivalent, so an ORM has to emulate it. Propulsion supports two strategies:

  • Single Table Inheritance — the cheapest at query time, but limited to a small, fixed set of inherited columns.
  • Class Table Inheritance — one table per class, joined together. Propulsion doesn’t implement this directly; the delegate behavior gives you the same result.

Propulsion doesn’t have a dedicated Concrete Table Inheritance feature — a concrete_inheritance behavior did this in 2.x, but it’s gone in 3.0 (see Upgrading from 2.x to 3.0). Model the relationship with a foreign key to the parent table instead, or use single-table inheritance if the columns fit.

One table backs every subclass, so the table needs every column any subclass might use. Propulsion generates a stub subclass for each.

Consider Book, with two subclasses, Essay and Comic. A discriminator column (conventionally class_key, but any name works) needs the inheritance="single" attribute and one <inheritance> child per subclass:

schema.xml
<table name="book">
<column name="id" type="integer" primaryKey="true" autoIncrement="true"/>
<column name="title" type="varchar" size="100"/>
<column name="class_key" type="integer" inheritance="single">
<inheritance key="1" class="Book"/>
<inheritance key="2" class="Essay" extends="Book"/>
<inheritance key="3" class="Comic" extends="Book"/>
</column>
</table>

Rebuilding generates Book, Essay, Comic (each extending Book), and matching BookQuery, EssayQuery, ComicQuery classes (each extending BookQuery). An <inheritance> element can itself extends another inherited class, so a Manga type could extend Comic instead of Book directly.

Use the generated classes exactly like any other model class — Propulsion sets the discriminator column for you:

<?php
$book = new Book();
$book->setTitle('War And Peace');
$book->save();
$essay = new Essay();
$essay->setTitle('On the Duty of Civil Disobedience');
$essay->save();
id | title | class_key
---|-----------------------------------|----------
1 | War And Peace | Book
2 | On the Duty of Civil Disobedience | Essay

Querying the parent class hydrates the correct subclass automatically:

<?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

Query with a subclass’s own Query class (ComicQuery::create()->find()) to restrict results to just that subclass. Mark a table abstract="true" to prevent instantiating the base class directly, forcing callers through a subclass:

<table name="book" abstract="true">
<!-- ... -->
</table>

Propulsion has no dedicated class-table-inheritance feature; the delegate behavior gets you the same result by proxying method calls from a child table to a “parent” table it doesn’t actually inherit from in PHP terms.

schema.xml
<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="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>

Basketballer delegates unknown method calls to its related Player — setting Player columns directly on a Basketballer instance:

<?php
$basketballer = new Basketballer();
$basketballer->setPoints(101);
$basketballer->setFirstName('Michael'); // delegated to a Player instance
$basketballer->setLastName('Giordano');
$basketballer->save(); // saves both the basketballer and the player row

If no Player is already attached, delegate creates one automatically. Delegation is single-level — a deeper hierarchy needs delegation to every ancestor (to="basketballer, player"), simulating multiple inheritance. The delegated methods are real, typed methods on the delegating class, so IDEs and PHPStan resolve them the same as any other generated method.