Search code examples
phplaravelelasticsearchalgolialaravel-scout

Laravel Scout - searching trashed records and keeping soft deleted models updated


By default Laravel Scout will remove any model from the search index upon it being deleted, even if that model is soft deleted.

Instead of being removed, how can we keep the model in the search index and update it to have the current timestamp for deleted_at?


Solution

  • The key lies in the source code for laravel-scout. First we should get familiar with the Searchable.php file, after-all it's the trait we apply to our model that kicks off all the magic. The methods searchable and unsearchable are pretty clear to their purpose.

    Now notice the two static methods enableSearchSyncing and disableSearchSyncing. This will let us control the syncing behavior. If we look back at the introduction to laravel scout, it gives us this hint:

    Using model observers, Scout will automatically keep your search indexes in sync with your Eloquent records.

    So we should turn our attention to the ModelObserver.php. This is where it all happens. The observer looks after four model events, created, updated, deleted, and restored.

    updated, and restored simply call the created method, which will check if to make sure syncing is not disabled, then run $model->searchable().

    deleted, what we want to prevent from happening is very similar. It will check to see if syncing is enabled, and then run $model->unsearchable();.

    Solution:

    Now that we understand how it works, it's relatively simple to get our desired effect. We will take a page out of scouts book and do use observers as well to update our search index when it is deleted. Here's what it looks like:

    class UserObserver
    {
        /**
         * Listen to the User deleting event.
         * 
         * @param User $user
         */
        public function deleting(User $user)
        {
            $user::disableSearchSyncing();
        }
    
        /**
         * Listen to the User deleted event.
         * 
         * @param User $user
         */
        public function deleted(User $user)
        {
            $user::enableSearchSyncing();
    
            $user->searchable();
        }
    }
    

    After creating the observer make sure you don't forget to add it to your AppServiceProvider's boot method, otherwise it will never get registered.

    class AppServiceProvider extends ServiceProvider
    {
        public function boot()
        {
            User::observe(UserObserver::class);
        }
    
    ...
    

    To recap on how this works. Before the model is deleted (deleting event), we tell scout to stop syncing. Then when the model is deleted, we re-enable syncing, and call the searchable method to perform the update. Our record in our search database will now be updated with the proper deleted_at timestamp.