Installation:
composer require f9webltd/laravel-deletable
Publish the config (optional):
php artisan vendor:publish --provider="F9\Deletable\DeletableServiceProvider" --tag="config"
First Use Case:
Apply the Deletable trait to an Eloquent model:
use F9\Deletable\Traits\Deletable;
class User extends Model
{
use Deletable;
}
Now, User::destroy() will soft-delete by default (if using SoftDeletes trait) or throw an exception if deletion is restricted.
config/deletable.php (defines default behavior, e.g., throw_exception_on_delete).F9\Deletable\Traits\Deletable (core logic).F9\Deletable\Exceptions\DeletableException (custom exceptions for blocked deletions).Soft Deletes + Deletable:
Combine with Laravel’s SoftDeletes for soft deletion:
use Illuminate\Database\Eloquent\SoftDeletes;
class User extends Model
{
use Deletable, SoftDeletes;
protected $dates = ['deleted_at'];
}
Hard Deletes with Restrictions:
Override isDeletable() to enforce rules:
public function isDeletable(): bool
{
return $this->role === 'admin' || $this->isActive();
}
Custom Logic:
Extend the trait or use events (deleting, deleted) to add pre/post logic:
protected static function booted()
{
static::deleting(function ($model) {
// Log deletion attempts
Log::info("Deleting {$model->class} ID: {$model->id}");
});
}
API Controllers: Wrap deletion in a try-catch to return user-friendly messages:
try {
$user->delete();
} catch (DeletableException $e) {
return response()->json(['error' => $e->getMessage()], 403);
}
Admin Panels:
Use the isDeletable() method to disable delete buttons conditionally:
<button disabled="{{ !$user->isDeletable() }}">
Delete
</button>
Seeding:
Bypass restrictions during seeding by setting config(['deletable.throw_exception_on_delete' => false]).
Soft Deletes Conflict:
If using SoftDeletes, ensure deleted_at is in $dates. Otherwise, the trait silently fails.
Circular Dependencies:
Avoid calling delete() recursively (e.g., in model observers or events). Use forceDelete() explicitly if needed.
Mass Deletion:
Model::destroy([1, 2, 3]) checks each model individually. For bulk operations, use:
$models->each(fn ($model) => $model->delete());
Check Restrictions:
Log isDeletable() results to debug why deletion fails:
dd($model->isDeletable()); // Debug in Tinker or logs
Exception Handling:
Override getDeletableException() for custom messages:
protected function getDeletableException(): DeletableException
{
return new DeletableException("Cannot delete inactive users.");
}
Custom Rules: Create a reusable rule class:
class CanDeleteIfActive implements ShouldBeDeletable
{
public function isDeletable(Model $model): bool
{
return $model->isActive();
}
}
Then use it in isDeletable():
public function isDeletable(): bool
{
return (new CanDeleteIfActive())->isDeletable($this);
}
Global Overrides:
Modify the config to change default behavior (e.g., throw_exception_on_delete → false for silent failures).
Event Listeners:
Listen for deleting events to add dynamic restrictions:
event(new Deleting($model));
// Check external API or DB state here
How can I help you explore Laravel packages today?