Kembali ke Artikel
10 November 2025
vandyahmad24
Diperbarui: 18 April 2026

Contoh Implementasi Observer di Laravel 12: Auto-Slug, Audit Trail, dan Cache

Artikel ini melanjutkan penjelasan konsep Observer di Laravel 12 dengan studi kasus implementasi lengkap: sistem audit trail dan auto-slug generation.

Studi Kasus 1: Auto-Slug Generation

Masalah umum: setiap kali artikel dibuat atau diupdate, slug harus di-generate dari title. Tanpa Observer, logika ini tersebar di berbagai controller.

Dengan Observer, cukup satu tempat:

<?php

namespace AppObservers;

use AppModelsArticle;
use IlluminateSupportStr;

class ArticleObserver
{
    public function creating(Article $article): void
    {
        $article->slug = $this->generateUniqueSlug($article->title);
    }

    public function updating(Article $article): void
    {
        if ($article->isDirty('title')) {
            $article->slug = $this->generateUniqueSlug($article->title, $article->id);
        }
    }

    private function generateUniqueSlug(string $title, ?int $excludeId = null): string
    {
        $slug  = Str::slug($title);
        $query = Article::where('slug', $slug);

        if ($excludeId) {
            $query->where('id', '!=', $excludeId);
        }

        if (!$query->exists()) {
            return $slug;
        }

        // Tambah angka kalau slug sudah ada
        $counter = 1;
        while (Article::where('slug', "{$slug}-{$counter}")
                      ->when($excludeId, fn ($q) => $q->where('id', '!=', $excludeId))
                      ->exists()) {
            $counter++;
        }

        return "{$slug}-{$counter}";
    }
}

Studi Kasus 2: Audit Trail Otomatis

Rekam semua perubahan pada model penting, berguna untuk compliance, debugging, atau fitur “lihat riwayat perubahan”:

<?php

namespace AppObservers;

use AppModelsArticle;
use AppModelsAuditLog;

class ArticleObserver
{
    public function created(Article $article): void
    {
        $this->log('created', $article, [], $article->getAttributes());
    }

    public function updated(Article $article): void
    {
        $this->log('updated', $article, $article->getOriginal(), $article->getChanges());
    }

    public function deleted(Article $article): void
    {
        $this->log('deleted', $article, $article->getAttributes(), []);
    }

    private function log(string $action, Article $article, array $old, array $new): void
    {
        AuditLog::create([
            'user_id'     => auth()->id(),
            'model_type'  => Article::class,
            'model_id'    => $article->id,
            'action'      => $action,
            'old_values'  => $old,
            'new_values'  => $new,
            'ip_address'  => request()->ip(),
        ]);
    }
}

Model AuditLog:

Schema::create('audit_logs', function (Blueprint $table) {
    $table->id();
    $table->foreignId('user_id')->nullable()->constrained()->nullOnDelete();
    $table->string('model_type');
    $table->unsignedBigInteger('model_id');
    $table->string('action'); // created, updated, deleted
    $table->json('old_values')->nullable();
    $table->json('new_values')->nullable();
    $table->string('ip_address')->nullable();
    $table->timestamp('created_at');

    $table->index(['model_type', 'model_id']);
});

Studi Kasus 3: Cache Invalidation

Cache artikel halaman statis dan harus di-clear saat artikel berubah:

<?php

namespace AppObservers;

use AppModelsArticle;
use IlluminateSupportFacadesCache;

class ArticleObserver
{
    public function saved(Article $article): void
    {
        // Clear cache artikel individual
        Cache::forget("article:{$article->id}");
        Cache::forget("article:{$article->slug}");

        // Clear cache daftar artikel
        Cache::forget('articles:latest');
        Cache::forget("articles:category:{$article->category_id}");
    }

    public function deleted(Article $article): void
    {
        Cache::forget("article:{$article->id}");
        Cache::forget("article:{$article->slug}");
        Cache::forget('articles:latest');
    }
}

Studi Kasus 4: Observer dengan Multiple Models

Kalau beberapa model butuh audit trail yang sama, buat Observer yang reusable:

<?php

namespace AppObservers;

use AppModelsAuditLog;

class AuditableObserver
{
    public function created($model): void
    {
        AuditLog::create([
            'user_id'    => auth()->id(),
            'model_type' => get_class($model),
            'model_id'   => $model->id,
            'action'     => 'created',
            'new_values' => $model->getAttributes(),
        ]);
    }

    public function updated($model): void
    {
        AuditLog::create([
            'user_id'    => auth()->id(),
            'model_type' => get_class($model),
            'model_id'   => $model->id,
            'action'     => 'updated',
            'old_values' => $model->getOriginal(),
            'new_values' => $model->getChanges(),
        ]);
    }
}

Register ke beberapa model sekaligus:

// Di AppServiceProvider
Article::observe(AuditableObserver::class);
Product::observe(AuditableObserver::class);
Order::observe(AuditableObserver::class);

Bypass Observer saat Seeding

<?php

namespace DatabaseSeeders;

use AppModelsArticle;

class ArticleSeeder extends Seeder
{
    public function run(): void
    {
        // Bypass observer agar tidak trigger audit trail saat seeding
        Article::withoutObservers(function () {
            Article::factory(100)->create();
        });
    }
}

Baca Juga

Butuh tim yang bantu implementasi arsitektur yang clean di aplikasi Laravel? Lihat layanan pengembangan aplikasi kami.

Tag: #laravel #php #tutorial
BACA JUGA

Artikel Lainnya di Kategori Laravel

Laravel

10 November 2025

Contoh Penggunaan Concurrency di Laravel 12: Dashboard, API Paralel, dan Defer

Artikel sebelumnya membahas konsep Concurrency di Laravel 12. Artikel ini fokus pada implementasi: studi kasus nyata bagaimana Concurrency bisa mempercepat aplikasi secara signifikan. Studi Kasus 1: Dashboard dengan Banyak Data Source Dashboard admin yang butuh data dari beberapa tabel berbeda. Ini biasanya jadi bottleneck karena diquery satu per satu. Sebelum (sequential — sekitar 800ms): public […]

Baca Artikel
Laravel

10 November 2025

Apa Itu Policy dan Gate di Laravel 12: Sistem Otorisasi yang Tepat

Bayangkan ada dua pertanyaan berbeda soal keamanan di aplikasi Anda: “Apakah user ini boleh edit artikel?” dan “Apakah user yang login adalah editor?” Pertanyaan pertama terkait Policy: otorisasi berdasarkan resource. Pertanyaan kedua terkait Gate: otorisasi berdasarkan kemampuan/role. Keduanya bagian dari sistem Authorization di Laravel. Apa Itu Gate? Gate adalah cara mendefinisikan otorisasi berbasis kemampuan (ability) […]

Baca Artikel
Laravel

10 November 2025

Contoh Penggunaan Contract di Laravel 12: Implementasi dan Binding

Kalau Anda sudah membaca artikel tentang apa itu Contract di Laravel 12, artikel ini melanjutkannya dengan contoh penggunaan nyata: bagaimana membuat implementasi Contract sendiri dan kapan ini berguna dalam proyek. Menggunakan Contract Bawaan Laravel Contract bawaan Laravel ada di namespace Illuminate\Contracts\*. Contoh yang paling sering dipakai adalah type-hinting di constructor untuk decoupling: <?php namespace App\Services; […]

Baca Artikel

Ingin Membaca Artikel Lainnya?

Temukan lebih banyak insight dan tips tentang teknologi dan bisnis digital.

Lihat Semua Artikel