كيفية حظر المستخدم مؤقتًا في Laravel باستخدام Middleware وbanned_until

توفر Laravel نظام مصادقة Authentication متكاملًا يمكن من خلاله تسجيل الدخول والخروج وإدارة جلسات المستخدمين بسهولة، كما يمكن توسيع هذا النظام وإضافة قواعد خاصة حسب متطلبات التطبيق.

من الميزات التي قد نحتاج إليها في بعض التطبيقات إمكانية حظر مستخدم مؤقتًا ومنعه من الوصول إلى النظام حتى وقت أو تاريخ معين، ثم فك الحظر عنه تلقائيًا بعد انتهاء المدة المحددة.

على سبيل المثال، قد نرغب في حظر المستخدم حتى:

2026-09-10 15:00:00

وبعد الوصول إلى هذا الوقت يصبح بإمكانه استخدام حسابه مرة أخرى دون الحاجة إلى قيام المدير بفك الحظر يدويًا.

سننفذ ذلك باستخدام:

  • حقل banned_until داخل جدول users.
  • Datetime Cast داخل User Model.
  • Middleware لفحص حالة المستخدم.
  • Carbon للمقارنة بين التواريخ.
  • تسجيل الخروج الآمن عند اكتشاف مستخدم محظور.
  • إمكانية الحظر وفك الحظر من لوحة التحكم.
الفكرة الأساسية بسيطة: إذا كانت قيمة banned_until أكبر من الوقت الحالي، فالحساب ما زال محظورًا. أما إذا انتهى التاريخ، فلا نحتاج إلى تعديل أي شيء؛ المستخدم يصبح غير محظور تلقائيًا.

كيف سيعمل نظام الحظر؟

لنفترض أن الوقت الحالي هو:

2026-09-01 10:00:00

ولدينا مستخدم قيمة banned_until الخاصة به:

2026-09-05 10:00:00

بما أن:

banned_until > now()

فالمستخدم ما زال محظورًا.

أما إذا أصبحت القيمة:

2026-08-30 10:00:00

فإن تاريخ الحظر انتهى، وبالتالي يمكن السماح للمستخدم باستخدام النظام بشكل طبيعي.

المخطط العام

User Request
     │
     ▼
Authentication
     │
     ▼
CheckBanned Middleware
     │
     ├── banned_until = null
     │        │
     │        └── Allow Request
     │
     ├── banned_until <= now()
     │        │
     │        └── Allow Request
     │
     └── banned_until > now()
              │
              ▼
            Logout
              │
              ▼
        Invalidate Session
              │
              ▼
        Redirect to Login

الخطوة الأولى: إضافة banned_until إلى جدول users

نقوم بإنشاء Migration جديد:

php artisan make:migration add_banned_until_to_users_table --table=users

ثم داخل ملف Migration:

<?php

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('banned_until')
                ->nullable();
        });
    }

    public function down(): void
    {
        Schema::table('users', function (Blueprint $table) {
            $table->dropColumn('banned_until');
        });
    }
};

ثم ننفذ:

php artisan migrate

لماذا nullable؟

لأن معظم المستخدمين غير محظورين، وبالتالي يمكن أن تكون القيمة:

NULL

والحالات تصبح كالتالي:

banned_until = NULL
→ المستخدم غير محظور


banned_until = تاريخ مستقبلي
→ المستخدم محظور


banned_until = تاريخ انتهى
→ انتهى الحظر

الخطوة الثانية: إضافة Cast داخل User Model

حتى نستطيع التعامل مع banned_until ككائن تاريخ باستخدام Carbon، نضيف له datetime cast.

داخل:

app/Models/User.php

نضيف:

protected function casts(): array
{
    return [
        'email_verified_at' => 'datetime',
        'banned_until' => 'datetime',
    ];
}

بعد ذلك يستطيع Laravel تحويل:

$user->banned_until

إلى DateTime / Carbon object يمكن استخدام عمليات التاريخ عليه.

مثال

$user->banned_until->isFuture();

$user->banned_until->diffForHumans();

$user->banned_until->diffInDays(now());
في المشاريع الحديثة لا تحتاج إلى استخدام protected $dates لهذا الغرض؛ استخدام الـdatetime cast أو casts() هو الأسلوب الأوضح والأحدث.

هل نضيف banned_until إلى $fillable؟

