Laravel Eloquent - Relationship One to Many

Pada artikel kali ini kita akan membahas mengenai Laravel Eloquent Relationship untuk tipe One to Many atau hasMany

Arman Dwi Pangestu

22 Januari 20241 menit baca

Pendahuluan

Setelah pada pembahasan sebelumnya kita membahas relationship One to One atau hasOne dan relationship invers nya yaitu belongsTo, pada pembahasan kali ini kita akan membahas relationship One to Many atau hasMany.

One to Many

Sebuah relationship one-to-many digunakan untuk mendefinisikan hubungan dimana satu model adalah parent atau induk dari satu atau lebih model turunan. Misalnya, sebuah postingan blog mungkin memiliki jumlah comment atau komentar yang tidak terbatas. Agar lebih jelas atau terbayang mengenai relationship antar model Post dan Comment tersebut kalian bisa lihat gambar dibawah ini

Catatan: Tips

Seperti pada semua Eloquent Relationship, relationship one-to-many juga di definisikan melalui sebuah method pada model Eloquent

Design Model Post and Model Comment

Model Post

Untuk membuat sebuah relationship dengan studi kasus satu postingan blog memiliki jumlah komentar yang tidak terbatas, kita perlu membuat model, migration, factory dan seeder nya terlebih dahulu. Kita bisa mulai membuat dari model Post beserta migartion, factory dan seeder nya secara sekaligus menggunakan perintah artisan berikut ini:

Catatan: Tips

Flag atau option -mfs disini artinya kita buat model sekaligus migration, factory, dan juga seeder nya

php artisan make:model -mfs Post

Selanjutnya kita ubah file model Post nya di app\Models\Post.php

<?php

namespace App\Models;

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

class Post extends Model
{
    use HasFactory;

    protected $guarded = ['id'];
}

Migration Post

Sekarang kita sesuaikan kode migration nya agar kolom dari tabel posts nya sesuai dengan design yang sudah saya buat, file migration tersebut berada di database\migration\2024_01_22_072537_create_posts_table.php

<?php

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

return new class extends Migration
{
    public function up(): void
    {
        Schema::create('posts', function (Blueprint $table) {
            $table->id();
            $table->foreignId('user_id');
            $table->string('title');
            $table->string('slug')->unique();
            $table->text('excerpt');
            $table->text('body');
            $table->timestamps();
        });
    }

    public function down(): void
    {
        Schema::dropIfExists('posts');
    }
};

Factory Post

Selanjutnya kita siapkan data dummy untuk model Post tersebut menggunakan faker di file factory database\factories\PostFactory.php

<?php

namespace Database\Factories;

use Illuminate\Database\Eloquent\Factories\Factory;

class PostFactory extends Factory
{
    public function definition(): array
    {
        return [
            'user_id' => mt_rand(1, 5),
            'title' => fake()->sentence(mt_rand(2, 8)),
            'slug' => fake()->slug(),
            'excerpt' => fake()->paragraph(),
            'body' => collect(fake()->paragraphs(mt_rand(5, 10)))
                ->map(fn ($p) => "<p>$p</p>")
                ->implode('')
        ];
    }
}

Seeder Post

Setelah factory faker nya sudah siap, sekarang kita eksekusi pembuatan data dummy nya di file database\seeders\PostSeeder.php

<?php

namespace Database\Seeders;

use App\Models\Post;
use Illuminate\Database\Console\Seeds\WithoutModelEvents;
use Illuminate\Database\Seeder;

class PostSeeder extends Seeder
{
    public function run(): void
    {
        Post::factory(20)->create();
    }
}

Jangan lupa kita panggil class PostSeeder tersebut di file database\seeders\DatabaseSeeder.php

<?php

namespace Database\Seeders;

use Illuminate\Database\Seeder;

class DatabaseSeeder extends Seeder
{
    public function run(): void
    {
        $this->call([
            UserSeeder::class,
            PhoneSeeder::class,
            PostSeeder::class
        ]);
    }
}

Setelah semua nya siap, sekarang kita jalankan migration nya menggunakan perintah artisan berikut ini

php artisan migrate:fresh --seed

Maka sekarang seharusnya sudah terbuat sebuah tabel baru dengan nama posts didalam database nya dengan isian kolom dan data dummy seperti berikut ini

