Be part of JetBrains PHPverse 2026 on June 9 – a free online event bringing PHP devs worldwide together.

oscaribarra25's avatar

Relation from a model to another with a model between

Hi,

I have a question just to know if I am doing things rights or if there is a more "elegant" way to do this.

I have these three models Ability, Role, User which DB schemas are created as follows (only relevants fields are shown):

return new class extends Migration
{
    public function up(): void
    {
        Schema::create('users', function (Blueprint $table) {
            $table->id();
			//Other fields here
        });
    }
    public function down(): void
    {
        Schema::dropIfExists('users');
    }
};
return new class extends Migration
{
    public function up()
    {
        Schema::create('abilities', function (Blueprint $table) {
            $table->id();
            //Other fields here
        });
    }

    public function down()
    {
        Schema::dropIfExists('abilities');
    }
};
return new class extends Migration
{
    public function up()
    {
        Schema::create('roles', function (Blueprint $table) {
            $table->id();
             //Other fields here
        });
    }

    public function down()
    {
        Schema::dropIfExists('roles');
    }
};
return new class extends Migration
{
    public function up()
    {
        Schema::create('ability_role', function (Blueprint $table) {
            $table->id();
            $table->unsignedBigInteger('ability_id');
            $table->foreign('ability_id')
                ->references('id')->on('abilities')
                ->onDelete('cascade')
                ->onUpdate('cascade');
            $table->unsignedBigInteger('role_id');
            $table->foreign('role_id')
                ->references('id')->on('roles')
                ->onDelete('cascade')
                ->onUpdate('cascade');
            //Other fields here
	       $table->unique([ 'ability_id', 'role_id' ]);
        });
    }

    public function down()
    {
        Schema::dropIfExists('ability_role');
    }
};
return new class extends Migration
{
    public function up()
    {
        Schema::create('role_user', function (Blueprint $table) {
            $table->id();
            $table->unsignedBigInteger('role_id');
            $table->foreign('role_id')
                ->references('id')->on('roles')
                ->onDelete('cascade')
                ->onUpdate('cascade');
            $table->unsignedBigInteger('user_id');
            $table->foreign('user_id')
                ->references('id')->on('users')
                ->onDelete('cascade')
                ->onUpdate('cascade');            
            //Other fields here
            $table->unique([ 'role_id', 'user_id' ]);
        });
    }

    public function down()
    {
        Schema::dropIfExists('role_user');
    }
};

And the models are as follows (only relevant methods are shown):

namespace App\Models;

use Illuminate\Database\Eloquent\Model;
use Illuminate\Foundation\Auth\User as Authenticatable;

/**
 *
 */
class User extends Authenticatable
{
    //Other uses and properties here

    public function roles()
    {
        return $this->belongsToMany(Role::class);
    }

    //Other methods here
}
namespace App\Models;

use Illuminate\Database\Eloquent\Model;

class Role extends Model
{
    //Other class constants here

    public function abilities()
    {
        return $this->belongsToMany(Ability::class)->withTimestamps();
    }

    public function users()
    {
        return $this->belongsToMany(User::class);
    }
}
namespace App\Models;

use Illuminate\Database\Eloquent\Model;

class Ability extends Model
{
     //Other class constants here

    public function roles()
    {
        return $this->belongsToMany(Role::class);
    }
}

I am trying to add a new method to User model to get all the abilities it has according to the roles it is related to. I end up doing this using Eloquent and a repository method as follows:

namespace App\Repositories;

use App\Models\Ability;
use App\Repositories\Interfaces\SecurityRepositoryInterface;

class SecurityRepository implements SecurityRepositoryInterface
{
    //Other repository methods here
    public static function getUserAbilities($user_id)
    {
        return Ability::whereHas('roles', function($query1) use ($user_id) {
            $query1->whereHas('users', function($query2) use ($user_id) {
                $query2->where('users.id', $user_id);
            });
        })->get();
    }
}

And then in the user model

    public function abilities()
    {
        return SecurityRepository::getUserAbilities($this->id);
    }

But I am wondering is there is a way to do this using anything relation-wise like $this->hasMany, $this->belongsToMany, etc.

Can anyone get me some lights on this?

Best regards,

0 likes
2 replies
oscaribarra25's avatar

I will give it a try but the DB structure mentioned in the docs is quite different from mine. In the docs they refer to 3 entities (Projects, Environments, Deployments) which have one to many relationships between them (which in Eloquent will be a combo of hasMany and belongsTo); in my scenario the relations are not one to many but many to many (which in Eloquent will be belongsToMany).

While I try this, is there any belongsToManyThrough?

I will let you know if hasManyThrough also work for many to many relationships after I test it.

Best regards,

Please or to participate in this conversation.