يمكن إضافته:

protected $fillable = [
    'name',
    'email',
    'password',
    'banned_until',
];

لكن في التطبيقات الحقيقية يجب التفكير في الجانب الأمني.

إذا كان المستخدم يستطيع تحديث بيانات حسابه باستخدام:

User::update($request->all());

فوجود:

banned_until

داخل $fillable قد يسمح بحدوث Mass Assignment غير مرغوب فيه إذا لم يتم التحكم بالمدخلات جيدًا.

لذلك يمكن عدم إضافته إلى $fillable أصلًا إذا كان يتم تعديله فقط بواسطة الإدارة.

ثم تعديله بشكل صريح:

$user->banned_until = now()->addDays(7);

$user->save();
لا تستخدم $request->all() لتحديث المستخدمين، خصوصًا إذا كانت هناك حقول إدارية مثل role أو is_admin أو banned_until.

الخطوة الثالثة: إنشاء Middleware

نقوم بإنشاء Middleware:

php artisan make:middleware CheckBanned

سيتم إنشاء الملف غالبًا داخل:

app/Http/Middleware/CheckBanned.php

نعدل محتواه:

<?php

namespace App\Http\Middleware;

use Closure;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Symfony\Component\HttpFoundation\Response;

class CheckBanned
{
    public function handle(
        Request $request,
        Closure $next
    ): Response {

        $user = $request->user();

        if (
            $user &&
            $user->banned_until &&
            $user->banned_until->isFuture()
        ) {

            $bannedUntil = $user->banned_until;

            Auth::logout();

            $request->session()->invalidate();

            $request->session()->regenerateToken();

            return redirect()
                ->route('login')
                ->with(
                    'message',
                    'Your account is suspended until '
                    .$bannedUntil->format('Y-m-d H:i:s')
                );
        }

        return $next($request);
    }
}

شرح CheckBanned Middleware

الحصول على المستخدم الحالي

$user = $request->user();

إذا لم يكن المستخدم مسجلًا، ستكون القيمة:

null

التأكد من وجود banned_until

$user->banned_until

إذا كانت:

null

فالحساب غير محظور.


التأكد أن تاريخ الحظر ما زال في المستقبل

$user->banned_until->isFuture()

وهو يعبر بصورة واضحة عن الشرط:

banned_until > now()

لماذا لا نكتفي بـAuth::logout()؟

عند إجبار مستخدم على تسجيل الخروج بسبب الحظر، الأفضل إنهاء Session الحالية بصورة صحيحة.

Auth::logout();

$request->session()->invalidate();

$request->session()->regenerateToken();

لدينا ثلاث خطوات:

1. تسجيل الخروج

Auth::logout();

2. إبطال Session

$request->session()->invalidate();

3. إنشاء CSRF Token جديد

$request->session()->regenerateToken();

وهذا أفضل أمنيًا من تنفيذ Logout فقط.


عرض المدة المتبقية من الحظر

يمكن استخدام Carbon لعرض المدة بصورة سهلة.

عرض الفرق بصورة بشرية

$remaining = $user
    ->banned_until
    ->diffForHumans();

قد تكون النتيجة مثل:

3 days from now

أو يمكن حساب عدد الأيام:

$days = now()
    ->diffInDays(
        $user->banned_until
    );

إظهار رسالة

$message =
    'Your account is suspended until '
    .$user->banned_until->format('Y-m-d H:i:s');

تحسين الرسالة بالعربية

$message =
    'تم تعليق حسابك حتى '
    .$user->banned_until->format('Y-m-d H:i:s');

أو:

$message =
    'تم تعليق حسابك مؤقتًا. سيتم فك الحظر '
    .$user->banned_until->diffForHumans();

الخطوة الرابعة: تسجيل Middleware في Laravel 12

في Laravel الحديث يمكن تعريف Alias للـMiddleware داخل:

bootstrap/app.php

مثال:

<?php

use App\Http\Middleware\CheckBanned;
use Illuminate\Foundation\Application;
use Illuminate\Foundation\Configuration\Middleware;

return Application::configure(
    basePath: dirname(__DIR__)
)
->withRouting(
    web: __DIR__.'/../routes/web.php',
    commands: __DIR__.'/../routes/console.php',
    health: '/up',
)
->withMiddleware(
    function (Middleware $middleware) {

        $middleware->alias([
            'not-banned' => CheckBanned::class,
        ]);

    }
)
->create();

