404 Not Found

404 Not Found


nginx

تحليل معمق لبرمجيات Laravel الوسيطة

البرمجيات الوسيطة هي "خط أنابيب نقاط التفتيش الأمنية" في Laravel — يمر كل طلب عبر طبقة تلو طبقة من الفحوصات، تمامًا مثل الأمتعة؛ تلك التي تفشل يتم اعتراضها فورًا، بينما تلك التي تجتاز يُسمح لها بالانتقال إلى المرحلة التالية.

1. ما ستتعلمه


2. قصة حقيقية لمهندس معماري

(1) مشكلة: جميع فحوصات الأمن مركزة في وحدة التحكم

كتب Bob 10 أسطر من كود التحقق في بداية كل طريقة في وحدة التحكم — فحوصات المصادقة، وعزل المستأجرين، وتحديد المعدل، وCOS، والتسجيل. 50 طريقة x 10 أسطر = 500 سطر من الكود المكرر. عندما أضافت Alice سياسة أمنية جديدة، كان عليها إجراء تغييرات في 50 مكانًا؛ فاتتها ثلاثة، مما أدى إلى ثغرة أمنية. سأل Charlie: "هل سمعتم عن البرمجيات الوسيطة؟"

(2) حل البرمجيات الوسيطة

تستخرج البرمجيات الوسيطة منطق التحقق المشترك إلى فئات منفصلة، ويمر كل طلب تلقائيًا عبر طبقات متعددة من البرمجيات الوسيطة — المصادقة، وتحديد المعدل، وCORS، وعزل المستأجرين — بينما تركز وحدات التحكم فقط على منطق الأعمال.

PHP
// قبل — 10 أسطر من الفحوصات في كل طريقة
public function index() {
    if (!auth()->check()) abort(401);
    if (!tenant()->isActive()) abort(403);
    if (RateLimiter::tooManyAttempts(...)) abort(429);
    // ... أخيرًا، منطق الأعمال
}

// بعد — البرمجيات الوسيطة تعالج جميع الفحوصات
Route::middleware(['auth', 'tenant.resolve', 'throttle:60,1'])
    ->get('/shops', [ShopController::class, 'index']);
// وحدة التحكم تحتوي فقط على منطق الأعمال

(3) العائد

بعد أن نفذ Bob البرمجيات الوسيطة، انخفض كود وحدة التحكم بنسبة 60%؛ سياسة Alice الأمنية الجديدة تطلبت تعديل برمجية وسيطة واحدة فقط، بدون أي إغفالات.


3. تدفق تنفيذ خط أنابيب البرمجيات الوسيطة

(1) العملية الكاملة لمرور طلب عبر البرمجيات الوسيطة

100%
flowchart LR
    A[طلب] --> B[برمجية وسيطة عالمية 1]
    B --> C[برمجية وسيطة عالمية 2]
    C --> D[برمجية وسيطة مسار 1]
    D --> E[برمجية وسيطة مسار 2]
    E --> F[وحدة التحكم]
    F --> G[استجابة]
    G --> H[بعد برمجية وسيطة 2]
    H --> I[بعد برمجية وسيطة 1]
    I --> J[العميل]

(2) نموذج البصل

TEXT
طلب →
  برمجية وسيطة A (قبل) →
    برمجية وسيطة B (قبل) →
      وحدة التحكم → استجابة
    برمجية وسيطة B (بعد) →
  برمجية وسيطة A (بعد) →
استجابة

كل برمجية وسيطة يمكنها:

(1) ▶ مثال:فهم نموذج البصل للبرمجيات الوسيطة

PHP
// app/Http/Middleware/LogRequests.php
class LogRequests
{
    public function handle(Request $request, Closure $next): Response
    {
        // قبل — تسجيل الطلب الوارد
        Log::info('Request:', [
            'method' => $request->method(),
            'url' => $request->fullUrl(),
            'ip' => $request->ip(),
        ]);

        // تمرير إلى البرمجية الوسيطة التالية
        $response = $next($request);

        // بعد — تسجيل حالة الاستجابة
        Log::info('Response:', [
            'status' => $response->getStatusCode(),
            'duration' => defined('LARAVEL_START') ? round((microtime(true) - LARAVEL_START) * 1000) : null,
        ]);

        return $response;
    }
}

الناتج:

TEXT
// التنفيذ ناجح

4. ثلاث طرق للتسجيل

(1) برمجيات وسيطة عالمية

