How to Use Model Observers in Laravel

Have you ever needed to run certain code every time a model is saved, updated, or deleted? For example, automatically creating an activity log, sending a notification, or clearing...

How to Use Model Observers in Laravel

Have you ever needed to run certain code every time a model is saved, updated, or deleted? For example, automatically creating an activity log, sending a notification, or clearing the cache. The naive way is to write it in every controller, but this violates the DRY (Don't Repeat Yourself) principle. The solution is a Model Observer — a class that "watches" Eloquent lifecycle events and reacts automatically.

What Are Eloquent Events?

Eloquent fires the following events during a model's lifecycle:

  • creating / created — before and after INSERT.
  • updating / updated — before and after UPDATE.
  • saving / saved — before and after CREATE or UPDATE.
  • deleting / deleted — before and after DELETE.
  • restoring / restored — for soft deletes, before and after restore.
  • retrieved — after the model is retrieved from the database.

Creating an Observer

Create an observer using Artisan:

php artisan make:observer ProductObserver --model=Product

The file is created at 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
    {
        // Automatically fill the slug before saving
        if (empty($product->slug)) {
            $product->slug = \Str::slug($product->name);
        }
    }

    public function created(Product $product): void
    {
        Log::info("New product created: {$product->name} (ID: {$product->id})");
    }

    public function updating(Product $product): void
    {
        // Update the slug if the name changes
        if ($product->isDirty('name')) {
            $product->slug = \Str::slug($product->name);
        }
    }

    public function updated(Product $product): void
    {
        // Clear the cache related to this product
        cache()->forget("product_{$product->id}");
        Log::info("Product updated: {$product->name}");
    }

    public function deleting(Product $product): void
    {
        // Delete all related images before the product is deleted
        foreach ($product->images as $image) {
            \Storage::disk('public')->delete($image->path);
            $image->delete();
        }
    }

    public function deleted(Product $product): void
    {
        Log::warning("Product deleted: {$product->name} (ID: {$product->id})");
    }
}

Registering the Observer

There are two ways to register an observer. The first is using the #[ObservedBy] attribute directly on the 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'];
}

The second way is to register it in 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);
    }
}

Real Example: Automatic Activity Log

Create an observer to record all User data changes to an activity log table:

<?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,
        ]);
    }
}

Temporarily Disabling an Observer

Sometimes you need to disable an observer, for example during a seeder or a bulk import process:

// Disable observers for one block of code
Product::withoutObservers(function () {
    Product::factory()->count(1000)->create();
});

// Or disable only a specific observer
Product::withoutObservers([ProductObserver::class], function () {
    Product::factory()->count(1000)->create();
});

Using isDirty() and getChanges()

Inside an observer, you can check which columns changed:

public function updating(Product $product): void
{
    // Check whether a specific column changed
    if ($product->isDirty('price')) {
        $oldPrice = $product->getOriginal('price');
        $newPrice = $product->price;
        // Send a price change notification to subscribers
    }

    // See all changes
    $changes = $product->getDirty();
    // ['price' => 15000, 'stock' => 20]
}

Conclusion

A Model Observer is a very clean pattern for separating side effects from the main business logic. Instead of scattering logging, cache clearing, or notification code across all controllers, just register one observer and it all runs automatically. This makes the code easier to test and maintain. Use an observer whenever you find a repeating pattern that reacts to model changes.

model observer laravel laravel observer eloquent observer event model laravel laravel creating updating deleting observer pattern laravel
Share this article
Back to Blog
🚀 Partner Recommendation

Need Premium Source Code & Business Apps?

Access Laravel applications, POS systems, School Management, Clinic Software, ERP solutions, and ready-to-use premium source code at GudangCode.

GudangCode
  • ✔ Premium Source Code
  • ✔ Ready-to-Use Systems
  • ✔ Lifetime Updates
  • ✔ Lifetime Membership
  • ✔ Daily App Updates
Join Membership →