بعد ذلك نستطيع استخدام:

not-banned

داخل Routes.

إذا كنت تعمل على إصدار Laravel أقدم، فقد تجد تسجيل Middleware داخل app/Http/Kernel.php. أما في بنية Laravel الحديثة فيتم ضبط Middleware من bootstrap/app.php.

تطبيق Middleware على Routes المحمية

يمكن وضع:

auth

و:

not-banned

معًا:

Route::middleware([
    'auth',
    'not-banned'
])->group(function () {

    Route::get(
        '/dashboard',
        function () {
            return view('dashboard');
        }
    )->name('dashboard');

});

مخطط تنفيذ الطلب

GET /dashboard

       │
       ▼

   auth middleware

       │
       ├── Not Authenticated
       │       │
       │       └── Login
       │
       ▼

CheckBanned

       │
       ├── Banned
       │       │
       │       └── Logout
       │
       ▼

   Dashboard

هل يجب وضع CheckBanned على كل الموقع؟

ليس بالضرورة.

الأفضل غالبًا تطبيقه فقط على Routes التي تحتاج إلى مستخدم مسجل:

Route::middleware([
    'auth',
    'not-banned'
])->group(function () {

    // authenticated application routes

});

وهذا أوضح من تشغيل فحص المستخدم المحظور على صفحات عامة مثل:

/
login
register
forgot-password

إنشاء Login Controller بشكل صحيح

إذا كنت تقوم ببناء Login يدويًا، يمكن كتابة:

<?php

namespace App\Http\Controllers\Auth;

use App\Http\Controllers\Controller;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;

class LoginController extends Controller
{
    public function store(Request $request)
    {
        $credentials = $request->validate([
            'email' => [
                'required',
                'email',
            ],

            'password' => [
                'required',
                'string',
            ],
        ]);

        if (! Auth::attempt($credentials)) {

            return back()
                ->withErrors([
                    'email' =>
                        'البريد الإلكتروني أو كلمة المرور غير صحيحة.',
                ])
                ->onlyInput('email');
        }

        $request->session()->regenerate();

        return redirect()
            ->intended(
                route(
                    'dashboard',
                    absolute: false
                )
            );
    }
}

لماذا نستخدم session()->regenerate()؟

بعد نجاح تسجيل الدخول يجب إنشاء Session ID جديد للمساعدة في الحماية من هجمات:

Session Fixation

Routes تسجيل الدخول

use App\Http\Controllers\Auth\LoginController;

Route::get(
    '/login',
    function () {
        return view('auth.login');
    }
)->name('login');

Route::post(
    '/login',
    [LoginController::class, 'store']
)->name('login.store');

Form تسجيل الدخول

<div class="card">

    <div class="card-header">
        تسجيل الدخول
    </div>

    <div class="card-body">

        @if (session('message'))

            <div class="alert alert-danger">
                {{ session('message') }}
            </div>

        @endif


        @if ($errors->any())

            <div class="alert alert-danger">

                <ul>

                    @foreach ($errors->all() as $error)

                        <li>
                            {{ $error }}
                        </li>

                    @endforeach

                </ul>

            </div>

        @endif


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

            @csrf


            <div>

                <label>
                    البريد الإلكتروني
                </label>

                <input
                    type="email"
                    name="email"
                    value="{{ old('email') }}"
                    required
                >

            </div>


            <div>

                <label>
                    كلمة المرور
                </label>

                <input
                    type="password"
                    name="password"
                    required
                >

            </div>


            <button type="submit">
                تسجيل الدخول
            </button>

        </form>

    </div>

</div>

حظر مستخدم لمدة يوم

$user->banned_until = now()
    ->addDay();

$user->save();

حظر لمدة 7 أيام

$user->banned_until = now()
    ->addDays(7);

$user->save();

حظر لمدة شهر

$user->banned_until = now()
    ->addMonth();

$user->save();

الحظر حتى تاريخ محدد

$user->banned_until =
    '2026-12-01 12:00:00';

$user->save();

وبسبب:

'banned_until' => 'datetime'

سيتعامل Eloquent مع الحقل كتاريخ.


فك الحظر يدويًا

الأمر بسيط:

$user->banned_until = null;

