How To Use Laravel Model Observers

How To Use Laravel Model Observers

Laravel’s Eloquent ORM is the rock-solid implementation of Active Record. Apart from other awesome features offered by Laravel Eloquent, Laravel implements Observer Pattern to fire some events, which can be listened to hook into, when various actions are performed on a model.

The observer pattern is a software design pattern in which an object, called the subject, maintains a list of its dependents, called observers, and notifies them automatically of any state changes, usually by calling one of their methods. Source : Wikipedia

In this post, we will be learning about Laravel’s model events and how we can create model observers for grouping event listeners.

Laravel Model Events

If you have used Laravel for a medium to large scale project, you might have encountered a situation where you want to perform some action while your Eloquent model is processing. Laravel’s Eloquent provide a convenient way to add your own action while the model is completing or has completed some action.

For example, if you have Post model and you want to set post slug automatically when a post is created. You can listen to the saving event and set the post slug like below:

namespace App;

use Illuminate\Database\Eloquent\Model;

class Post extends Model
{
    protected $table = 'posts';

    protected $fillable = ['title', 'slug', 'content'];

    protected static function boot()
    {
        parent::boot();
        static::saving(function ($model) {
            $model->slug = str_slug($model->title);
        });
    }
}

Eloquent provides a handful of events to monitor the model state which are:

  • retrieved : after a record has been retrieved.
  • creating : before a record has been created.
  • created : after a record has been created.
  • updating : before a record is updated.
  • updated : after a record has been updated.
  • saving : before a record is saved (either created or updated).
  • saved : after a record has been saved (either created or updated).
  • deleting : before a record is deleted or soft-deleted.
  • deleted : after a record has been deleted or soft-deleted.
  • restoring : before a soft-deleted record is going to be restored.
  • restored : after a soft-deleted record has been restored.

Laravel Model Observers

Imagine your application is growing, and you have to listen to most of the above events in your model, listening to the events within the model will make your model very big and messy of course.

Using model observers, you can group all your events into a single class. All method names in the observer class will reflect on the event you are listening to. You can create a model observer class using below artisan command.

php artisan make:observer PostObserver --model=Post

Above command will create a new class located in the app/Observers folder. Your newly created observer class will look like below:

namespace App\Observers;

use App\Post;

class PostObserver
{
    /**
     * Handle the post "created" event.
     *
     * @param  \App\Post  $post
     * @return void
     */
    public function created(Post $post)
    {
        //
    }

    /**
     * Handle the post "updated" event.
     *
     * @param  \App\Post  $post
     * @return void
     */
    public function updated(Post $post)
    {
        //
    }

    /**
     * Handle the post "deleted" event.
     *
     * @param  \App\Post  $post
     * @return void
     */
    public function deleted(Post $post)
    {
        //
    }

    /**
     * Handle the post "restored" event.
     *
     * @param  \App\Post  $post
     * @return void
     */
    public function restored(Post $post)
    {
        //
    }

    /**
     * Handle the post "force deleted" event.
     *
     * @param  \App\Post  $post
     * @return void
     */
    public function forceDeleted(Post $post)
    {
        //
    }
}

Now you have to register this class in Laravel’s Service Container, so we will add this class in AppServiceProvider‘s boot() method by telling Post model to observe the PostObserver class, like below:

namespace App\Providers;

use App\Post;
use App\Observers\PostObserver;
use Illuminate\Support\ServiceProvider;

class AppServiceProvider extends ServiceProvider
{
    /**
     * Register any application services.
     *
     * @return void
     */
    public function register()
    {
        //
    }

    /**
     * Bootstrap any application services.
     *
     * @return void
     */
    public function boot()
    {
        Post::observe(PostObserver::class);
    }
}

In our PostObserver class, Laravel created some function by default like created, updated and others. We will delete all of them and add a saving hook by telling to create a post slug from post title before the post is saved.

Replace your observer class with below.

namespace App\Observers;

use App\Post;

