Gracefully handle deletion of Eloquent models with related entities.
robotsinside/laravel-deletable is a Laravel package for gracefully handle deletion of eloquent models with related entities..
It currently has 1 GitHub stars and 1.215 downloads on Packagist (latest version 1.5.0).
Install it with composer require robotsinside/laravel-deletable.
Discover more Laravel packages by robotsinside
or browse all Laravel packages to compare alternatives.
Last updated
This package can be used to gracefully handle the deletion of Eloquent models which are related to other models through HasOne, HasMany, BelongsTo, BelongsToMany or Morph* relationships.
It provides a number of helpful additions:
DeletableRequest classRun composer require robotsinside/laravel-deletable.
Optionally register the service provider in config/app.php
/*
* Package Service Providers...
*/
\RobotsInside\DeletableServiceProvider::class,
Auto-discovery is enabled, so this step can be skipped.
Use the RobotsInside\Deletable\Deletable trait in your models. You must also define a protected deletableConfig() method which returns the configuration array.
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
use RobotsInside\Deletable\Deletable;
class Post extends Model
{
use Deletable, SoftDeletes;
protected function deletableConfig(): array
{
return [
'relations' => [
'authors',
]
]
}
public function authors()
{
return $this->belongsToMany(Author::class);
}
}
A Post implements a HasMany relation with a Like model.
<?php
$post = Post::create(['title' => 'My post']);
$like = new Like;
$like->post()->associate($post);
$like->save()
$post->delete(); // SQLSTATE[23000]: Integrity constraint violation
To avoid this error and provide the user with some more helpful feedback, we can use the DeletableRequest class.
<?php
namespace App\Http\Controllers;
use App\Post;
use RobotsInside\Deletable\Requests\DeletableRequest;
class PostController extends Controller
{
/**
* Remove the specified resource from storage.
*
* @param DeletableRequest $request
* @param Post $post
* @return \Illuminate\Http\Response
*/
public function destroy(DeletableRequest $request, Post $post)
{
$post->delete();
return redirect()->route('posts.index');
}
}
Now we can display the Integrity contraint violation as validation errors instead..
<div>
@if ($errors->any())
<div class="alert alert-danger">
<ul>
@foreach ($errors->all() as $error)
<li>{{ $error }}</li>
@endforeach
</ul>
</div>
@endif
</div>
// Output: This Post has one or more Likes.
This feature supports all relation types. It's particularly helpful when Laravel's soft deletes are in use, since soft-deleting always succeeds without throwing an Integrity Constraint Violation error.
<?php
$post = Post::create(['title' => 'My post']);
$author = Author::create(['name' => 'Billy Bob']);
$post->authors()->save($author);
if($post->deletable()) {
$post->delete();
}
To validate delete requests, you can type-hint the provided RobotsInside\Deletable\Requests\DeletableRequest class in your controller method.
This class will attempt to automatically resolve the model's route binding, however it currently only supports a single URI route binding.
+-----------+--------------+---------------+----------------------------------------------
| Method | URI | Name | Action
+-----------+--------------+---------------+----------------------------------------------
| DELETE | posts/{post} | posts.destroy | App\Http\Controllers\PostController@destroy
If your route has more than one binding, such as authors/{author}/posts/{post}, you'll need to create your own form request, which extends DeletableRequest and define a getRouteModel method which returns the models' route binding.
Below is an example for routes with more than one route binding. This is all that is required for validation to kick in.
<?php
namespace App\Http\Requests;
use RobotsInside\Deletable\Requests\DeletableRequest;
class DeletePostRequest extends DeletableRequest
{
protected function getRouteModel()
{
return 'post';
}
}
As before, type-hint the extended form request in your controller.
<?php
namespace App\Http\Controllers;
use App\Http\Requests\DeletePostRequest;
class PostContoller extends Controller
{
...
/**
* Remove the specified resource from storage.
*
* @param App\Http\Requests\DeletePostRequest;
* @param App\Post $post
* @return \Illuminate\Http\Response
*/
public function destroy(DeletePostRequest $request, Post $post)
{
$post->delete();
return back();
}
}
If you don't want to rely on the default validation messages, you can define a deletableValidationMessage method on your model. You are free to add custom messages for each related model that is preventing a delete.
<?php
namespace App\Models\Post;
use App\Models\Author;
use App\Models\Like;
use Illuminate\Database\Eloquent\Model;
use RobotsInside\Deletable\Deletable;
class Post extends Model
{
use Deletable;
public function deletableValidationMessage($model)
{
switch ($model) {
case Like::class:
return 'Posts with likes cannot be deleted.';
break;
case Author::class:
return 'Posts written by authors cannot be deleted.';
break;
default:
return 'This model cannot be deleted.';
break;
}
}
...
When using the safeDelete method, you have the option of defining a mode to be used when deleting a record. The mode can be set on the model's deletableConfig array.
Note that the mode configuration key can be left empty in exception mode, but must be set for cascade and custom modes.
Soft deleting a model in this situation will fail. If the model in question is referenced by another model, an UnsafeDeleteException will be thrown.
<?php
$post = Post::create(['title' => 'My post']);
$author = Author::create(['name' => 'Billy Bob']);
$post->authors()->save($author);
Post::find(1)->safeDelete(); // UnsafeDeleteException
In this mode related models will also be deleted.
<?php
use App\Post;
$post = Post::create(['title' => 'My post']);
$author = Author::create(['name' => 'Billy Bob']);
$post->authors()->save($author);
Post::find(1)->safeDelete(); // My Post and Billy Bob will be deleted.
custom.If soft deleting fails, the handler method is called.
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
use RobotsInside\Deletable\Deletable;
class Post extends Model
{
use Deletable, SoftDeletes;
protected function deletableConfig()
{
return [
'mode' => 'custom',
'handler' => 'myHandler',
'relations' => [
'authors'
]
];
}
public function authors()
{
return $this->belongsToMany(Author::class);
}
public function myHandler()
{
app('log')->info('Unsafe delete of ' . __CLASS__);
}
}
Run the provided tests:
composer test
If you discover any vulnerabilities, please email [email protected] instead of using the issue tracker.
Will work for :coffee::coffee::coffee:
The MIT License (MIT). Please see License File for more information.