Pernahkah kamu perlu menjalankan kode tertentu setiap kali sebuah model disimpan, diperbarui, atau dihapus? Misalnya, membuat log aktivitas otomatis, mengirim notifikasi, atau membersihkan cache. Cara naif adalah menulisnya di setiap controller, tapi ini melanggar prinsip DRY (Don't Repeat Yourself). Solusinya adalah Model Observer โ sebuah kelas yang "mengawasi" lifecycle event Eloquent dan bereaksi secara otomatis.
Apa itu Eloquent Events?
Eloquent memicu event-event berikut selama lifecycle sebuah model:
creating/createdโ sebelum dan sesudah INSERT.updating/updatedโ sebelum dan sesudah UPDATE.saving/savedโ sebelum dan sesudah CREATE atau UPDATE.deleting/deletedโ sebelum dan sesudah DELETE.restoring/restoredโ untuk soft delete, sebelum dan sesudah restore.retrievedโ setelah model diambil dari database.
Membuat Observer
Buat observer menggunakan Artisan:
php artisan make:observer ProductObserver --model=Product
File akan dibuat di app/Observers/ProductObserver.php:
<?php
namespace App\Observers;
use App\Models\Product;
use Illuminate\Support\Facades\Log;
class ProductObserver
{
public function creating(Product $product): void
{
// Otomatis isi slug sebelum disimpan
if (empty($product->slug)) {
$product->slug = \Str::slug($product->name);
}
}
public function created(Product $product): void
{
Log::info("Produk baru dibuat: {$product->name} (ID: {$product->id})");
}
public function updating(Product $product): void
{
// Update slug jika nama berubah
if ($product->isDirty('name')) {
$product->slug = \Str::slug($product->name);
}
}
public function updated(Product $product): void
{
// Bersihkan cache terkait produk ini
cache()->forget("product_{$product->id}");
Log::info("Produk diperbarui: {$product->name}");
}
public function deleting(Product $product): void
{
// Hapus semua gambar terkait sebelum produk dihapus
foreach ($product->images as $image) {
\Storage::disk('public')->delete($image->path);
$image->delete();
}
}
public function deleted(Product $product): void
{
Log::warning("Produk dihapus: {$product->name} (ID: {$product->id})");
}
}
Mendaftarkan Observer
Ada dua cara mendaftarkan observer. Cara pertama adalah menggunakan attribute #[ObservedBy] langsung di model (Laravel 10.22+):
<?php
namespace App\Models;
use App\Observers\ProductObserver;
use Illuminate\Database\Eloquent\Attributes\ObservedBy;
use Illuminate\Database\Eloquent\Model;
#[ObservedBy([ProductObserver::class])]
class Product extends Model
{
protected $fillable = ['name', 'slug', 'price', 'stock'];
}
Cara kedua adalah mendaftarkan di AppServiceProvider:
<?php
namespace App\Providers;
use App\Models\Product;
use App\Observers\ProductObserver;
use Illuminate\Support\ServiceProvider;
class AppServiceProvider extends ServiceProvider
{
public function boot(): void
{
Product::observe(ProductObserver::class);
}
}
Contoh Nyata: Activity Log Otomatis
Buat observer untuk mencatat semua perubahan data User ke tabel activity log:
<?php
namespace App\Observers;
use App\Models\ActivityLog;
use App\Models\User;
class UserObserver
{
public function created(User $user): void
{
$this->log('created', $user, null, $user->toArray());
}
public function updated(User $user): void
{
$this->log('updated', $user, $user->getOriginal(), $user->getChanges());
}
public function deleted(User $user): void
{
$this->log('deleted', $user, $user->toArray(), null);
}
private function log(string $event, User $user, ?array $before, ?array $after): void
{
ActivityLog::create([
'user_id' => auth()->id(),
'model_type' => User::class,
'model_id' => $user->id,
'event' => $event,
'before' => $before ? json_encode($before) : null,
'after' => $after ? json_encode($after) : null,
]);
}
}
Menonaktifkan Observer Sementara
Terkadang kamu perlu menonaktifkan observer, misalnya saat seeder atau proses bulk import:
// Nonaktifkan observer untuk satu blok kode
Product::withoutObservers(function () {
Product::factory()->count(1000)->create();
});
// Atau nonaktifkan observer tertentu saja
Product::withoutObservers([ProductObserver::class], function () {
Product::factory()->count(1000)->create();
});
Menggunakan isDirty() dan getChanges()
Di dalam observer, kamu bisa mengecek kolom mana yang berubah:
public function updating(Product $product): void
{
// Cek apakah kolom tertentu berubah
if ($product->isDirty('price')) {
$oldPrice = $product->getOriginal('price');
$newPrice = $product->price;
// Kirim notifikasi perubahan harga ke subscriber
}
// Lihat semua perubahan
$changes = $product->getDirty();
// ['price' => 15000, 'stock' => 20]
}
Kesimpulan
Model Observer adalah pola yang sangat bersih untuk memisahkan side effects dari logic bisnis utama. Alih-alih menyebar kode logging, cache clearing, atau notifikasi di seluruh controller, cukup daftarkan satu observer dan semua itu akan berjalan otomatis. Ini membuat kode lebih mudah ditest dan dipelihara. Gunakan observer setiap kali kamu menemukan pola berulang yang bereaksi terhadap perubahan model.