l2t

Laravel Eloquent: One To Many Relationship

One To Many Relationship in Laravel Eloquent

What is One to Many relationship in laravel?

A one-to-many relationship is a very basic type of database relationship. When one table refers to many rows in another table that is called a one-to-many relationship.

Let's take following diagram:

  • Imagine you have posts table and categories table
  • Category may have many posts i.e. you can find all posts that belongs to Laravel category

You can now say that you can find many posts that belongs to single category therefore this type of relationship is called one-to-many where one category has many posts.

How to define One to Many relationship in Laravel?

In order to create this type of relationship in Laravel we would have to create two migrations for our categories and posts table. Go to your laravel root folder and open up your terminal and run following commands:

It is important you follow order in order to generate migrations due to the fact that we would have to add foreign key constraint in post table.

# create post categories table
php artisan make:migration create_categories_table

# create post table
php artisan make:migration create_posts_table​

Alright, above command will generate two migrations in database/migrations folder:

Categories table migration would look like following:

<?php

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

class CreateCategoriesTable extends Migration
{
    /**
     * Run the migrations.
     *
     * @return void
     */
    public function up()
    {
        Schema::create('categories', function (Blueprint $table) {
            $table->id();
            $table->string('title');
            $table->string('description');
            $table->timestamps();
        });
    }

    /**
     * Reverse the migrations.
     *
     * @return void
     */
    public function down()
    {
        Schema::dropIfExists('categories');
    }
}

Posts table migration would look like following:

You can add more columns as you need for demo purpose we choose to use minimal columns. We also have to add foreign key constraint here.

<?php

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

class CreatePostsTable extends Migration
{
    /**
     * Run the migrations.
     *
     * @return void
     */
    public function up()
    {
        Schema::create('posts', function (Blueprint $table) {
            $table->id();
            $table->string('title');

            // define foreign key
            $table->foreignId('category_id')
                  ->constrained('categories')
                  ->onUpdate('cascade')
                  ->onDelete('cascade');

            $table->timestamps();
        });
    }

    /**
     * Reverse the migrations.
     *
     * @return void
     */
    public function down()
    {
        Schema::dropIfExists('posts');
    }
}

Generate Eloquent Models Using Command Line:

Now, that we have our table migrations ready we would create models based on above table structure. Open your terminal again and run following command to create two models as show below:

# create post model
php artisan make:model Post

# create post category model
php artisan make:model Category​

Above command will create models in app/Models directory.

Define One-to-Many relation in Laravel Model

To define category has many posts we have to define this relationship in our Category model. Now, open your Category model and add following function to define relationship to Post model.

<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Database\Eloquent\Factories\HasFactory;

class Category extends Model
{
    use HasFactory;

    /**
     * @return HasMany
     * @description get all posts for the category
     */
    public function posts(): BelongsTo
    {
        return $this->hasMany(Post::class);
    }
}

Using above defined relation we would be able to fetch all posts that belongs to this category. Let's see how we can achieve this by writing some real examples:

use App\Models\Category;

// fetch category with id=1
// then fetch all the posts that
// belongs to this category
$posts = Category::find(1)->posts;

foreach ($posts as $post) {
    // dd($post);
}

// fetch only single post
$singlePost = Category::find(1)->posts()->first();

// fetch category with id=1 then
// fetch all the posts where title is laravel
$laravelPosts = Category::find(1)->post()->where('title', 'laravel')->all();

Defining The Inverse Of The Relationship (One To Many (Inverse) / Belongs To)

Now, open your Post model and add following function. The idea is when we have a post we can find related category using below relationship.

<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Factories\HasFactory;

class Post extends Model
{
    use HasFactory;

    /**
     * @return BelongsTo
     * @description get the category for the blog post.
     */
    public function category(): HasOne
    {
        return $this->belongsTo(Category::class);
    }
}

Let's take a look at some example how we can fetch this relationship:

use App\Models\Post;

// find post by id 1 and then
// fetch matching record from Category model
$postCategory = Post::find(1)->category;

In the example above, Eloquent will attempt to find a Category model that has an id which matches the category_id column on the Post model.

Sometime you may use different column name in other table. Let say instead of using post_id you preffered to use blog_id in your details table. The diagram would now look like following:

If you are using different column then category_id you would have to make changes to your migration file for post and add following new changes:

$table->foreignId('cat_id')
    ->constrained('categories')
    ->onUpdate('cascade')
    ->onDelete('cascade');

Run your migrations again. You would also have to make changes in your appropriate models to accomodate this new change:

# change following line in your Post Model
return $this->belongsTo(Category::class);

# change above line to 
return $this->belongsTo(Category::class, 'cat_id');

It is preffered to use laravel standard naming conventions for your table names and their foregin keys otherwise you would have to make additional changes to make that happen.

Laravel is flexible enough to make those changes however sticking to naming standards will reduce your time and effort.

Writing seeder for One to Many relationship

If you want to create some local data using above one to many relationship in your laravel applications. Open your terminal and run following command to create two factories:

# create category factory
php artisan make:factory Category

# create post factory
php artisan make:factory Post​

Above command will create two factory classes under database/factories folder:

CategoryFactory class:

<?php

namespace Database\Factories;

use App\Models\Category;
use Illuminate\Database\Eloquent\Factories\Factory;

class CategoryFactory extends Factory
{
    /**
     * The name of the factory's corresponding model.
     *
     * @var string
     */
    protected $model = Category::class;

    /**
     * Define the model's default state.
     *
     * @return array
     */
    public function definition()
    {
        return [
            'title' => $this->faker->title(),
            'description' => $this->faker->text(),
        ];
    }
}

PostFactory class:

<?php

namespace Database\Factories;

use App\Models\Post;
use Illuminate\Database\Eloquent\Factories\Factory;

class PostFactory extends Factory
{
    /**
     * The name of the factory's corresponding model.
     *
     * @var string
     */
    protected $model = Post::class;

    /**
     * Define the model's default state.
     *
     * @return array
     */
    public function definition()
    {
        return [
            'title' => $this->faker->title(),
        ];
    }
}

Once you have this classes ready open your DatabaseSeeder.php file located in database/seeders folder and add following lines of code:

<?php

namespace Database\Seeders;

use App\Models\Post;
use App\Models\Category;
use Illuminate\Database\Seeder;

class DatabaseSeeder extends Seeder
{
    /**
     * Seed the application's database.
     *
     * @return void
     */
    public function run()
    {
        // create 10 categories
        factory(App\Models\Category::class, 10)->create()->each(function ($category) {

            // Create 10 posts for each category
            $posts = factory(App\Models\Post::class, 10)->make();
            $category->posts()->saveMany($posts);

        });
    }
}

Once your seeder is defined as above you can run following command to generate some fake data in your table.

php artisan db:seed

I hope I was able to explain this useful cocept of One to One relationship in laravel more in depth. Stay tune for my upcoming tutorials on other types of relationships in laravel.