Column Data Type
id bigint unsigned
user_id bigint unsigned
title varchar(255)
slug varchar(255)
excerpt text
body text
created_at timestamp
updated_at timestamp

Data Dummy Model Post

Model Comment

Setelah model Post siap, selanjutnya kita siapkan untuk model Comment, untuk membuat model, migration, factory dan seeder nya secara sekaligus kita bisa gunakan perintah artisan berikut ini:

php artisan make:model -mfs Comment

Selanjutnya kita ubah file model Comment nya di app\Models\Comment.php

<?php

namespace App\Models;

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

class Comment extends Model
{
    use HasFactory;

    protected $guarded = ['id'];
}

Migration Comment

Sekarang kita sesuaikan kode migration nya agar kolom dari tabel comments sesuai dengan design yang sudah saya buat, file migration tersebut berada di database\migrations\2024_01_22_074358_create_comments_table.php

<?php

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

return new class extends Migration
{
    public function up(): void
    {
        Schema::create('comments', function (Blueprint $table) {
            $table->id();
            $table->foreignId('post_id');
            $table->foreignId('user_id');
            $table->text('body');
            $table->timestamps();
        });
    }

    public function down(): void
    {
        Schema::dropIfExists('comments');
    }
};

Factory Comment

Selanjutnya kita siapkan data dummy untuk model Comment tersebut menggunakan faker di file factory database\factories\CommentFactory.php

<?php

namespace Database\Factories;

use Illuminate\Database\Eloquent\Factories\Factory;

class CommentFactory extends Factory
{
    public function definition(): array
    {
        return [
            'post_id' => mt_rand(1, 20),
            'user_id' => mt_rand(1, 5),
            'body' => fake()->paragraph()
        ];
    }
}

Seeder Comment

Setelah factory faker nya sudah siap, sekarang kita bisa eksekusi pembuatan data dummy nya di file database\seeders\CommentSeeder.php

<?php

namespace Database\Seeders;

use App\Models\Comment;
use Illuminate\Database\Console\Seeds\WithoutModelEvents;
use Illuminate\Database\Seeder;

class CommentSeeder extends Seeder
{
    public function run(): void
    {
        Comment::factory(40)->create();
    }
}

Jangan lupa kita panggil class CommentSeeder tersebut di file database\seeders\DatabaseSeeder.php

<?php

namespace Database\Seeders;

use Illuminate\Database\Seeder;

class DatabaseSeeder extends Seeder
{
    public function run(): void
    {
        $this->call([
            UserSeeder::class,
            PhoneSeeder::class,
            PostSeeder::class,
            CommentSeeder::class
        ]);
    }
}

Setelah semuanya siap, sekarang kita bisa jalankan migration nya menggunakan perintah artisan berikut ini:

php artisan migration:fresh --seed

Maka sekarang seharusnya sudah terbuat sebuah tabel baru dengan nama comments didalam database dengan isian kolom dan dummy data seperti berikut ini

Column Data Type
id bigint unsigned
post_id bigint unsigned
user_id bigint unsigned
body text
created_at timestamp
updated_at timestamp

Data Dummy Model Comment

Membuat Method hasMany

Setelah kita siapkan model, migration, factory dan seeder untuk masing-masing model yaitu Post dan Comment. Sekarang kita akan membuat method hasMany di parent model atau model Post agar membuat sebuah relationship one-to-many

<?php

namespace App\Models;

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

class Post extends Model
{
    use HasFactory;

    protected $guarded = ['id'];

    public function comments(): HasMany
    {
        return $this->hasMany(Comment::class);
    }
}

Perlu kalian ingat, Eloquent akan secara otomatis menentukan kolom foreign key yang tepat untuk model Comment. Berdasarkan konvensi atau aturan Laravel, Eloquent akan mengambil nama "snake case" dari parent atau induk model dan menambahkan akhiran atau suffix nya dengan _id. Jadi, dalam contoh ini, Eloquent akan menganggap nama kolom dari foreign key post pada model Comment adalah post_id.

Setelah method relationship di definisikan, kita dapat mengakses collection atau kumpulan komentar terkait post nya dengan mengakses dynamic property comments. Ingat, karena Eloquent menyediakan "dynamic relationship properties", maka kita dapat mengakses relationship method tersebut seolah-olah method tersebut di definisikan sebagai properti pada model.

