One of the least used functions in web projects is the ability for the user to delete their account. In this article I will show three cases of how this can be done: blocking, hiding and deleting data.


Case 1. Prevent the user from logging in.

Sometimes it is necessary to simply restrict user access, but all their data must remain in the system, for historical purposes.

It is therefore not really a deletion action, but rather a block or a “to forbid”.

To achieve this, simply add a field in users array, called blocked_at.


php artisan make:migration add_blocked_at_to_users_table

And then the migration:


public function up()
{
    Schema::table('users', function (Blueprint $table) {
        $table->timestamp('blocked_at')->nullable();
    });
}

Same logic as the other timestamp fields: either it will be NULL if the user is active, or it will contain a time value indicating when the user was blocked.

Since this block/ban action will probably be rare, you can’t even create a button in the admin panel to block a user, just set the field value directly with a SQL query, like this:


update users set blocked_at = NOW() where id = X;

Antitype: before launching update Or DELETE statements directly from the SQL client, execute select with the same WHERE clause, to ensure you are updating/deleting the correct entries.

I mean, run this first: select * from users where id = X;

Then look at the results and make sure it’s the same user. And only then run update statement.


Now how to check if the user is blocked? We should probably use middleware that would be attached to all routes with “auth”, and if the user is blocked, they would be redirected to the login form with an error message.

Step 1. Generate a Middleware class.


php artisan make:middleware ActiveUser

Then in generated app/Http/Middleware/ActiveUser.php:


class ActiveUser
{
    public function handle($request, Closure $next)
    {
        if (auth()->user()->blocked_at) {
            $user = auth()->user();
            auth()->logout();
            return redirect()->route('login')
                ->withError('Your account was blocked at ' . $user->blocked_at);
        }

        return $next($request);
    }
}

So, we force logout of the user and redirect them to the login form with an error message.

Step 2. Register the middleware in the kernel

Next, we need to register this middleware in app/Http/Kernel.php – we will give him a pseudonym of active_user:


protected $routeMiddleware = [
    'auth' => \App\Http\Middleware\Authenticate::class,
    // ... other middlewares 

    'active_user' => \App\Http\Middleware\ActiveUser::class,
];

Step 3. Assign middleware to routes

Here is our routes/web.php – we will assign this route to all authenticated routes.


Route::get('/', function () {
    return view('welcome');
});

Auth::routes();

Route::group(['middleware' => ['auth', 'active_user']], function() {
    Route::get('/home', 'HomeController@index')->name('home');
    // ... Any other routes that are accessed only by non-blocked user
});

Step 4. Show Error on Login Page

Finally we redirect with an error message, remember ->withError(‘Your account has been blocked at ‘ . $user->blocked_at); above. So we have to show it visually.

In resources/views/auth/login.blade.php we add this:


<div class="card-header">{{ __('Login') }}</div>

<div class="card-body">

    @if (session('error'))
        <div class="alert alert-danger">
            {{ session('error') }}
        </div>
    @endif

    <form method="POST" action="{{ route('login') }}">

Visual result:

In this case, the user’s data remains in the system, other users/administrators will see the history of their actions.

Let’s move on to the second case, where the story should become invisible.


Case 2. Hide user with software deletion.

Another case where the user wants to delete their account and hide their whole story in the system.

The main word is hide. Not completely removed. Because maybe some data is important for reporting like financial numbers, it may also change your mind and want to be retrieved.

In this case, you should use eloquent software deletions and fill users.deleted_at with the timestamp value.


php artisan make:migration add_deleted_at_to_users_table

Then Migration code:


public function up()
{
    Schema::table('users', function (Blueprint $table) {
        $table->softDeletes();
    });
}

Then, in app/User.php model:


use Illuminate\Database\Eloquent\SoftDeletes;

class User extends Authenticatable
{
    use Notifiable, SoftDeletes;

Next, delete a user per SQL query, as in the previous example:


update users set deleted_at = NOW() where id = X;

Or, if you prefer not to connect to the database and use Artisan Tinker, you can do the following:


User::find(1)->delete();

As in the previous case, the user will not be able to log in, but will simply see the default error message: “These credentials do not match our records.”.

Soft removal of Laravel user

Now the question here is what do you do with other database data that has User ID or similar fields, linked to the users table?

You have the choice:

  1. Restrict deletion of users if they have associated data
  2. Associated cascaded and soft-deleted data
  3. Don’t do anything with it and just make sure that every time you want to view this related data it doesn’t throw errors. Like, instead of {{ $project->user->name }} TO DO {{ $project->user->name or ” }}

Learn more about soft delete relationships in this article: One-to-many with soft deletes. Deleting a parent: restrict or cascade?


Case 3. Actually delete the user with all data.

Now sometimes you need to delete user data from a legal point of view. They just want all their data gone forever, they have the right to demand it.

Before deleting, make sure that it won’t affect any already aggregated data, like monthly reports or some important financial figures. Double-check that you won’t have any incorrect numbers after deleting the user.

And then, to delete a user with all associated data, there are two methods:

Method 1. Remove cascading in migrations.

If you think about this case from the beginning of your project, you just set up foreign keys in the migration file, with cascading deletion.


Schema::create('posts', function (Blueprint $table) {
    $table->bigIncrements('id');
    $table->unsignedBigInteger('user_id');
    $table->foreign('user_id')->references('id')->on('users')
        ->onDelete('cascade');

If you don’t specify onDelete() value, then the default is RESTRICT, which means that MySQL will prevent parent records from being deleted if there is a foreign key.

But if you specify onDelete(‘cascade’)then it will delete all messages whenever user is deleted, like User::find(1)->delete();.

Remember: this happens on database level, not in Laravel or Eloquent. So the cascading delete will happen even if you run a direct SQL query delete users where id = 1;.

Method 2. Manually delete recordings via eloquent

If you don’t have database level cascading delete, you have to delete everything manually. So if you have for example, User Controller And destroy() method, list all related deleted phrases one by one, starting from the deepest.

Let’s say the user has posts and the posts have comments. So you would do something like this:


public function destroy(User $user)
{
    $posts = Post::where('user_id', $user->id)->pluck('id');
    Comment::whereIn('post_id', $posts)->delete();
    Post::where('user_id', $user->id)->delete();
    $user->delete();
}

Now, be extremely careful with such chained deletion phrases. Any of them may fail, for other foreign key reasons.

Here what I would suggest is to use database transactions. What if a delete request fails and the previous one is no longer recoverable?

So here is the actual code:


public function destroy(User $user)
{
    $posts = Post::where('user_id', $user->id)->pluck('id');
    \DB::transaction(function () use ($posts, $user) {
        \DB::table('comments')->whereIn('post_id', $posts)->delete();
        \DB::table('posts')->where('user_id', $user->id)->delete();
        \DB::table('users')->where('id', $user->id)->delete();
    });
}

So there you go. Three ways to delete the user with their data. Which one is relevant in your project?



Technology

Another Tech Information

Similar Posts