Laravel Eloquent: One To One Relationship

What is One to One relationship in laravel?

A one-to-one relationship is a very basic type of database relationship. When one table refers to single row in another table that is called a one-to-one relationship. There should only be one matching record found in another table where you have one-to-one relationship defined.

Let's take following diagram:

Imagine you are developing a blog website. You have posts table, each post has title and description associated to it. Let's imagine you have a table called details where you define single post related information.

Followings are some of the constraints:

  • Every post should have only one detail associted with it.
  • You can not have more details for single post.

In this case because every post has only single details associated with it you can call this relationship to be one-to-one.

How to define One to One relationship in Laravel?

In order to create above relationship in laravel framework we would first need to create two migrations. Go to root directory of your laravel project and in your terminal run following commands to create two database migrations:

# create post table
php artisan make:migration create_posts_table

# create post details table
php artisan make:migration create_details_table​

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

Post table migration file would look like following:

<?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->timestamps();
        });
    }

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

Details table migration would look like following (add columns shown below):

  • add title, description columns
  • define foregin key constraint in details table because it refers to post table
<?php

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

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

            $table->string('title');
            $table->string('description');

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

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

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

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 detail model
php artisan make:model Detail

Above command will create models in app/Models directory. Now, open your Post model and make following changes to define one-to-one relationship:

<?php

namespace App\Models;

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

class Post extends Model
{
    use HasFactory;

    /**
     * @return HasOne
     * @description get the detail associated with the post
     */
    public function detail(): HasOne
    {
        return $this->hasOne(Detail::class);
    }
}

Using above changes you are telling eloquent that your post model has a matching record in Detail table. In order to access this matching record you can run following query:

use App\Models\Post;

// find post by id 1 and then
// fetch matching record from Detail model
$postDetail = Post::find(1)->detail;

Defining The Inverse Of The Relationship (One to One (inverse))

Now, you know how to access detail from post model however sometime you have detail and you want to access post associated with it. You can retrive the post from the detail model .

In order to do that we would have to define the inverse relationship in your Detail model. Open your Detail model and make following changes to define inverse relationship:

<?php

namespace App\Models;

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

class Detail extends Model
{
    use HasFactory;

    /**
     * @return BelongsTo
     * @description Get the post that owns the details
     */
    public function post(): BelongsTo
    {
        return $this->belongsTo(Post::class);
    }
}

We know Details belongs to the post. Using above defined function using belongsTo relationship you can now access Post from Detail model as shown below:

use App\Models\Detail;

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

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:

You have to change your migration to have this new foreign key:

$table->foreignId('blog_id')
      ->constrained('posts')
      ->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->hasOne(Detail::class);

# change above line to 
return $this->hasOne(Detail::class, 'blog_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 One relationship

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

# create post factory
php artisan make:factory Post

# create detail factory
php artisan make:factory Detail​

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

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 [
            //
        ];
    }
}

DetailFactory class:

<?php

namespace Database\Factories;

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

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

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

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\Detail;
use Illuminate\Database\Seeder;

class DatabaseSeeder extends Seeder
{
    /**
     * Seed the application's database.
     *
     * @return void
     */
    public function run()
    {
        $posts = Post::factory(10)->create();
        $posts->each(function ($u) {
            $u->detail()->save(Detail::factory()->make());
        });
    }
}

Once your seeder is defined as above you can run following command to generate some fake data in your table. Aboove code will add 10 fake post and detail to your database.

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.