DEV Community

Cover image for Laravel SoftDelete: Avoiding the Unique Constraint Problem
Hafiq Iqmal
Hafiq Iqmal

Posted on

Laravel SoftDelete: Avoiding the Unique Constraint Problem

This article originated from https://medium.com/@hafiqiqmal93/laravel-softdelete-avoiding-the-unique-constraint-problem-45381d9745a0

In case you’ve been using Laravel for a while, especially when projects involve data integrity, most likely you have already encountered the SoftDelete feature. Pretty useful because you can “delete” records without really taking them out of the database. What Laravel does is just add a deleted_at timestamp so it marks it as deleted, but remains in the system. That’s all well and good to retain historical data, but it does introduce one potentially sticky problem — what happens to unique constraints when you restore soft-deleted records?


This becomes a problem when you want to restore a record that already has, for instance, a unique email or username, in the database. Laravel will just throw an error and stop the proceedings. Fortunately, there’s an easy way of avoiding this problem in a very clean manner.

Let’s walk through a solution using a trait that will help you bypass the unique constraints when using SoftDelete in Laravel.

Understanding the Problem

Let’s take a basic example. Imagine you have a users table with an email field that must be unique:

Schema::create('users', function (Blueprint $table) {
    $table->string('email')->unique();
    $table->softDeletes();
});
Enter fullscreen mode Exit fullscreen mode

If you soft delete a user with the email johncena@wwe.com and then later create a new user with the same email, Laravel will complain about the unique constraint on the email field, throwing an error. In the same situation, when you try to restore the deleted user, Laravel also will complain about the unique constraint on the email field and throwing an same error.

This becomes a headache, especially when dealing with large systems where record restoration is a common task.

To prevent this, we can temporarily alter the values of unique fields when a record is soft deleted and restore the original values when the record is brought back. This way, the database doesn’t trip over the unique constraint during soft deletes or restores.

The Solution: Using a Trait to Avoid Duplicate Constraints

A Laravel trait is a great way to encapsulate this functionality. Here’s a trait we can use to handle the problem:

<?php

trait AvoidDuplicateConstraintSoftDelete
{
    abstract public function getDuplicateAvoidColumns(): array;
    public static function bootAvoidDuplicateConstraintSoftDelete(): void
    {
        static::restoring(function ($model): void {
            if ($model->trashed()) {
                foreach ($model->getDuplicateAvoidColumns() as $column) {
                    // handle for Spatie Translatable library
                    if (method_exists($model, 'getTranslatableAttributes')) {
                        $translates = $model->getTranslatableAttributes();
                        if (in_array($column, $translates)) {
                            foreach ($translates as $translate) {
                                if ($translate === $column) {
                                    $values = $model->getTranslations($column);
                                    foreach ($values as $translation => $value) {
                                        $values[$translation] = (explode('--', $value)[1] ?? $value);
                                    }
                                    $model->setTranslations($column, $values);
                                    break;
                                }
                            }
                            continue;
                        }
                    }
                    if ($value = (explode('--', $model->{$column})[1] ?? null)) {
                        $model->{$column} = $value;
                    }
                }
            }
        });
        static::deleted(function ($model): void {
            foreach ($model->getDuplicateAvoidColumns() as $column) {
                // handle for Spatie Translatable library
                if (method_exists($model, 'getTranslatableAttributes')) {
                    $translates = $model->getTranslatableAttributes();
                    if (in_array($column, $translates)) {
                        foreach ($translates as $translate) {
                            if ($translate === $column) {
                                $values = $model->getTranslations($column);
                                foreach ($values as $translation => $value) {
                                    $values[$translation] = time() . '--' . $value;
                                }
                                $model->setTranslations($column, $values);
                                break;
                            }
                        }
                        continue;
                    }
                }
                $model->{$column} = time() . '--' . $model->{$column};
            }
            $model->save();
        });
    }
}
Enter fullscreen mode Exit fullscreen mode

This trait does a couple of things:

  • On deletion, it appends a timestamp to the unique field, essentially making the field unique again without affecting the original value. This trick is useful for keeping unique constraints satisfied while the record is soft-deleted.
  • On restoration, it strips away the timestamp, restoring the original value of the unique field.

How It Works:

  • Unique Field Handling: Whenever a model with this trait is deleted, the trait takes care of appending a timestamp to the fields that you want to keep unique (example: email, username). This prevents conflicts if you try to add a new record with the same unique values.
  • Handling Translatable Fields: If your model uses the Spatie Translatable library, this trait is smart enough to handle multilingual fields as well. It looks for the translatable attributes, adjusts their values, and saves them with the timestamp trick.
  • Restoration: When you restore a soft-deleted record, the trait strips off the timestamp from the unique fields, returning the field to its original value.

Applying the Trait to Your Model

Here’s how you can apply this trait in your Laravel model:

class User extends Model
{
    use SoftDeletes, AvoidDuplicateConstraintSoftDelete;
    // Specify which columns should avoid the unique constraint issue
    public function getDuplicateAvoidColumns(): array
    {
        return ['email', 'username'];
    }
}
Enter fullscreen mode Exit fullscreen mode

By adding the **AvoidDuplicateConstraintSoftDelete** trait to your model and specifying which columns need to avoid unique constraint conflicts (like email and username), you can easily prevent these issues.

Why This Approach Works?

What that means is that, in the event of a soft delete record, it would not cause a conflict with further operations because of some unique constraints. Or, in other words, this way you will be able to, by appending the timestamp to unique fields, render the record “hidden” for the database in terms of uniqueness but still recoverable when needed.

This is quite useful when you’re dealing with a large database and restoration of records is quite common. You won’t have to deal every time with the “duplicate entry” error whenever you bring a soft-deleted user or any other model.


Final Thoughts

The most useful thing in Laravel is SoftDelete, but sometimes it gives headaches while working with unique constraints. Here comes a simple, trait-based solution that will give an elegant way of avoiding the problem, just by temporary changes of unique fields on deletion and restore afterward. This way you will avoid frustrating mistakes and let your application work smoothly not breaking unique constraints in your database.

If any of your fields have been made multilingual or make use of libraries like Spatie’s Translatable, the above solution will work without problems in each of these cases. The SoftDeletes are meant to give you flexibility, not get in your way. With the above minor fix in place you’ll avoid most the pitfalls and keep your data tidy and your users happy.


By adding this trait to your models, you’ll be saving yourself time and headaches, especially if you’re dealing with large datasets where soft-deleting and restoring are frequent operations. Give it a try in your Laravel project, and you’ll see how smoothly it handles those tricky unique constraint problems!


Thank you for reading! Don’t forget to subscribe to stay informed about the latest updates in system design and e-commerce innovations. Happy designing!

If you found this article insightful and want to stay updated with more content on system design and technology trends, be sure to follow me on :-

Twitter: https://twitter.com/hafiqdotcom
LinkedIn: https://www.linkedin.com/in/hafiq93
Buy Me Coffee: https://paypal.me/mhi9388 /
https://buymeacoffee.com/mhitech
Medium: https://medium.com/@hafiqiqmal93

Top comments (0)