Over a million developers have joined DZone.
{{announcement.body}}
{{announcement.title}}

Practical PHP Patterns: Single Table Inheritance

DZone 's Guide to

Practical PHP Patterns: Single Table Inheritance

· Web Dev Zone ·
Free Resource

The idea behind the Single Table Inheritance mapping pattern is to represent an inheritance hierarchy (of entity classes) as a single table, which contains columns for all the fields of the various classes.

Inheritance and rdbms

This pattern is the simplest of the various strategies that can be employed to fit an inheritance-capable model into the relational one, which out of the box does not support it (tables which are children of others have never been seen in MySQL and other dbms.)

These mapping techniques are the same ones used in detailing an entity/relationship model into a logical one which does not understand inheritance. But that's for academics.

Object models promotes inheritance (to the point that it is often overused), and in PHP the extends keyword can be used for subclassing. Note that interface inheritance, even multiple, does not influence object-relational mapping since there is no additional data to store for the ORM when a class implements an interface.

I'm not a fan of inheritance and I favor composition in my models, as the Gang of Four suggests in their original book. However when dealing with applications that mostly shuffle data around (from the storage to the user interface, and the converse) inheritance can be handy if in the domain there are real is-a relationships and subclassing is not introduced only to reuse methods and avoid writing delegation or currying code.

Implementation

The Single Table pattern is characterized, of course, by one table that contains all the fields that the various classes (in the same hierarchy) define. This table is commonly named like the root class of the hierarchy. Not all objects define the whole set of fields available in the table: empty columns are used for values not present in a particular object's state, and the NULL value fills them.

Moreover, the table schema needs an additional field that someway encodes the class name (discriminator column), in order for the ORM to know which class to instantiate when recreating an object from the storage after queries or simple find*() calls.

The discriminator column can contain special codes (enums) or simply the class name of the object, since space is not an issue nowadays. Defining a code for every class is thus an aid for refactoring since it eliminates duplication (you can change the name of the class without updating the code in the schema) and it can be useful for interoperability with other applications that access the relational database directly. Ideally you will never see these class codenames in application code.

Pros & cons

There are some advantages of this pattern over other inheritance mapping strategies. As defined by Fowler:

  • a single table contains every data of the hierarchy, in a cohesive way.
  • There are no SQL joins for recreating a single object, which is not true for some other strategies.
  • Pull Up Field and Push Down Field refactorings do not require change to the schema, which may result in data migration scripts when pushing the update in production.

Of course this pattern is a trade-off, and presents also disadvantages:

  • not all the columns are relevant for a particular row (conceptual waste in the relational schema).
  • There is unused space due to all the NULL values in non-relevant columns (space waste).
  • The table can get very large in deep hierarchies (this can be a sign of refactoring needed to avoid deep hierarchies.)

So when we should use this pattern?

Usually when there is a stable hierarchy of classes, and there are no frequent additions of subclasses in the domain model. Adding a subclass results in every supplemental field added to the schema of the single table.

Another use case is in case that high performance is required. Other strategies may require different tables to store fields of the same object, even when it does not have relationships.

And when we should not use this pattern?

When we want to provide entities as subclasses of a class from another package. The mapping information are kept on the uppermost superclass, so if it comes from a vendor package and you subclass it you can't modify the mapping metadata. The trade-off is that you can provide all the info in one place, but then can't add a class to the hierarchy without modifying another one (the root). This kind of strong coupling defeats some of the purpose of inheritance from a modelling point of view, but we can argue that mapping metadata is part of infrastructure and not of the model itself.

You should avoid this pattern also when there are very different fieldsets in classes in the same hierarchy, that render the single table large in columns count and very clumsy.

Examples

Support for inheritance has been a killer feature of the Doctrine ORM in its version 1. In version 2.x, it is implemented with respect to the JPA specification (borrowed from Java).

In this article I'll provide a bit of sample code in which the metadata are defined with annotations. Here @DiscriminatorMap defines an associative map of enumerative values that point to class names, which is the primary point of reference to subclasses in the root one.

As you can see, all the metadata are on the root class, and there is no specific metadata on inheritance strategies on subclasses (they only need generic metadata like @Column, @OneToOne annotations.) This way, class-specific metadata like column types are still kept on the relevant class, even if the informations on the whole hierarchy are on the root, in the only place that makes sense.

<?php
namespace MyProject\Model;

/**
* Example taken from the manual.
* @Entity
* Define Single Table Inheritance as the mapping strategy for this whole hierarchy:
* @InheritanceType("SINGLE_TABLE")
* Define the discriminator column:
* @DiscriminatorColumn(name="discr", type="string")
* Define a map of keys (which will be repeated in the various rows) and values (class names):
* @DiscriminatorMap({"person" = "Person", "employee" = "Employee"})
*/
class Person
{
// ...
}

/**
* @Entity
* Define annotations as if Employee does not extend anything.
*/
class Employee extends Person
{
// ...
}
Topics:

Opinions expressed by DZone contributors are their own.

{{ parent.title || parent.header.title}}

{{ parent.tldr }}

{{ parent.urlSource.name }}