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 class NameParser {
Parsing a full name into components in PHP
string-parsing
arrays
normalization
Intermediate
8 steps
php
<?php namespace App\Services\Checkout;
Validating coupons with Laravel's Pipeline
pipeline
chain of responsibility
transactions
Intermediate
7 steps
php
<?php namespace App\Services;
How a password strength validator works in PHP
validation
regular-expressions
data-driven
Intermediate
8 steps
php
<?php namespace App\Services;
Building a cached daily leaderboard in Laravel
caching
aggregation
eager-loading
Intermediate
9 steps
php
<?php final class RotatingFileLogger {
How a rotating file logger works in PHP
logging
file-rotation
io
Intermediate
9 steps
php
class TwoFactorController extends Controller { public function show(Request $request): View|RedirectResponse {
Two-factor auth challenge flow in Laravel
authentication
two-factor
middleware
Intermediate
10 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.