Laravel 9 Eloquent Relationships

laravel eloquent relationships

Hello guys! today we will look at laravel relationships. Laravel relationships are very important especially when developing big applications.

You may find relationships between posts, users, categories, tags etc. in an app. So how do you define them?

We will create a mini-blog that will utilize all the three common relationships in laravel. They are…

a) One To One (hasOne)
b) One To Many (Inverse) / Belongs To
c) Many To Many (belongsToMany)

Let’s started. Am going to use laravel 9 with a jetstream package which will provide authentication for our app.

Start by running the following command:

 laravel new mylaravel9app --jet. 

once it gets installed we can begin.

  1. Step one

Let’s begin with the models and tables.

create a Post model and posts table by running the following command:

php artisan make:model Post --migration 

create_posts_table.php

            Schema::create('posts', function (Blueprint $table) {
                $table->id();
                $table->unsignedBigInteger('user_id');
                $table->string('title')->unique();
                $table->text('details');
                $table->timestamps();
    
                $table->foreign('user_id')->references('id')->on('users')->onDelete('cascade');
            });

Post.php

class Post extends Model
{
    use HasFactory;

    protected $fillable = ['user_id', 'title', 'details'];

    /**
     * Get the user that owns the post.
     */
    public function user () 
    {
        return $this->belongsTo(User::class); 
    }

    /**
     * Get all of the categories for the post.
     */
    public function categories()
    {
        return $this->belongsToMany(Category::class, 'category_posts');
    }
}

From the above, we have a posts table that has entries for creating a post. The Post model defines for us the relationships.

Remember the user table comes created by default. An id gets recorded when a user logins or registers in our app.

Since an id from the user’s table relates with the posts table user_id. The match is checked by eloquent and a post becomes associated with a user. These define for us one to one relationship from the Post model.

Now our posts need categories or every post can have a category. So we will define this as we continue.

Use the same command above to create the model and the table:

php artisan make:model Category --migration 

create_categories_table.php

 Schema::create('categories', function (Blueprint $table) {
            $table->id();
            $table->unsignedBigInteger('user_id');
            $table->string('name')->unique();
            $table->string('slug')->unique();
            $table->timestamps();
        });

Category.php

class Category extends Model
{
    use HasFactory;

    protected $fillable = ['user_id','name', 'slug'];

    /**
     * Get the user that owns the category.
     */
    public function user()
    {
        return $this->belongsTo(User::class);
    }

     /**
     * Get all of the posts that are assigned this category.
     */
    public function posts()
    {
        return $this->belongsToMany(Post::class, 'category_posts');
    }
}

The concept is as have explained above our categories have a user_id this represents the person who creates the category.

In the category model, I have commented on the functions for you to understand easily. You may be wondering about the category_posts this is the pivot table that registers the category_id and the post_id bringing about the relationship between posts and categories i.e (many to many relationships). Lastly, let’s create the category_posts.

Run this command:

php artisan make:model CategoryPost --migration 

create_category_posts_table.php

 Schema::create('category_posts', function (Blueprint $table) {
            $table->id();
            $table->unsignedBigInteger('category_id');
            $table->unsignedBigInteger('post_id');
            $table->timestamps();

            $table->foreign('category_id')->references('id')->on('categories')->onDelete('cascade');
            $table->foreign('post_id')->references('id')->on('posts')->onDelete('cascade');
        });

CategoryPost.php

class CategoryPost extends Model
{
    use HasFactory;
    
    protected $fillable = ['category_id', 'post_id'];
}

About the user’s table let me show you so that you may not get lost.

create_users_table.php

 Schema::create('users', function (Blueprint $table) {
            $table->id();
            $table->string('name');
            $table->string('email')->unique();
            $table->timestamp('email_verified_at')->nullable();
            $table->string('password');
            $table->rememberToken();
            $table->foreignId('current_team_id')->nullable();
            $table->string('profile_photo_path', 2048)->nullable();
            $table->timestamps();
        });

