Charrua's avatar

HasOneTrough / HasManyTrough relations

Hello, I’m trying to get a model through other models and tables.

Users → hasMany (one to many) → Enrollments → belongsToMany (many to many) → Courses

I wanted to get the courses that the user has enrollments. Something like $user->courses

I tried both HasOneTrough and HasManyTrough but I can’t figure it out, maybe I don’t need to use this and use another package called has-many-deep I’ve found, what do you think?

Also it would be great to get the inverse, for example to know the users that have enrollments to the course, something like $course->users

Thank you.

0 likes
2 replies
JohnBraun's avatar

@charrua I have a way to approach this problem, which uses 3 relatively fast queries on the database and retrieves the courses without employing a relationship method.

Please let me know if this helps you out. If you use the code listed below, you should be able to fetch all courses for a user by calling $user->courses().

Models

class Course extends Model
{
    public function enrollments()
    {
        return $this->belongsToMany(Enrollment::class);
    }
}
class Enrollment extends Model
{
    public function user()
    {
        return $this->belongsTo(User::class);
    }

    public function courses()
    {
        return $this->belongsToMany(Course::class);
    }
}
class User extends Authenticatable
{
    // default methods omitted for clarity... 

    public function enrollments()
    {
        return $this->hasMany(Enrollment::class)->with('courses');
    }

    public function courses()
    {
        return collect($this->enrollments)->flatMap(function ($enrollment) {
            return $enrollment->courses;
        });
    }
}

Migrations

The users table migration is default.

// For the 'enrollments' table
    public function up()
    {
        Schema::create('enrollments', function (Blueprint $table) {
            $table->id();
            $table->foreignId('user_id');
            $table->timestamps();
        });
    }
// For the 'courses' table
    public function up()
    {
        Schema::create('courses', function (Blueprint $table) {
            $table->id();
            $table->timestamps();
        });
    }
// For the course_enrollment pivot table
    public function up()
    {
        Schema::create('course_enrollment', function (Blueprint $table) {
            $table->foreignId('course_id');
            $table->foreignId('enrollment_id');
            $table->unique(['course_id', 'enrollment_id']);
        });
    }
BezhanSalleh's avatar

I'm not sure that's possible for your situation but you can do it through collections as follow: using memoization will help out regarding performance. User.php

private $courses;

    public function courses()
    {
        if(!$this->courses) {

            return $this->enrollments->flatMap(fn ($enrollment) => $enrollment->courses);
        }

        return $this->courses;
    }

and then you can query it as

User::first()->courses();

cheers!

Please or to participate in this conversation.