class PostObserver
{
    /**
     * Handle the post "saving" event.
     *
     * @param  \App\Post  $post
     * @return void
     */
    public function saving(Post $post)
    {
        $post->slug = str_slug($post->title);
    }
}

Now we have successfully added a model observer to observe our Post model, whenever you create a new post, it’s slug will be automatically created before saving the post.

Practicle Example

For example, our Post model has associated comments. What will happen if I delete a post, will that delete the comments as well?

Of Course Not

Obviously, we don’t want to delete the post comments manually in our code base where we are deleting the post. We can use the deleting hook to do this for us.

You might will have a HasMany relationship to the Comment model and you model will look like below.

namespace App;

use Illuminate\Database\Eloquent\Model;

class Post extends Model
{
    protected $table = 'posts';

    protected $fillable = ['title', 'slug', 'content'];

    public function comments()
    {
        return $this->hasMany(Comment::class);
    }
}

Now we can add a deleting hook in our PostObserver class like below and delete the attached comments in it.

namespace App\Observers;

use App\Post;

class PostObserver
{
    /**
     * Handle the post "saving" event.
     *
     * @param  \App\Post  $post
     * @return void
     */
    public function saving(Post $post)
    {
        $post->slug = str_slug($post->title);
    }

    /**
     * Handle the post "deleting" event.
     *
     * @param  \App\Post  $post
     * @return void
     */
    public function deleting(Post $post)
    {
        $post->comments()->delete();
    }
}

Now every time, you delete a post, all comments associated with that post will get deleted.

Limitations

There are some limitations attached to Model Observer which you should keep in mind while using the model observers.

  • When you use saved or saving hooks, you should never call the model’s save method.
  • If you are using saved hook and want to call the save method, then you should probably use the saving hook.
  • If your logic need to call model’s save method, then rethink your logic or avoid using observers.

Conclusion

Laravel Model Observers are a very powerful yet useful feature and can make your codebase more maintainable. You should keep in mind about the limitations and where you need to apply observers. My formula for using observers is to apply as minimal logic in the observer as I can.

If you have any questions or comments about Eloquent, be sure to post them below!

10 comments on “How To Use Laravel Model Observers

  1. Nice article, can you please elaborate when & why we shouldn’t call save method as said in limitation? while creating new model object, or updating existing one?

    I know we can use create function to create record, what will we do for saving the updated model?

    1. Hi Zain,

      If you call the save method inside a saving or saved hook, it will trigger an indefinite loop because you are hooking in to saving or saved method and when eloquent will fire that hook it will call your save method and then that save method will obviously again run the observer hook and it keeps going on and on. That’s why you should never call a save method inside saving or saved hook.

      For an update, you should use updating or updated hook.

      For this post I have used a simple example to make it simpler for people to understand that’s why I used slug example. While using observers, if you use an eloquent method inside it, will be considered as an antipattern. You should perform tasks like logging, caching and other bits in observers.

      Thanks

  2. It’s clear why ->save() should not be used. However, is your change to the model ($post->slug = str_slug($post->title);) persisted at all without calling ->save()? I’m trying something similar (saving the image ratio for an uploaded photo to the model) and am experiencing trouble getting the value into the database ..

    1. Yes, when we use

      static::saving(function ($model) {
      $model->slug = str_slug($model->title);
      });

      We are actually intervening the default saving behavior so this code will set the slug to whatever we define and save method will be applied from when you call the save method on a post model.

      1. That explains why it wasn’t working for me: I was using the ‘saved’ method, not the ‘saving’, so I wasn’t able to get my value into the save process. Works like a charme hooking into the correct events. Thanks!

  3. Is there anyway to order Observers besides placing them all in the boot method in AppServiceProvider?
    I feel it will get big pretty fast in case you have many observers.

Leave a Reply

Your email address will not be published. Required fields are marked *

*

*
*

This site uses Akismet to reduce spam. Learn how your comment data is processed.

Laravel Routing Made Easy
Routing is one of the core elements of any web framework. Laravel Routing is very easy and simple to use because of its...