Many to Many

Join lots of one type of entity to lots of another type of entity.

A many-to-many relationship is where both sides of the relationship hold a collection of child objects. As each side of the relationship can have many foreign keys, this requires additional database mapping, as a bridging table (also known as a 'junction table') is needed to store the keys for each side.

For example, suppose we have a Course entity and a Student entity. A course can have many students, and a student can have many courses. Sometimes, with a relationship like this, there might be properties of the relationship that require an intermediate entity. In that case, you can just use one-to-many relationships between each entity. However, for a true many-to-many relationship, a bridging table is required, and this must be mapped. In this example, our entities might look similar to this:

class Course
{
    public int $id;
    public string $name;
    public array $students = []; // <--Many to Many
}

// Note: Each class would of course normally be
// in a separate file.

class Student
{
    public int $id;
    public string $firstName;
    public string $lastName;
    public array $courses = []; // <--Many to Many
}

We store the values of the properties for these entities in two database tables, named course and student, with an additional bridging table named student_course (or course_student if you prefer) which holds the foreign keys for both entities:

course

id

name

student

id

first_name

last_name

student_course

id

course_id <-- Foreign key

student_id <-- Foreign key

Examples

Here are two examples of how to set up the mapping for a many-to-many relationship using attributes or annotations (these examples are all equivalent; you only need to use one type of mapping provider, it is up to you which one).

With a many-to-many relationship, there is no obvious 'owning side' of the relationship, as neither entity's table has a foreign key table. To map the relationship then, you should choose a side to be the owner of the relationship, and specify the mapping information on that side (it does not matter which side you choose). The opposite side should have a mappedBy attribute (which must be the property name, not a column name), telling Objectiphy where to look for the mapping information. Do not put mapping information on both sides of the relationship!

Example 1: Minimal Mapping

The following example shows the bare minimum mapping information necessary, on the assumption that the bridging table uses a naming convention that matches de-pluralised source and target properties (note: if your property names are not in English, or you otherwise encounter problems with the mapping, you might need to define all of the mapping in full - see example 2, below).

use Objectiphy\Objectiphy\Table;
use Objectiphy\Objectiphy\Column;
use Objectiphy\Objectiphy\Relationship;

#[Table(name: 'course')]
class Course
{
    #[Column(isPrimaryKey: true)]
    public int $id;
    
    #[Column]
    public string $name;
    
    #[Relationship(
        relationshipType: 'many_to_many',
        childClassName: Student::class,
        bridgeJoinTable: 'student_course'
    )]
    public array $students = []; 
}

// Note: Each class would of course normally be
// in a separate file.

#[Table(name: 'student')]
class Student
{
    #[Column(isPrimaryKey: true)]
    public int $id;
    
    #[Column]
    public string $firstName;
    
    #[Column]
    public string $lastName;
    
    #[Relationship(
        relationshipType: 'many_to_many',
        childClassName: Course::class,
        mappedBy: 'students'
    )]
    public array $courses; 
}

Example 2: Full Mapping

The following example shows all of the mapping information defined, even though some items could be omitted as Objectiphy can guess some of the mapping if it follows standard conventions. You only need to specify all of the mapping information if you are not joining in a standard way. Example 1, above, shows the exact same mapping as this in an abbreviated format.

use Objectiphy\Objectiphy\Table;
use Objectiphy\Objectiphy\Column;
use Objectiphy\Objectiphy\Relationship;

#[Table(name: 'course')]
class Course
{
    #[Column(isPrimaryKey: true)]
    public int $id;
    
    #[Column]
    public string $name;
    
    #[Relationship(
        relationshipType: 'many_to_many',
        childClassName: Student::class,
        bridgeJoinTable: 'student_course',
        sourceJoinColumn: 'id',
        targetJoinColumn: 'id',
        bridgeSourceJoinColumn: 'course_id',
        bridgeTargetJoinColumn: 'student_id'
    )]
    public array $students = []; 
}

// Note: Each class would of course normally be
// in a separate file.

#[Table(name: 'student')]
class Student
{
    #[Column(isPrimaryKey: true)]
    public int $id;
    
    #[Column]
    public string $firstName;
    
    #[Column]
    public string $lastName;
    
    #[Relationship(
        relationshipType: 'many_to_many',
        childClassName: Course::class,
        mappedBy: 'students'
    )]
    public array $courses; 
}

Last updated