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
-mfs
disini artinya kita akan membuat model sekaligus denganmigration
,factory
, dan jugaseeder
nya.
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
ofMany
disini kita akan ambil data harga yang dari kolompublished_at
danid
nya terkini- Pada argument kedua di method
ofMany
disini 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.