مقدمة في الوسائط (Middleware)
توفر الوسائط آلية مريحة لفحص وتصفية طلبات HTTP التي تدخل تطبيقك. فكر في الوسائط كطبقات تمر من خلالها الطلبات قبل الوصول إلى منطق تطبيقك. تشمل الاستخدامات الشائعة المصادقة، التسجيل، معالجة CORS، وتعديل الطلبات/الاستجابات.
ملاحظة: الوسائط تعمل كـ "جسر" بين طلب HTTP وتطبيقك. كل وسيط يمكنه تمرير الطلب إلى الوسيط التالي في المكدس، أو إنهاء الطلب مبكراً.
كيف تعمل الوسائط
عندما يصل طلب إلى تطبيق Laravel الخاص بك، يمر عبر سلسلة من طبقات الوسائط:
الطلب → وسيط 1 → وسيط 2 → وسيط 3 → المتحكم
↓
الاستجابة ← وسيط 3 ← وسيط 2 ← وسيط 1 ← المتحكم
كل وسيط يمكنه:
- فحص الطلب الوارد قبل وصوله إلى المتحكم
- تعديل الطلب (إضافة رؤوس، تغيير البيانات، إلخ)
- إنهاء الطلب مبكراً (إعادة توجيه، إرجاع خطأ، إلخ)
- فحص أو تعديل الاستجابة بعد تنفيذ المتحكم
الوسائط المدمجة في Laravel
يتضمن Laravel عدة وسائط جاهزة:
// app/Http/Kernel.php
protected $middleware = [
// الوسائط العامة (تعمل على كل طلب)
\Illuminate\Foundation\Http\Middleware\ValidatePostSize::class,
\Illuminate\Foundation\Http\Middleware\TrimStrings::class,
\Illuminate\Foundation\Http\Middleware\ConvertEmptyStringsToNull::class,
];
protected $middlewareGroups = [
'web' => [
\Illuminate\Cookie\Middleware\EncryptCookies::class,
\Illuminate\Session\Middleware\StartSession::class,
\Illuminate\View\Middleware\ShareErrorsFromSession::class,
\Illuminate\Foundation\Http\Middleware\ValidateCsrfToken::class,
\Illuminate\Routing\Middleware\SubstituteBindings::class,
],
'api' => [
\Illuminate\Routing\Middleware\ThrottleRequests::class,
\Illuminate\Routing\Middleware\SubstituteBindings::class,
],
];
protected $middlewareAliases = [
'auth' => \Illuminate\Auth\Middleware\Authenticate::class,
'guest' => \App\Http\Middleware\RedirectIfAuthenticated::class,
'throttle' => \Illuminate\Routing\Middleware\ThrottleRequests::class,
'verified' => \Illuminate\Auth\Middleware\EnsureEmailIsVerified::class,
];
نصيحة: في Laravel 11+، تم نقل تكوين الوسائط إلى bootstrap/app.php للحصول على هيكل أنظف. المفاهيم تبقى نفسها.
إنشاء وسائط مخصصة
استخدم أمر Artisan لإنشاء وسيط:
# إنشاء وسيط
php artisan make:middleware CheckAge
# ينشئ: app/Http/Middleware/CheckAge.php
إليك مثال وسيط أساسي يتحقق من أن المستخدم أكبر من 18 عاماً:
<?php
namespace App\Http\Middleware;
use Closure;
use Illuminate\Http\Request;
class CheckAge
{
/**
* التعامل مع طلب وارد.
*/
public function handle(Request $request, Closure $next)
{
// فحص الشرط قبل التمرير إلى الوسيط التالي
if ($request->age < 18) {
return redirect('home')->with('error', 'يجب أن تكون 18 عاماً أو أكبر');
}
// تمرير الطلب إلى الوسيط/المتحكم التالي
return $next($request);
}
}
الوسائط قبل وبعد
يمكن للوسائط تشغيل المنطق قبل أو بعد معالجة الطلب:
<?php
// وسيط قبل - المنطق يعمل قبل وصول الطلب إلى المتحكم
class BeforeMiddleware
{
public function handle(Request $request, Closure $next)
{
// تنفيذ إجراء قبل الطلب
Log::info('تم استلام الطلب: ' . $request->path());
return $next($request);
}
}
// وسيط بعد - المنطق يعمل بعد إرجاع المتحكم للاستجابة
class AfterMiddleware
{
public function handle(Request $request, Closure $next)
{
// الحصول على الاستجابة من الوسيط/المتحكم التالي
$response = $next($request);
// تنفيذ إجراء بعد توليد الاستجابة
$response->header('X-Custom-Header', 'Value');
return $response;
}
}
// مدمج - تشغيل المنطق قبل وبعد
class CombinedMiddleware
{
public function handle(Request $request, Closure $next)
{
// منطق قبل
Log::info('قبل: ' . $request->path());
$response = $next($request);
// منطق بعد
Log::info('بعد: تم إرسال الاستجابة');
return $response;
}
}
تسجيل الوسائط
سجل الوسائط في app/Http/Kernel.php (Laravel 10 وما قبل) أو bootstrap/app.php (Laravel 11+):
<?php
// app/Http/Kernel.php (Laravel 10)
// 1. الوسائط العامة (تعمل على كل طلب)
protected $middleware = [
\App\Http\Middleware\CheckAge::class,
];
// 2. مجموعات الوسائط (web, api)
protected $middlewareGroups = [
'web' => [
\App\Http\Middleware\CheckAge::class,
],
];
// 3. وسائط المسار (تُعيّن لمسارات محددة)
protected $middlewareAliases = [
'age' => \App\Http\Middleware\CheckAge::class,
'admin' => \App\Http\Middleware\IsAdmin::class,
];
تطبيق الوسائط على المسارات
طبق الوسائط على المسارات بطرق عديدة:
<?php
use Illuminate\Support\Facades\Route;
// وسيط واحد
Route::get('/dashboard', [DashboardController::class, 'index'])
->middleware('auth');
// وسائط متعددة
Route::get('/admin', [AdminController::class, 'index'])
->middleware(['auth', 'admin']);
// مجموعة وسائط
Route::middleware(['auth', 'verified'])->group(function () {
Route::get('/dashboard', [DashboardController::class, 'index']);
Route::get('/profile', [ProfileController::class, 'show']);
});
// استثناء وسيط
Route::middleware(['web'])->withoutMiddleware([\Illuminate\Foundation\Http\Middleware\ValidateCsrfToken::class])->group(function () {
Route::post('/webhook', [WebhookController::class, 'handle']);
});
// التطبيق في constructor المتحكم
class AdminController extends Controller
{
public function __construct()
{
$this->middleware('auth');
$this->middleware('admin')->only(['create', 'edit']);
$this->middleware('log')->except(['index']);
}
}
معاملات الوسائط
مرر معاملات إلى الوسائط للحصول على منطق مرن:
<?php
namespace App\Http\Middleware;
use Closure;
use Illuminate\Http\Request;
class CheckRole
{
/**
* التعامل مع الطلب مع معامل الدور.
*/
public function handle(Request $request, Closure $next, string $role)
{
if (!$request->user()->hasRole($role)) {
abort(403, "غير مصرح. الدور المطلوب: {$role}");
}
return $next($request);
}
}
// الاستخدام في المسارات
Route::get('/admin/users', [UserController::class, 'index'])
->middleware('role:admin');
Route::get('/editor/posts', [PostController::class, 'index'])
->middleware('role:editor');
// معاملات متعددة (مفصولة بفاصلة)
class CheckPermissions
{
public function handle(Request $request, Closure $next, string ...$permissions)
{
foreach ($permissions as $permission) {
if (!$request->user()->can($permission)) {
abort(403, "صلاحية مفقودة: {$permission}");
}
}
return $next($request);
}
}
// الاستخدام
Route::get('/posts/create', [PostController::class, 'create'])
->middleware('permissions:create-post,publish-post');
ملاحظة: معاملات الوسائط مفصولة بنقطتين في تعريفات المسار، والفواصل داخل سلسلة المعامل تفصل القيم المتعددة.
أمثلة عملية للوسائط
1. وسيط التسجيل
<?php
namespace App\Http\Middleware;
use Closure;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Log;
class LogRequests
{
public function handle(Request $request, Closure $next)
{
$start = microtime(true);
// تسجيل الطلب الوارد
Log::info('بدأ الطلب', [
'method' => $request->method(),
'url' => $request->fullUrl(),
'ip' => $request->ip(),
'user_id' => $request->user()?->id,
]);
$response = $next($request);
// تسجيل وقت الاستجابة
$duration = microtime(true) - $start;
Log::info('اكتمل الطلب', [
'duration' => round($duration * 1000, 2) . 'ms',
'status' => $response->status(),
]);
return $response;
}
}
2. وسيط مصادقة مفتاح API
<?php
namespace App\Http\Middleware;
use Closure;
use Illuminate\Http\Request;
class ValidateApiKey
{
public function handle(Request $request, Closure $next)
{
$apiKey = $request->header('X-API-Key');
if (!$apiKey || $apiKey !== config('app.api_key')) {
return response()->json([
'error' => 'مفتاح API غير صالح أو مفقود'
], 401);
}
return $next($request);
}
}
// الاستخدام
Route::middleware(['api.key'])->group(function () {
Route::get('/api/data', [ApiController::class, 'getData']);
});
3. وسيط فرض استجابة JSON (لـ APIs)
<?php
namespace App\Http\Middleware;
use Closure;
use Illuminate\Http\Request;
class ForceJsonResponse
{
public function handle(Request $request, Closure $next)
{
// فرض رأس Accept: application/json
$request->headers->set('Accept', 'application/json');
return $next($request);
}
}
4. وسيط الترجمة
<?php
namespace App\Http\Middleware;
use Closure;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\App;
class SetLocale
{
public function handle(Request $request, Closure $next)
{
// فحص اللغة من معامل الاستعلام، الجلسة، أو تفضيل المستخدم
$locale = $request->query('lang')
?? $request->session()->get('locale')
?? $request->user()?->locale
?? config('app.locale');
// التحقق من اللغة
if (in_array($locale, ['en', 'ar', 'fr'])) {
App::setLocale($locale);
$request->session()->put('locale', $locale);
}
return $next($request);
}
}
الوسائط القابلة للإنهاء (Terminable)
أحياناً تحتاج إلى تنفيذ عمل بعد إرسال الاستجابة إلى المتصفح. الوسائط القابلة للإنهاء تسمح بهذا:
<?php
namespace App\Http\Middleware;
use Closure;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
class LogActivity
{
public function handle(Request $request, Closure $next)
{
return $next($request);
}
/**
* يعمل بعد إرسال الاستجابة إلى المتصفح.
*/
public function terminate(Request $request, $response): void
{
// عملية تسجيل ثقيلة لا تعيق الاستجابة
DB::table('activity_log')->insert([
'user_id' => $request->user()?->id,
'path' => $request->path(),
'method' => $request->method(),
'ip' => $request->ip(),
'user_agent' => $request->userAgent(),
'created_at' => now(),
]);
}
}
نصيحة: استخدم الوسائط القابلة للإنهاء للمهام مثل التسجيل، التحليلات، أو التنظيف التي لا تحتاج إلى تأخير الاستجابة للمستخدم.
أولويات الوسائط
تحكم في الترتيب الذي تُنفذ به الوسائط بتعريف الأولويات:
<?php
// app/Http/Kernel.php
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\Session\Middleware\AuthenticateSession::class,
\Illuminate\Routing\Middleware\SubstituteBindings::class,
\Illuminate\Auth\Middleware\Authorize::class,
];
تمرين 1: إنشاء وسيط المشرف
أنشئ وسيطاً يتحقق من أن المستخدم المصادق لديه علامة "is_admin" مضبوطة على true.
- شغل
php artisan make:middleware IsAdmin
- تحقق من مصادقة المستخدم
- تحقق من أن المستخدم لديه خاصية is_admin مضبوطة على true
- أرجع خطأ 403 إذا لم يكن مشرفاً
- سجل الوسيط في Kernel.php كـ 'admin'
- طبقه على مسارات المشرف
تمرين 2: وسيط تقييد الطلبات
أنشئ وسيطاً مخصصاً يحد المستخدمين من 10 طلبات في الدقيقة على مسارات محددة.
- أنشئ الوسيط باستخدام
php artisan make:middleware ThrottleCustom
- استخدم Cache facade لتخزين عدد الطلبات لكل مستخدم/IP
- زد العداد في كل طلب
- أرجع 429 Too Many Requests إذا تجاوز الحد
- أضف رؤوس X-RateLimit إلى الاستجابة
- اختبر مع طلبات سريعة متعددة
تمرين 3: وسيط وقت الاستجابة
أنشئ وسيطاً يضيف رأس X-Response-Time يوضح المدة التي استغرقها معالجة الطلب.
- أنشئ الوسيط مع منطق التوقيت
- سجل وقت البداية قبل تمرير الطلب
- احسب المدة بعد الحصول على الاستجابة
- أضف رأس X-Response-Time مع الملي ثانية
- اختيارياً سجل الطلبات البطيئة (>1000ms)
- طبقه بشكل عام على مجموعة وسائط web
تحذير: كن حذراً مع الوسائط العامة - تعمل على كل طلب ويمكن أن تؤثر على الأداء. أضف الوسائط إلى المكدس العام فقط إذا كان ذلك ضرورياً حقاً.
الملخص
في هذا الدرس، تعلمت:
- ما هي الوسائط وكيف تعمل في Laravel
- أنواع الوسائط المدمجة في Laravel (عامة، مجموعات، مسار)
- إنشاء وسائط مخصصة باستخدام Artisan
- أنماط الوسائط قبل وبعد ومدمجة
- تسجيل وتطبيق الوسائط على المسارات
- تمرير معاملات إلى الوسائط
- أمثلة عملية للوسائط (التسجيل، مفاتيح API، الترجمة)
- الوسائط القابلة للإنهاء للمهام بعد الاستجابة
- أولويات تنفيذ الوسائط
الدرس التالي: سنستكشف تخزين الملفات والتحميلات في Laravel، بما في ذلك التخزين المحلي، S3، والتحقق من الملفات.