belongsToMany and Through explanation needed


#1

I do not understand what the or when to use the ‘through’ option.

the book (https://book.cakephp.org/3.0/en/orm/associations.html#using-the-through-option) reads that the through should be used if one wants to add extra data to the belongsToMany.

So I recreated the Tables from the book and ran a test.

Students
id | name

Courses
id | course_name

CoursesStudens
id | student_id | course_id | days_attended | grade

Created the models:

Students

class StudentsTable extends Table
{

    public function initialize(array $config)
    {
        parent::initialize($config);

        $this->setTable('students');
        $this->setDisplayField('name');
        $this->setPrimaryKey('id');

//        $this->belongsToMany('Courses', [
//            'foreignKey' => 'student_id',
//            'targetForeignKey' => 'course_id',
//            'joinTable' => 'courses_students'
//        ]);

        $this->belongsToMany('Courses', [
            'through' => 'CoursesStudents',
        ]);

    }
}

Courses

class CoursesTable extends Table
{

    public function initialize(array $config)
    {
        parent::initialize($config);

        $this->setTable('courses');
        $this->setDisplayField('id');
        $this->setPrimaryKey('id');

//        $this->belongsToMany('Students', [
//            'foreignKey' => 'course_id',
//            'targetForeignKey' => 'student_id',
//            'joinTable' => 'courses_students'
//        ]);

        $this->belongsToMany('Students', [
            'through' => 'CoursesStudents',
        ]);


    }
}

And the join table:

class CoursesStudentsTable extends Table
{

    public function initialize(array $config)
    {
        parent::initialize($config);

        $this->setTable('courses_students');
        $this->setDisplayField('id');
        $this->setPrimaryKey('id');

        $this->belongsTo('Students', [
            'foreignKey' => 'student_id'
        ]);
        $this->belongsTo('Courses', [
            'foreignKey' => 'course_id'
        ]);
    }
}

Ran the query:

$result = $this->Students->find('all')
    ->contain([
        'Courses'
    ]);

debug($result->toArray());

Result:

(int) 1 => object(App\Model\Entity\Student) {

		'id' => (int) 2,
		'name' => 'Peter',
		'courses' => [
			(int) 0 => object(App\Model\Entity\Course) {

				'id' => (int) 1,
				'course_name' => 'PHP',
				'_joinData' => object(App\Model\Entity\CoursesStudent) {

					'id' => (int) 3,
					'student_id' => (int) 2,
					'course_id' => (int) 1,
					'days_attended' => (int) 14,
					'grade' => 'C',
					'[new]' => false,
					'[accessible]' => [
						'*' => true,
						'id' => false
					],
					'[dirty]' => [],
					'[original]' => [],
					'[virtual]' => [],
					'[errors]' => [],
					'[invalid]' => [],
					'[repository]' => 'CoursesStudents'
				
				},
				'[new]' => false,
				'[accessible]' => [
					'*' => true,
					'id' => false
				],
				'[dirty]' => [],
				'[original]' => [],
				'[virtual]' => [],
				'[errors]' => [],
				'[invalid]' => [],
				'[repository]' => 'Courses'
			
			}
		],
		'[new]' => false,
		'[accessible]' => [
			'*' => true,
			'id' => false
		],
		'[dirty]' => [],
		'[original]' => [],
		'[virtual]' => [],
		'[errors]' => [],
		'[invalid]' => [],
		'[repository]' => 'Students'
	
	}
]

Changed the through option back to the default for both tables:

    $this->belongsToMany('Students', [
        'foreignKey' => 'course_id',
        'targetForeignKey' => 'student_id',
        'joinTable' => 'courses_students'
    ]);

Ran the query:

(int) 1 => object(App\Model\Entity\Student) {

		'id' => (int) 2,
		'name' => 'Peter',
		'courses' => [
			(int) 0 => object(App\Model\Entity\Course) {

				'id' => (int) 1,
				'course_name' => 'PHP',
				'_joinData' => object(App\Model\Entity\CoursesStudent) {

					'id' => (int) 3,
					'student_id' => (int) 2,
					'course_id' => (int) 1,
					'days_attended' => (int) 14,
					'grade' => 'C',
					'[new]' => false,
					'[accessible]' => [
						'*' => true,
						'id' => false
					],
					'[dirty]' => [],
					'[original]' => [],
					'[virtual]' => [],
					'[errors]' => [],
					'[invalid]' => [],
					'[repository]' => 'CoursesStudents'
				
				},
				'[new]' => false,
				'[accessible]' => [
					'*' => true,
					'id' => false
				],
				'[dirty]' => [],
				'[original]' => [],
				'[virtual]' => [],
				'[errors]' => [],
				'[invalid]' => [],
				'[repository]' => 'Courses'
			
			}
		],
		'[new]' => false,
		'[accessible]' => [
			'*' => true,
			'id' => false
		],
		'[dirty]' => [],
		'[original]' => [],
		'[virtual]' => [],
		'[errors]' => [],
		'[invalid]' => [],
		'[repository]' => 'Students'
	
	}
]

Both results are exactly the same an both contain the _joinData data.

So now i am confused. Reading the book i would expect the normal/default table setting to not contain the _joinData. So when to use the through?


#2

Did you leave the CoursesStudentsTable class in place?
Because if you did Cake probably picks it up and uses it.
I think that not using the “through” implies not defining the class and jsut having the simple 3 column join table…