Laravel Eloquent - Relationship Advanced Has One of Many
Pada artikel kali ini kita akan membahas mengenai Laravel Eloquent Relationship Advanced Has One of Many

Arman Dwi Pangestu
30 Januari 2024•1 menit baca

Pendahuluan
Setelah pada pembahasan sebelumnya kita membahas mengenai Laravel Eloquent - Relationship Has One of Many, pada artikel kali ini kita akan membahas mengenai Advance dari Has One of Many atau studi kasus yang lebih kompleks dari sekedar model User dan model Order. Pada pembahasan kali ini terdapat sebuah studi kasus mengenai product yang memiliki banyak nya harga yang bisa diterapkan atau ditetapkan. Misalkan, sebuah model Product mungkin memiliki banyak model Price terkait yang dipertahankan dalam sistem bahkan setelah harga baru di publish. Selain itu, data harga baru untuk produk tersebut mungkin dapat di publish terlebih dahulu agar belaku di masa mendatang melalui kolom published_at.
Jadi, secara singkat, kita perlu mengambil harga terbaru yang di publish (latest published) dimana tanggal published nya bukan di masa depan. Selain itu, jika terdapat dua harga yang memiliki tanggal publish yang sama, maka Eloquent akan memilih harga berdasarkan urutan id terbesar. Untuk mencapai hal tersebut, kita harus meneruskan (passing) array ke method ofMany pada argumen pertama yang berisi kolom yang dapat diurutkan (sortable) yang menentukan harga terbaru. Selain itu, sebuah closure akan diberikan pada argumen kedua pada method ofMany. Closure tersebut akan bertanggung jawab untuk menambahkan batasan tanggal publish tambahan kedalam relationship query.
Agar lebih terbayang mungkin kalian bisa melihat gambar relationship dibawah ini antara model Product dan model Price

Studi Kasus Harga Jagung
Jika masih belum terbayang mungkin kalian bisa lihat gambar dibawah ini sebagai representasi data nya, Saya ambil kasus misalkan product nya adalah jagung, yang dimana harga jagung tersebut biasanya akan naik jika mendekati tahun baru dan jika sudah lewat tahun baru maka harga nya akan kembali normal. Nah, dikarenakan banyak nya harga yang akan ditetapkan sesuai dengan tanggal tertentu maka kita bisa simpan harga dari product tersebut lebih dari satu kemudian untuk menerapkan harga terkini nya kita bisa ambil berdasarkan kolom published_at.

Implementasi Studi Kasus
Setelah memahami studi kasus diatas, sekarang kita akan mencoba meng-implementasikan pada project yang sudah kita buat.
Model Product
Untuk membuat sebuah relationship dengan studi kasus seperti yang sudah saya buat diatas, kita perlu membuat model, migration, factory dan seeder nya Product terlebih dahulu. Untuk membuat model, migration, factory dan seeder secara sekaligus kita bisa gunakan perintah artisan berikut ini:
Catatan: Tips
Flag atau option
-mfsdisini artinya kita akan membuat model sekaligus denganmigration,factory, dan jugaseedernya.
php artisan make:model -mfs Product
Selanjutnya kita ubah file model Product nya di app\Models\Product.php
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
class Product extends Model
{
use HasFactory;
protected $guarded = ['id'];
}
Migration Product
Sekarang kita sesuaikan kode migration nya agar kolom dari tabel products nya sesuai dengan design yang sudah saya buat diatas, file migration tersebut berada di database\migrations\2024_01_30_181940_create_products_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('products', function (Blueprint $table) {
$table->id();
$table->string('name');
$table->timestamps();
});
}
public function down(): void
{
Schema::dropIfExists('products');
}
};
Factory Product
Selanjutnya kita siapkan data dummy untuk model Product tersebut menggunakan faker di file factory database\factories\ProductFactory.php
<?php
namespace Database\Factories;
use Illuminate\Database\Eloquent\Factories\Factory;
class ProductFactory extends Factory
{
public function definition(): array
{
return [
'name' => fake()->unique()->word
];
}
}
Seeder Product
Setelah factory faker nya sudah siap, sekarang kita eksekusi pembuatan data dummy nya di file database\seeders\ProductSeeder.php
<?php
namespace Database\Seeders;
use App\Models\Product;
use Illuminate\Database\Console\Seeds\WithoutModelEvents;
use Illuminate\Database\Seeder;
class ProductSeeder extends Seeder
{
public function run(): void
{
Product::factory(10)->create();
}
}
Jangan lupa kita panggil class ProductSeeder 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,
OrderSeeder::class,
ProductSeeder::class
]);
}
}
Setelah semuanya 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 products didalam database nya dengan isian kolom dan data dummy seperti berikut ini
| Column | Data Type |
|---|---|
| id | bigint unsigned |
| name | bigint unsigned |
| created_at | timestamp |
| updated_at | timestamp |