يتم تنفيذها لكل طلب؛ لا حاجة لتحديدها في المسار.

PHP
// bootstrap/app.php
->withMiddleware(function (Middleware $middleware) {
    $middleware->append([
        \App\Http\Middleware\LogRequests::class,
        \App\Http\Middleware\SetLocale::class,
    ]);

    $middleware->remove([
        \Illuminate\Foundation\Http\Middleware\TrimStrings::class,
    ]);
})

(2) برمجيات وسيطة للمسار

برمجيات وسيطة محددة في تعريف المسار.

PHP
// تسجيل اسم مستعار
->withMiddleware(function (Middleware $middleware) {
    $middleware->alias([
        'tenant' => \App\Http\Middleware\TenantResolve::class,
        'role' => \App\Http\Middleware\CheckRole::class,
        'ability' => \App\Http\Middleware\CheckAbility::class,
    ]);
})

// استخدام في المسارات
Route::middleware(['auth', 'tenant', 'role:admin'])
    ->get('/admin/users', [AdminController::class, 'users']);

(3) مجموعة البرمجيات الوسيطة

تجميع مجموعة من البرمجيات الوسيطة وتطبيقها كمجموعة.

اسم المجموعة الافتراضي مناسب لـ
web StartSession, EncryptCookies, VerifyCsrfToken مسارات الويب
api Throttle:api, SubstituteBindings مسارات API
PHP
// تخصيص مجموعات البرمجيات الوسيطة
->withMiddleware(function (Middleware $middleware) {
    $middleware->appendToGroup('api', [
        \App\Http\Middleware\TenantResolve::class,
    ]);

    $middleware->prependToGroup('web', [
        \App\Http\Middleware\SetLocale::class,
    ]);
})
طريقة التسجيل التوقيت السيناريوهات المطبقة
عالمية جميع الطلبات السجلات، CORS
اسم مستعار مسار مسار محدد المصادقة، المستأجر
مجموعة برمجيات وسيطة مسارات داخل المجموعة مجموعة Web/API

(1) ▶ مثال:تسجيل البرمجيات الوسيطة لـ ShopMetrics

PHP
// bootstrap/app.php
->withMiddleware(function (Middleware $middleware) {
    // برمجيات وسيطة عالمية
    $middleware->append([
        \App\Http\Middleware\SetLocale::class,
    ]);

    // أسماء مستعارة
    $middleware->alias([
        'tenant' => \App\Http\Middleware\TenantResolve::class,
        'role' => \App\Http\Middleware\CheckRole::class,
        'ability' => \App\Http\Middleware\CheckAbility::class,
    ]);

    // إضافة إلى مجموعة API
    $middleware->appendToGroup('api', [
        \App\Http\Middleware\EnsureJsonAccept::class,
    ]);
})

الناتج:

TEXT
// التنفيذ ناجح

5. برمجيات وسيطة مخصصة

(1) إنشاء برمجيات وسيطة

BASH
php artisan make:middleware TenantResolve
php artisan make:middleware CheckRole
php artisan make:middleware EnsureJsonAccept

(2) برمجية TenantResolve الوسيطة

PHP
// app/Http/Middleware/TenantResolve.php
class TenantResolve
{
    public function handle(Request $request, Closure $next): Response
    {
        $user = $request->user();
        if (!$user || !$user->tenant_id) {
            abort(403, 'No tenant associated with this account.');
        }

        $tenant = $user->tenant;

        if ($tenant->status !== 'active') {
            abort(403, 'Tenant account is ' . $tenant->status . '.');
        }

        // تخزين المستأجر في الحاوية للنطاق العام
        app()->instance(Tenant::class, $tenant);

        // تعيين سياق المستأجر على الطلب
        $request->attributes->set('tenant', $tenant);

        return $next($request);
    }
}

(3) برمجية CORS الوسيطة

PHP
// app/Http/Middleware/HandleCors.php
class HandleCors
{
    public function handle(Request $request, Closure $next): Response
    {
        $response = $next($request);

        $response->headers->set('Access-Control-Allow-Origin', config('cors.allowed_origins'));
        $response->headers->set('Access-Control-Allow-Methods', 'GET,POST,PUT,PATCH,DELETE,OPTIONS');
        $response->headers->set('Access-Control-Allow-Headers', 'Content-Type,Authorization,X-Tenant-Id');
        $response->headers->set('Access-Control-Max-Age', '86400');

        if ($request->isMethod('OPTIONS')) {
            $response->setStatusCode(204);
        }

        return $response;
    }
}