In the model, User.php copy and paste the code below.

    /**
    * Get the posts associated with the user.
    */
    public function posts()
    {
        return $this->hasMany(Post::class);
    }

    /**
     * Get the categories associated with the user.
     */  
    public function categories()
    {
        return $this->hasMany(Category::class);
    }

Good, we are done with the tables now you can run this command:

//php artisan migrate// after creating a database in the //.env//

  1. Step Two

Creating the blade templates

Begin by creating a folder called post in the views section. Below the post, the folder creates a blade and names it create.blade.php this will enable us to create posts in our app. So update it as follows (…resources/views/create.blade.php)

create.blade.php

@extends('layouts.app')

@section('content')
  <div class="container">
        <div class="row justify-content-center">
            <div class="col-md-12">
            <br>
                <div class="card">
                    <div class="card-header">Post - create</div>
                    <div class="card-body">
                        {!! Form::open(['route' => 'posts.store']) !!}
                        <div class="form-group @if($errors->has('title')) has-error @endif">
                            {!! Form::label('Title') !!}
                            {!! Form::text('title', null, ['class' => 'form-control', 'placeholder' => 'title']) !!}
                            @if ($errors->has('title'))
                                <span class="help-block">{!! $errors->first('title') !!}</span>@endif
                        </div>

                        <div class="form-group @if($errors->has('details')) has-error @endif">
                            {!! Form::label('Details') !!}
                            {!! Form::textarea('details', null, ['class' => 'form-control', 'placeholder' => 'Details']) !!}
                            @if ($errors->has('details'))
                                <span class="help-block">{!! $errors->first('details') !!}</span>@endif
                        </div>

                        <div class="form-group @if($errors->has('category_id')) has-error @endif">
                            {!! Form::label('Category') !!}
                            {!! Form::select('category_id[]', $categories, null, ['class' => 'form-control', 'id' => 'category_id', 'multiple' => 'multiple']) !!}
                            @if ($errors->has('category_id'))
                                <span class="help-block">{!! $errors->first('category_id') !!}</span>
                            @endif
                        </div>

                        {!! Form::submit('Create',['class' => 'btn btn-sm btn-primary']) !!}
                        {!! Form::close() !!}
                    </div>
              </div>
          </div>
      </div>
</div>
@endsection

    <script src="https://code.jquery.com/jquery-3.5.1.min.js"
            integrity="sha256-9/aliU8dGd2tb6OSsuzixeV4y/faTqgFtohetphbbj0=" crossorigin="anonymous"></script>
    <script src="https://cdn.ckeditor.com/4.14.0/standard/ckeditor.js"></script>
    <script src="https://cdn.jsdelivr.net/npm/select2@4.0.13/dist/js/select2.min.js"></script>

    <script>
        $(document).ready(function () {
            CKEDITOR.replace('details');

            $('#category_id').select2({
                placeholder: "Select categories"
            });
        });
    </script>

As you can see am using the laravel collective form so go ahead and run this command to install it.

composer require laravelcollective/html

So we have the title, details the category_id and the CK Editor which gives us the platform for

creating a post by editing and giving it other features. The Jquery code will allow us to select different categories for

our post. So far we have defined the three relationships.

We are routing to the posts.store which means we need to create a controller the PostsController that will register our posts

In the database. Let’s go ahead and do so.

Start by running this command:

php artisan make:controller PostsController --resource

PostsController

<?php
namespace App\Http\Controllers;

use App\Models\Post;
use App\Models\Category;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth; 
use Illuminate\Support\Facades\Session;

class PostsController extends Controller
{
    /**
     * Display a listing of the resource.
     *
     * @return \Illuminate\Http\Response
     */
    public function index()
    {
        $posts = Post::orderBy('id', 'DESC')->get();
        return view('dashboard', compact('posts'));      
    }

    /**
     * Show the form for creating a new resource.
     *
     * @return \Illuminate\Http\Response
     */
    public function create()
    { 
        $categories = Category::orderBy('name', 'ASC')->pluck('name', 'id');
        return view('post.create', compact('categories'));
    }

