Using has many through relationship with users, roles and posts table

Suppose user has many posts and post belongs to user. Then role has many users and user belongs to role. Thing to note here, there is no direct connection between posts and roles.

To bridge this relationship gap between posts and roles, the Eloquent feature "hasManyThrough" can be utilized through the users relationship. The upcoming example snippet will demonstrate this concept.

Step 01: create fresh laravel project

composer create-project laravel/laravel has_many_through

Step 02: Setting Up scaffolding migration and model of Role with artisan command

php artisan make:model Role -m

Step 03: Setting Up scaffolding migration and model of Post with artisan command

php artisan make:model Post -m

Step 04: Specifying migration definition roles

public function up()
{
    Schema::create('roles', function (Blueprint $table) {
        $table->id();
        $table->string('name');
        $table->timestamps();
    });
}

Step 05: Specifying migration definition for users

public function up()
{
	Schema::create('users', function (Blueprint $table) {
		$table->id();
		$table->foreignId('role_id')->constrained();
		$table->string('name');
		$table->string('email')->unique();
		$table->string('password');
		$table->timestamps();
	});
}

Step 06: Specifying migration definition for posts

public function up()
{
    Schema::create('posts', function (Blueprint $table) {
        $table->id();
	$table->foreignId('user_id')->constrained();
        $table->string('title');
	$table->text('description');
        $table->timestamps();
    });
}

Step 07: Setting up relationship in User model in App\Models\User.php

public function role()
{
   return $this->belongsTo(Role::class);
}  

public function posts()
{
   return $this->hasMany(Post::class);
} 

Step 08: Setting up relationship in Role model in App\Models\Role.php

public function users()
{
    return $this->hasMany(User::class);
} 

Step 09: Setting up relationship in Post model in App\Models\Post.php

public function user()
{
    return $this->belongsTo(User::class);
}  

Step 10: Seeding User, Role and Post in DatabaseSeeder.php

// at the top
use Faker\Generator;

public function run()
{
	$faker = app(Generator::class);

	\App\Models\Role::create(['name' => 'Administrator']);
	\App\Models\Role::create(['name' => 'Editor']);
	\App\Models\Role::create(['name' => 'Author']);
	
	$user1 = \App\Models\User::create([
			'name' => 'Administrator', 
			'role_id' => 1, 
			'email' => 'admin@admin.com', 
			'password' => bcrypt('password') 		
		]); 
								
	$user2 = \App\Models\User::create([
		'name' => 'Editor', 
		'role_id' => 3, 
		'email' => 'editor@editor.com', 
		'password' => bcrypt('password') 		
	]);  
	
	$user3 = \App\Models\User::create([
		'name' => 'Author', 
		'role_id' => 3, 
		'email' => 'author@author.com', 
		'password' => bcrypt('password') 		
	]);
	
	$userIds = \App\Models\User::pluck('id');
	
	for( $i=0; $i < 10 ; $i++ ) {
		\App\Models\Post::create([
			'user_id' => $userIds->random(), 
			'title' => $faker->text(50),
			'description' => $faker->text(100)	
		]);
	}
} 

Step 11: Create HomeController using artisan command

php artisan make:controller HomeController

Step 12: Create index() method with following snippet

// at the top
use App\Models\Role;

function index() {
	$roles = Role::with('users.posts')->get(); 
	return view( 'home', compact('roles') );
}   

Step 13: Defining route for index() method inside HomeController.php

// at the top 
use App\Http\Controllers\HomeController;

Route::get('/', [HomeController::class, 'index'])->name('home');

Step 14: Setting up resources/view/home.blade.php

<!doctype html>
<html lang="{{ str_replace('_', '-', app()->getLocale()) }}">
<head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">

    <!-- CSRF Token -->
    <meta name="csrf-token" content="{{ csrf_token() }}">

    <title>{{ config('app.name', 'Has Many Through') }}</title>

	<link href="https://stackpath.bootstrapcdn.com/bootstrap/4.1.3/css/bootstrap.min.css" rel="stylesheet" />
    <link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/css/font-awesome.css" rel="stylesheet" />

    <!-- Fonts -->
    <link rel="dns-prefetch" href="//fonts.gstatic.com">
    <link href="https://fonts.googleapis.com/css?family=Nunito" rel="stylesheet">


</head>
<body>
    <div id="app">
        <main class="py-4">
		<div class="container">
			<div class="row justify-content-center">
				<div class="col-md-8">
					<h2 class="">Has Many Through relation example</h2>
					<table class="table mt-3">                
						<thead class="table-dark">
							<tr>
								<th>Roles</th>
								<th>Posts</th>
							</tr>
						</thead>
						<tbody>
								@foreach ($roles as $role)
								<tr>
									<td>{{$role->name}}</td>
									<td>
										@foreach( $role->users as $user )
											@foreach( $user->posts as $post )
												{{ $post->id }}.{{ $post->title }} <br>
											@endforeach										
										@endforeach
									</td>
								</tr>
								@endforeach								
						</tbody>
					</table>         
				</div>
			</div>
		</div>
        </main>
    </div>
</body>
</html>
Code above will create html table with two columns having roles listed in left side and post titles on the right.  The thing to notice here is to achieve the output of listing out posts for each roles there has been use of three foreach loops which is not optimized coding.

Step 15: Now we can use has many through relationship for roles with posts to optimize two steps above as there is no direct but indirect relationship between them with users. We need to do this in following three steps. 

Firstly, changing relationship in Role model in App\Models\Role.php  with posts
public function posts()
{
   return $this->hasManyThrough(Post::class, User::class);
} 

Secondly, In HomeController changing code like below

function index() {
	$roles = Role::with('posts')->get(); 
	return view( 'home', compact('roles') );
} 
FInally,  changing home.blade.php like below
<table class="table mt-3">                
	<thead class="table-dark">
		<tr>
			<th>Roles</th>
			<th>Posts</th>
		</tr>
	</thead>
	<tbody>
			@foreach ($roles as $role)
			<tr>
				<td>{{$role->name}}</td>
				<td>
					@foreach( $role->posts as $post )
						{{ $post->id }}.{{ $post->title }} <br>
					@endforeach										
				</td>
			</tr>
			@endforeach								
	</tbody>
</table>
So, after implementing three steps, we have optimized to two foreach loops instead of three by using eloquent feature hasmanythrough.

In conclusion, code implemented above will show roles and the posts in two columns with optimized loops using hasmanythrough relation in blade. 

Related Posts