Mengolah Data Relasi Antar Table Menggunakan Eloquent

Mengolah Data Relasi Antar Table Menggunakan Eloquent

Pendahuluan

Seperti kotak serba ada, Eloquent memiliki banyak "cara" menarik untuk menyelesaikan berbagai masalah yang berhubungan dengan database. Bekerja dengan data yang saling berelasi tentu saja membutuhkan query yang sedikit kompleks untuk mengambil data yang terkait. Query join seolah telah khatam di telinga kita namun masih menjadi masalah yang cukup berarti ketika berinteraksi dengannya dengan data yang cukup kompleks. Pada serial ini kita akan belajar bagaimana me-load data yang saling berelasi menggunakan Eloquent dengan teknik join ala Laravel.

Fokus kita kali ini bukan membuat fungsi join-nya tapi bagaimana menggunakannya, adapun bagaimana cara membuat fungsinya sudah dibahas pada artikel Mengenal Relationships di Laravel 5.4.

Baca Juga: Aplikasi Laundry (Laravel 5.8-Vue.js-SPA) #3: Management Outlets

Struktur Data

Tahap pertama adalah kita akan membuat struktur database menggunakan migration, saya asumsikan kamu sudah meng-install Laravel dimana pada artikel ini menggunakan Laravel 5.8. Pada command line, jalankan command:

php artisan make:model Product -m
php artisan make:model Comment -m

Command diatas akan men-generate model beserta dengan migration-nya, buka file migration dari product yang terletak di dalam folder database/migrations dan modifikasi menjadi:

<?php

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