    /**
     * Store a newly created resource in storage.
     *
     * @param  \Illuminate\Http\Request  $request
     * @return \Illuminate\Http\Response
     */
    public function store(Request $request)
    {
        $validated = $request->validate([
            'title' => 'required|unique:posts|max:255',
            'details" => "required',
            "category_id" => "required"
        ],

        [
            'title.required' => 'Enter the title',
            'details.required' => 'Enter details',
            'category_id.required' => 'Select categories',
        ]

        );

        $post = new  Post();
        $post->user_id = Auth::id();
        $post->title = $request->title;
        $post->details = $request->details;
        $post->save();
        $post->categories()->sync($request->category_id, false);

        Session::flash('message', 'Post created successfully');
        return redirect()->route('posts.index');
    }

    /**
     * Display the specified resource.
     *
     * @param  int  $id
     * @return \Illuminate\Http\Response
     */
    public function show($id)
    {
        //
    }

    /**
     * Show the form for editing the specified resource.
     *
     * @param  int  $id
     * @return \Illuminate\Http\Response
     */
    public function edit($id)
    {
        //
    }

    /**
     * Update the specified resource in storage.
     *
     * @param  \Illuminate\Http\Request  $request
     * @param  int  $id
     * @return \Illuminate\Http\Response
     */
    public function update(Request $request, $id)
    {
        //
    }

    /**
     * Remove the specified resource from storage.
     *
     * @param  int  $id
     * @return \Illuminate\Http\Response
     */
    public function destroy(Post $post)
    {
        $post->delete();

        Session::flash('delete-message', 'Post deleted successfully');
        return redirect()->route('posts.index');
    }
}

Let us make blade templates for creating categories as well. So you should have a folder category and these blades …resources/views/category/create.blade.php and …resources/views/category/index.blade.php and update them as follows.

create.blade.php


@extends('layouts.app')

@section('content')
    <div class="container">
        <div class="row justify-content-center">
            <div class="col-md-12">
                <br>
                <div class="card">
                    <div class="card-header">Category - create</div>

                    <div class="card-body">
                        {!! Form::open(['route' => 'categories.store']) !!}

                        <div class="form-group @if($errors->has('name')) has-error @endif">
                            {!! Form::label('Name') !!}
                            {!! Form::text('name', null, ['class' => 'form-control', 'placeholder' => 'Name']) !!}
                            @if ($errors->has('name'))
                                <span class="help-block">{!! $errors->first('name') !!}</span>@endif
                        </div>

                        {!! Form::submit('Create',['class' => 'btn btn-sm btn-primary']) !!}
                        {!! Form::close() !!}
                    </div>
                </div>
            </div>
        </div>
    </div>
@endsection

index.blade.php

@extends('layouts.app')

@section('content')
    <div class="container">
        <div class="row">
            <div class="col">
            <br>
                @if(Session::has('message'))
                    <div class="alert alert-success alert-dismissible">
                        <button type="button" class="close" data-dismiss="alert" aria-hidden="true">x</button>
                        {{ Session('message') }}
                    </div>
                @endif
             
                    @if(Session::has('delete-message'))
                        <div class="alert alert-danger alert-dismissible">
                            <button type="button" class="close" data-dismiss="alert" aria-hidden="true">x</button>
                            {{ Session('delete-message') }}
                        </div>
                    @endif
            </div>
        </div>
       

        <div class="row justify-content-center">
            <div class="col-md-12">
                <br>
                <div class="card">
                    <div class="card-header">
                        Category - list
                        <a href="{{ route('categories.create') }}" class="btn btn-sm btn-primary float-right">Add
                            New</a>
                    </div>

                    <div class="card-body">
                        <table class="table table-bordered mb-0">
                            <thead>
                            <tr>
                                <th scope="col" width="60">#</th>
                                <th scope="col">Name</th>
                                <th scope="col" width="200">Created By</th>
                                <th scope="col" width="150">Action</th>
                            </tr>
                            </thead>

                            <tbody>
                            @foreach($categories as $category)
                                <tr>
                                    <td>{{ $category->id }}</td>
                                    <td>{{ $category->name }}</td>
                                    <td>{{ $category->user->name }}</td>
                                    <td>
                                        <a href="{{ route('categories.edit', $category->id) }}"
                                           class="btn btn-sm btn-primary">Edit</a>
                                        <button class="btn btn-danger btn-flat btn-sm remove-user" data-id="{{ $category->id }}" data-action="{{ route('categories.destroy', $category->id) }}"> Delete</button>
                                    </td>
                                </tr>
                            @endforeach
                            </tbody>
                        </table>
                    </div>
                </div>
            </div>
        </div>
    </div>
