skip to Main Content

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!

Table Structure

curriculum
id
..

courses
id
curriculum_id
..

students_courses
id
course_id
student_id
..

students
id
..

Models

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

2

Answers


  1. 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.

    Login or Signup to reply.
  2. Seems you are confusing models with pivots when setting up your relations. StudentCourse is not a model, but a pivot, and for such simple example you don’t need to define this class at all.

    Students have a direct relationship with courses already, via a belongsToMany relation. And this relationship will make use of student_courses table to resolve the relation.

    And for your goal to retrieve a collection of Students (only) which are enrolled in a course that belongs to a curriculum model, you can query relationships however deep you want.

    Student::query()
      ->whereHas('courses', function (Builder $query) {
        $query->whereHas('curriculum', function (Builder $query) {
          $query->where('name', $curriculum_name);
        });
      });
    

    rel: https://laravel.com/docs/11.x/eloquent-relationships#many-to-many

    query: https://laravel.com/docs/11.x/eloquent-relationships#querying-relations

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search