class CreateProductsTable extends Migration
{
    /**
     * Run the migrations.
     *
     * @return void
     */
    public function up()
    {
        Schema::create('products', function (Blueprint $table) {
            $table->bigIncrements('id');
            $table->unsignedBigInteger('user_id');
            $table->string('name');
            $table->string('slug');
            $table->text('description');
            $table->integer('price');
            $table->boolean('status');
            $table->timestamps();
        });
    }

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

Selanjutnya migration untuk comment, modifikasi menjadi:

<?php

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

class CreateCommentsTable extends Migration
{
    /**
     * Run the migrations.
     *
     * @return void
     */
    public function up()
    {
        Schema::create('comments', function (Blueprint $table) {
            $table->bigIncrements('id');
            $table->unsignedBigInteger('user_id');
            $table->unsignedBigInteger('product_id');
            $table->text('comment');
            $table->timestamps();
        });
    }

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

Adapun penjelasan terkait migration telah dibahas pada artikel yang berbeda dengan judul, Migration, Fitur Ajaib Laravel.

Kembali ke command line, jalankan command php artisan migrate tapi sebelumnya pastikan kamu telah mengatur file .env untuk disesuaikan dengan informasi database yang kamu punya.

Database Seeding

Struktur table yang diinginkan telah tersedia, tugas selanjutnya ada membuat dummy data untuk dijadikan bahan uji coba dalam me-load data tersebut. Model factory menjadi senjata andalan untuk membuat data dummy secara otomatis sehingga kita tidak perlu repot dalam memasukkan data satu persatu, materi ini juga sudah dibahas dalam artikel lainnya dengan judul, Berkenalan dengan model factory Laravel.

Pada command line, buat factory untuk table products, jalankan command:

php artisan make:factory ProductFactory

Buka file yang dihasilkan dan terletak di dalam folder database/factories, kemudian modifikasi menjadi:

<?php

/* @var $factory \Illuminate\Database\Eloquent\Factory */

use App\Product;
use Faker\Generator as Faker;
use Illuminate\Support\Str;

$factory->define(Product::class, function (Faker $faker) {
    $name = $faker->sentence;
    return [
        'name' => $name,
        'slug' => Str::slug($name),
        'description' => $faker->paragraph,
        'price' => 100000,
        'status' => 1
    ];
});

Penjelasan: Fungsi diatas akan mengambil value dari Faker generator yang kemudian nantinya akan secara otomatis mengisi field dari table products.

Selanjutnya buat CommentFactory menggunakan command:

php artisan make:factory CommentFactory

Kemudian buka file tersebut dan modifikasi menjadi:

<?php

/* @var $factory \Illuminate\Database\Eloquent\Factory */

use App\Comment;
use Faker\Generator as Faker;
use Illuminate\Support\Str;

$factory->define(Comment::class, function (Faker $faker) {
    return [
        'user_id' => function() {
            return factory(App\User::class)->create()->id;
        },
        'product_id' => function() {
            return factory(App\Product::class)->create()->id;
        },
        'comment' => $faker->sentence,
    ];
});

Adapun UserFactory secara default telah disediakan oleh Laravel, maka tugas selanjutnya adalah membuat seeder. Pada command line, jalankan command:

php artisan make:seeder PostTableSeeder

Kemudian buka file tersebut yang terletak di dalam folder database/seeds dan modifikasi menjadi:

<?php

use Illuminate\Database\Seeder;

class PostTableSeeder extends Seeder
{
    /**
     * Run the database seeds.
     *
     * @return void
     */
    public function run()
    {
        factory(App\User::class, 50)->create()->each(function ($user) {
            $post = $user->product()->save(factory(App\Product::class)->make());
            factory(App\Comment::class, 10)->create(['user_id' => $user->id, 'product_id' => $post->id]);
        });
    }
}

Penjelasan: Fungsi diatas akan membuat 50 data users, dimana tiap user memiliki sebuah postingan products dan masing-masing products memiliki 10 data komentar.

Terakhir, buka file DatabaseSeeder.php dan modifikasi menjadi:

<?php

use Illuminate\Database\Seeder;

class DatabaseSeeder extends Seeder
{
    /**
     * Seed the application's database.
     *
     * @return void
     */
    public function run()
    {
        $this->call(PostTableSeeder::class);
    }
}

Kemudian pada command line, jalankan command:

php artisan migrate

Membuat Eloquent Relationships

Setelah struktur database tersedia dan data dummy yang akan digunakan sebagai bahan eksperimen juga sudah ada, maka langkah selanjutnya adalah membuat fungsi relasi antar table melalui eloquent. Melalui fungsi ini kita dapat mengambil data yang saling terkait, dimana pada fokus kita kali ini adalah bagaimana me-load data yang saling terkait tersebut menggunakan Eloquent.

Buka file model User.php, kemudian tambahkan method:

public function product()
{
    return $this->hasMany(Product::class);
}

Penjelasan: Fungsi ini untuk mengambil lebih dari 1 data product yang terkait dengan user dengan menggunakan hasMany().

Selanjutnya buka file Product.php dan tambahkan method:

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

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

Note: Sama dengan code sebelumnya untuk mengambil lebih dari 1 data relasi yang dimiliki oleh data induk. Sedangkan belongsTo untuk mengambil data dari induk relasi.

Terakhir, buka file Comment.php dan tambahkan method:

public function product()
{
    return $this->belongsTo(Product::class);
}

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

Adapun hal yang akan kita lakukan selanjutnya adalah bagaimana cara menggunakan fungsi relasi yang sudah dibuat sebelumnya, meskipun materi ini pernah dibahas tapi pada artikel ini akan dibuat sedikit kompleks relasi yang beruntun dalam hal ini relasi pertama memiliki relasi lainnya.

Load Data With Eager Loading

Me-load data yang berelasi di Laravel menjadi lebih mudah karena framework ini memanjakan penggunanya, contohnya salah kita dapat mengakses langsung relasi tersebut tanpa memanfaatkan fitur eager loading akan tetapi cara tersebut tidak direkomendasikan karena akan membuat query semakin banyak. Dalam pengolahan data skala kecil mungkin tidak akan terasa efeknya akan tetapi jika sudah memasuki skala yang lebih kompleks, maka fitur ini tentu saja akan sangat berguna.

Tahap awal kita buat controller-nya terlebih dahulu agar lebih rapi saja, pada command line jalankan command:

php artisan make:controller APIController

Kemudian buka file APIController.php dan tambahkan method:

public function getDataByUser()
{
    $users = User::orderBy('created_at', 'DESC')->get();
    return response()->json(['data' => $users]);
}

Cara diatas adalah cara biasa untuk mengambil data dari table users tanpa me-load relasinya namun relasi yang ada masih tetap bisa diakses sesuai kebutuhan. Jangan lupa untuk menghubungkannya dengan route yang dimiliki oleh Laravel, buka file routes/web.php kemudian modifikasi menjadi:

<?php

/*
|--------------------------------------------------------------------------
| 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('/', 'APIController@getDataByUser');

Jangan lupa untuk menambahkan use statement:

use App\User

Mari kita sedikit modifikasi code diatas untuk me-load sebuah relasi yang telah dibuat menjadi:

public function getDataByUser()
{
    // $users = User::orderBy('created_at', 'DESC')->get();
    $users = User::with(['product'])->orderBy('created_at', 'DESC')->get();
    return response()->json(['data' => $users]);
}

Maka hasil yang akan diperoleh akan tampak seperti berikut

Seperti yang kita lihat diatas bahwa secara otomatis Laravel akan menampilkan data yang terkait berdasarkan jenis relasi yang dimilikinya, dimana pada kasus kali ini adalah hasMany() maka data yang ditampilkan dari product dalam bentuk array.

Apabila kita menemukan sebuah case, misalnya dalam hal ini kita akan me-load relasi comments yang dimiliki oleh Product melalui user, maka lakukan hal seperti ini:

public function getDataByUser()
{
    // $users = User::orderBy('created_at', 'DESC')->get();
    // $users = User::with(['product'])->orderBy('created_at', 'DESC')->get();
    $users = User::with(['product', 'product.comments'])->orderBy('created_at', 'DESC')->get();
    return response()->json(['data' => $users]);
}

Penjelasannya: product.comments berarti kita akan mengambil data comments yang berasal dari product. Silahkan cek model Product.php dimana didalamnya terdapat sebuah fungsi bernama comments(), sedangkan pada model User.php memiliki fungsi yang bernama product().

Hasil yang akan diperoleh akan tampak seperti berikut:

Note: Perhatikan didalam object product terdapat object comments dimana datanya dalam bentuk array karena relasi yang dimilikinya adalah hasMany() dari file Product.php.

Order By Relationships

Fungsi untuk mengurutkan data berdasarkan yang terbaru ke data yang paling lama biasanya banyak digunakan dalam sebuah aplikasi agar selalu menampilkan data yang paling baru. Laravel juga telah menyediakan cara yang menarik untuk mengubah urutan pada sebuah data yang dihasilkan dari relasi. Pada file yang sama, lakukan perubahan code menjadi:

public function getDataByUser()
{
    // $users = User::orderBy('created_at', 'DESC')->get();
    // $users = User::with(['product'])->orderBy('created_at', 'DESC')->get();
    // $users = User::with(['product', 'product.comments'])->orderBy('created_at', 'DESC')->get();
    
    $users = User::with(['product', 'product.comments' => function($q) {
        $q->orderBy('created_at', 'DESC');
      	//$q->orderBy('comment', 'DESC');
    }])->orderBy('created_at', 'DESC')->get();
    return response()->json(['data' => $users]);
}

Penjelasan: Ketika me-load sebuah relasi, maka kita juga memiliki kemampuan untuk menambahkan sub query didalam-nya dengan membuat sebuah fungsi baru. Pada contoh diatas, kita sebuah fungsi untuk mengurutkan data menggunakan orderBy(), dimana field yang digunakan adalah created_at dan type-nya adalah descending.

Select Data From Relationships

Mengambil data yang diperlukan saja juga terkadang menjadi sebuah case yang sering ditemui, akan tetapi Laravel juga telah memikirkan hal tersebut, sehingga kita dapat melakukannya dengan cara:

public function getDataByUser()
{
    // $users = User::orderBy('created_at', 'DESC')->get();
    // $users = User::with(['product'])->orderBy('created_at', 'DESC')->get();
    // $users = User::with(['product', 'product.comments'])->orderBy('created_at', 'DESC')->get();
    // $users = User::with(['product', 'product.comments' => function($q) {
    //     $q->orderBy('created_at', 'DESC');
    // }])->orderBy('created_at', 'DESC')->get();

    $users = User::with(['product:id,user_id,name,slug,price', 'product.comments:id,product_id,comment,created_at'])->orderBy('created_at', 'DESC')->get();
    return response()->json(['data' => $users]);
}

Penjelasan: Mengambil field tertentu menggunakan eager loading dapat dilakukan dengan cara nama_relasi:field1,field2,dst.

Hasil yang akan diperoleh kurang lebih akan tampak seperti berikut

Adapun cara lainnya jika menggunakan constraining eager loading, yakni dengan menambahkan sub query pada relations yang di-load adalah dengan cara:

public function getDataByUser()
{
    // $users = User::orderBy('created_at', 'DESC')->get();
    // $users = User::with(['product'])->orderBy('created_at', 'DESC')->get();
    // $users = User::with(['product', 'product.comments'])->orderBy('created_at', 'DESC')->get();
    // $users = User::with(['product', 'product.comments' => function($q) {
    //     $q->orderBy('created_at', 'DESC');
    // }])->orderBy('created_at', 'DESC')->get();

    // $users = User::with(['product:id,user_id,name,slug,price', 'product.comments:id,product_id,comment,created_at'])->orderBy('created_at', 'DESC')->get();
    $users = User::with(['product', 'product.comments' => function($q) {
        $q->orderBy('created_at', 'DESC')->select('id', 'product_id', 'comment', 'created_at');
    }])->orderBy('created_at', 'DESC')->get();
    return response()->json(['data' => $users]);
}

Note: Cukup dengan cara menambahkan select() didalam sub query-nya.

Add New Object Into Result Query

Memanfaatkan Accessor pada sebuah field secara langsung akan mengubah value original dari field tersebut, maka cara lainnya adalah dengan membuat object baru yang nantinya akan ikut ditampilkan pada hasil query dari model terkait. Sebagai contoh kita akan membuat sebuah object status_label yang berisi tag html untuk membedakan status dari masing-masing produk.

Buka file Product.php dan tambahkan baris code berikut:

//[.. CODE LAINNYA ..]

protected $appends = ['status_label'];
public function getStatusLabelAttribute()
{
    if ($this->status == 0) {
        return '<span class="badge badge-secondary>Draft</span>';
    }
    return '<span class="badge badge-success>Publish</span>';
}

Penjelasan: Pada baris pertama berfungsi untuk melakukan append / menambahkan sebuah property ke dalam hasil dari model terkait, dimana dalam hal ini adalah access dari status_label. Pada baris berikutnya adalah membuat sebuah accessor dengan nama status_label yang didalamnya berisi logic sederhana untuk memberikan return yang diinginkan sesuai status yang dimilikinya.

Load Eager Loading

Ada beragam kasus yang terkadang ditemui, contohnya saja kita perlu untuk me-load sebuah relasi ketika data induk telah diterima, maka cara ini merupakan cara lain untuk me-load data yang berelasi menggunakan fungsi load().

Kita akan membuatnya pada method baru di Controller agar lebih rapi saja, pada file APIController.php tambahkan method:

public function getDataByProduct()
{
    $product = Product::orderBy('created_at', 'DESC')->get();
    $product->load('user');
    return response()->json(['data' => $product]);
}

Jangan lupa untuk menambahkannya pada route, buka file routes/web.php:

Route::get('/product', 'APIController@getDataByProduct');

Juga tambahkan use statement dari model Product.php:

use App\Product;

Ketika mengakses url /product, maka hasil yang akan diperoleh sama seperti yang telah dilakukan sebelumnya, yakni data yang berhubungan dalam hal ini adalah user akan ditampilkan juga pada hasil query-nya.

Selain itu untuk metode select-nya juga sama seperti sebelumnya, yakni dengan cara:

public function getDataByProduct()
{
    $product = Product::orderBy('created_at', 'DESC')->get();
    // $product->load('user');
    $product->load('user:id,name,email');
    return response()->json(['data' => $product]);
}

Yang membedakan keduanya hanya penggunakan fungsinya saja, dimana pada versi pertama kita menggunakan fungsi with() langsung pada query, sedangkan versi kedua kita menggunakan fungsi load() setelah query dimuat.

Show Data If Have a Relationships.

Kerap kali kita hanya ingin menampilkan sebuah data yang memiliki setidaknya 1 data yang berelasi dengan table lain, Laravel juga telah men-cover hal ini untuk memudahkan kita dalam melakukan query data. Pada controller APIController.php, modifikasi method berikut menjadi:

public function getDataByProduct()
{
    // $product = Product::orderBy('created_at', 'DESC')->get();
    // $product->load('user');
    // $product->load('user:id,name,email');

    $filter = 'suscipit';
    $product = Product::with(['comments'])->whereHas('comments', function($q) use($filter) {
        $q->where('comment', 'LIKE', '%' . $filter . '%');
    })->get();
    return response()->json(['data' => $product]);
}

Penjelasan: Fungsi diatas akan menampilkan data product yang memiliki relasi ke comments, dimana kondisi pada relasi comments adalah pada field comment memiliki kata suscipit.

Hasil yang akan diperoleh jika di-load secara biasa, maka kumpulan datanya ada 50 items.

Setelah menggunakan fungsi where has diatas maka hasil yang akan diperoleh akan berkurang dari item sebelumnya.

Filter result of relationships data

Case lain yang sering ditemukan adalah memberikan kondisi pada hasil dari sebuah relationships, misalnya saja pada kasus kali ini adalah kita hanya akan menampilkan data comments dari relasi product yang memiliki kata suscipit pada komentar yang dimilikinya. Lakukan modifikasi pada controller kita menjadi:

public function getDataByProduct()
{
    // $product = Product::orderBy('created_at', 'DESC')->get();
    // $product->load('user');
    // $product->load('user:id,name,email');

    $filter = 'suscipit';
    // $product = Product::with(['comments'])->whereHas('comments', function($q) use($filter) {
    //     $q->where('comment', 'LIKE', '%' . $filter . '%');
    // })->get();

    $product = Product::with(['comments' => function($q) use($filter) {
        $q->where('comment', 'LIKE', '%' . $filter . '%');
    }])->get();
    return response()->json(['data' => $product]);
}

Penjelasan: Jika pada case sebelumnya hanya akan menampilkan data product yang memiliki relasi dengan kata kunci tertentu, maka pada case ini adalah menampilkan semua data product akan tetapi data komentar yang dimilikinya hanya komentar yang memiliki kata kunci yang telah ditentukan, pada code diatas kita membuat sebuah fungsi pada eager loading untuk memberikan kondisi berdasarkan value dari variable $filter.

Adapun hasil yang akan diperoleh akan menjadi

Baca Juga: Membuat Repository Pattern Laravel 5.7

 

Kesimpulan

Eloquent dengan beragam kemampuannya menjadi khas sekaligus andalan bagi pengguna Laravel, karena fitur ini tentu saja memudahkan bagi penggunanya dalam mempercepat pengerjaan aplikasi yang dimilikinya.

Serial diatas tentu saja belum lengkap saya bahas, apabila teman-teman memiliki case tertentu, tinggalkan di komentar nanti akan saya update sesuai dengan request yang diminta. Adapun dokumentasi dari artikel ini dapat kamu lihat di Github.

Category:
Share:

Comments