@endsection

We also need to create a CategoryContrller for handling the categories.

Run this command:

php artisan make:controller CategoryController --resource

CategoryController

<?php
namespace App\Http\Controllers;

use App\Models\Category;
use Illuminate\Support\Str; 
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Session;

class CategoryController extends Controller
{
      /**
     * Display a listing of the resource.
     *
     * @return \Illuminate\Http\Response
     */
    public function index()
    {
        $categories = Category::orderBy('id', 'DESC')->get();
        return view('category.index', compact('categories'));
    }

    /**
     * Show the form for creating a new resource.
     *
     * @return \Illuminate\Http\Response
     */
    public function create()
    {
        return view('category.create');
    }

    /**
     * Store a newly created resource in storage.
     *
     * @param  \Illuminate\Http\Request $request
     * @return \Illuminate\Http\Response
     */
    public function store(Request $request)
    {
        $this->validate($request, [
            'name' => 'required|unique:categories'
        ],
            [
                'name.required' => 'Enter name',
                'name.unique' => 'Category already exist',
            ]);

        $category = new Category();
        $category->user_id = Auth::id();
        $category->name = $request->name;
        $category->slug = Str::slug($request->name);
        $category->save();

        Session::flash('message', 'Category created successfully');
        return redirect()->route('categories.index');

    }

    /**
     * Display the specified resource.
     *
     * @param  \App\Category $category
     * @return \Illuminate\Http\Response
     */
    public function show(Category $category)
    {
        //
    }

    /**
     * Show the form for editing the specified resource.
     *
     * @param  \App\Category $category
     * @return \Illuminate\Http\Response
     */
    public function edit(Category $category)
    {

    }

    /**
     * Update the specified resource in storage.
     *
     * @param  \Illuminate\Http\Request $request
     * @param  \App\Category $category
     * @return \Illuminate\Http\Response
     */
    public function update(Request $request, Category $category)
    {
       
    }

    /**
     * Remove the specified resource from storage.
     *
     * @param  \App\Category $category
     * @return \Illuminate\Http\Response
     */
    public function destroy(Category $category)
    {
        $category->delete();

        Session::flash('delete-message', 'Category deleted successfully');
        return redirect()->route('categories.index');
    }
}

Now you need to update the dashboard.blade.php which comes with jetstream to show all the created posts so go to …resources/views/dashboard.blade.php and update it as follows.

dashboard.blade.php

<x-app-layout>
<x-slot name="header">
        <h2 class="font-semibold text-xl text-gray-800 leading-tight">
            {{ __('Dashboard') }}
        </h2>
    </x-slot>

