Laravel Soft Deletes

Laravel Soft Deletes

2020-05-13 Technology 0

While investigating the Laravel framework I ran into the following problem:

I have a model for which I enabled Soft Deletes. I want to allow the user to restore those models as well, using Soft Delete as a view-filtering option. Usually, the model should remain invisible from the overview and is only retained, because it links into other models that cannot be removed ever. The use case seems fine for Soft Delete.

I created a default Resource Controller and routing the various routes using the Route::resource shortcut. Unfortunately, this shortcut does not allow for restore and forceDelete routes, so I had to add those manually:

Route::delete("/model/{model}/restore",'ModelController@restore')
    ->middleware('can:restore,model')->name('model.restore');
Route::delete("/model/{model}/destroy",'ModelController@forceDelete')
    ->middleware('can:forceDelete,model')->name('model.forceDelete');
Route::resource('model', 'ModelController');

The additional routes I created as delete methods as well, to mimic the standard delete route. A get ought to be fine though as well.

This whole model is authorized using a relevant Policy, which is initiated in the Controller constructor:

public function __construct()
{
    $this->authorizeResource(Model::class, 'model');
}

I added the relevant forceDelete and restore methods, much in the same way as the destroy method:

public function destroy(Model $model)
{
    $fund->delete();
    return redirect()->route('model.index')->with('success', __('The model was removed'));
}
public function forceDelete(Model $model)
{
    $fund->forceDelete();
    return redirect()->route('model.index')->with('success', __('The model was destroyed'));
}
public function restore(Model $model)
{
    $fund->restore();
    return redirect()->route('model.index')->with('success', __('The model was restored'));
}

This does not work, unfortunately. It returns a 404 on the restore and forceDelete requests, although the rest works fine.

It took me a few hours to find out why, but the cause is quite logical. The Soft Delete trait puts an additional where clause on any query on the Model. If you want to look in the soft deleted models collection, you need to apply the trashed() query builder extension.

Now, when the Router is binding the route parameter for the Model, it will perform a basic find query using the model identifier. However, because of the Soft Delete, it will only look into the non-deleted models. Ofcourse, for restore and forceDelete, we should be looking into the trashed models as well.

The workaround is to implement the resolveRouteBinding method of the Model class. This allows you to run a custom query to resolve the route identifier to a model instance, bypassing the trashed models.

Arguably, all Soft Deleted models should implement this, to avoid having user 1 delete a model that user 2 is currently editting or viewing. If user 2 then submits the model, he or she will get a nasty 404 on something they were able to view just a moment ago. If you are working with API calls under the hood, they will suddenly start returning errors although user 2 changed nothing.

 

Geef een reactie

Je e-mailadres wordt niet gepubliceerd. Vereiste velden zijn gemarkeerd met *