The entities from different classes with a common ancestor are placed in a single table. STI creates one table for each class hierarchy.
The ORM provides the ability to store multiple model variations inside one table. In order to achieve that you must extend your parent entity and declare relations/columns specific to the child.
We can define the strategy we want to use by adding the #[SingleTable]
attribute to a subclass.
Note
Make sure the parent class properties are not private.
use Cycle\Annotated\Annotation\Entity;
use Cycle\Annotated\Annotation\Column;
use Cycle\Annotated\Annotation\Inheritance\SingleTable;
use Cycle\Annotated\Annotation\Inheritance\DiscriminatorColumn;
#[Entity]
#[DiscriminatorColumn(name: 'type')]
class Person
{
#[Column(type: 'primary', primary: true)]
protected int $id;
#[Column(type: 'string')]
protected string $name;
#[Column(type: 'string')]
protected string $type;
}
#[Entity]
#[SingleTable] // discriminator value: employee
class Employee extends Person
{
#[Column(type: 'int')]
protected int $salary;
}
#[Entity]
#[SingleTable] // discriminator value: customer
class Customer extends Person
{
#[Column(type: 'json')]
protected array $preferences;
}
#[Entity]
#[SingleTable]
class Ceo extends Employee
{
#[Column(type: 'int')]
public int $stocks;
}
Since the records for all entities will be in the same table, CycleORM needs a way to differentiate between them. By
default, this is done through a discriminator column called type
that has the name of the entity role as a value. The
discriminator column #[DiscriminatorColumn]
attribute is mandatory.
Next, we need to tell CycleORM what value each subclass entity will have for the type
column. By default, CycleORM
uses entity role as a discriminator value, but you can change it by passing a value via #[SingleTable]
attribute:
#[Entity]
#[SingleTable(value: 'super_customer')] // discriminator value: super_customer
class Customer extends Person
{
//...
}
You can configure Single table inheritance without attributes by defining SchemaInterface::CHILDREN
and SchemaInterface::DISCRIMINATOR
segments for root entity.
use Cycle\ORM\SchemaInterface;
$schema = [
'person' => [
...,
SchemaInterface::CHILDREN => [
// discriminator value => Entity class
'employee' => Employee::class,
'foo_customer' => Customer::class,
'ceo' => Ceo::class,
],
SchemaInterface::DISCRIMINATOR => 'type'
],
'employee' => [
...,
SchemaInterface::ENTITY => Employee::class,
],
'customer' => [
...,
SchemaInterface::ENTITY => Customer::class,
],
'ceo' => [
...,
SchemaInterface::ENTITY => Ceo::class,
]
]
You have to remember that fetching entities from the repository might return any of child entity:
// persons, employees and customers will be returned
$people = $orm->getRepository(Person::class)->findAll();
You are currently not allowed to assign custom repositories or scopes to child entities. However, you can use type
in
your queries to pre-filter the selection.