(1) ▶ مثال:برمجية تحديد المعدل لـ ShopMetrics

PHP
// app/Http/Middleware/PerTenantRateLimit.php
class PerTenantRateLimit
{
    public function handle(Request $request, Closure $next, int $maxAttempts = 60, int $decayMinutes = 1): Response
    {
        $tenant = app()->make(Tenant::class);
        $key = 'tenant:' . $tenant->id . ':' . $request->ip();

        if (RateLimiter::tooManyAttempts($key, $maxAttempts)) {
            $seconds = RateLimiter::availableIn($key);
            abort(429, "Too many requests. Try again in {$seconds} seconds.");
        }

        RateLimiter::hit($key, $decayMinutes * 60);

        $response = $next($request);

        $response->headers->set('X-RateLimit-Limit', $maxAttempts);
        $response->headers->set('X-RateLimit-Remaining', $maxAttempts - RateLimiter::attempts($key));

        return $response;
    }
}

الناتج:

TEXT
// التنفيذ ناجح

6. معاملات البرمجيات الوسيطة

(1) تمرير المعاملات

PHP
// تعريف المسار مع المعاملات
Route::middleware('role:admin,super_admin')
    ->get('/admin/dashboard', [AdminController::class, 'dashboard']);

// البرمجية الوسيطة تستقبل المعاملات
class CheckRole
{
    public function handle(Request $request, Closure $next, string ...$roles): Response
    {
        if (!in_array($request->user()->role, $roles)) {
            abort(403, 'Insufficient role. Required: ' . implode(', ', $roles));
        }
        return $next($request);
    }
}

(2) معامل throttle

PHP
// throttle المدمج مع المعاملات
Route::middleware('throttle:60,1')->group(function () {
    // 60 طلب لكل 1 دقيقة
});

Route::middleware('throttle:10,1')->group(function () {
    // 10 طلبات لكل 1 دقيقة (لنقاط النهاية المكلفة)
});

// محدد معدل مخصص
RateLimiter::for('api', function (Request $request) {
    return $request->user()
        ? Limit::perMinute(60)->by($request->user()->id)
        : Limit::perMinute(10)->by($request->ip());
});

RateLimiter::for('tenant-api', function (Request $request) {
    $tenant = app()->make(Tenant::class);
    $plan = $tenant->subscription?->plan;

    return Limit::perMinute(match ($plan->slug ?? 'starter') {
        'enterprise' => 300,
        'pro' => 120,
        default => 60,
    })->by($tenant->id);
});
تنسيق المعامل الوصف مثال
middleware:p1 معامل واحد role:admin
middleware:p1,p2 معاملات متعددة role:admin,editor
throttle:max,decay معاملات التحديد throttle:60,1

(1) ▶ مثال:تحديد المعدل القائم على الاشتراك لـ ShopMetrics

PHP
// app/Providers/AppServiceProvider.php
public function boot(): void
{
    RateLimiter::for('tenant-api', function (Request $request) {
        $tenant = app()->make(Tenant::class);
        $plan = $tenant->plan ?? null;

        return Limit::perMinute(match ($plan->slug ?? 'starter') {
            'enterprise' => 600,
            'pro' => 120,
            default => 60,
        })->by($tenant->id)->response(function () {
            return response()->json([
                'message' => 'Rate limit exceeded. Upgrade your plan for higher limits.',
                'upgrade_url' => route('pricing'),
            ], 429);
        });
    });
}

// routes/api.php
Route::middleware(['auth:sanctum', 'tenant', 'throttle:tenant-api'])
    ->prefix('v1')
    ->group(function () {
        Route::apiResource('shops', Api\ShopController::class);
    });

الناتج:

TEXT
// التنفيذ ناجح

7. ترتيب البرمجيات الوسيطة والأولوية

(1) الأولوية الافتراضية

يتم تنفيذ البرمجيات الوسيطة بالترتيب الذي تم تسجيلها به، لكن بعض البرمجيات الوسيطة يجب تنفيذها أولاً (على سبيل المثال، يجب معالجة CORS قبل المصادقة).

PHP
// bootstrap/app.php
->withMiddleware(function (Middleware $middleware) {
    $middleware->priority([
        \Illuminate\Foundation\Http\Middleware\HandlePrecognitiveRequests::class,
        \Illuminate\Http\Middleware\HandleCors::class,
        \App\Http\Middleware\PreventRequestsDuringMaintenance::class,
        \Illuminate\Http\Middleware\ValidatePostSize::class,
        \App\Http\Middleware\TenantResolve::class,  // يجب أن يعمل بعد CORS
        \App\Http\Middleware\Authenticate::class,
    ]);
})

