Di bagian sebelumnya kita sudah membuat struktur database dengan Migration. Sekarang saatnya belajar cara berinteraksi dengan database tersebut menggunakan Eloquent ORM — salah satu fitur Laravel yang paling dicintai developer. Eloquent memungkinkan kamu membaca, menulis, mengupdate, dan menghapus data dari database menggunakan sintaks PHP yang ekspresif dan intuitif, tanpa harus menulis SQL secara manual untuk operasi-operasi yang umum.
💡 Ini adalah bagian keenam dari seri "Belajar Laravel dari Nol". Pastikan kamu sudah menyelesaikan Bagian 5 (Migration) dan database sudah terisi data menggunakan Seeder dan Factory, karena kita akan langsung mempraktikkan berbagai query Eloquent terhadap data yang sudah ada.
Apa Itu Eloquent ORM?
ORM adalah singkatan dari Object-Relational Mapping — teknik yang memetakan tabel database ke kelas PHP dan record database ke objek PHP. Dengan Eloquent, tabel posts di database direpresentasikan oleh kelas Post, setiap baris di tabel menjadi instance dari kelas tersebut, dan setiap kolom menjadi properti yang bisa diakses langsung. Kamu berinteraksi dengan database melalui objek PHP yang familiar, bukan string SQL yang rawan typo dan sulit di-maintain.
"Eloquent is Laravel's beautiful, simple ActiveRecord implementation for working with your database. Each database table has a corresponding Model that is used to interact with that table."
Anatomi Eloquent Model
Model adalah representasi PHP dari sebuah tabel database. Laravel mengikuti konvensi yang sangat kuat untuk Model — jika kamu mengikuti konvensi ini, kamu hampir tidak perlu konfigurasi apapun. Tapi jika perlu menyimpang dari konvensi, semuanya bisa dikustomisasi.
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\SoftDeletes;
use Illuminate\Database\Eloquent\Casts\Attribute;
class Post extends Model
{
use HasFactory; // Aktifkan Factory untuk model ini
use SoftDeletes; // Aktifkan Soft Delete
// ── KONFIGURASI TABEL ─────────────────────────────
// Jika nama tabel TIDAK mengikuti konvensi (plural snake_case dari nama Model)
// Konvensi: Post → posts, BlogPost → blog_posts (otomatis, tidak perlu ditulis)
// protected $table = 'artikel'; // Override jika nama tabel berbeda
// Primary key — default: 'id'
// protected $primaryKey = 'post_id'; // Override jika nama PK berbeda
// public $incrementing = false; // Jika PK bukan auto-increment
// protected $keyType = 'string'; // Jika PK bukan integer (misal UUID)
// Timestamps — default: true (butuh kolom created_at & updated_at)
// public $timestamps = false; // Matikan jika tabel tidak punya timestamps
// ── MASS ASSIGNMENT ───────────────────────────────
// Kolom yang BOLEH diisi via create() atau fill()
protected $fillable = [
'title',
'slug',
'excerpt',
'content',
'thumbnail',
'status',
'is_featured',
'category_id',
'user_id',
'published_at',
'meta_title',
'meta_description',
];
// ATAU: Kolom yang TIDAK BOLEH diisi (kebalikan fillable)
// protected $guarded = ['id']; // Semua kolom kecuali id bisa diisi
// ── CASTING: Konversi tipe data otomatis ──────────
protected $casts = [
'is_featured' => 'boolean', // String '0'/'1' → false/true
'published_at' => 'datetime', // String → Carbon instance
'metadata' => 'array', // JSON string → PHP array
'settings' => 'collection',// JSON string → Laravel Collection
'views' => 'integer',
'price' => 'decimal:2',
'deleted_at' => 'datetime',
];
// ── HIDDEN: Kolom yang tidak muncul saat toArray()/toJson() ──
protected $hidden = [
'meta_title',
'meta_description',
];
// ── APPENDS: Tambahkan attribute virtual ke output ──
protected $appends = ['read_time', 'is_published'];
}
Operasi Dasar CRUD dengan Eloquent
Eloquent menyediakan method yang sangat intuitif untuk semua operasi database dasar — Create, Read, Update, Delete. Mari pelajari setiap operasi secara mendalam beserta berbagai variasinya yang sering dibutuhkan dalam pengembangan nyata.
Create: Menyimpan Data Baru
<?php
use App\Models\Post;
// ── CARA 1: create() — Mass Assignment ────────────────
// Simpan record baru sekaligus, kolom harus ada di $fillable
$post = Post::create([
'title' => 'Belajar Eloquent ORM',
'slug' => 'belajar-eloquent-orm',
'content' => 'Konten artikel...',
'status' => 'published',
'user_id' => auth()->id(),
'category_id' => 1,
'published_at'=> now(),
]);
// $post sudah berisi instance Post yang baru tersimpan
// termasuk id yang baru di-generate
// ── CARA 2: new + save() — Lebih eksplisit ────────────
$post = new Post();
$post->title = 'Belajar Eloquent ORM';
$post->slug = 'belajar-eloquent-orm';
$post->content = 'Konten artikel...';
$post->status = 'published';
$post->user_id = auth()->id();
$post->published_at = now();
$post->save();
// $post->id sekarang sudah terisi
// ── CARA 3: firstOrCreate() ───────────────────────────
// Cari berdasarkan kondisi pertama, buat jika tidak ada
$category = Category::firstOrCreate(
['slug' => 'laravel'], // Kondisi pencarian
['name' => 'Laravel', 'color' => '#FF2D20'] // Data jika dibuat baru
);
// ── CARA 4: updateOrCreate() ─────────────────────────
// Update jika ditemukan, create jika tidak ada
$post = Post::updateOrCreate(
['slug' => 'belajar-eloquent'], // Kondisi pencarian
[ // Data untuk update/create
'title' => 'Belajar Eloquent ORM',
'content' => 'Konten yang diperbarui...',
'status' => 'published',
]
);
// ── CARA 5: insert() — Bulk insert tanpa Model ────────
// Lebih cepat untuk data banyak, tapi bypass Model events & casting
Post::insert([
['title' => 'Post 1', 'slug' => 'post-1', 'content' => '...', 'user_id' => 1],
['title' => 'Post 2', 'slug' => 'post-2', 'content' => '...', 'user_id' => 1],
['title' => 'Post 3', 'slug' => 'post-3', 'content' => '...', 'user_id' => 1],
]);
Read: Mengambil Data dari Database
<?php
// ── MENGAMBIL SEMUA RECORD ────────────────────────────
$posts = Post::all();
// HATI-HATI: all() mengambil SEMUA record tanpa batas
// Gunakan hanya untuk tabel kecil
// ── MENGAMBIL DENGAN KONDISI ──────────────────────────
// where() — filter berdasarkan kondisi
$publishedPosts = Post::where('status', 'published')->get();
$featuredPosts = Post::where('is_featured', true)->get();
$recentPosts = Post::where('created_at', '>=', now()->subDays(7))->get();
// Operator perbandingan
Post::where('views', '>', 1000)->get();
Post::where('views', '>=', 500)->get();
Post::where('status', '!=', 'archived')->get();
// whereIn / whereNotIn
Post::whereIn('status', ['published', 'draft'])->get();
Post::whereNotIn('category_id', [3, 5, 7])->get();
// whereNull / whereNotNull
Post::whereNull('published_at')->get(); // Draft posts
Post::whereNotNull('thumbnail')->get(); // Post dengan thumbnail
// whereBetween
Post::whereBetween('views', [100, 1000])->get();
Post::whereBetween('created_at', [
now()->startOfMonth(),
now()->endOfMonth()
])->get();
// whereDate, whereMonth, whereYear, whereDay
Post::whereDate('published_at', today())->get();
Post::whereMonth('published_at', now()->month)->get();
Post::whereYear('created_at', 2026)->get();
// ── MENGAMBIL SATU RECORD ─────────────────────────────
// find() — cari berdasarkan primary key
$post = Post::find(1); // Null jika tidak ditemukan
$post = Post::find([1, 2, 3]); // Array of posts
// findOrFail() — throw ModelNotFoundException jika tidak ada
$post = Post::findOrFail(1); // ← Ini yang paling sering dipakai di Controller
// first() — ambil record pertama yang cocok
$post = Post::where('status', 'published')->first();
// firstOrFail() — throw exception jika tidak ada
$post = Post::where('slug', $slug)->firstOrFail();
// firstWhere() — shorthand untuk where + first
$post = Post::firstWhere('slug', $slug);
// sole() — ambil satu, throw jika lebih dari satu atau tidak ada
$post = Post::where('is_featured', true)->sole();
// ── ORDERING DAN LIMITING ─────────────────────────────
Post::orderBy('created_at', 'desc')->get(); // Terbaru
Post::orderBy('views', 'desc')->get(); // Terpopuler
Post::orderByDesc('published_at')->get(); // Shorthand desc
Post::latest()->get(); // Shorthand orderByDesc('created_at')
Post::latest('published_at')->get(); // latest berdasarkan kolom lain
Post::oldest()->get(); // orderBy('created_at', 'asc')
Post::limit(10)->get(); // Ambil 10 record
Post::take(5)->get(); // Alias limit()
Post::skip(10)->take(5)->get(); // Skip 10, ambil 5 (manual pagination)
// ── MULTIPLE WHERE (AND / OR) ─────────────────────────
// AND — default saat chain where()
Post::where('status', 'published')
->where('is_featured', true)
->where('views', '>', 100)
->get();
// OR
Post::where('status', 'published')
->orWhere('is_featured', true)
->get();
// Grouping kondisi OR
Post::where('status', 'published')
->where(function ($query) {
$query->where('views', '>', 1000)
->orWhere('is_featured', true);
})
->get();
// SQL: WHERE status = 'published' AND (views > 1000 OR is_featured = 1)
Update: Mengubah Data yang Ada
<?php
// ── CARA 1: update() pada instance Model ──────────────
$post = Post::findOrFail(1);
$post->title = 'Judul yang Diperbarui';
$post->status = 'published';
$post->save();
// ── CARA 2: update() dengan array ─────────────────────
$post = Post::findOrFail(1);
$post->update([
'title' => 'Judul yang Diperbarui',
'status' => 'published',
'published_at' => now(),
]);
// ── CARA 3: Mass update — update banyak record sekaligus ──
Post::where('status', 'draft')
->where('created_at', '<', now()->subDays(30))
->update(['status' => 'archived']);
// Update semua post milik user tertentu
Post::where('user_id', $userId)->update(['is_active' => false]);
// ── CARA 4: increment() dan decrement() ───────────────
// Lebih aman untuk operasi counter (atomic di database)
$post->increment('views'); // views + 1
$post->increment('views', 5); // views + 5
$post->decrement('stock'); // stock - 1
$post->decrement('stock', 3); // stock - 3
// Dengan update kolom lain sekaligus
$post->increment('views', 1, ['last_viewed_at' => now()]);
// ── touch(): Update timestamps saja ───────────────────
$post->touch(); // Update updated_at ke sekarang
Delete: Menghapus Data
<?php
// ── DELETE BIASA ───────────────────────────────────────
$post = Post::findOrFail(1);
$post->delete(); // Hapus record ini dari database
// Delete langsung tanpa ambil dulu (lebih efisien)
Post::destroy(1); // Hapus berdasarkan ID
Post::destroy([1, 2, 3]); // Hapus beberapa ID sekaligus
Post::destroy(1, 2, 3); // Sama, tanpa array
// Mass delete
Post::where('status', 'archived')
->where('created_at', '<', now()->subYear())
->delete();
// ── SOFT DELETE ────────────────────────────────────────
// Jika Model menggunakan trait SoftDeletes:
// delete() tidak menghapus dari database, tapi mengisi deleted_at
// Record dengan deleted_at terisi tidak muncul di query biasa
$post->delete(); // Soft delete — isi deleted_at, record masih ada
// Mengambil termasuk yang sudah di-soft-delete
Post::withTrashed()->get();
Post::withTrashed()->where('user_id', 1)->get();
// Mengambil HANYA yang sudah di-soft-delete
Post::onlyTrashed()->get();
// Restore — batalkan soft delete
$post = Post::withTrashed()->find(1);
$post->restore();
// Restore banyak sekaligus
Post::onlyTrashed()->where('user_id', 1)->restore();
// Force delete — hapus permanen meski pakai SoftDeletes
$post->forceDelete();
Post::withTrashed()->where('created_at', '<', now()->subYears(2))->forceDelete();
Query Scopes: Query yang Dapat Digunakan Ulang
Query yang sama sering diulang di banyak tempat berbeda — misalnya "hanya post yang sudah dipublikasikan" atau "hanya post milik user yang login". Daripada menulis kondisi yang sama berulang-ulang, Eloquent menyediakan Query Scopes — cara mendefinisikan query reusable langsung di dalam Model.
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Builder;
class Post extends Model
{
// ── LOCAL SCOPES ──────────────────────────────────
// Nama method harus diawali 'scope'
// Dipanggil tanpa awalan 'scope' dan tanpa ()
// Hanya post yang published
public function scopePublished(Builder $query): Builder
{
return $query->where('status', 'published')
->whereNotNull('published_at')
->where('published_at', '<=', now());
}
// Hanya post yang featured
public function scopeFeatured(Builder $query): Builder
{
return $query->where('is_featured', true);
}
// Filter berdasarkan kategori (scope dengan parameter)
public function scopeInCategory(Builder $query, int|string $category): Builder
{
if (is_string($category)) {
return $query->whereHas('category', fn($q) => $q->where('slug', $category));
}
return $query->where('category_id', $category);
}
// Post populer berdasarkan views
public function scopePopular(Builder $query, int $minViews = 1000): Builder
{
return $query->where('views', '>=', $minViews)
->orderByDesc('views');
}
// Post terbaru N item
public function scopeRecent(Builder $query, int $limit = 5): Builder
{
return $query->latest('published_at')->limit($limit);
}
// ── GLOBAL SCOPE ──────────────────────────────────
// Otomatis diterapkan ke SEMUA query model ini
protected static function booted(): void
{
// Contoh: hanya tampilkan post yang active
// static::addGlobalScope('active', function (Builder $builder) {
// $builder->where('is_active', true);
// });
}
}
<?php
// ── MENGGUNAKAN SCOPES ────────────────────────────────
// Scope dasar
$posts = Post::published()->get();
$posts = Post::featured()->get();
// Chaining beberapa scope
$posts = Post::published()
->featured()
->latest('published_at')
->get();
// Scope dengan parameter
$posts = Post::published()->inCategory('laravel')->get();
$posts = Post::published()->popular(500)->get();
$posts = Post::published()->recent(10)->get();
// Scope + method Eloquent biasa
$posts = Post::published()
->inCategory('laravel')
->popular()
->with(['user', 'category'])
->paginate(12);
// Scope di Controller — kode jadi sangat bersih dan readable
public function index(Request $request): View
{
$posts = Post::published()
->when($request->category, fn($q, $cat) => $q->inCategory($cat))
->when($request->featured, fn($q) => $q->featured())
->with(['user', 'category', 'tags'])
->latest('published_at')
->paginate(12);
return view('posts.index', compact('posts'));
}
Accessor dan Mutator: Transformasi Data Otomatis
Accessor memungkinkan kamu mentransformasi nilai kolom saat dibaca dari database — misalnya memformat nama selalu dalam Title Case, atau membuat attribute virtual yang tidak ada di database tapi dihitung dari kolom lain. Mutator melakukan kebalikannya — mentransformasi nilai saat disimpan ke database, seperti otomatis membuat slug dari judul atau meng-hash password.
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Casts\Attribute;
class Post extends Model
{
// ── ACCESSOR + MUTATOR (cara modern Laravel 9+) ───
// Definisikan keduanya dalam satu method menggunakan Attribute::make()
// Accessor: format judul selalu Title Case saat dibaca
protected function title(): Attribute
{
return Attribute::make(
get: fn (string $value) => ucwords(strtolower($value)),
set: fn (string $value) => strtolower($value), // simpan selalu lowercase
);
}
// Accessor only: Attribute virtual (tidak ada di database)
protected function readTime(): Attribute
{
return Attribute::make(
get: function () {
$wordCount = str_word_count(strip_tags($this->content));
$minutes = ceil($wordCount / 200); // 200 kata per menit
return $minutes . ' menit';
}
);
}
// Accessor: isPublished — cek apakah post sudah dipublikasikan
protected function isPublished(): Attribute
{
return Attribute::make(
get: fn () => $this->status === 'published'
&& $this->published_at?->isPast()
);
}
// Accessor: URL thumbnail yang lengkap
protected function thumbnailUrl(): Attribute
{
return Attribute::make(
get: fn () => $this->thumbnail
? asset('storage/' . $this->thumbnail)
: asset('images/default-thumbnail.jpg')
);
}
// Mutator only: otomatis buat slug dari title saat title di-set
protected function slug(): Attribute
{
return Attribute::make(
set: fn (string $value) => \Str::slug($value)
);
}
// Harus didaftarkan di $appends agar muncul di toArray()/toJson()
protected $appends = ['read_time', 'is_published', 'thumbnail_url'];
}
<?php
// ── MENGGUNAKAN ACCESSOR ──────────────────────────────
$post = Post::find(1);
// Accessor title (auto Title Case)
echo $post->title; // "Belajar Eloquent Orm" (formatted)
// Accessor virtual (tidak ada di tabel)
echo $post->read_time; // "5 menit"
echo $post->is_published; // true/false (boolean)
echo $post->thumbnail_url; // "https://app.com/storage/thumbnails/..."
// Accessor muncul di toArray() dan toJson() karena ada di $appends
$data = $post->toArray();
// ['id' => 1, 'title' => '...', 'read_time' => '5 menit', ...]
// ── MENGGUNAKAN MUTATOR ───────────────────────────────
$post = new Post();
$post->slug = 'Ini Judul yang Panjang!';
// Mutator otomatis mengubah menjadi: 'ini-judul-yang-panjang'
echo $post->slug; // "ini-judul-yang-panjang"
Agregasi dan Query Tingkat Lanjut
Selain operasi CRUD dasar, Eloquent juga menyediakan method untuk berbagai kebutuhan query yang lebih kompleks — agregasi statistik, subquery, conditional query, dan operasi yang melibatkan relasi antar tabel.
<?php
// ── AGREGASI ──────────────────────────────────────────
$totalPost = Post::count();
$totalViews = Post::sum('views');
$avgViews = Post::avg('views');
$maxViews = Post::max('views');
$minViews = Post::min('views');
// Agregasi dengan kondisi
$publishedCount = Post::where('status', 'published')->count();
$totalRevenue = Order::where('status', 'completed')->sum('total');
// ── EXIST DAN DOESN'T EXIST ───────────────────────────
$hasPost = Post::where('user_id', 1)->exists(); // bool
$hasNoPost = Post::where('user_id', 1)->doesntExist(); // bool
// ── WHEN: Query Kondisional ────────────────────────────
// Sangat berguna untuk filter yang opsional dari request
$posts = Post::query()
->when($request->status, function ($query, $status) {
$query->where('status', $status);
})
->when($request->category, fn($q, $cat) => $q->where('category_id', $cat))
->when($request->search, fn($q, $search) => $q->where(function ($q) use ($search) {
$q->where('title', 'like', "%{$search}%")
->orWhere('content', 'like', "%{$search}%");
}))
->when($request->sort === 'popular', fn($q) => $q->orderByDesc('views'))
->when($request->sort !== 'popular', fn($q) => $q->latest('published_at'))
->paginate(12);
// ── SELECT: Pilih Kolom Tertentu ──────────────────────
$posts = Post::select('id', 'title', 'slug', 'published_at')
->published()
->get();
// Dengan kolom kalkulasi
$posts = Post::select('id', 'title', 'views')
->selectRaw('DATEDIFF(NOW(), created_at) as days_ago')
->selectRaw('(views / DATEDIFF(NOW(), created_at)) as views_per_day')
->get();
// ── CHUNK: Proses Data Besar dalam Batch ──────────────
// Hindari memory exhaustion untuk dataset sangat besar
Post::chunk(100, function ($posts) {
foreach ($posts as $post) {
// Proses setiap post — hanya 100 post di memory sekaligus
$post->update(['views' => $post->views + 1]);
}
});
// chunkById: lebih aman jika data bisa berubah selama proses
Post::chunkById(100, function ($posts) {
$posts->each->update(['processed' => true]);
});
// ── LAZY COLLECTION: Hemat Memory ─────────────────────
// Ambil satu per satu menggunakan generator, bukan semuanya sekaligus
foreach (Post::lazy() as $post) {
// Proses setiap post tanpa load semua ke memory
}
Pagination: Membagi Data ke Beberapa Halaman
Menampilkan ribuan record sekaligus adalah resep untuk aplikasi yang lambat dan boros memori. Pagination memecah data menjadi halaman-halaman kecil yang lebih manageable. Eloquent menyediakan tiga jenis pagination yang masing-masing cocok untuk use case berbeda.
<?php
// ── paginate(): Pagination dengan nomor halaman ────────
// Paling umum digunakan — cocok untuk halaman dengan navigasi angka
$posts = Post::published()->latest()->paginate(12);
// Otomatis baca ?page=X dari URL
// $posts->currentPage(), $posts->lastPage(), $posts->total()
// ── simplePaginate(): Hanya prev/next, lebih cepat ───
// Cocok untuk dataset besar karena tidak menghitung total
$posts = Post::published()->latest()->simplePaginate(12);
// ── cursorPaginate(): Untuk infinite scroll / API ─────
// Paling efisien untuk dataset sangat besar
$posts = Post::published()->latest()->cursorPaginate(12);
// ── DI BLADE: Render link pagination ─────────────────
// {{ $posts->links() }} otomatis render navigasi pagination
// Untuk kustomisasi style, publish vendor view:
// php artisan vendor:publish --tag=laravel-pagination
{{-- resources/views/posts/index.blade.php --}}
@foreach ($posts as $post)
<article>
<h2>{{ $post->title }}</h2>
<p>{{ $post->excerpt }}</p>
</article>
@endforeach
{{-- Render link pagination (Tailwind CSS style) --}}
<div class="mt-8">
{{ $posts->links() }}
</div>
{{-- Informasi pagination --}}
<p class="text-sm text-gray-500">
Menampilkan {{ $posts->firstItem() }}–{{ $posts->lastItem() }}
dari {{ $posts->total() }} artikel
</p>
{{-- Pertahankan query string saat berpindah halaman --}}
{{ $posts->withQueryString()->links() }}
Praktik: PostController Lengkap dengan Eloquent
Mari satukan semua konsep Eloquent yang sudah dipelajari dalam satu Controller yang komprehensif — menggunakan scope, eager loading, pagination, when untuk filter opsional, dan berbagai method Eloquent yang sudah kita bahas.
<?php
namespace App\Http\Controllers;
use App\Models\Post;
use App\Models\Category;
use Illuminate\Http\Request;
use Illuminate\View\View;
class PostController extends Controller
{
public function index(Request $request): View
{
$posts = Post::published()
// Filter opsional dari request
->when($request->category, fn($q, $cat) => $q->inCategory($cat))
->when($request->search, fn($q, $s) => $q->where(function ($q) use ($s) {
$q->where('title', 'like', "%{$s}%")
->orWhere('excerpt', 'like', "%{$s}%");
}))
->when($request->sort === 'popular',
fn($q) => $q->orderByDesc('views'),
fn($q) => $q->latest('published_at')
)
// Eager load relasi yang dibutuhkan di view
->with(['user:id,name', 'category:id,name,slug,color'])
->withCount('comments') // Tambah kolom comments_count
->paginate(12)
->withQueryString(); // Pertahankan filter di link pagination
// Data tambahan untuk sidebar/filter
$categories = Category::withCount('posts')->orderBy('name')->get();
$featuredPost = Post::published()->featured()->with('user')->first();
return view('posts.index', compact('posts', 'categories', 'featuredPost'));
}
public function show(Post $post): View
{
// Abort jika bukan published dan bukan pemilik
abort_if(
$post->status !== 'published' && auth()->id() !== $post->user_id,
404
);
// Load semua relasi yang dibutuhkan sekaligus
$post->load([
'user:id,name,avatar',
'category:id,name,slug',
'tags:id,name,slug',
]);
// Increment views (atomic, aman untuk concurrent request)
$post->increment('views');
// Post terkait dari kategori yang sama
$relatedPosts = Post::published()
->where('category_id', $post->category_id)
->where('id', '!=', $post->id)
->with('user:id,name')
->latest('published_at')
->take(3)
->get();
return view('posts.show', compact('post', 'relatedPosts'));
}
public function store(Request $request)
{
$validated = $request->validate([
'title' => 'required|string|max:255',
'content' => 'required|string|min:100',
'category_id' => 'required|exists:categories,id',
'status' => 'required|in:draft,published',
]);
// updateOrCreate untuk idempotent operation
$post = Post::updateOrCreate(
['slug' => \Str::slug($validated['title'])],
[
...$validated,
'user_id' => auth()->id(),
'published_at' => $validated['status'] === 'published' ? now() : null,
]
);
return redirect()->route('posts.show', $post)
->with('success', 'Post berhasil disimpan!');
}
}
💡 Perhatikan penggunaan with('user:id,name') dengan format kolom terpilih — ini memastikan hanya kolom yang benar-benar dibutuhkan yang diambil dari database, bukan seluruh record user. Teknik ini sangat penting untuk performa karena mencegah pengambilan data sensitif yang tidak perlu (seperti password hash) dan mengurangi ukuran data yang ditransfer dari database.
Langkah Selanjutnya
Luar biasa — kamu sudah menguasai Eloquent ORM secara menyeluruh! Mulai dari anatomi Model, operasi CRUD lengkap, Query Scopes yang reusable, Accessor dan Mutator untuk transformasi data otomatis, query tingkat lanjut dengan when dan agregasi, hingga pagination yang siap pakai. Di bagian ketujuh seri ini, kita akan menyelami Relasi Antar Model — cara Eloquent mengekspresikan hubungan hasOne, hasMany, belongsTo, belongsToMany, dan relasi-relasi yang lebih kompleks dengan cara yang sangat elegan.
🚀 Sebelum lanjut ke Bagian 7, pastikan kamu sudah berlatih: (1) menggunakan berbagai method Eloquent (where, orderBy, paginate, find, create, update, delete) di Tinker dengan php artisan tinker, (2) membuat minimal dua Query Scope di Model dan menggunakannya di Controller, (3) membuat Accessor untuk attribute virtual seperti read_time atau formatted_date, dan (4) menggunakan when() untuk filter opsional dari request. Tinker adalah cara terbaik untuk bereksperimen dengan Eloquent tanpa harus membuka browser.