$user->save();

بمجرد أن تصبح القيمة:

NULL

فإن Middleware لن يعتبر المستخدم محظورًا.


إضافة Ban وUnban إلى لوحة التحكم

Routes

Route::middleware([
    'auth',
    'not-banned',
])->group(function () {

    Route::post(
        '/admin/users/{user}/ban',
        [UserBanController::class, 'store']
    )->name('users.ban');

    Route::delete(
        '/admin/users/{user}/ban',
        [UserBanController::class, 'destroy']
    )->name('users.unban');

});
في التطبيق الحقيقي يجب إضافة Authorization مثل Policy أو Gate حتى لا يستطيع المستخدم العادي تنفيذ عمليات Ban وUnban.

Controller لحظر المستخدم

<?php

namespace App\Http\Controllers;

use App\Models\User;
use Illuminate\Http\Request;

class UserBanController extends Controller
{
    public function store(
        Request $request,
        User $user
    ) {

        $data = $request->validate([
            'banned_until' => [
                'required',
                'date',
                'after:now',
            ],
        ]);

        $user->banned_until =
            $data['banned_until'];

        $user->save();

        return back()->with(
            'success',
            'تم حظر المستخدم بنجاح.'
        );
    }


    public function destroy(User $user)
    {
        $user->banned_until = null;

        $user->save();

        return back()->with(
            'success',
            'تم فك حظر المستخدم.'
        );
    }
}

لماذا نستخدم after:now؟

حتى لا يستطيع المدير إرسال تاريخ حظر انتهى بالفعل.

'banned_until' => [
    'required',
    'date',
    'after:now',
]

بذلك يجب أن يكون التاريخ في المستقبل.


إضافة Helper إلى User Model

بدل تكرار:

$user->banned_until &&
$user->banned_until->isFuture()

في عدة أماكن، يمكن إنشاء Method داخل User:

public function isBanned(): bool
{
    return $this->banned_until !== null
        && $this->banned_until->isFuture();
}

ثم يصبح Middleware أبسط:

if ($user && $user->isBanned()) {

    // logout

}

User Model

<?php

namespace App\Models;

use Illuminate\Foundation\Auth\User as Authenticatable;

class User extends Authenticatable
{
    protected function casts(): array
    {
        return [
            'email_verified_at' => 'datetime',
            'banned_until' => 'datetime',
        ];
    }


    public function isBanned(): bool
    {
        return $this->banned_until !== null
            && $this->banned_until->isFuture();
    }
}

تحسين Middleware باستخدام isBanned()

<?php

namespace App\Http\Middleware;

use Closure;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Symfony\Component\HttpFoundation\Response;

class CheckBanned
{
    public function handle(
        Request $request,
        Closure $next
    ): Response {

        $user = $request->user();

        if ($user && $user->isBanned()) {

            $bannedUntil =
                $user->banned_until;

            Auth::logout();

            $request
                ->session()
                ->invalidate();

            $request
                ->session()
                ->regenerateToken();

            return redirect()
                ->route('login')
                ->with(
                    'message',
                    'تم تعليق حسابك حتى '
                    .$bannedUntil
                        ->format('Y-m-d H:i:s')
                );
        }

        return $next($request);
    }
}

الحظر عند تسجيل الدخول أم بعد تسجيل الدخول؟

هناك طريقتان شائعتان.

الطريقة الأولى: السماح بالمصادقة ثم Middleware

Login
  │
  ▼
Auth::attempt()
  │
  ▼
Dashboard Request
  │
  ▼
CheckBanned
  │
  └── Banned → Logout

هذه الطريقة سهلة وتضمن منع المستخدم المحظور من الوصول إلى Routes المحمية.


الطريقة الثانية: منع تسجيل الدخول من الأساس

يمكن أيضًا فحص الحساب مباشرة بعد نجاح Auth::attempt().

if (Auth::attempt($credentials)) {

    $request->session()->regenerate();

    $user = $request->user();

    if ($user->isBanned()) {

        $bannedUntil =
            $user->banned_until;

        Auth::logout();

        $request
            ->session()
            ->invalidate();

        $request
            ->session()
            ->regenerateToken();

        return back()
            ->withErrors([
                'email' =>
                    'الحساب محظور حتى '
                    .$bannedUntil
                        ->format('Y-m-d H:i:s'),
            ]);
    }

    return redirect()
        ->intended('/dashboard');
}

