Middleware المتقدم و HTTP Kernel
فهم Middleware في Laravel
Middleware توفر آلية ملائمة لتصفية طلبات HTTP الداخلة إلى تطبيقك. بالإضافة إلى المصادقة والتخويل، يمكن لـ Middleware المتقدمة تنفيذ عمليات معقدة وتعديل الطلبات/الاستجابات وتنفيذ الاهتمامات المشتركة.
- المصادقة والتخويل
- تعديل الطلب/الاستجابة
- التسجيل والمراقبة
- تحديد المعدل والتقييد
- معالجة CORS
- التخزين المؤقت للاستجابة
- التحقق من الطلب وتعقيمه
- أعلام الميزات واختبار A/B
إنشاء Middleware مخصصة
لنستكشف أنواعاً مختلفة من تنفيذات Middleware المخصصة.
// توليد middleware
php artisan make:middleware LogRequestMiddleware
// app/Http/Middleware/LogRequestMiddleware.php
namespace App\Http\Middleware;
use Closure;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Log;
use Symfony\Component\HttpFoundation\Response;
class LogRequestMiddleware
{
public function handle(Request $request, Closure $next): Response
{
// قبل معالجة الطلب
$startTime = microtime(true);
Log::info('Request started', [
'method' => $request->method(),
'url' => $request->fullUrl(),
'ip' => $request->ip(),
'user_agent' => $request->userAgent(),
]);
// معالجة الطلب
$response = $next($request);
// بعد معالجة الطلب
$duration = microtime(true) - $startTime;
Log::info('Request completed', [
'status' => $response->status(),
'duration' => round($duration * 1000, 2) . 'ms',
]);
return $response;
}
}
// app/Http/Middleware/CheckUserRole.php
namespace App\Http\Middleware;
use Closure;
use Illuminate\Http\Request;
use Symfony\Component\HttpFoundation\Response;
class CheckUserRole
{
public function handle(Request $request, Closure $next, string ...$roles): Response
{
if (!auth()->check()) {
return redirect('login');
}
$user = auth()->user();
// التحقق من أن المستخدم لديه أي من الأدوار المطلوبة
if (!in_array($user->role, $roles)) {
abort(403, 'Unauthorized action.');
}
return $next($request);
}
}
// الاستخدام في المسارات
Route::get('/admin', [AdminController::class, 'index'])
->middleware('role:admin,super-admin');
Route::group(['middleware' => ['role:admin,editor']], function () {
Route::resource('posts', PostController::class);
});
// app/Http/Middleware/SanitizeInput.php
namespace App\Http\Middleware;
use Closure;
use Illuminate\Http\Request;
use Symfony\Component\HttpFoundation\Response;
class SanitizeInput
{
public function handle(Request $request, Closure $next): Response
{
// تعقيم جميع المدخلات
$input = $request->all();
array_walk_recursive($input, function (&$value) {
if (is_string($value)) {
// إزالة محاولات XSS
$value = strip_tags($value);
// قص المسافات البيضاء
$value = trim($value);
// تحويل الأحرف الخاصة
$value = htmlspecialchars($value, ENT_QUOTES, 'UTF-8');
}
});
// استبدال مدخلات الطلب بالنسخة المعقمة
$request->merge($input);
return $next($request);
}
}
// app/Http/Middleware/AddSecurityHeaders.php
namespace App\Http\Middleware;
use Closure;
use Illuminate\Http\Request;
use Symfony\Component\HttpFoundation\Response;
class AddSecurityHeaders
{
public function handle(Request $request, Closure $next): Response
{
$response = $next($request);
// إضافة رؤوس الأمان
$response->headers->set('X-Frame-Options', 'SAMEORIGIN');
$response->headers->set('X-Content-Type-Options', 'nosniff');
$response->headers->set('X-XSS-Protection', '1; mode=block');
$response->headers->set('Referrer-Policy', 'strict-origin-when-cross-origin');
$response->headers->set(
'Content-Security-Policy',
"default-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline';"
);
return $response;
}
}
Middleware القابلة للإنهاء (Terminable Middleware)
Middleware القابلة للإنهاء تنفذ العمل بعد إرسال الاستجابة إلى المتصفح، وهي مثالية للمهام التي لا يجب أن تؤخر الاستجابة.
// app/Http/Middleware/LogActivityMiddleware.php
namespace App\Http\Middleware;
use Closure;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
use Symfony\Component\HttpFoundation\Response;
class LogActivityMiddleware
{
public function handle(Request $request, Closure $next): Response
{
return $next($request);
}
public function terminate(Request $request, Response $response): void
{
// يتم تشغيل هذا بعد إرسال الاستجابة إلى المتصفح
if (auth()->check()) {
DB::table('activity_logs')->insert([
'user_id' => auth()->id(),
'action' => $request->method() . ' ' . $request->path(),
'ip_address' => $request->ip(),
'user_agent' => $request->userAgent(),
'response_status' => $response->status(),
'created_at' => now(),
]);
}
}
}
// app/Http/Middleware/WarmCache.php
namespace App\Http\Middleware;
use Closure;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Cache;
use Symfony\Component\HttpFoundation\Response;
class WarmCache
{
public function handle(Request $request, Closure $next): Response
{
return $next($request);
}
public function terminate(Request $request, Response $response): void
{
// تدفئة الذاكرة المؤقتة بعد إرسال الاستجابة
if ($response->status() === 200 && $request->isMethod('GET')) {
$cacheKey = 'page.' . md5($request->fullUrl());
Cache::put($cacheKey, $response->getContent(), now()->addHour());
}
}
}
أنماط Middleware المتقدمة
لنستكشف بعض تنفيذات Middleware المتطورة.
// app/Http/Middleware/CheckFeatureFlag.php
namespace App\Http\Middleware;
use Closure;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Cache;
use Symfony\Component\HttpFoundation\Response;
class CheckFeatureFlag
{
public function handle(Request $request, Closure $next, string $feature): Response
{
// التحقق من تمكين الميزة
$enabled = Cache::remember("feature.{$feature}", 3600, function () use ($feature) {
return DB::table('feature_flags')
->where('name', $feature)
->value('enabled') ?? false;
});
if (!$enabled) {
// الميزة معطلة
if ($request->expectsJson()) {
return response()->json([
'message' => 'This feature is currently unavailable'
], 503);
}
abort(503, 'Feature temporarily unavailable');
}
return $next($request);
}
}
// الاستخدام
Route::get('/beta-feature', [BetaController::class, 'index'])
->middleware('feature:new-dashboard');
// app/Http/Middleware/ApiVersion.php
namespace App\Http\Middleware;
use Closure;
use Illuminate\Http\Request;
use Symfony\Component\HttpFoundation\Response;
class ApiVersion
{
public function handle(Request $request, Closure $next, string $version): Response
{
// تخزين إصدار API في الطلب
$request->attributes->set('api_version', $version);
// الحصول على الاستجابة
$response = $next($request);
// إضافة الإصدار إلى رؤوس الاستجابة
if ($response instanceof \Illuminate\Http\JsonResponse) {
$response->header('X-API-Version', $version);
}
return $response;
}
}
// الاستخدام في المسارات
Route::prefix('v1')
->middleware('api.version:1.0')
->group(function () {
Route::get('/users', [UserController::class, 'index']);
});
Route::prefix('v2')
->middleware('api.version:2.0')
->group(function () {
Route::get('/users', [UserV2Controller::class, 'index']);
});
// app/Http/Middleware/ValidateRequestSignature.php
namespace App\Http\Middleware;
use Closure;
use Illuminate\Http\Request;
use Symfony\Component\HttpFoundation\Response;
class ValidateRequestSignature
{
public function handle(Request $request, Closure $next): Response
{
$signature = $request->header('X-Signature');
if (!$signature) {
return response()->json([
'error' => 'Missing signature'
], 401);
}
// الحصول على نص الطلب
$body = $request->getContent();
// حساب التوقيع المتوقع
$secret = config('app.webhook_secret');
$expectedSignature = hash_hmac('sha256', $body, $secret);
// مقارنة التوقيعات
if (!hash_equals($expectedSignature, $signature)) {
return response()->json([
'error' => 'Invalid signature'
], 401);
}
return $next($request);
}
}
تكوين HTTP Kernel
HTTP Kernel هو قلب دورة الطلب/الاستجابة لتطبيقك. فهم كيفية تكوينه أمر بالغ الأهمية للتطبيقات المتقدمة.
// app/Http/Kernel.php
namespace App\Http;
use Illuminate\Foundation\Http\Kernel as HttpKernel;
class Kernel extends HttpKernel
{
/**
* Middleware عامة (تعمل على كل طلب)
*/
protected $middleware = [
// \App\Http\Middleware\TrustHosts::class,
\App\Http\Middleware\TrustProxies::class,
\Illuminate\Http\Middleware\HandleCors::class,
\App\Http\Middleware\PreventRequestsDuringMaintenance::class,
\Illuminate\Foundation\Http\Middleware\ValidatePostSize::class,
\App\Http\Middleware\TrimStrings::class,
\Illuminate\Foundation\Http\Middleware\ConvertEmptyStringsToNull::class,
];
/**
* مجموعات Middleware (يمكن تعيينها للمسارات)
*/
protected $middlewareGroups = [
'web' => [
\App\Http\Middleware\EncryptCookies::class,
\Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse::class,
\Illuminate\Session\Middleware\StartSession::class,
\Illuminate\View\Middleware\ShareErrorsFromSession::class,
\App\Http\Middleware\VerifyCsrfToken::class,
\Illuminate\Routing\Middleware\SubstituteBindings::class,
],
'api' => [
\Laravel\Sanctum\Http\Middleware\EnsureFrontendRequestsAreStateful::class,
'throttle:api',
\Illuminate\Routing\Middleware\SubstituteBindings::class,
],
];
/**
* Middleware المسار (تُعيّن بشكل فردي للمسارات)
*/
protected $middlewareAliases = [
'auth' => \App\Http\Middleware\Authenticate::class,
'auth.basic' => \Illuminate\Auth\Middleware\AuthenticateWithBasicAuth::class,
'auth.session' => \Illuminate\Session\Middleware\AuthenticateSession::class,
'cache.headers' => \Illuminate\Http\Middleware\SetCacheHeaders::class,
'can' => \Illuminate\Auth\Middleware\Authorize::class,
'guest' => \App\Http\Middleware\RedirectIfAuthenticated::class,
'password.confirm' => \Illuminate\Auth\Middleware\RequirePassword::class,
'signed' => \App\Http\Middleware\ValidateSignature::class,
'throttle' => \Illuminate\Routing\Middleware\ThrottleRequests::class,
'verified' => \Illuminate\Auth\Middleware\EnsureEmailIsVerified::class,
// Middleware مخصصة
'role' => \App\Http\Middleware\CheckUserRole::class,
'feature' => \App\Http\Middleware\CheckFeatureFlag::class,
'api.version' => \App\Http\Middleware\ApiVersion::class,
'log.request' => \App\Http\Middleware\LogRequestMiddleware::class,
];
}
أولوية Middleware
Laravel يسمح لك بالتحكم في الترتيب الذي يتم فيه تنفيذ middleware من خلال تحديد الأولوية.
// app/Http/Kernel.php
namespace App\Http;
use Illuminate\Foundation\Http\Kernel as HttpKernel;
class Kernel extends HttpKernel
{
/**
* قائمة middleware مرتبة حسب الأولوية.
*
* يفرض أن تكون middleware غير العامة دائماً بالترتيب المحدد.
*/
protected $middlewarePriority = [
\Illuminate\Foundation\Http\Middleware\HandlePrecognitiveRequests::class,
\Illuminate\Cookie\Middleware\EncryptCookies::class,
\Illuminate\Session\Middleware\StartSession::class,
\Illuminate\View\Middleware\ShareErrorsFromSession::class,
\Illuminate\Contracts\Auth\Middleware\AuthenticatesRequests::class,
\Illuminate\Routing\Middleware\ThrottleRequests::class,
\Illuminate\Routing\Middleware\ThrottleRequestsWithRedis::class,
\Illuminate\Contracts\Session\Middleware\AuthenticatesSessions::class,
\Illuminate\Routing\Middleware\SubstituteBindings::class,
\Illuminate\Auth\Middleware\Authorize::class,
// إضافة أولوية middleware مخصصة
\App\Http\Middleware\LogRequestMiddleware::class,
\App\Http\Middleware\CheckFeatureFlag::class,
];
}
تقنيات Middleware المتقدمة
// في routes/web.php
Route::get('/profile', [ProfileController::class, 'show'])
->middleware(function ($request, $next) {
if ($request->user()->is_banned) {
return redirect('/banned');
}
return $next($request);
});
// أو إنشاء middleware شرطية قابلة لإعادة الاستخدام
class ConditionalMiddleware
{
public function handle(Request $request, Closure $next): Response
{
if ($this->shouldSkip($request)) {
return $next($request);
}
// منطق Middleware هنا
return $next($request);
}
protected function shouldSkip(Request $request): bool
{
return $request->is('api/*') || $request->ajax();
}
}
// app/Providers/RouteServiceProvider.php
public function boot(): void
{
$this->configureRateLimiting();
$this->routes(function () {
// مسارات API مع middleware ديناميكية
Route::middleware($this->getApiMiddleware())
->prefix('api')
->group(base_path('routes/api.php'));
// مسارات الويب
Route::middleware('web')
->group(base_path('routes/web.php'));
});
}
protected function getApiMiddleware(): array
{
$middleware = ['api'];
// إضافة تحديد المعدل بناءً على البيئة
if (app()->environment('production')) {
$middleware[] = 'throttle:60,1';
}
// إضافة التحقق من مفتاح API لبيئات معينة
if (config('api.require_key')) {
$middleware[] = 'api.key';
}
return $middleware;
}
تمرين 1: إنشاء محدد معدل API
أنشئ middleware تحديد معدل مخصصة تقوم بـ:
- تحديد الطلبات لكل مستخدم (مصادق عليه) أو IP (ضيف)
- تخزين الأعداد في Redis مع انتهاء صلاحية
- إرجاع رؤوس مناسبة (X-RateLimit-Limit, X-RateLimit-Remaining)
- إرجاع حالة 429 عند تجاوز الحد
- قبول معاملات للحدود (مثل
ratelimit:100,60= 100 طلب لكل 60 ثانية)
تمرين 2: مسجل الطلب/الاستجابة
أنشئ middleware قابلة للإنهاء تسجل:
- طريقة الطلب، URL، IP، user agent
- حمولة الطلب (معقمة، بدون كلمات مرور)
- رمز حالة الاستجابة والحجم
- مدة الطلب بالميلي ثانية
- معرف المستخدم إذا كان مصادقاً عليه
- تخزين السجلات في قاعدة البيانات أو الملف، في قائمة انتظار للأداء
تمرين 3: Middleware متعدد المستأجرين
أنشئ middleware لتطبيق متعدد المستأجرين:
- استخراج معرف المستأجر من النطاق الفرعي أو الرأس
- التحقق من وجود المستأجر ونشاطه
- تعيين اتصال قاعدة البيانات إلى قاعدة بيانات خاصة بالمستأجر
- تخزين سياق المستأجر في الطلب للوصول السهل
- معالجة المستأجر المفقود أو غير الصالح بشكل جيد
- Middleware تصفي وتعدل طلبات/استجابات HTTP
- Middleware القابلة للإنهاء تعمل بعد إرسال الاستجابة
- Middleware يمكن أن تقبل معاملات للمرونة
- HTTP Kernel يتحكم في middleware العامة والمجموعات والأسماء المستعارة
- أولوية Middleware تحدد ترتيب التنفيذ
- استخدم middleware للاهتمامات المشتركة (التسجيل، المصادقة، التخزين المؤقت)
- حافظ على middleware مركزة وذات غرض واحد