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

Practical PHP Patterns: Inheritance Mapping

DZone's Guide to

Practical PHP Patterns: Inheritance Mapping

· Web Dev Zone ·
Free Resource

Jumpstart your Angular applications with Indigo.Design, a unified platform for visual design, UX prototyping, code generation, and app development.

So far we have seen the three standard choices to map an inheritance-capable model, like a class hierarchy, into a relational model like a MySQL, Oracle or SQL Server database. While the actual mapping of fields and relationships is based on the particular strategy, there are some common traits and recommendations for inheritance mapping that apply in all the three cases. Just like in the case of every Strategy pattern.

The three strategies, summed up in a phrase, are the following.

  • Single Table Inheritance: use one table, put all the objects instances of classes in the hierarchy in it. This table collapses all different fields but you have only one place where looking for objects.
  • Class Table Inheritance: use a table for every class, and normalize the is-a relationship by creating columns only for the fields which are defined locally and not for inherited ones. Fragment then the objects to follow this normalized model and recompose them when they are needed.
  • Concrete Table Inheritance: use a table for every class, but put every field in it, inherited or not. Put an object in its class's table, but look up objects in multiple tables when doing polymorphic queries like find*() on the root class.

How to use it

Object-relational mapping has a non-trivial implementation, especially when inheritance is involved. Moreover, the three strategies are very different and refactor from one to the other will be difficult at the implementation level. Fortunately, no one codes object-relational mapping strategies by hand nowadays, except for generic ones which can be reused in different application, outsourcing the mapping to a generic ORM. Reuse is a must even if it means limiting the modelling freedom: the complexity of the mapping must be dealt with in one place, the ORM test suite.

Thus you can use a generic ORM which lets you define metadata on your object model, like Doctrine 2: it has two implementations ready for usage (Single Table Inheritance and Class Table Inheritance). Usually annotations are fine for defining mapping strategies: define inheritance-related metadata on the root class to minimize coupling between the classes, and generic metadata like column types in all the subclasses as well.

When to use it

A question you should ask to yourself and your team is: do we really need inheritance in this domain model? Find out if it reflects the existing relationships in the domain (like in the classic Employee/Person case), or you only want to reuse methods from existing classes.

Composition is easy to manage with an ORM (Doctrine 2 also lets you use private properties for the sake of encapsulation), and you can write delegate methods which refer the collaborators, even when they are not loaded. The composed object will be replaced by proxies which load themselves automatically when one of their methods is called.

On the other hand, inheritance is tricky, thus there are multiple strategies with different trade offs on speed and duplication of data (and metadata). The relational model has the advantages of its diffusion as a standard, but it has also limits.

Doctrine 2 (and JPA) MappedSuperclass

This bit couldn't fit into the strategy-specific articles, but @MappedSuperclass is a standard JPA annotation which has been ported to PHP in the Doctrine 2 implementation. I thought including this explanation would be a bonus.

The purpose of this annotation is is to refine your model without complicating it so much with inheritance strategies. It is similar to Concrete Table Inheritance since every concrete class has its own table with all the columns it needs, but the Mapped Superclass is not an entity, so you cannot do queries by referencing this class (in fact, it should be abstract).

However, you can share mapping metadata between subclasses, so that they are copied at "compile time" (at the creation of the metadata model and thus of the schema) from the Mapped Superclass to its children.

The exclusion of the Mapped Superclass from the entities group is an artificial limitation, employed to eliminate duplication of the metadata but at the same time avoiding introducing another entity when it is not needed; it is useful for sharing common fields like a modified_date or deleted ones (the latter for soft deletion).

This code sample is taken from the official manual: a MappedSuperclass plus a subclass which produces only a single table, the subclass's one.

<?php

/** @MappedSuperclass */
class MappedSuperclassBase
{
/** @Column(type="integer") */
private $mapped1;
/** @Column(type="string") */
private $mapped2;
/**
* @OneToOne(targetEntity="MappedSuperclassRelated1")
* @JoinColumn(name="related1_id", referencedColumnName="id")
*/
private $mappedRelated1;

// ... more fields and methods
}

/** @Entity */
class EntitySubClass extends MappedSuperclassBase
{
/** @Id @Column(type="integer") */
private $id;
/** @Column(type="string") */
private $name;

// ... more fields and methods
}

This PHP classes will be mapped onto this relational model (expressed in DDL):

CREATE TABLE EntitySubClass (mapped1 INTEGER NOT NULL, 
mapped2 TEXT NOT NULL,
id INTEGER NOT NULL,
name TEXT NOT NULL,
related1_id INTEGER DEFAULT NULL,
PRIMARY KEY(id))

If you add other subclasses of MappedSuperclassBase, each of them will have its own table, but you could manage their common metadata from one place, the MappedSuperclassBase source code.

Take a look at an Indigo.Design sample application to learn more about how apps are created with design to code software.

Topics:

Opinions expressed by DZone contributors are their own.

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

{{ parent.tldr }}

{{ parent.urlSource.name }}