هذه الطريقة تجعل تجربة المستخدم أوضح لأنه يعرف مباشرة أثناء تسجيل الدخول أن الحساب محظور.

حتى إذا فحصت الحظر أثناء Login، من الأفضل أيضًا وجود Middleware على Routes الحساسة؛ لأن المستخدم قد تكون لديه Session نشطة ثم يقوم المدير بحظره أثناء وجوده داخل النظام.

لماذا Middleware مهم حتى مع فحص Login؟

لنفترض أن المستخدم قام بتسجيل الدخول الساعة:

10:00

ثم قام المدير بحظره الساعة:

10:15

إذا كان فحص الحظر موجودًا فقط في Login، سيبقى المستخدم قادرًا على استخدام Session الحالية.

أما مع Middleware:

User request
    │
    ▼
CheckBanned
    │
    ▼
Database / User state
    │
    ├── Not banned → Continue
    │
    └── Banned → Logout

فسيتم منعه في أول Request لاحق.


الحظر الدائم والحظر المؤقت

إذا كان التطبيق يحتاج إلى حظر دائم أيضًا، توجد عدة طرق لتصميم ذلك.

مثال:

is_banned
banned_until

لكن الأفضل غالبًا أن تجعل التصميم واضحًا حسب احتياجات المشروع.

مثال

$table->boolean('is_banned')
    ->default(false);

$table->timestamp('banned_until')
    ->nullable();

في هذه الحالة:

is_banned = true
banned_until = null

→ حظر دائم


is_banned = true
banned_until = future date

→ حظر مؤقت

لكن إذا كان التطبيق يحتاج فقط إلى الحظر المؤقت، فوجود banned_until وحده أبسط.


إضافة سبب الحظر

في التطبيقات الإدارية من المفيد تسجيل سبب الحظر.

يمكن إضافة:

$table->text('ban_reason')
    ->nullable();

ثم:

$user->banned_until =
    now()->addDays(7);

$user->ban_reason =
    'Violation of platform rules';

$user->save();

تسجيل من قام بالحظر

إذا كان لديك أكثر من Administrator فمن المفيد أيضًا معرفة من قام بتنفيذ العملية.

يمكن إضافة:

banned_by

بحيث يشير إلى ID المدير.

مثال:

$table->foreignId('banned_by')
    ->nullable()
    ->constrained('users')
    ->nullOnDelete();

وعند الحظر:

$user->banned_until =
    now()->addDays(7);

$user->banned_by =
    auth()->id();

$user->save();

تصميم أكثر احترافية: Ban History

إذا كان المشروع كبيرًا ولا تريد معرفة حالة الحظر الحالية فقط، وإنما تريد الاحتفاظ بتاريخ جميع عمليات الحظر السابقة، فالأفضل إنشاء جدول مستقل.

مثل:

user_bans

+----------------+
| id             |
| user_id        |
| banned_by      |
| reason         |
| starts_at      |
| ends_at        |
| revoked_at     |
| created_at     |
| updated_at     |
+----------------+

بهذه الطريقة تستطيع معرفة:

  • كم مرة تم حظر المستخدم.
  • من قام بالحظر.
  • سبب كل عملية حظر.
  • مدة كل حظر.
  • هل تم فك الحظر يدويًا.
الحقل banned_until ممتاز للتطبيقات الصغيرة والمتوسطة. أما الأنظمة التي تحتاج Audit Trail كاملًا فقد يكون جدول منفصل لعمليات الحظر أكثر ملاءمة.

التعامل مع API

إذا كان التطبيق عبارة عن API، فقد لا يكون من الصحيح إعادة المستخدم إلى صفحة:

/login

بل يمكن إعادة Response من نوع JSON.

if ($user && $user->isBanned()) {

    return response()->json([
        'message' => 'Account suspended.',
        'banned_until' =>
            $user->banned_until,
    ], 403);
}

HTTP Status

يمكن استخدام:

403 Forbidden

للتعبير عن أن المستخدم معروف لكن غير مسموح له بتنفيذ الطلب.


دعم Web وAPI داخل نفس Middleware

