This guide will walk through implementing user suspension in a Laravel application. This functionality allows you to temporarily or permanently suspend users and notify them accordingly.
Step 1: Add Suspension Columns to the Users Table
First, we need to update our users
table to include columns for tracking suspension status and reason.
- Create a new migration:
php artisan make:migration add_suspension_columns_to_users_table --table=users
- In the migration file, add the following code:
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
public function up(): void
{
Schema::table('users', function (Blueprint $table) {
$table->timestamp('suspended_at')->nullable();
$table->timestamp('suspension_ends_at')->nullable();
$table->string('suspension_reason')->nullable();
});
}
public function down(): void
{
Schema::table('users', function (Blueprint $table) {
$table->dropColumn([
'suspended_at',
'suspension_ends_at',
'suspension_reason'
]);
});
}
};
- Run the migration
php artisan migrate
Step 2: Update the User Model
- Open
app/Models/User.php
file. - Use the
Suspendable
trait and add the necessary attribute casts:
use App\Traits\Suspendable;
...
class User extends Authenticatable
{
...
use Suspendable;
protected $casts = [
'email_verified_at' => 'datetime',
'password' => 'hashed',
'suspended_at' => 'datetime',
'suspension_ends_at' => 'datetime',
];
...
}
Laravel 11 and newer versions utilize casts
methods for property casting:
protected function casts(): array
{
return [
'email_verified_at' => 'datetime',
'password' => 'hashed',
'suspended_at' => 'datetime',
'suspension_ends_at' => 'datetime',
];
}
Step 3: Create the Suspendable Trait
- Create a new PHP file at
app/Traits/Suspendable.php
- Add the following code to that file:
<?php
namespace App\Traits;
use App\Notifications\UserSuspendedNotification;
use App\Notifications\UserUnsuspendedNotification;
use Carbon\CarbonInterface;
use Illuminate\Database\Eloquent\Casts\Attribute;
trait Suspendable
{
/**
* Account is banned for lifetime.
*/
protected function isBanned(): Attribute
{
return Attribute::get(
fn () => $this->suspended_at && is_null($this->suspension_ends_at)
);
}
/**
* Account is suspended for some time.
*/
protected function isSuspended(): Attribute
{
return Attribute::get(
fn () => $this->suspended_at && $this->suspension_ends_at?->isFuture()
);
}
/**
* Suspend account and notify them.
*/
public function suspend(string $reason, CarbonInterface $ends_at = null): void
{
$this->update([
'suspended_at' => now(),
'suspension_reason' => $reason,
'suspension_ends_at' => $ends_at,
]);
$this->notify(new UserSuspendedNotification($this));
}
/**
* Un-suspend account and notify them.
*/
public function unsuspend(): void
{
if (! $this->suspended_at) {
return;
}
$this->update([
'suspended_at' => null,
'suspension_reason' => null,
'suspension_ends_at' => null,
]);
$this->notify(new UserUnsuspendedNotification($this));
}
}
This trait adds the suspend
and unsuspend
methods to the User
model for suspending and unsuspending accounts easily. This also provides the is_banned
and is_suspended
attributes for checking suspension status.
Step 4: Create Notifications
- Create notification classes:
php artisan make:notification UserSuspendedNotification
php artisan make:notification UserUnsuspendedNotification
- Edit
app/Notifications/UserSuspendedNotification.php
namespace App\Notifications;
use App\Models\User;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Notifications\Messages\MailMessage;
use Illuminate\Notifications\Notification;
class UserSuspendedNotification extends Notification
{
use Queueable;
public function __construct(
public readonly User $user
) {
}
/**
* Get the notification's delivery channels.
*/
public function via(object $notifiable): array
{
return ['mail'];
}
/**
* Get the mail representation of the notification.
*/
public function toMail(object $notifiable): MailMessage
{
if ($this->user->is_banned) {
$subject = __('Your account has been banned');
$message = null;
} else {
$subject = __('Your account has been suspended');
$message = __('Suspension will end on: :date', ['date' => $this->user->suspention_ends_at])
}
return (new MailMessage)
->subject($subject)
->line($subject)
->line(__('Reason: **:reason**', ['reason' => $this->user->suspension_reason]))
->line($message)
->line(__('If you believe this is a mistake, please contact us.'))
->line(__('Thank you for your understanding.'));
}
}
- Edit
app/Notifications/UserUnsuspendedNotification.php
namespace App\Notifications;
use App\Models\User;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Notifications\Messages\MailMessage;
use Illuminate\Notifications\Notification;
class UserUnsuspendedNotification extends Notification
{
use Queueable;
public function __construct(
public readonly User $user
) {
}
/**
* Get the notification's delivery channels.
*/
public function via(object $notifiable): array
{
return ['mail'];
}
/**
* Get the mail representation of the notification.
*/
public function toMail(object $notifiable): MailMessage
{
return (new MailMessage)
->subject(__('Your Suspension Removed'))
->greeting(__('Hello :name,', ['name' => $this->user->name]))
->line(__('Suspension has been removed. Your account is now active.'))
->line(__('You can now log into your account.'))
->action(__('Log in'), route('login'))
->line(__('Thank you for staying with us.'));
}
}
We are almost done π Let's take a look at the usage example:
$user = \App\Models\User::find(1);
// temporary suspension (for 7 days)
$user->suspend('suspension reason', now()->addDays(7));
// permanent suspension
$user->suspend('suspension reason');
// unsuspension
$user->unsuspend();
Now, The only thing that remains is to check whether the authenticated user is suspended and restrict their access to the application. Let's do this in the next step.
Step 5: Restrict Application Access for Suspended Users
- Create Middleware
php artisan make:middleware CheckUserSuspension
- In the middleware file
app/Http/Middleware/CheckUserSuspension.php
, add the following logic to handle restricted access for suspended users:
namespace App\Http\Middleware;
use Closure;
use Illuminate\Http\Request;
use Symfony\Component\HttpFoundation\Response;
class CheckUserSuspension
{
public function handle(Request $request, Closure $next): Response
{
$user = $request->user();
abort_if(
$user && ($user->is_suspended || $user->is_banned),
Response::HTTP_FORBIDDEN,
__('Your account has been suspended or banned. Check your email for details.')
);
return $next($request);
}
}
- Apply Middleware to Routes:
In routes/web.php
or routes/api.php
apply the middleware to the routes you want to protect:
use App\Http\Middleware\CheckUserSuspension;
// Protected routes
Route::group(['middleware' => ['auth', CheckUserSuspension::class]], function () {
Route::get('/dashboard', DashboardController::class]);
});
// Other routes
Otherwise, you can add this middleware to the web
or api
middleware group to apply it to a set of routes.
Step 6: Applying to Middleware Groups (optional)
- Laravel 11 or newer
// file: bootstrap/app.php
use App\Http\Middleware\CheckUserSuspension;
return Application::configure(basePath: dirname( __DIR__ ))
// other code
->withMiddleware(function (Middleware $middleware) {
$middleware->web(append: [
CheckUserSuspension::class,
]);
})
// other code
- For Laravel 10 or older
// file: app/Http/Kernel.php
protected $middlewareGroups = [
'web' => [
// other middlewares
CheckUserSuspension::class,
],
'api' => [
// other middlewares
CheckUserSuspension::class,
],
];
Conclusion
By following this guide, you have successfully implemented user suspension functionality in your Laravel application. This approach keeps your User
model clean and encapsulates the suspension logic within a reusable Suspendable
trait.
This feature allows you to manage user access effectively by suspending and unsuspending users as needed. This not only enhances the security and control over user activities but also ensures a better user management system.
Happy coding! β€οΈ
Top comments (1)
nice work, I saw the repo.