Practical PHP Patterns: Class Table Inheritance
Join the DZone community and get the full member experience.
Join For FreeIn the previous article we have stressed that relational databases do not support inheritance, so we have to make a mandatory translation to fit an object model which involves subclasses in them.
One of the strategies employed for this job is Single Table Inheritance, which prescribes a single table for a every tree of classes; a different strategy is Class Table Inheritance. But this pattern is quite the opposite of Single Table Inheritance: it prescribes to create a table for every class in the hierarchy to map it into the relational database.
In this pattern every class has its table, following the natural mapping that is already used in an ORM for the dichotomy table-class, which is the simplest translation of an object model.
However, only the fields defined in a class are created as columns, while inherited fields reside in the table representing the class where they are originally defined (usually as protected properties). This means that an object's data will be spreaded among multiple tables, depending where its fields were inherited from.
Different tables can share the primary key value for their rows so that an object can be recomposed by joining them. An alternative, which also involves JOINs, is putting a foreign key that points to the parent table on every table of the hierarchy.
In either case, recomposing an object can be a performance problem.
Multiple tables
The reconstitution of an object usually needs to perform a join between all the tables that contain columns of interest (a number which can range from two to infinite.) When this process does not involve JOINs, it requires multiple queries, a solution which we strive to avoid to keep the work in the database side and avoid chatty communication.
Another problem in the implementation of this pattern is understanding in what table to look when doing queries. The metadata should be introspected to find out how the WHERE conditions should be constructed, and all tables must be included in the SELECT to correctly rebuild the object with all its fields.
Inserting a new object or updating its state also requires multiple queries, but this is not as frequent as the SELECT operation, thus it is a secondary issue.
Pros & cons
The pros of this pattern are obviously the opposites of the Single Table Inheritance ones:- all columns in a table are stricly needed and there is no data duplication. Hence, no waste of space in the database nor in your memory to keep in mind which columns belong to each class.
- Adding or removing fields (in opposition to moving them around between classes) is easier because it involves only the specialized table.
- Multiple tables are joined or multiple queries are executed even when only one object is involved in the operation. But everyone deals with performance problems differently however, and the advantage of a transparent ORM overcomes the limitations of the mapping process in many use cases. For example, if your inheritance-based entities are not intensely modified (like categories, products, and similar objects) you can introduce a caching layer. Remember what premature optimazition is: you can refactor to another inheritance pattern in every moment with a modern ORM like Doctrine 2.
- There is no zero-impact refactorings on fields (moving them between classes) without changes to the schema. You'll have to write a migration for moving a field and all its values from a table to a different one.
Fowler cites also high normalization as a problem when understanding and maintaining the queries, which can become longer and longer for leaf classes. This is not a problem nowadays because ORMs in Java and PHP generate SQL for you, and you formulate queries in an higher-level language oriented to objects like Doctrine's DQL (similar to Hibernate's HQL). So since an object contains logically all the fields, you can write higher-level queries without even knowing it is a subclass (at least from the client side; the metadata will have to specify it someway).
For example, if Employee is a subclass of Person, in DQL you can write:
SELECT Employee e WHERE e.name = ?even if name is a property defined on Person. Inheritance mapping and queries are orthogonal as you only see the object model outside of entities code.
Examples
Doctrine 2 supports this pattern as one of the possibilities for inheritance mapping, and it calls it Joined inheritance type; in the manual it is called Class Table Inheritance with the name originally defined by Fowler, since these pattern names are now a standard.
There is still a discriminator column with its map defined in the metadata, as it is an easy way to link the root table with its children (when it identifies a record with a query on the topmost table, it needs a way to know which children table the other fields are in.)
Thus, the only difference in mapping is in the @InheritanceType annotation:
<?php
namespace MyProject\Model;
/**
* Example taken from the manual.
* @Entity
* Define Class Table Inheritance as the mapping strategy for this whole hierarchy:
* @InheritanceType("JOINED")
* Define the discriminator column:
* @DiscriminatorColumn(name="discr", type="string")
* Define a map of keys 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
{
// ...
}
Opinions expressed by DZone contributors are their own.
Trending
-
RAML vs. OAS: Which Is the Best API Specification for Your Project?
-
Which Is Better for IoT: Azure RTOS or FreeRTOS?
-
Using OpenAI Embeddings Search With SingleStoreDB
-
Health Check Response Format for HTTP APIs
Comments