Mengakses atau Menjalankan Relation One to Many

Untuk mencoba nya seperti biasa kita bisa gunakan shell tinker dengan cara masuk terlebih dahulu kedalam shell nya menggunakan perintah berikut ini

php artisan tinker

Jika sudah didalam shell nya, sekarang kita bisa buat sebuah variabel dengan nama comments yang value nya adalah model Post yang men-chaining relationship method atau dynamic property nya

Catatan: Tips

Nama dari dynamic property Eloquent comments berikut ini adalah merepresentasikan nama relationship method yang ada di model Post

$comments = App\Models\Post::find(1)->comments;

Maka seharusnya perintah diatas akan me-return value dari data komentar yang terkait dengan postingan nya

= Illuminate\Database\Eloquent\Collection {#6034
    all: [
      App\Models\Comment {#6037
        id: 9,
        post_id: 1,
        user_id: 2,
        body: "Aut impedit occaecati aut ea et magnam. Aut tempora exercitationem impedit cumque possimus. Quasi commodi vero aut eos molestiae ea.",
        created_at: "2024-01-22 07:51:47",
        updated_at: "2024-01-22 07:51:47",
      },
      App\Models\Comment {#6038
        id: 19,
        post_id: 1,
        user_id: 5,
        body: "Consequuntur et iure aperiam. Numquam velit consequatur omnis dolorem quia omnis laborum. Dolore quia et sint unde nulla.",
        created_at: "2024-01-22 07:51:47",
        updated_at: "2024-01-22 07:51:47",
      },
    ],
  }

Tinker Get Comment Post

Karena semua relationship juga berfungsi sebagai pembuat query, jika kalian ingin menambahkan atau memberikan batasan atau kondisi tertentu lebih lanjut pada query relationship tersebut, kalian bisa men-chaining nya seperti berikut ini

$comment = Post::find(1)->comments()->where('title', 'foobar')->first();

Format atau Aturan Penulisan

Foreign Key dan Primary Key

Sama seperti method hasOne, jika kalian ingin mengganti atau menggunakan nama lain dari column foreign key dan primary key yang digunakan sebagai relationship, kalian bisa menambahkan nya pada argument kedua dan ketiga di method hasMany nya seperti berikut ini:

public function comments(): HasMany
{
    return $this->hasMany(Comment::class, 'foreign_key', 'local_key');
}

Inverse Relationship

Setelah mendefinisikan relationship hasMany atau One to Many dari model Post ke model Comment, maka kita sekarang dapat mengakses model Comment secara langsung dari model Post dengan cara men-chaining method comments pada instance Post. Selanjutnya, kita akan tentukan relationship kebalikannya atau inverse dari model Comment ke model Post, relationship inverse tersebut nantinya memungkinkan kita dapat mengakses data post secara langsung melalui model Comment. Kita dapat mendefinisikan invers relationship dari method hasMany dengan menggunakan method belongsTo.

Agar lebih terbayang, kalian bisa lihat gambar dibawah ini mengenai relationship hasMany dari model Post ke model Comment dan belongsTo (inverse) dari model Comment ke model Post

Invers Relation Design

Membuat Method belongsTo

Untuk membuat inverse relation nya, kita perlu membuat sebuah method dengan nama post di model Comment. Method post tersebut harus memanggil method belongsTo dan mengembalikan nilai atau return value nya.

<?php

namespace App\Models;

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

class Comment extends Model
{
    use HasFactory;

    protected $guarded = ['id'];

    public function post(): BelongsTo
    {
        return $this->belongsTo(Post::class);
    }
}

Pada contoh diatas, Eloquent akan beruasaha mencari model Post yang memiliki id yang cocok dengan kolom post_id pada model Comment.

Eloquent menentukan foreign key secara default dengan cara memeriksa nama relationship method nya dan menambahkan akhiran atau suffix _ diikuti dengan nama kolom primary key nya pada parent atau induk model. Jadi, dalam contoh ini, Eloquent akan menganggap bahwa foreign key model Post di tabel comments adalah post_id

  • post = nama method yang di definisikan pada model Comment
  • _ = otomatis ditambahkan oleh eloquent
  • id = nama primary key yang ada di parent atau induk model yaitu Post