Model Price
Setelah model Product siap, sekarang kita siapkan untuk model Price, untuk membuat model, migration, factory dan seeder nya secara sekaligus kita bisa gunakan perintah artisan berikut ini:
php artisan make:model -mfs Price
Selanjutnya kita ubah file model Price nya di app\Models\Price.php
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
class Price extends Model
{
use HasFactory;
protected $guarded = ['id'];
}
Migration Price
Sekarang kita sesuaikan kode migration nya agar kolom dari tabel prices nya sesuai dengan design yang sudah saya buat, file migration tersebut berada di database\migrations\2024_01_30_183427_create_prices_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('prices', function (Blueprint $table) {
$table->id();
$table->foreignId('product_id');
$table->decimal('amount', 10, 2);
$table->timestamp('published_at');
$table->timestamps();
});
}
public function down(): void
{
Schema::dropIfExists('prices');
}
};
Factory Price
Selanjutnya kita siapkan data dummy untuk model Price tersebut menggunakan faker di file factory database\factories\PriceFactory.php
<?php
namespace Database\Factories;
use Illuminate\Database\Eloquent\Factories\Factory;
class PriceFactory extends Factory
{
public function definition(): array
{
return [
'product_id' => mt_rand(1, 10),
'amount' => fake()->randomFloat(2, 1, 1000),
'published_at' => fake()->dateTimeThisMonth()
];
}
}
Seeder Price
Setelah factory nya sudah siap, sekarang kita bisa eksekusi pembuatan data dummy nya di file database\seeders\PriceSeeder.php
<?php
namespace Database\Seeders;
use App\Models\Price;
use Illuminate\Database\Console\Seeds\WithoutModelEvents;
use Illuminate\Database\Seeder;
class PriceSeeder extends Seeder
{
public function run(): void
{
Price::factory(20)->create();
}
}
Jangan lupa kita panggil class PriceSeeder 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,
OrderSeeder::class,
ProductSeeder::class,
PriceSeeder::class
]);
}
}
Setelah semuanya siap, sekarang kita bisa jalankan migration nya menggunakan perintah artisan berikut ini:
php artisan migrate:fresh --seed
Maka sekarang seharusnya sudah terbuat tabel baru dengan nama prices didalam database dengan isian kolom dan dummy data seperti berikut ini
| Column | Data Type |
|---|---|
| id | bigint unsigned |
| product_id | bigint unsigned |
| amount | decimal(10,2) |
| published_at | timestamp |
| created_at | timestamp |
| updated_at | timestamp |

Membuat Method Relationship di Model Product Agar Terhubung ke Model Price
Setelah model Product dan Price nya sudah siap, sekarang kita akan buat method dengan nama currentPricing yang relationship nya adalah hasOne yang kemudian men-chaining method ofMany pada model Product nya
Catatan:
- Pada argument pertama di method
ofManydisini kita akan ambil data harga yang dari kolompublished_atdanidnya terkini- Pada argument kedua di method
ofManydisini kita kirimkan sebuah closure untuk untuk memberikan kondisi batasan agar harga yang di ambil adalah harga yang bukan dari masa yang akan mendatang
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\HasOne;
class Product extends Model
{
use HasFactory;
protected $guarded = ['id'];
public function currentPricing(): HasOne
{
return $this->hasOne(Price::class)->ofMany([
'published_at' => 'max',
'id' => 'max',
], function (Builder $query) {
$query->where('published_at', '<', now());
});
}
}
Mengakses atau Menjalankan Method Relationship
Untuk mencoba nya seperti biasa kita akan gunakan shell tinker dengan cara masuk terlebih dahulu kedalam shell nya menggunakan perintah artisan berikut ini
php artisan tinker
Jika sudah didalam shell nya, sekarang kita bisa buat sebuah variabel dengan nama price yang value nya adalah model Product yang men-chaining relationship method currentPricing atau dynamic property nya
$price = App\Models\Product::find(1)->currentPricing;
Maka seharusnya perintah diatas akan me-return value dari data harga yang terkait dengan product nya
= App\Models\Price {#6049
id: 10,
product_id: 1,
amount: "902.14",
published_at: "2024-01-25 09:23:39",
created_at: "2024-01-30 18:42:42",
updated_at: "2024-01-30 18:42:42",
}


Tes Data Dengan Tanggal Publish Yang Sama
Sekarang kita coba ubah data dengan id 10 tersebut agar data published_at nya sama dengan yang id nya 18

$price = App\Models\Product::find(1)->currentPricing;
Maka seharusnya sekarang harga dari product nya adalah harga yang id nya 18 karena id nya lebih besar
= App\Models\Price {#6069
id: 18,
product_id: 1,
amount: "136.15",
published_at: "2024-01-18 05:01:13",
created_at: "2024-01-30 18:42:42",
updated_at: "2024-01-30 18:42:42",
}


Agar lebih terbayang kalian bisa lihat gambar dibawah ini

Membuat Method Relationship Dapat Diakses Melalui Web
Sekarang kita buat agar method relationship Has One of Many yang lebih kompleks tersebut agar dapat berjalan di web sehingga kita bisa liat RAW Query SQL yang berjalan seperti apa menggnunakan clockwork. Untuk melakukannya kita bisa buat sebuah method dengan nama advancedHasOneOfMany di file controller RelationController
<?php
namespace App\Http\Controllers;
use App\Models\Product;
use Illuminate\Http\Request;
class RelationController extends Controller
{
...
public function advancedHasOneOfMany(Request $request)
{
$price = Product::find($request->id)->currentPricing;
return $price;
}
}
Setelah method di controller nya dibuat, sekarang kita buat route baru untuk menangani method tersebut. Kita bisa buat route nya di file routes\web.php
Route::get('/relation/advancedHasOneOfMany', [RelationController::class, 'advancedHasOneOfMany']);
Sekarang kita bisa akses route tersebut dengan endpoint /relation/advancedHasOneOfMany dengan mengirimkan request data id product di URL nya untuk menjalankan relationship yang sudah kita buat.