(2) برمجيات وسيطة قابلة للإنهاء

PHP
// app/Http/Middleware/SendAnalyticsEvent.php
class SendAnalyticsEvent
{
    public function handle(Request $request, Closure $next): Response
    {
        return $next($request);
    }

    // يعمل بعد إرسال الاستجابة إلى العميل
    public function terminate(Request $request, Response $response): void
    {
        // تسجيل التحليلات بدون حظر
        AnalyticsEvent::create([
            'tenant_id' => app()->make(Tenant::class)?->id,
            'path' => $request->path(),
            'method' => $request->method(),
            'status' => $response->getStatusCode(),
            'duration_ms' => defined('LARAVEL_START')
                ? round((microtime(true) - LARAVEL_START) * 1000)
                : null,
        ]);
    }
}
نهج دورة الحياة التوقيت الغرض
handle() (قبل) عند استلام الطلب المصادقة، التصفية، وتعديل الطلب
handle() (بعد) عند إرجاع الاستجابة تعديل رؤوس الاستجابة
terminate() بعد إرسال الاستجابة السجلات، الإحصائيات، التنظيف

(1) ▶ مثال:تكوين أولوية البرمجيات الوسيطة لـ ShopMetrics

PHP
// bootstrap/app.php
return Application::configure(basePath: dirname(__DIR__))
    ->withMiddleware(function (Middleware $middleware) {
        // الأولوية — CORS أولاً، المصادقة قبل المستأجر
        $middleware->priority([
            \Illuminate\Http\Middleware\HandleCors::class,
            \App\Http\Middleware\TenantResolve::class,
            \App\Http\Middleware\Authenticate::class,
            \App\Http\Middleware\CheckRole::class,
        ]);

        // أسماء مستعارة
        $middleware->alias([
            'tenant' => \App\Http\Middleware\TenantResolve::class,
            'role' => \App\Http\Middleware\CheckRole::class,
            'tenant.throttle' => \App\Http\Middleware\PerTenantRateLimit::class,
        ]);

        // إضافات مجموعة API
        $middleware->appendToGroup('api', [
            \App\Http\Middleware\EnsureJsonAccept::class,
        ]);
    });

الناتج:

TEXT
// التنفيذ ناجح

8. مثال شامل: بنية البرمجيات الوسيطة لـ ShopMetrics

PHP
// ============================================
// شامل: مكدس البرمجيات الوسيطة لـ ShopMetrics
// يغطي: عالمية، مسار، مجموعات، معاملات، أولوية، قابلة للإنهاء
// ============================================

// app/Http/Middleware/TenantResolve.php
class TenantResolve
{
    public function handle(Request $request, Closure $next): Response
    {
        $user = $request->user();
        if (!$user?->tenant_id) {
            abort(403, 'No tenant associated.');
        }
        $tenant = $user->tenant;
        if ($tenant->status !== 'active') {
            abort(403, 'Tenant is ' . $tenant->status);
        }
        app()->instance(Tenant::class, $tenant);
        return $next($request);
    }
}

// app/Http/Middleware/CheckRole.php
class CheckRole
{
    public function handle(Request $request, Closure $next, string ...$roles): Response
    {
        if (!in_array($request->user()?->role, $roles)) {
            abort(403, 'Required role: ' . implode('|', $roles));
        }
        return $next($request);
    }
}

// app/Http/Middleware/EnsureSubscriptionActive.php
class EnsureSubscriptionActive
{
    public function handle(Request $request, Closure $next): Response
    {
        $tenant = app()->make(Tenant::class);
        $subscription = $tenant->subscription;
        if (!$subscription || $subscription->status !== 'active') {
            abort(402, 'Active subscription required.');
        }
        return $next($request);
    }
}

// app/Http/Middleware/LogApiRequests.php
class LogApiRequests
{
    public function handle(Request $request, Closure $next): Response
    {
        return $next($request);
    }

    public function terminate(Request $request, Response $response): void
    {
        ApiLog::create([
            'tenant_id' => app()->make(Tenant::class)?->id,
            'user_id' => $request->user()?->id,
            'method' => $request->method(),
            'path' => $request->path(),
            'status' => $response->getStatusCode(),
            'ip' => $request->ip(),
        ]);
    }
}