Namun seperti biasa, jika kalian tidak menggunakan aturan standar dari Laravel misalkan nama kolom dari foreign key dan primary key nya berbeda, kalian bisa menambahkannya pada argument keuda dan ketiga di method belongsTo

public function post(): BelongsTo
{
    return $this->belongsTo(Post::class, 'foreign_key', 'owner_key');
}

Mengakses atau Menjalankan Relation belongsTo

Setelah inverse relationship ditentukan, sekarang kita dapat mengambil data postingan yang berada di parent atau induk model nya melalui model Comment dengan cara mengakses "dynamic relationship property" seperti berikut ini

Catatan: Tips

Kalian perlu melakukan restart session tinker nya dengan cara exit kemudian masuk kembali agar pembaruan kode yang sudah dilakukan dapat dijalankan

$post = App\Models\Comment::find(1)->post

Maka seharusnya perintah diatas akan me-return value data postingan yang terkait dengan komentar nya

= App\Models\Post {#6033
    id: 18,
    user_id: 3,
    title: "Natus at quibusdam voluptatum ipsam iusto dolorum maxime qui ea.",
    slug: "ipsa-dolor-excepturi-ut",
    excerpt: "Tempore commodi accusantium et assumenda dolor et aspernatur et. Quo quo ullam repellendus occaecati ab labore est natus. Velit labore totam praesentium aut quae distinctio aut.",
    body: "<p>Cum maiores ab non error et exercitationem. Soluta iure saepe omnis hic harum beatae natus. Qui harum accusamus quia incidunt incidunt autem.</p><p>Illum labore fugiat omnis ut voluptatem ut. Praesentium aut est enim laborum debitis et cumque ea. Ut numquam error odio quis.</p><p>Et voluptas nostrum ut doloribus. Laudantium nihil eum ducimus. Harum reiciendis perspiciatis quisquam est ad unde dolores. Magnam nam odit recusandae tempora illo eligendi. Libero esse alias aut veritatis quos.</p><p>Expedita totam qui non officia quasi necessitatibus quidem. Rerum est qui qui illo. Similique quasi ipsam cum cupiditate a aut. Velit qui consequuntur magnam ex dolor pariatur.</p><p>Quis eum pariatur quod sed vel. Quo vel nisi ab voluptatem. Iure perspiciatis voluptatum ipsum quia. Aut saepe vero hic distinctio ratione doloribus. Dolores at tempore ad odio autem.</p><p>Eos ipsum enim sunt error accusamus tempora. Odit tenetur repudiandae architecto ex error praesentium. Quisquam quidem delectus voluptatum neque est ad quaerat. Modi soluta quibusdam aut.</p><p>Architecto sunt aperiam aut et. Fugiat temporibus quo possimus sunt voluptate dicta. Pariatur quod eaque possimus cum nemo.</p><p>Aut et dolorem qui earum voluptatibus et eius. Suscipit molestiae modi reiciendis deleniti rem. Minus quas quam quia aut non.</p>",
    created_at: "2024-01-22 07:51:47",
    updated_at: "2024-01-22 07:51:47",
  }

Tinker Get Post Comment

Membuat Eloquent Relationship Agar Dapat Diakses Melalui Web

Seperti pada pembahasan One to One, kita sudah mempunyai sebuah controller khusus untuk membuat Eloquent Relationship yang sudah kita buat bisa diakses melalui web, kita bisa tambahkan 2 buah method yaitu oneToMany dan oneToManyInverse pada RelationController

<?php

namespace App\Http\Controllers;

use App\Models\Comment;
use App\Models\Phone;
use Illuminate\Http\Request;

class RelationController extends Controller
{
    ...

    public function oneToMany(Request $request)
    {
        $comments = Post::find($request->id)->comments;
        return $comments;
    }

    public function oneToManyInverse(Request $request)
    {
        $post = Comment::find($request->id)->post;
        return $post;
    }
}

Selanjutnya kita tambahkan routing untuk mengakses method controller tersebut di file routes\web.php

Route::get('/relation/oneToMany', [RelationController::class, 'oneToMany']);
Route::get('/relation/oneToManyInverse', [RelationController::class, 'oneToManyInverse']);
  • One to Many

One to Many Via Web

  • One to Many (Inverse) / Belongs To

One to Many Inverse Via Web