if ($user && $user->isBanned()) {

    $bannedUntil =
        $user->banned_until;

    if ($request->expectsJson()) {

        return response()->json([
            'message' =>
                'Your account has been suspended.',
            'banned_until' =>
                $bannedUntil,
        ], 403);
    }


    Auth::logout();

    $request
        ->session()
        ->invalidate();

    $request
        ->session()
        ->regenerateToken();


    return redirect()
        ->route('login')
        ->with(
            'message',
            'تم تعليق حسابك حتى '
            .$bannedUntil
                ->format('Y-m-d H:i:s')
        );
}

أخطاء شائعة عند بناء نظام الحظر

الخطأ الأول: استخدام banned_unitl

الاسم الصحيح:

banned_until

وليس:

banned_unitl

الخطأ الثاني: استخدام $dates في Laravel الحديث

بدل:

protected $dates = [
    'banned_until'
];

استخدم:

protected function casts(): array
{
    return [
        'banned_until' => 'datetime',
    ];
}

الخطأ الثالث: الاعتماد على فحص الحظر وقت Login فقط

هذا لا يغطي حالة قيام المدير بحظر مستخدم يمتلك Session نشطة.

الحل:

Login Check
+
Middleware Check

الخطأ الرابع: تسجيل الخروج دون إبطال Session

الأفضل:

Auth::logout();

$request
    ->session()
    ->invalidate();

$request
    ->session()
    ->regenerateToken();

الخطأ الخامس: السماح للمستخدم بتغيير banned_until

لا تستخدم مدخلات المستخدم مباشرة:

User::update(
    $request->all()
);

خصوصًا إذا كانت هناك حقول إدارية حساسة.


الخطأ السادس: عدم إضافة Authorization إلى لوحة الإدارة

وجود Route مثل:

/admin/users/10/ban

لا يعني أنها آمنة.

يجب استخدام Authorization مناسب مثل:

Policy

Gate

Role / Permission Middleware

النسخة النهائية المختصرة للنظام

Migration

$table->timestamp('banned_until')
    ->nullable();

User Model

protected function casts(): array
{
    return [
        'banned_until' => 'datetime',
    ];
}


public function isBanned(): bool
{
    return $this->banned_until !== null
        && $this->banned_until->isFuture();
}

Middleware

public function handle(
    Request $request,
    Closure $next
): Response {

    $user = $request->user();

    if ($user && $user->isBanned()) {

        $until = $user->banned_until;

        Auth::logout();

        $request
            ->session()
            ->invalidate();

        $request
            ->session()
            ->regenerateToken();

        return redirect()
            ->route('login')
            ->with(
                'message',
                'تم تعليق حسابك حتى '
                .$until->format(
                    'Y-m-d H:i:s'
                )
            );
    }

    return $next($request);
}

Routes

Route::middleware([
    'auth',
    'not-banned',
])->group(function () {

    Route::get(
        '/dashboard',
        DashboardController::class
    )->name('dashboard');

});

حظر المستخدم

$user->banned_until =
    now()->addDays(7);

$user->save();

فك الحظر

$user->banned_until = null;

$user->save();

الخلاصة

يمكن بناء نظام حظر مؤقت للمستخدمين في Laravel بطريقة بسيطة وفعالة عن طريق إضافة حقل:

banned_until

إلى جدول المستخدمين ثم تحويله إلى:

datetime

داخل User Model.

بعد ذلك نستخدم Middleware للتحقق من:

$user->banned_until->isFuture()

فإذا كان التاريخ ما زال في المستقبل، يتم منع المستخدم من متابعة الطلب وتسجيل خروجه.

أما إذا:

banned_until = null

أو:

banned_until <= now()

فيسمح Laravel له باستخدام التطبيق بصورة طبيعية.

التصميم الأساسي يصبح:

Users Table
     │
     │
     └── banned_until
              │
              ▼
          User Model
              │
              ▼
           isBanned()
              │
              ▼
      CheckBanned Middleware
              │
        ┌─────┴─────┐
        │           │
     Banned      Allowed
        │           │
      Logout      Continue
في المشاريع البسيطة يكفي banned_until مع Middleware. أما في المشاريع الأكبر التي تحتاج إلى أسباب الحظر، وسجل كامل للعمليات، ومعرفة المدير الذي نفذ الحظر، فمن الأفضل الانتقال إلى تصميم مستقل مثل user_bans للاحتفاظ بتاريخ كامل لجميع عمليات الحظر.