Relationships
Learn how to define relations
Opis ORM allows you to define multiple types of relationships between entities.
Defining a relationship is done by calling the relation
method, on a entity mapper instance,
and passing a name that uniquely identifies that relation on that entity mapper.
The return value of this call is a factory object that provides several methods
that will help you to declare the type of the relationship being defined.
Has one
Setting a has one
relation is done with the help of the hasOne
method.
This kind of relationship can be defined when on the child table is set a unique
foreign key pointing to the parent table’s primary key.
users (parent table)
id | name |
---|---|
1 | john |
2 | jane |
profiles (child table)
id | user_id | age | avatar |
---|---|---|---|
100 | 1 | 24 | john.jpg |
101 | 2 | 32 | jane.jpg |
class User extends Entity implements IMappableEntity
{
public static function mapEntity(IEntityMapper $mapper)
{
$mapper->relation('profile')->hasOne(Profile::class);
}
}
By default, the name of the foreign key column is derived form the entity name
and the name of the candidate column.
For example, an entity named foo
, that has a candidate column bar
, would be
referenced by a foreign key set on a column named foo_bar
.
If the foreign key is set to a different column,
you must specify this by using the Opis\ORM\Core\ForeignKey
class.
$mapper->relation('foo')->hasOne(Bar::class, new ForeignKey([
'candidate_column' => 'foreign_key_column'
]));
To better exemplify, the above is just a shortcut for the following:
class User extends Entity implements IMappableEntity
{
public static function mapEntity(IEntityMapper $mapper)
{
$foreignKey = new ForeignKey([
// users.id => profiles.user_id
'id' => 'user_id',
]);
$mapper->relation('profile')->hasOne(Profile::class, $foreignKey);
}
}
The ForeignKey
object can also be used to reference composite primary keys.
$mapper->relation('foo')->hasOne(Bar::class, new ForeignKey([
'candidate_column_1' => 'foreign_key_column_1',
'candidate_column_2' => 'foreign_key_column_2',
]));
Has many
A has many
relation is defined by using the hasMany
method. This type of
relationship is very similar with the has one
relation type. The only
difference is that the value contained by the column on which the foreign key
was set, doesn’t have to be unique to the child table.
users (parent table)
id | name |
---|---|
1 | john |
2 | jane |
articles (child table)
id | user_id | title | content |
---|---|---|---|
100 | 1 | Hello, World! | Content for my article |
101 | 1 | Second article | Content for my second article |
102 | 2 | First article | Content for my first article |
class User extends Entity implements IMappableEntity
{
public static function mapEntity(IEntityMapper $mapper)
{
$mapper->relation('articles')->hasMany(Article::class);
}
}
Manually setting the foreign key column is done by passing
a ForeignKey
object as the second argument.
$mapper->relation('articles')->hasOne(Article::class, new ForeignKey([
'id' => 'user_id'
]));
Belongs to
This is the inverse relation for the has one
and has many
relationships. It
can be defined with the help of the belongsTo
method
class Article extends Entity implements IMappableEntity
{
public static function mapEntity(IEntityMapper $mapper)
{
$mapper->relation('author')->belongsTo(User::class);
}
}
As like for the previous relation types, manually setting the foreign key column is
done by passing a ForeignKey
object as the second argument.
$mapper->relation('author')->hasOne(User::class, new ForeignKey([
'id' => 'user_id',
]));
Many to many
Defining many to many
relationships is done by using either the shareOne
or the shareMany
methods, depending on the context. Establishing a many to many
relationship between two entities is done with the
help of a so called junction table.
employees (parent table)
id | name |
---|---|
1 | John |
2 | Jane |
3 | Chris |
4 | Jeff |
job_titles (child table)
id | name |
---|---|
1 | CEO |
2 | Software developer |
3 | UI/UX designer |
employees_job_titles (junction table)
employee_id | job_title_id |
---|---|
1 | 2 |
2 | 2 |
3 | 1 |
4 | 3 |
In the above example John and Jane are software developers, Chris is CEO, and Jeff is an
UI/UX designer. The relation between Employee
and JobTitle
entities
is set with the help of the shareOne
method. That’s because two, or more, employees
can share the same job title, but every employee can have only a single job title at a
given moment.
class Employee extends Entity implements IMappableEntity
{
public static function mapEntity(IEntityMapper $mapper)
{
$mapper->relation('job_title')->shareOne(JobTitle::class);
}
}
Of course, since a job title is shared by multiple employees, the
relation between a JobTitle
and an Employee
entity is set with the help of the
shareMany
method.
class JobTitle extends Entity implements IMappableEntity
{
public static function mapEntity(IEntityMapper $mapper)
{
$mapper->relation('employees')->shareMany(Employee::class);
}
}
The name of the junction table is determined from the table names
of related entities, alphabetically sorted. So, if the table names
of the related entities are foo
and bar
, the junction table name
will be bar_foo
. If your junction table is named differently, you must
specify this by using an Opis\ORM\Core\Junction
object.
To better exemplify this, the above example is just a shortcut for the following:
class Employee extends Entity implements IMappableEntity
{
public static function mapEntity(IEntityMapper $mapper)
{
$junction = new Junction('employees_job_titles', [
// job_title.id => employess_job_titles.job_title_id
'id' => 'job_title_id',
]);
$mapper->relation('job_title')->shareOne(JobTitle::class, null, $junction));
}
}
class JobTitle extends Entity implements IMappableEntity
{
public static function mapEntity(IEntityMapper $mapper)
{
$junction = new Junction('employees_job_titles', [
// empoyees.id => employess_job_titles.employee_id
'id' => 'employee_id',
]);
$mapper->relation('employees')->shareMany(Employee::class, null, $junction);
}
}
Which is also just a shortcut for the following:
class Employee extends Entity implements IMappableEntity
{
public static function mapEntity(IEntityMapper $mapper)
{
$junction = new Junction('employees_job_titles', [
// job_title.id => employess_job_titles.job_title_id
'id' => 'job_title_id',
]);
$foreignKey = new ForeignKey([
// employees.id => employess_job_titles.employee_id
'id' => 'employee_id'
]);
$mapper->relation('job_title')->shareOne(JobTitle::class, $foreignKey, $junction));
}
}
class JobTitle extends Entity implements IMappableEntity
{
public static function mapEntity(IEntityMapper $mapper)
{
$junction = new Junction('employees_job_titles', [
// empoyees.id => employess_job_titles.employee_id
'id' => 'employee_id',
]);
$foreignKey = new ForeignKey([
// job_titles.id => employess_job_titles.job_title_id
'id' => 'employee_id'
]);
$mapper->relation('employees')->shareMany(Employee::class, $foreignKey, $junction);
}
}