I'm using latest version of Laravel. I'm trying to setup a model method relationship that passes through other intermediary tables.
Understanding the table structure below, my goal is retrieve a collection of Students (only) which are enrolled in a course that belongs to a curriculum model.
Curriculum->Course->Students Course->Student
I've come close, as you can see in the Curriculum model, student() method, I can retrieve a collection of all courses along with their students... but I don't want the courses along with it.
Is there an Eloquent way to accomplish this?
Thank you!
curriculum
id
..
courses
id
curriculum_id
..
students_courses
id
course_id
student_id
..
students
id
..
curriculum
/**
* @return HasMany
*/
public function course(): HasMany
{
return $this->hasMany(Course::class);
}
/**
* @return HasManyThrough
*/
public function class(): HasManyThrough
{
return $this->hasManyThrough(ClassModel::class, Course::class);
}
/**
* @return HasManyThrough
*/
public function student()
{
// this works as expected, but brings courses too, I only want students.
return $this->hasManyThrough(StudentCourse::class, Course::class)->with('student');
}
course
/**
* @return HasMany
*/
public function class(): HasMany
{
return $this->hasMany(ClassModel::class);
}
/**
* @return BelongsTo
*/
public function curriculum(): BelongsTo
{
return $this->belongsTo(Curriculum::class, 'curriculum_id', 'id');
}
/**
* @return HasManyThrough
*/
public function student(): HasManyThrough
{
return $this->hasManyThrough(Student::class, StudentCourse::class,
'course_id',
'id',
'id',
'student_id'
);
}
students_courses
/**
* @return BelongsTo
*/
public function student(): BelongsTo
{
return $this->BelongsTo(Student::class);
}
/**
* @return BelongsTo
*/
public function course(): BelongsTo
{
return $this->BelongsTo(Course::class);
}
student
/**
* @return HasOne
*/
public function user(): HasOne
{
return $this->HasOne(User::class, 'userID', 'user_id');
}
/**
* @return HasManyThrough
*/
public function course(): HasManyThrough
{
return $this->HasManyThrough(Course::class, StudentCourse::class,
'student_id',
'id',
'id',
'course_id'
);
}
/**
* @return HasManyThrough
*/
public function class(): HasManyThrough
{
return $this->HasManyThrough(ClassModel::class , StudentClass::class,
'student_id',
'id',
'id',
'class_id'
);
}
I don't think you're going to find an entirely Eloquent based solution to your problem. In my experience Eloquent works well with one-to-many (two table) and many-to-many (three table) relationships but beyond that it gets messy.
As you don't want any Course information you might be better off using a Builder query, something like:
class Curriculum extends Model
{
...
public function students()
{
return Student::select('students.*')
->join('students_courses', 'students.id', '=', 'students_courses.student_id')
->join('courses', 'students_courses.course_id', '=', 'courses.id')
->where('courses.curriculum_id', $this->id)
->distinct()
->get();
}
}
You need the select('students.*')
to prevent any other table columns being dragged into the Student models, but at least you end up with just a collection of students.
I recommend putting function like this in a separate custom builder class for your Curriculum model so it keeps the model class cleaner. Have a look at using newEloquentBuilder
, there are a number of articles describing how to implement them.