// المسارات باستخدام مكدس البرمجيات الوسيطة الكامل
Route::middleware(['auth:sanctum', 'tenant', 'throttle:tenant-api', 'subscription.active'])
    ->prefix('v1')->group(function () {
        Route::apiResource('shops', ShopController::class);
        Route::middleware('role:tenant_owner,analyst')
            ->apiResource('analytics', AnalyticsController::class)->only(['index', 'show']);
        Route::middleware('role:tenant_owner')
            ->apiResource('settings', SettingsController::class);
    });

❓ أسئلة شائعة

س ما الفرق بين برمجية وسيطة المسار وبرمجية وسيطة مُنشئ وحدة التحكم؟
ج برمجية وسيطة المسار تُطبق عند مطابقة المسار، مما يجعلها أكثر مرونة (يمكن تخصيصها لكل مسار)؛ برمجية وسيطة مُنشئ وحدة التحكم تُنفذ عند إنشاء مثيل لوحدة التحكم، مما يجعلها مناسبة لجميع الطرق داخل وحدة التحكم. يجب استخدام برمجية وسيطة المسار قدر الإمكان.
س متى تُنفذ طريقة terminate؟
ج تُنفذ بعد إرسال الاستجابة إلى العميل (مشابهة لـ register_shutdown_function). مناسبة للعمليات مثل التسجيل والإحصائيات التي لا تحتاج إلى التأثير على الاستجابة. terminate لا يحظر المستخدم.
س ما المشاكل التي يمكن أن تنشأ إذا كان ترتيب البرمجيات الوسيطة غير صحيح؟
ج يجب تنفيذ برمجية CORS الوسيطة قبل المصادقة؛ وإلا فإن طلب الفحص المسبق (OPTIONS) سيتم اعتراضه بواسطة برمجية المصادقة الوسيطة ويعيد خطأ 401. يجب تنفيذ TenantResolve قبل برمجية الأعمال الوسيطة؛ وإلا فإن tenant() ستكون null.
س كيف أقوم بعمل دائرة قصر لطلب في برمجية وسيطة؟
ج أعد كائن Response مباشرة دون استدعاء $next($request): return response()->json(['error' => 'Unauthorized'], 401);. البرمجيات الوسيطة ووحدات التحكم اللاحقة لن تُنفذ.
س هل يمكن للبرمجيات الوسيطة العالمية استبعاد مسارات معينة؟
ج في Laravel 11، يمكنك استخدام $middleware->skipWhen(callback) أو $request->is('api/*') داخل البرمجية الوسيطة لتحديد ما إذا كنت تريد تخطي مسار. لا يُنصح بتضمين فحوصات المسار في البرمجيات الوسيطة العالمية.
س ما العلاقة بين RateLimiter وبرمجية throttle الوسيطة؟
ج برمجية throttle الوسيطة تستخدم RateLimiter Facade داخليًا. عند إنشاء منطق تحديد معدل مخصص، حدد الاستراتيجية أولاً في RateLimiter::for()، ثم أشر إليها في المسار باستخدام throttle:اسم_الاستراتيجية.

📖 ملخص


📝 تمارين

  1. تمرين أساسي (⭐): أنشئ برمجية TenantResolve الوسيطة التي تحدد المستأجر من المستخدم المصادق عليه وتخزنه في حاوية، ثم استخدمها في مجموعة مسارات API. اختبر أن المستخدمين بدون مستأجر مرتبط يتلقون خطأ 403 عند محاولتهم الوصول إلى API.

  2. تمرين متقدم (⭐⭐): نفذ برمجية PerTenantRateLimit الوسيطة لفرض تحديد المعدل بناءً على معرف المستأجر وعنوان IP، مع تعيين حدود معدل ديناميكيًا وفقًا لخطة الاشتراك (Starter: 60/دقيقة، Pro: 120/دقيقة، Enterprise: 600/دقيقة).

  3. تحدي (⭐⭐⭐): صمم نظام أولوية كامل للبرمجيات الوسيطة — CORS → TenantResolve → Auth → SubscriptionActive → CheckRole → throttle — لضمان تنفيذ كل برمجية وسيطة في الوقت الصحيح. اكتب اختبارات للتحقق من أن طلبات الفحص المسبق OPTIONS لا يتم اعتراضها بواسطة المصادقة.

Web-Tutorial.com

فريق Web-Tutorial التقني

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

100%