@section('content')
    <div class="container">
        <div class="row">
            <div class="col">
            <br>
                @if(Session::has('message'))
                    <div class="alert alert-success alert-dismissible">
                        <button type="button" class="close" data-dismiss="alert" aria-hidden="true">x</button>
                        {{ Session('message') }}
                    </div>
                @endif

                @if(Session::has('delete-message'))
                    <div class="alert alert-danger alert-dismissible">
                        <button type="button" class="close" data-dismiss="alert" aria-hidden="true">x</button>
                        {{ Session('delete-message') }}
                    </div>
                @endif
            </div>
        </div>

        <div class="row justify-content-center">
            <div class="col-md-12">
                <br>
                <div class="card">
                    <div class="card-header">
                        Post - list
                        <a href="{{ route('posts.create') }}" class="btn btn-sm btn-primary float-right">Add
                            New</a>
                    </div>

                    <div class="card-body">
                        <table class="table table-bordered mb-0">
                            <thead>
                            <tr>
                                <th scope="col" width="60">#</th>
                                <th scope="col">Title</th>
                                <th scope="col" width="200">Body</th>
                                <th scope="col" width="200">Created By</th>
                                <th scope="col" width="150">Action</th>
                            </tr>
                            </thead>

                            <tbody>
                            @foreach($posts as $post)
                                <tr>
                                    <td>{{ $post->id }}</td>
                                    <td>{{ $post->title }}</td>
                                    <td>{!! $post->details !!}</td>
                                    <td>{{ $post->user->name }}</td>
                                    <td>
                                        <a href="{{ route('posts.edit', $post->id) }}"
                                           class="btn btn-sm btn-primary">Edit</a>
                                        {!! Form::open(['route' => ['posts.destroy', $post->id], 'method' => 'delete', 'style' => 'display:inline']) !!}
                                        {!! Form::submit('Delete', ['class' => 'btn btn-sm btn-danger']) !!}
                                        {!! Form::close() !!}
                                    </td>
                                </tr>
                            @endforeach
                            </tbody>
                        </table>
                    </div>
                </div>
            </div>
        </div>
    </div>   
@endsection
</x-app-layout>

Before we go to web.php let’s implement three concepts remaining. In the welcome.blade.php update, it is as follows to show our created posts from the backend.

welcome.blade.php

<x-guest-layout>
@section('content')
<div class="heading text-center pt-3">
          <h1>Web Dev Tutorials</h1>
      </div>
 
<section>
  <div class="container">
    <div class="row">
    @foreach($posts as $post)
    <div class="col-md-4">
        <div class="card">
            <div class="bg-image hover-overlay ripple" data-mdb-ripple-color="light">
              <img src="{{url('/images/web-1.jpg')}}" class="img-fluid"/>
              <a href="#">
              <div class="mask" style="background-color: rgba(251, 251, 251, 0.15);"></div>
              </a>
            </div>
            <div class="card-body">
              <h5 class="card-title">{{ $post->title }}</a></h5>
              <p class="card-text">
              {!! $post->details !!}
              </p>
              
              <p class="post-meta">Posted by
                            <a href="#">{{ $post->user->name }}</a>
                            on {{ date('M d, Y', strtotime($post->created_at)) }}
              </p>   @foreach($post->categories as $category)
                                        <a href="{{ url('category/' . $category->slug) }}">{{ $category->name }}</a>,
                                    @endforeach
            </div>  
          </div>
          </div>
          @endforeach 
         
       </section>
          </x-guest-layout>

Since this represents the frontend let us make a controller i.e PagesController to handle all the frontend stuff in our app.

Run the following command:

php artisan make:controller PagesController

PagesController.php

<?php
namespace App\Http\Controllers;

use App\Models\Post;
use App\Models\Category;
use Illuminate\Http\Request;

class PagesController extends Controller
{
    public function index() {
        $posts = Post::orderBy('id', 'DESC')->paginate(3);
        $categories = Category::orderBy('name', 'ASC')->get();
        return view('welcome', compact('posts', 'categories'));      
    }

    public function category($slug)
    {
        $category = Category::where('slug', $slug)->first();

        if ($category) {
            $posts = $category->posts()->orderBy('posts.id', 'DESC')->paginate(5);
            return view('category', compact('category', 'posts'))
                  ->with('categories', Category::all());
        }
    }
   }

One thing you need to know our categories have a slug this enables us to view posts related to particular categories.

So we will create a …resources/views/category.blade.php which will show all the posts belonging to a particular category.

category.blade.php

@extends('layouts.app')

@section('content')

