Laravel المتقدم
Middleware المتقدم و HTTP Kernel
فهم Middleware في Laravel
Middleware توفر آلية ملائمة لتصفية طلبات HTTP الداخلة إلى تطبيقك. بالإضافة إلى المصادقة والتخويل، يمكن لـ Middleware المتقدمة تنفيذ عمليات معقدة وتعديل الطلبات/الاستجابات وتنفيذ الاهتمامات المشتركة.
حالات استخدام Middleware الشائعة:
- المصادقة والتخويل
- تعديل الطلب/الاستجابة
- التسجيل والمراقبة
- تحديد المعدل والتقييد
- معالجة CORS
- التخزين المؤقت للاستجابة
- التحقق من الطلب وتعقيمه
- أعلام الميزات واختبار A/B
إنشاء Middleware مخصصة
لنستكشف أنواعاً مختلفة من تنفيذات 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;
}
}
Middleware مع معاملات:
// 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);
});
Middleware تعديل الطلب:
// 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);
}
}
Middleware تعديل الاستجابة:
// 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 القابلة للإنهاء تنفذ العمل بعد إرسال الاستجابة إلى المتصفح، وهي مثالية للمهام التي لا يجب أن تؤخر الاستجابة.
إنشاء 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(),
]);
}
}
}
Middleware قابلة للإنهاء لتدفئة الذاكرة المؤقتة:
// 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 المتقدمة
لنستكشف بعض تنفيذات 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');
Middleware إصدار API:
// 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 هو قلب دورة الطلب/الاستجابة لتطبيقك. فهم كيفية تكوينه أمر بالغ الأهمية للتطبيقات المتقدمة.
فهم بنية 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 من خلال تحديد الأولوية.
تعيين أولوية 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 مهم: ترتيب تنفيذ middleware يمكن أن يؤثر بشكل كبير على سلوك تطبيقك. يجب أن تعمل middleware المصادقة قبل التخويل، ويجب أن يعمل التسجيل عادةً في وقت مبكر أو متأخر (قابل للإنهاء)، ويجب أن يحدث تعديل الاستجابة بعد المنطق الرئيسي.
تقنيات Middleware المتقدمة
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();
}
}
مجموعات Middleware مع التحميل الديناميكي:
// 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 مركزة وذات غرض واحد