The Soft Delete Trap
In Laravel, enabling SoftDeletes is as easy as adding a trait to your Eloquent model and a deleted_at timestamp to your migration. It feels like a massive win for data safety—if a user accidentally deletes a critical project, you can easily restore it. However, as your B2B SaaS scales, this convenience quickly transforms into an architectural nightmare.
The primary issue lies with Unique Constraints. Imagine a user deletes their account, effectively setting deleted_at to today's date. A month later, they try to sign up again with the exact same email address. Your database will throw a fatal error because the email is still physically in the table, violating the unique index. You now have to write complex, messy logic to check for soft-deleted emails, restore them, or uniquely scope your database indexes across the entire platform.
The Solution: State Machines and Archiving
In enterprise-grade software built at Smart Tech Devs, "deleted" is rarely a physical state; it is a business state. Instead of relying on a magic timestamp that alters how every single database query behaves, we use explicit State Machines or Status columns.
Implementing a Robust Status Architecture
Instead of soft-deleting a record, we transition its status to archived or cancelled. This makes the data's lifecycle explicit and completely avoids breaking standard database constraints.
namespace App\Enums;
enum ProjectStatus: string
{
case DRAFT = 'draft';
case ACTIVE = 'active';
case ARCHIVED = 'archived';
}
Now, our Eloquent model relies on explicit scoping rather than hidden global scopes that modify underlying queries unexpectedly.
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Builder;
use App\Enums\ProjectStatus;
class Project extends Model
{
protected $casts = [
'status' => ProjectStatus::class,
];
/**
* Scope a query to only include active projects.
*/
public function scopeActive(Builder $query): void
{
$query->where('status', ProjectStatus::ACTIVE);
}
/**
* The explicit business logic to "remove" a project.
*/
public function archive(): void
{
$this->update(['status' => ProjectStatus::ARCHIVED]);
}
}
Handling True Deletions at Scale
If legal compliance (like GDPR) dictates that data *must* be removed from the active system, but you need an audit trail, use a dedicated Archive Table. When a record is deleted, an Event Listener moves the serialized JSON payload of that record into a cold-storage audit_logs table, and then performs a hard, physical delete on the main table.
Conclusion
Soft deletes solve a simple problem but introduce massive complexity regarding database indexing, foreign keys, and unique constraints. By shifting to explicit status columns and state machines, your Laravel backend becomes significantly more predictable, easier to query, and infinitely more scalable.