Pertama kali pakai Eloquent, saya cuma tahu Model::all() dan Model::find($id). Lama-lama baru sadar kalau Eloquent itu jauh lebih powerful dari yang saya kira. Banyak fitur tersembunyi yang bisa bikin kode lebih clean dan query lebih efisien. Di sini saya mau share beberapa tips yang baru saya temuin setelah bertahun-tahun pakai Laravel.
Bayangkan kamu punya query yang sama di banyak controller: filter user yang aktif, ambil postingan yang sudah publish, atau cari produk yang stoknya masih ada. Kalau di-copy paste terus, kodenya bakal berantakan. Local scope solusinya.
// Di Model User.php
public function scopeActive($query)
{
return $query->where('is_active', 1);
}
public function scopeVerified($query)
{
return $query->whereNotNull('email_verified_at');
}
public function scopeRecent($query, $days = 30)
{
return $query->where('created_at', '>=', now()->subDays($days));
}
Pakainya tinggal chain aja, clean banget:
// Ambil user aktif dan verified, daftar 7 hari terakhir
$users = User::active()->verified()->recent(7)->get();
// Bisa di-combine dengan query builder biasa
$users = User::active()->where('role', 'admin')->recent()->paginate(15);
Yang bikin scope powerful itu fleksibilitasnya. Kamu bisa bikin scope yang menerima parameter, kayak recent($days) di atas. Jadi satu scope bisa dipakai dengan opsi berbeda.
Kalau local scope harus dipanggil manual, global scope jalan otomatis. Ini cocok buat kasus kayak soft delete atau multi-tenant app di mana setiap query harus filter berdasarkan tenant_id.
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Scope;
class TenantScope implements Scope
{
public function apply(Builder $builder, Model $model)
{
$builder->where('tenant_id', session('tenant_id'));
}
}
// Di Model
protected static function booted()
{
static::addGlobalScope(new TenantScope);
}
Sekarang setiap kali kamu panggil Post::all() atau Post::find(1), filter tenant_id otomatis ditambahkan. Mau lewati global scope? Pakai withoutGlobalScope:
// Lewati semua global scope
$allPosts = Post::withoutGlobalScope(TenantScope::class)->get();
// Lewati semua global scope sekaligus
$allPosts = Post::withoutGlobalScopes()->get();
Accessor buat baca data, mutator buat nulis data. Ini sering diabaikan padahal sangat berguna buat formatting dan validasi.
use Illuminate\Database\Eloquent\Casts\Attribute;
// Accessor - otomatis format saat diakses
protected function fullName(): Attribute
{
return Attribute::make(
get: fn () => $this->first_name . ' ' . $this->last_name,
);
}
// Mutator - otomatis hash saat disimpan
protected function password(): Attribute
{
return Attribute::make(
set: fn ($value) => bcrypt($value),
);
}
// Accessor + Mutator sekaligus (Laravel 9+)
protected function name(): Attribute
{
return Attribute::make(
get: fn ($value) => ucfirst($value),
set: fn ($value) => strtolower($value),
);
}
Pakainya kayak property biasa:
$user = User::find(1);
echo $user->fullName; // "John Doe" (accessor jalan otomatis)
$user->password = 'rahasia123'; // otomatis di-hash oleh mutator
$user->save();
Ini masalah klasik yang bikin aplikasi Laravel lemot. Bayangkan load 100 postingan, tiap postingan load author-nya satu per satu. Itu 101 query! Eager loading cuma butuh 2 query.
// JANGAN ini - N+1 problem
$posts = Post::all();
foreach ($posts as $post) {
echo $post->author->name; // query baru tiap iterasi!
}
// INI yang benar - eager loading
$posts = Post::with('author')->get();
foreach ($posts as $post) {
echo $post->author->name; // data sudah di-load sebelumnya
}
Bisa juga eager load beberapa relasi sekaligus dan pakai closure buat filter:
// Load multiple relations
$posts = Post::with(['author', 'comments', 'tags'])->get();
// Eager load dengan kondisi
$posts = Post::with(['comments' => function ($query) {
$query->where('is_approved', true)
->latest()
->limit(5);
}])->get();
// Eager load count aja (hemat memori)
$posts = Post::withCount('comments')->get();
echo $posts[0]->comments_count; // "5"
Kadang kamu butuh data dari tabel lain tanpa join. Subquery di select bisa jadi solusi yang clean.
use Illuminate\Database\Eloquent\Builder;
// Ambil user dengan total order terakhir
$users = User::select('users.*')
->selectSub(function (Builder $query) {
$query->selectRaw('SUM(total)')
->from('orders')
->whereColumn('orders.user_id', 'users.id');
}, 'total_spent')
->get();
// Atau pakai addSelect buat nambah kolom
$users = User::query()
->addSelect(['last_order_date' => Order::select('created_at')
->whereColumn('user_id', 'users.id')
->latest()
->limit(1)
])
->get();
Banyak developer yang masih fetch dulu baru update. Kalau cuma update kolom tertentu, langsung pakai query builder aja, lebih hemat satu query.
// Kurang efisien (2 query: SELECT + UPDATE)
$user = User::find(1);
$user->status = 'active';
$user->save();
// Lebih efisien (1 query: UPDATE langsung)
User::where('id', 1)->update(['status' => 'active']);
// Bulk update dengan kondisi
Post::where('published_at', '<', now())
->where('status', 'draft')
->update(['status' => 'archived']);
// Increment/Decrement tanpa fetch
Product::where('category_id', 5)->increment('view_count');
Product::where('category_id', 5)->decrement('stock', 3);
Kalau kamu perlu proses jutaan record, jangan pakai all() atau get(). Memori bisa habis. Pakai chunk atau lazy collection.
// Chunk - proses per batch
User::chunk(100, function ($users) {
foreach ($users as $user) {
// Proses tiap user
sendEmail($user);
}
});
// Chunk dengan query filter
User::where('is_active', 1)
->orderBy('id')
->chunk(100, function ($users) {
foreach ($users as $user) {
// Proses
}
});
// Lazy - stream satu per satu (lebih hemat memori)
User::lazy()->each(function ($user) {
// Proses satu per satu
});
// Lazy dengan chunk size
User::lazy(1000)->each(function ($user) {
// Proses per 1000
});
Mau ambil post yang komentarnya banyak? Atau user yang punya order? whereHas jawabannya.
// Post yang punya minimal 1 komentar
$posts = Post::whereHas('comments')->get();
// Post yang komentarnya dari user tertentu
$posts = Post::whereHas('comments', function ($query) {
$query->where('user_id', 1);
})->get();
// Post dengan minimal 5 komentar
$posts = Post::has('comments', '>=', 5)->get();
// User yang TIDAK punya order
$users = User::doesntHave('orders')->get();
// Kombinasi
$posts = Post::whereHas('comments', function ($query) {
$query->where('is_approved', true);
}, '>=', 3)->get();
Eloquent bisa otomatis convert tipe data kolom. Ini bikin kode lebih aman dan clean.
protected $casts = [
'is_active' => 'boolean',
'price' => 'decimal:2',
'settings' => 'array',
'metadata' => 'json',
'published_at' => 'datetime',
'expires_at' => 'date',
'password' => 'hashed',
];
// Sekarang bisa langsung pakai tipe yang sesuai
$product = Product::find(1);
if ($product->is_active) { // boolean, bukan string "1"
echo number_format($product->price, 2); // "150.00"
}
// Array cast - simpan JSON, akses sebagai array
$product->settings = ['theme' => 'dark', 'lang' => 'id'];
$product->save(); // otomatis jadi JSON string di DB
echo $product->settings['theme']; // "dark"
Dua method ini sering banget kepake, terutama buat import data atau sync dari API.
// Cari user berdasarkan email, kalau gak ada buat baru
$user = User::firstOrCreate(
['email' => '[email protected]'], // attributes buat cari
['name' => 'John', 'password' => 'secret'] // attributes tambahan kalau buat baru
);
// Update kalau ketemu, buat baru kalau gak ada
$user = User::updateOrCreate(
['email' => '[email protected]'],
['name' => 'John Updated', 'last_login' => now()]
);
// Buat import CSV
$csvData = [
['email' => '[email protected]', 'name' => 'Alice'],
['email' => '[email protected]', 'name' => 'Bob'],
];
foreach ($csvData as $row) {
User::updateOrCreate(
['email' => $row['email']],
['name' => $row['name'], 'imported_at' => now()]
);
}
Pas development, kamu pasti butuh tau query apa yang sebenarnya jalan. Beberapa cara buat debug:
// dd() hasil query
dd(User::where('active', 1)->toRawSql());
// Log semua query di provider
DB::listen(function ($query) {
logger()->info($query->sql, $query->bindings);
});
// Pakai Laravel Debugbar (package)
composer require barryvdh/laravel-debugbar --dev
Eloquent itu luas banget, dan tips di atas baru permukaan aja. Yang penting, jangan cuma pakai all() dan find(). Explore scope, accessor, eager loading, dan fitur lainnya. Kodenya bakal lebih maintainable dan performanya juga lebih baik. Kalau ada tips Eloquent favorit yang belum saya sebut, share di komentar dong!