php
63 lines · 10 steps
Building soft deletes as an Eloquent trait in Laravel
A reusable trait marks records deleted with a timestamp and hides them from queries instead of removing rows.
Explained by
highlit
1<?php
2
3namespace App\Models\Concerns;
4
5use Illuminate\Database\Eloquent\Builder;
6use Illuminate\Support\Carbon;
7
8trait SoftDeletable
9{
10 public static function bootSoftDeletable(): void
11 {
12 static::addGlobalScope('notTrashed', function (Builder $builder) {
13 $builder->whereNull($builder->getModel()->getQualifiedDeletedAtColumn());
14 });
15 }
16
17 public function getDeletedAtColumn(): string
18 {
19 return defined(static::class . '::DELETED_AT') ? static::DELETED_AT : 'deleted_at';
20 }
21
22 public function getQualifiedDeletedAtColumn(): string
23 {
24 return $this->qualifyColumn($this->getDeletedAtColumn());
25 }
26
27 public function trashed(): bool
28 {
29 return ! is_null($this->{$this->getDeletedAtColumn()});
30 }
31
32 public function delete(): bool
33 {
34 $column = $this->getDeletedAtColumn();
35 $this->{$column} = $this->freshTimestamp();
36
37 return $this->save();
38 }
39
40 public function restore(): bool
41 {
42 $this->{$this->getDeletedAtColumn()} = null;
43
44 return $this->save();
45 }
46
47 public function scopeWithTrashed(Builder $query): Builder
48 {
49 return $query->withoutGlobalScope('notTrashed');
50 }
51
52 public function scopeOnlyTrashed(Builder $query): Builder
53 {
54 return $query
55 ->withoutGlobalScope('notTrashed')
56 ->whereNotNull($this->getQualifiedDeletedAtColumn());
57 }
58
59 protected function freshTimestamp(): Carbon
60 {
61 return Carbon::now();
62 }
63}
01 / 01
STEP 01
‹ swipe to step through ›
Walkthrough
Space play
←→ step
click any line
Three takeaways
- 1Soft deletion means overwriting delete to set a timestamp and filtering that column out of every query.
- 2A global scope applies a where clause automatically so trashed rows stay hidden without touching each query.
- 3Making the column name configurable through a constant keeps the trait reusable across models with different schemas.
Related explainers
php
<?php namespace App\Services;
Idempotent role assignment in Laravel
many-to-many
pivot-data
idempotency
Intermediate
8 steps
php
<?php namespace App\Services;
Merging overlapping busy time blocks in PHP
interval-merging
sorting
datetime
Intermediate
8 steps
php
<?php namespace App\Listeners;
How event subscribers group listeners in Laravel
event-driven
subscribers
queues
Intermediate
6 steps
php
<?php namespace App\Services;
How user impersonation works in Laravel
authentication
authorization
session
Intermediate
8 steps
php
class OrderReceiptController extends Controller { public function store(Request $request, Order $order) {
Handling receipt uploads in a Laravel controller
file-upload
validation
authorization
Intermediate
5 steps
php
<?php final class RememberMeCookie {
Signed remember-me cookies in PHP
authentication
hmac
cookies
Intermediate
8 steps
Share this explainer
Here's the card — post it anywhere.
Made with highlit — turn any snippet into a walkthrough like this in about a minute.
Explain your code
Embed this explainer
Drop the interactive walkthrough into a blog or docs. Views never cost a credit.
<iframe src="https://highlit.co/explainers/building-soft-deletes-as-an-eloquent-trait-in-laravel-explained-php-0418/embed?autoplay=1" width="100%" height="520" loading="lazy" style="border:0"></iframe>
Autoplay is on by default — add ?autoplay=0 to start paused.