<!-- Main Content -->
<section>
  <div class="container">
    <div class="row">
    <div class="col-lg-8 col-md-8 mx-auto">
                @foreach($posts as $post)
                 <div class="blog-entry">
                    <a href="#"><img src="#" alt="" class="img-fluid mb30"></a>
                        <a href="#">
                            <h2 class="post-title">
                                {{ $post->title }}
                            </h2>
                        </a>

                        <p class="post-meta">Posted by
                            <a href="#">{{ $post->user->name }}</a>
                            on {{ date('M d, Y', strtotime($post->created_at)) }}
                            @if(count($post->categories) > 0)
                                | <span class="post-category">
                            Category :
                                    @foreach($post->categories as $category)
                                        <a href="{{ url('category/' . $category->slug) }}">{{ $category->name }}</a>,
                                    @endforeach
                        </span>
                            @endif
                        </p>
                        </div>
                    <hr>
            @endforeach
            </div>
          </div>
        <div>
    </section>
@endsection()

Alright so we are done with almost everything let’s look at web.php

web.php

use Illuminate\Support\Facades\Route;
use App\Http\Controllers\PostsController;
use App\Http\Controllers\CategoryController;

/*
|--------------------------------------------------------------------------
| Web Routes
|--------------------------------------------------------------------------
|
| Here is where you can register web routes for your application. These
| routes are loaded by the RouteServiceProvider within a group which
| contains the "web" middleware group. Now create something great!
|
*/

Route::get('/', function () {
    return view('welcome');
});

Route::middleware(['auth:sanctum', config('jetstream.auth_session'), 'verified'
])->group(function () {
    Route::get('/dashboard', 'App\Http\Controllers\PostsController@index', function () {
        return view('dashboard');
    })->name('dashboard');
});


// web
Route::get('/', 'App\Http\Controllers\PagesController@index');
Route::get('category/{slug}', 'App\Http\Controllers\PagesController@category')->name('category');


// admin
Route::resources([
    'posts' => PostsController::class,
    'categories' => CategoryController::class
]);

Also, update the layouts/app.blade.php and layouts/guest.blade.php as follows

app.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">
        <meta name="csrf-token" content="{{ csrf_token() }}">

        <title>{{ config('app.name', 'Laravel') }}</title>

        <!-- Fonts -->
        <link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Nunito:wght@400;600;700&display=swap">

        <!-- Styles -->
        <link rel="stylesheet" href="{{ mix('css/app.css') }}">
        <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@4.6.1/dist/css/bootstrap.min.css" integrity="sha384-zCbKRCUGaJDkqS1kPbPd7TveP5iyJE0EjAuZQTgFLD2ylzuqKfdKlfG/eSrtxUkn" crossorigin="anonymous">


        @livewireStyles

        <!-- Scripts -->
        <script src="{{ mix('js/app.js') }}" defer></script>
    </head>
    <body class="font-sans antialiased">
        <x-jet-banner />

        <div class="min-h-screen bg-gray-100">
            @livewire('navigation-menu')

            <!-- Page Heading -->
            @if (isset($header))
                <header class="bg-white shadow">
                    <div class="max-w-7xl mx-auto py-6 px-4 sm:px-6 lg:px-8">
                        {{ $header }}
                    </div>
                </header>
            @endif

            <!-- Page Content -->
            <main>
             @yield('content')
            </main>
        </div>

        @stack('modals')

        @livewireScripts
    </body>
</html>

guest.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">
        <meta name="csrf-token" content="{{ csrf_token() }}">

        <title>{{ config('app.name', 'Laravel') }}</title>

        <!-- Fonts -->
        <link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Nunito:wght@400;600;700&display=swap">

        <!-- Styles -->
        <link rel="stylesheet" href="{{ mix('css/app.css') }}">
        <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@4.6.1/dist/css/bootstrap.min.css" integrity="sha384-zCbKRCUGaJDkqS1kPbPd7TveP5iyJE0EjAuZQTgFLD2ylzuqKfdKlfG/eSrtxUkn" crossorigin="anonymous">

        <!-- Scripts -->
        <script src="{{ mix('js/app.js') }}" defer></script>
    </head>
    <body>
        <div class="font-sans text-gray-900 antialiased">
       {{$slot}}
        </div>
    </body>
</html>

You are good to go so you should expect the following results.

Sign up for free tutorials in your inbox.

We don’t spam! Read our privacy policy for more info.

Leave a Comment

Your email address will not be published. Required fields are marked *