Skip to main content

Inheritance

The ORM supports inheritance. This means that entities can extend other entities. However, try to avoid inheritance ("Inheritance") and instead prefer aggregations / compositions and resort to the Decorator pattern. Modern architectures are based on interfaces and generally try to avoid inheritance. This makes the architecture more flexible.

Of course, it can still be useful to implement abstract super-classes solely for the purpose of reusing properties or methods (e.g. adapters that implement methods of an interface that are almost always the same). "Mapped Superclasses" are also suitable for this.

Inheritance Strategies​

The inheritance strategy determines the table structure that should be used to realize the respective inheritance. An inheritance strategy must be defined for all entities that are at the top of the inheritance hierarchy. The annotation n2n\persistence\orm\annotation\AnnoInheritance is available for this purpose. The Id property must be defined in this entity.

Single Table​

The "Single Table" strategy combines the properties of the entity and all sub-entities into a single table. In order to assign each row to an entity, the ORM requires a discriminator column. You can specify the name of the discriminator column using the annotation n2n\persistence\orm\annotation\AnnoDiscriminatorColumn. Otherwise, it is assumed that the discriminator column is called "discr".

#[Table('st_item')]
#[Inheritance(InheritanceType::SINGLE_TABLE)]
#[DiscriminatorColumn('type')]
#[DiscriminatorValue('item')]
class Item extends ObjectAdapter {
public int $id;
public string $name;
}
#[DiscriminatorValue('linkItem')]
class LinkItem extends Item {
public string $url;
}
#[DiscriminatorValue('textItem')]
class TextItem extends Item {
public string $text;
}

For all non-abstract entities, you must specify the discriminator value using the annotation n2n\persistence\orm\annotation\AnnoDiscriminatorValue.

The following table is valid for the example above:

CREATE TABLE `st_item` (
`id` INT(10) UNSIGNED NOT NULL AUTO_INCREMENT,
`type` ENUM('item','linkItem','textItem') NOT NULL,
`name` VARCHAR(50) NOT NULL,
`url` VARCHAR(255) DEFAULT NULL,
`text` TEXT DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

For all unused fields, the value NULL is inserted.

Example​

public function doOrmSt(EntityManager $em) {
$this->beginTransaction();

$item = new Item();
$item->setName('foo');
$em->persist($item);

$linkItem = new LinkItem();
$linkItem->setName('n2n Support');
$linkItem->setUrl('https://support.n2n.ch');
$em->persist($linkItem);

$textItem = new TextItem();
$textItem->setName('bar');
$textItem->setText('lorem ipsum');
$em->persist($textItem);

$this->commit();
}

The example above writes the following rows into the table st_item:

idtypenameurltext
1itemfooNULLNULL
2linkItemn2n Supporthttps://docs.n2n.rocksNULL
3textItembarNULLlorem ipsum

Note: The "Single Table" inheritance strategy is the most performant in most cases. As long as there is no need to split the data across multiple tables, it should be the first choice.

Joined​

The "Joined" strategy expects a separate table for the entity and all sub-entities (as with normal entities).

#[Table('jt_item')]
#[Inheritance(InheritanceType::JOINED)]
class Item extends ObjectAdapter {
public int $id;
public string $name;
}

Joined (continued)​

#[Table('jt_text_item')]
class TextItem extends Item {
public string $text;
}
#[Table('jt_link_item')]
class LinkItem extends Item {
public string $url;
}

If you write an object of the entity LinkItem to the database, a row is inserted in the table jt_item as well as in jt_link_item. So that these two rows can be linked together, the Id column is taken over in all tables of the sub-entities.

⚠️ Caution: In tables of sub-entities, the Id values must not be generated automatically by the database.

The following tables are valid for the example above:

CREATE TABLE `jt_item` (
`id` INT(10) unsigned NOT NULL AUTO_INCREMENT,
`name` VARCHAR(50) NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

CREATE TABLE `jt_link_item` (
`id` INT(10) UNSIGNED NOT NULL,
`url` VARCHAR(255) NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

CREATE TABLE `jt_text_item` (
`id` INT(10) UNSIGNED NOT NULL,
`text` TEXT NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

Example​

public function doOrmJt(EntityManager $em) {
$this->beginTransaction();

$item = new Item();
$item->setName('foo');
$em->persist($item);

$linkItem = new LinkItem();
$linkItem->setName('n2n Support');
$linkItem->setUrl('https://support.n2n.ch');
$em->persist($linkItem);

$textItem = new TextItem();
$textItem->setName('bar');
$textItem->setText('lorem ipsum');
$em->persist($textItem);

$this->commit();
}

The example above writes the following rows into the tables:

jt_item

idname
1foo
2n2n Support
3bar

jt_link_item

idurl
2https://docs.n2n.rocks

jt_text_item

idtext
3lorem ipsum

Table Per Class​

The "Table Per Class" strategy creates a table for each entity. The table of an entity also contains all columns of the properties of its super-entities. With this inheritance strategy, it is not possible to have the Id generated by the database.

#[Table('tbc_item')]
#[Inheritance(InheritanceType::TABLE_PER_CLASS)]
class Item extends ObjectAdapter {
public int $id;
public string $name;
}
#[Table('tbc_link_item')]
class LinkItem extends Item {
public string $url;
}
#[Table('tbc_text_item')]
class TextItem extends Item {
public string $text;
}

The following tables are valid for the example above:

CREATE TABLE `tbc_item` (
`id` INT(10) UNSIGNED NOT NULL,
`name` VARCHAR(50) NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

CREATE TABLE `tbc_link_item` (
`id` INT(10) UNSIGNED NOT NULL,
`name` VARCHAR(50) NOT NULL,
`url` VARCHAR(255) NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

CREATE TABLE `tbc_text_item` (
`id` INT(10) UNSIGNED NOT NULL,
`name` VARCHAR(50) NOT NULL,
`text` TEXT NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

Example​

public function doOrmTbc(EntityManager $em) {
$this->beginTransaction();

$item = new Item();
$item->setId(1);
$item->setName('foo');
$em->persist($item);

$linkItem = new LinkItem();
$linkItem->setId(2);
$linkItem->setName('n2n Support');
$linkItem->setUrl('https://support.n2n.ch');
$em->persist($linkItem);

$textItem = new TextItem();
$textItem->setId(3);
$textItem->setName('bar');
$textItem->setText('lorem ipsum');
$em->persist($textItem);

$this->commit();
}

The example above writes the following rows into the tables:

tbc_item​

idname
1foo
idnameurl
2n2n Supporthttps://docs.n2n.rocks

tbc_text_item​

idnametext
3barlorem ipsum

class property​

In queries, you can use the pseudo-property class. This corresponds to the ReflectionClass of the respective entity.

Criteria API​

$criteria = $em->createCriteria();

$criteria
->select('i')
->from(Item::getClass(), 'i')
->where()
->match(
'i.class',
'IN',
array(LinkItem::getClass(), TextItem::getClass())
);

foreach ($criteria->toQuery()->fetchArray() as $item) {
var_dump($item);
}

NQL​

$criteria = $em->createNqlCriteria(
'SELECT i FROM Item i WHERE i.class IN :classes',
array(
'classes' => array(LinkItem::getClass(), TextItem::getClass())
)
);

foreach ($criteria->toQuery()->fetchArray() as $item) {
test($item);
}

In this example, all entity objects that are of the type LinkItem or TextItem are queried.