Skip to content

Skip deleted handler during force delete on SoftDeletes models#984

Merged
taylorotwell merged 1 commit into
laravel:11.xfrom
jobjen02:fix/double-remove-from-search-on-force-delete
May 13, 2026
Merged

Skip deleted handler during force delete on SoftDeletes models#984
taylorotwell merged 1 commit into
laravel:11.xfrom
jobjen02:fix/double-remove-from-search-on-force-delete

Conversation

@jobjen02

Copy link
Copy Markdown
Contributor

Calling forceDelete() on a Searchable model that uses SoftDeletes currently dispatches RemoveFromSearch twice.

SoftDeletes::forceDelete() delegates to Model::delete(), which always fires the deleted event after performing the delete. Scout's ModelObserver::deleted() calls $model->unsearchable(). Immediately after, SoftDeletes::forceDelete() fires forceDeleted, and ModelObserver::forceDeleted() calls unsearchable() again.

For bulk delete operations (for example tenant/account purges with large amounts of records), this doubles the load on the search index queue.

Event flow on $model->forceDelete() (SoftDeletes)

  1. SoftDeletes::forceDelete() fires forceDeleting
  2. Sets $this->forceDeleting = true
  3. Delegates to Model::delete():
    • Fires deleting
    • Performs the SQL delete
    • Fires deleted → first unsearchable()
  4. SoftDeletes::forceDelete() fires forceDeleted → second unsearchable()

Fix

Skip the deleted handler when the soft-deletes model is currently being force deleted.

The subsequent forceDeleted handler still dispatches unsearchable(), so the index is correctly cleaned.

Unchanged behaviour

  • Models without SoftDeletes are unaffected (usesSoftDelete($model) check)
  • Plain delete() calls on a SoftDeletes model still behave as before (isForceDeleting() returns false)
  • With scout.soft_delete = true, the force delete path still correctly removes the model via forceDeleted

This also avoids the unnecessary re-index/removal work in deleted, where the model is about to be hard-deleted anyway.

Reproduction

$model = SearchableSoftDeletesModel::find($id);
$model->forceDelete();

// Before: 2 RemoveFromSearch jobs dispatched
// After:  1 RemoveFromSearch job dispatched

@jobjen02

Copy link
Copy Markdown
Contributor Author

My first contribution to Laravel (or a Laravel Package)!

I hope this is the correct way of doing so, if not, please let me know!

@taylorotwell taylorotwell merged commit a3fa1a4 into laravel:11.x May 13, 2026
29 checks passed
@jobjen02 jobjen02 deleted the fix/double-remove-from-search-on-force-delete branch May 14, 2026 13:44
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants