Centralised model deletion

Written by Riari 10 months ago. Last updated 10 months ago.

Laravel-5

Deletion of records is one thing that's almost always consistent throughout an application, so it often makes sense to handle it in one place. In this example, I use SweetAlert2 to display confirmation dialogues for 'Delete' links in an admin context. Here's a quick overview of the steps involved:

Define a resource deletion route

// Admin
$r->group(['prefix' => 'admin', 'namespace' => 'Admin'], function ($r) {
    // Resource deletion
    $r->delete(
        '{model}/{id}',
        ['as' => 'admin.resource.delete', 'uses' => 'AdminController@postDeleteResource']
    );
});

Define the method

<?php namespace App\Http\Controllers\Admin;

use Illuminate\Http\Request;

class AdminController extends Controller
{
    ...

    /**
     * Handle a resource deletion request.
     *
     * @param  Request  $request
     * @return \Illuminate\Http\Response
     */
    public function postDeleteResource(Request $request)
    {
        $model = $request->route('model');
        $id = $request->route('id');

        $resource = $this->resolve($model, $id);
        $resource->delete();

        return redirect('admin');
    }

    /**
     * Resolve a resource using the given model name and ID.
     *
     * @param  string  $model
     * @param  int  $id
     * @return \Illuminate\Database\Eloquent\Model
     */
    private function resolve($model, $id)
    {
        $class = "\App\Models\\{$model}";
        return (new $class)->findOrFail($id);
    }
}

If you're using Laravel Authorisation to implement granular access control, you can use $this->authorize('delete', $resource) in the postDeleteResource() method to authorize the action on a per-model basis.

Write the markup and JS

A partial view can be written to contain the deletion link, which we can trigger SweetAlert2 on.

resources/views/partials/delete-link.blade.php

<form class="inline" method="post" action="{{ $action }}">
    {!! csrf_field() !!}
    {!! method_field('delete') !!}

    <button type="submit" data-confirm data-text="{{ $text }}" class="btn btn-link">Delete</button>
</form>

CSS

form.inline {
    display: inline-block;
}

JS

This may be better written as a jQuery plugin, but you get the idea:

function swalConfirmSubmit(button) {
    var confirmed = false;
    var text = button.data('text');

    button.on('click', function (e) {
        var target = e.target;

        if (!confirmed) {
            e.preventDefault();

            swal({
                title: 'Confirm Action',
                text: text,
                type: 'warning',
                showCancelButton: true,
                cancelButtonColor: '#90a4ae',
                confirmButtonColor: '#5d6070',
                reverseButtons: true
            }).then(function (isConfirm) {
                if (isConfirm === true) {
                    confirmed = true;
                    target.click();
                }
            });
        }
    });
}

// Action confirmation
$('button[data-confirm]').each(function () {
    swalConfirmSubmit($(this));
});

Usage:

@include('partials.delete-link', ['action' => route('admin.resource.delete', ['model' => 'Article', 'id' => $article->id]), 'text' => "Are you sure you want to remove {$article->title}?"])

The result is a simple link that triggers a SweetAlert2 'warning' dialogue containing the specified text, along with Confirm and Cancel buttons. If you don't like using forms in this way, an alternative would be to create an intermediate confirmation page by defining a GET route and a getDeleteResource() method in the controller, which would return a view containing the form that ultimately submits to postDeleteResource() - or maybe you use a front-end JS framework that can trigger POST requests from links without the need to use forms. Either way, with this approach, you only need to write the deletion code once and then use it for any current and future models in your application.

To make this more user-friendly, you could also define a public $friendlyName property in your models - this would contain a user-friendly version of the model name (e.g. "Forum\Category" would become "Forum Category") to be displayed by the controller via $resource->friendlyName after resolving the resource.