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);
    }
}