Laravel BelongsTo and BelongsToMany with Same Table: Possible? Worth it?
Founder of QuickAdminPanel
A few of QuickAdminPanel customers asked how to implement a situation when you need both one-to-many and many-to-many relationships to the same table. Like, for example, user belongs to many organizations but only one organization is founded by him. While we don’t have this ability in our generator – in this article, I will show two ways to implement this in the code.
1. Straightforward Way: belongsTo + belongsToMany
From DB migrations point of view, it’s this:
Schema::create('organizations', function (Blueprint $table) { $table->increments('id'); $table->string('name'); $table->timestamps(); }); // ... Schema::create('users', function (Blueprint $table) { $table->increments('id'); $table->string('name'); $table->string('email')->unique(); // ... $table->unsignedInteger('organization_id')->nullable(); $table->foreign('organization_id')->references('id')->on('organizations'); }); // ... Schema::create('organization_user', function (Blueprint $table) { $table->unsignedInteger('user_id'); $table->foreign('user_id')->references('id')->on('users'); $table->unsignedInteger('organization_id'); $table->foreign('organization_id')->references('id')->on('organizations'); });
Visually, it looks like this:
And then in app/User.php model we have this:
public function organizations() { return $this->belongsToMany(Organization::class); } public function organization() { return $this->belongsTo(Organization::class); }
And this is where I see the problem. It’s not readable or easy to understand. Imagine a new developer joining the project, they won’t immediately get it – so user has one or many organizations? Why both?
Of course, we can rename the field and the relationship name, and readability will increase:
public function owned_organization() { return $this->belongsTo(Organization::class, 'owned_organization_id'); }
But still, it doesn’t feel clean and nice to me. So, what if I told you there’s another way?
2. Inverse Way: Extra Field in Pivot Table
Think about it from real-life scenario: user belongs to many organization, and for one of them he’s an owner. So naturally it makes more sense to leave it all in organization_user table and add an extra field is_owner.
Schema::create('organization_user', function (Blueprint $table) { $table->unsignedInteger('user_id'); $table->foreign('user_id')->references('id')->on('users'); $table->unsignedInteger('organization_id'); $table->foreign('organization_id')->references('id')->on('organizations'); $table->boolean('is_owner')->default(false); });
Now, in the model app/User.php we need to specify that field to be accessible:
public function organizations() { return $this->belongsToMany(Organization::class)->withPivot('is_owner'); }
Visual structure:
Perhaps it seems more difficult to deal with this structure? But luckily, Laravel has all the necessary methods to operate additional pivot table columns easily.
Adding a new organization for a user where he’s an owner:
$organizationId = 1; $user = User::first(); $user->organizations()->attach($organizationId, ['is_owner' => 1]);
Getting if the user is owner or not:
$user = User::with('organizations')->first(); foreach ($user->organizations as $organization) { echo "Organization: " . $organization->name; if ($organization->pivot->is_owner) { echo " (owner)"; } }
By the way, with this structure we can define many organizations owned by the same user. So, in a way, it’s a little different structure – many-to-many inside of many-to-many. So, it’s even more flexible!
For more information on how to operate pivot tables in belongsToMany relationships, read this: