الـ Middleware في لارافيل هي الطبقة التي تمرّ عبرها كل طلبات HTTP قبل أن تصل إلى الكونترولر، وبعد أن تخرج منه في طريقها إلى المتصفح. في هذا الدليل سنغطي مفهوم الـ middleware، وكيفية إنشائه وتسجيله في لارافيل 11 و12 و13 (وأيضاً في
الإصدارات الأقدم التي ما زالت تستخدم Kernel.php)، مروراً بتمرير الوسائط، وترتيب التنفيذ، وterminable middleware، وصولاً إلى الأخطاء الشائعة وأفضل الممارسات.
ما هو الـ middleware؟
يمكن تعريف الـ middleware بأنه بوابة أو جسر بين الطلب request والاستجابة response؛ فهو بمثابة فلتر يقرر: هل يُسمح لهذا الطلب بالمرور والحصول على الاستجابة أم لا؟ إنه طبقة إضافية تعترض الطلب قبل وصوله إلى
منطق التطبيق.
تخيّل حارس عمارة: أنت طلبت الدخول إلى العمارة (request)، فيتأكد الحارس من هويتك وسبب دخولك، وبناءً على ذلك يقرر السماح لك بالدخول أو ردّك من الباب (response). الحارس هنا هو الـ middleware.
المثال الأشهر الذي نواجهه في كل تطبيق تقريباً هو auth middleware: عند محاولة الوصول إلى صفحة محمية، يتحقق الـ middleware من كون المستخدم مسجّل الدخول، فإن لم يكن كذلك أعاد توجيهه إلى صفحة تسجيل الدخول، وإن كان مسجلاً سمح للطلب بالمرور
إلى الكونترولر.
أين يقع الـ middleware في دورة حياة الطلب؟
تخيّل الـ middleware كطبقات متتالية يمرّ الطلب عبرها ذهاباً، ثم تمرّ الاستجابة عبرها إياباً:
Request
↓
[ Global Middleware ] ← يعمل على كل طلب
↓
[ Group Middleware: web / api ]
↓
[ Route Middleware: auth, verified, ... ]
↓
Controller / Closure
↓
[ Route Middleware ] ← الجزء الذي يلي ()next$ في كل middleware
↓
[ Group Middleware ]
↓
[ Global Middleware ]
↓
Response → المتصفح
↓
[ terminate() ] ← بعد إرسال الاستجابة
الفكرة المهمة هنا: كل middleware يستطيع إيقاف السلسلة بإرجاع استجابة مباشرة (redirect أو abort) دون استدعاء $next($request)، وحينها لن يصل الطلب إلى الكونترولر أصلاً.
متى تستخدم الـ middleware ومتى لا تستخدمه؟
حالات مناسبة لاستخدام الـ middleware
- التحقق من تسجيل الدخول والصلاحيات (authentication & authorization).
- ضبط لغة الموقع في التطبيقات متعددة اللغات (
setLocale). - تفعيل وضع الصيانة
maintenance mode. - تتبّع المستخدمين وتسجيل الطلبات (logging / analytics).
- إضافة ترويسات أمان إلى الاستجابة (Security Headers، CORS).
- تحديد معدل الطلبات
rate limitingلحماية الـ API. - فرض HTTPS أو إعادة التوجيه بناءً على الجهاز أو الدولة.
- التحقق من الاشتراك أو من تفعيل البريد الإلكتروني قبل الوصول إلى مجموعة صفحات.
حالات لا يُنصح فيها بالـ middleware
- التحقق من صحة المدخلات: مكانها الطبيعي هو
Form Requestوليس الـ middleware. - صلاحيات مرتبطة بسجل معيّن (هل يملك هذا المستخدم هذا المقال تحديداً؟): استخدم
GatesوPolicies، لأن الـ middleware يعمل قبل ربط النماذج بشكل كامل ولا يعرف سياق السجل. - منطق العمل (business logic): مكانه الكونترولر أو Service class؛ الـ middleware يجب أن يبقى قراراً بسيطاً: مرِّر أو اقطع.
- عمليات ثقيلة أو بطيئة: إرسال بريد، استدعاء API خارجي، أو استعلامات مكلفة — استخدم
Queueأوterminate().
كما يأتي لارافيل مع مجموعة من الـ middleware المبنية مسبقاً داخل الإطار نفسه، مثل التعامل مع مدخلات الطلب (TrimStrings, ConvertEmptyStringsToNull)، والتحقق من وضع الصيانة (PreventRequestsDuringMaintenance)، وحماية
النماذج من التزوير.
إنشاء middleware في لارافيل
في أحد المشاريع طلب مني العميل أنه في حال زيارة الموقع من جهاز هاتف يجب توجيه الزائر إلى نطاق فرعي مخصص للهواتف. سننفّذ هذا المطلب خطوة بخطوة كمثال عملي في هذا المقال.
ننشئ الـ middleware باستخدام أمر artisan:
php artisan make:middleware MobileRedirect
بعد تنفيذ الأمر سيُنشأ كلاس باسم MobileRedirect.php في المسار:
app/Http/Middleware/MobileRedirect.php<?php
namespace App\Http\Middleware;
use Closure;
use Illuminate\Http\Request;
use Symfony\Component\HttpFoundation\Response;
class MobileRedirect
{
/**
* Handle an incoming request.
*
* @param \Closure(\Illuminate\Http\Request): (\Symfony\Component\HttpFoundation\Response) $next
*/
public function handle(Request $request, Closure $next): Response
{
return $next($request);
}
}
الدالة handle هي العمود الفقري للـ middleware؛ بداخلها نضع المنطق الذي يقرر مصير الطلب. انتبه إلى نقطتين مهمتين في التوقيع:
- نوع الإرجاع هو
Symfony\Component\HttpFoundation\ResponseوليسIlluminate\Http\Response، لأن استجابات لارافيل جميعها ترث من كلاس Symfony، وهذا يشمل الـ redirect والـ JSON والـ streams. - يتم حل الـ middleware عبر
Service Container، أي يمكنك عمل type-hint لأي اعتمادية في الـ constructor وسيقوم لارافيل بحقنها تلقائياً.
أنواعه: before middleware و after middleware
يوجد نمطان أساسيان لكتابة الـ middleware، والفرق بينهما هو موضع الكود بالنسبة لاستدعاء $next($request):
Before middleware
ينفّذ المنطق قبل أن يعالج التطبيق الطلب، ويُستخدم للفحص والاعتراض وإعادة التوجيه.
public function handle(Request $request, Closure $next): Response
{
// الكود هنا ينفّذ قبل وصول الطلب إلى الكونترولر
if (! $request->user()?->is_active) {
return redirect('/suspended');
}
return $next($request);
}
After middleware
ينفّذ المنطق بعد أن يعالج التطبيق الطلب ويُنتج الاستجابة، ويُستخدم لتعديل الاستجابة نفسها.
public function handle(Request $request, Closure $next): Response
{
$response = $next($request);
// الكود هنا ينفّذ بعد توليد الاستجابة وقبل إرسالها
$response->headers->set('X-Frame-Options', 'SAMEORIGIN');
return $response;
}
في مثالنا سنقرأ الطلب ونقرر إعادة التوجيه قبل الوصول إلى الكونترولر، لذلك نحتاج إلى before middleware.
مثال عملي: إعادة توجيه زوّار الهاتف إلى نطاق فرعي
أبسط صيغة تعتمد على قيمة في الـ query string:
public function handle(Request $request, Closure $next): Response
{
if ($request->query('mobile') === '1') {
return redirect()->away('https://m.example.test');
}
return $next($request);
}
الآن عند زيارة https://example.test/?mobile=1 سيُنفَّذ الـ middleware ويعيد توجيهك إلى النطاق الفرعي، وفي حال غياب المعامل سيُستدعى $next($request) ويكمل الطلب طريقه الطبيعي.
لكن الاعتماد على query string ليس حلاً واقعياً في الإنتاج، لأن الزائر لن يضيفه بنفسه. الحل العملي هو فحص User-Agent، مع الانتباه إلى ثلاث نقاط: تجاهل عناكب محركات البحث حتى لا تتأثر أرشفة الموقع، وإتاحة خيار «النسخة الكاملة»
للمستخدم، وتجنّب الدخول في حلقة إعادة توجيه لا نهائية.
<?php
namespace App\Http\Middleware;
use Closure;
use Illuminate\Http\Request;
use Symfony\Component\HttpFoundation\Response;
class MobileRedirect
{
private const MOBILE_URL = 'https://m.example.test';
public function handle(Request $request, Closure $next): Response
{
// لا نعيد توجيه عناكب محركات البحث ولا طلبات الـ API
if ($request->is('api/*') || $this->isBot($request)) {
return $next($request);
}
// احترام رغبة المستخدم إذا اختار النسخة الكاملة
if ($request->cookie('force_desktop')) {
return $next($request);
}
if ($this->isMobile($request)) {
return redirect()->away(self::MOBILE_URL . $request->getRequestUri(), 302);
}
return $next($request);
}
private function isMobile(Request $request): bool
{
return (bool) preg_match(
'/Android|iPhone|iPod|BlackBerry|IEMobile|Opera Mini|Windows Phone/i',
(string) $request->userAgent()
);
}
private function isBot(Request $request): bool
{
return (bool) preg_match(
'/bot|crawl|slurp|spider|facebookexternalhit/i',
(string) $request->userAgent()
);
}
}
ملاحظة مهمة لتحسين محركات البحث: عند وجود نطاق فرعي للهواتف يُفضّل استخدام إعادة توجيه مؤقتة 302 وليس 301، مع إضافة وسم canonical في نسخة الهاتف يشير إلى نسخة سطح المكتب. والأفضل من ذلك كله في المشاريع الجديدة
هو التصميم المتجاوب بدلاً من نطاق منفصل.
تسجيل الـ middleware (لارافيل 11 وما بعده)
حتى الآن أنشأنا الـ middleware وكتبنا منطق العمل، لكن لارافيل لا يعلم بوجوده بعد. ابتداءً من لارافيل 11 حُذف ملف app/Http/Kernel.php نهائياً، وانتقل كل إعداد الـ middleware إلى ملف bootstrap/app.php عبر الدالة
withMiddleware(). هذا هو الشكل العام للملف:
<?php
use Illuminate\Foundation\Application;
use Illuminate\Foundation\Configuration\Exceptions;
use Illuminate\Foundation\Configuration\Middleware;
return Application::configure(basePath: dirname(__DIR__))
->withRouting(
web: __DIR__.'/../routes/web.php',
api: __DIR__.'/../routes/api.php',
commands: __DIR__.'/../routes/console.php',
health: '/up',
)
->withMiddleware(function (Middleware $middleware): void {
//
})
->withExceptions(function (Exceptions $exceptions): void {
//
})->create();
1. التسجيل العام (Global Middleware)
إذا أردت تنفيذ الـ middleware على كل طلب في التطبيق:
use App\Http\Middleware\MobileRedirect;
->withMiddleware(function (Middleware $middleware): void {
// إضافته في نهاية السلسلة
$middleware->append(MobileRedirect::class);
// أو في بدايتها ليعمل أولاً
$middleware->prepend(MobileRedirect::class);
})
الكائن $middleware هو نسخة من Illuminate\Foundation\Configuration\Middleware، وهو المسؤول عن إدارة كل الـ middleware في التطبيق.
وإذا احتجت التحكم الكامل بسلسلة الـ middleware العامة الافتراضية (لحذف أحدها أو إعادة ترتيبها)، استخدم الدالة use ومرّر لها السلسلة كاملة:
->withMiddleware(function (Middleware $middleware): void {
$middleware->use([
\Illuminate\Foundation\Http\Middleware\InvokeDeferredCallbacks::class,
// \Illuminate\Http\Middleware\TrustHosts::class,
\Illuminate\Http\Middleware\TrustProxies::class,
\Illuminate\Http\Middleware\HandleCors::class,
\Illuminate\Foundation\Http\Middleware\PreventRequestsDuringMaintenance::class,
\Illuminate\Http\Middleware\ValidatePostSize::class,
\Illuminate\Foundation\Http\Middleware\TrimStrings::class,
\Illuminate\Foundation\Http\Middleware\ConvertEmptyStringsToNull::class,
]);
})
2. تسجيل اختصار (Alias)
غالباً لا نريد تنفيذ الـ middleware على كل طلب، بل على مسارات محددة. في هذه الحالة نسجّل له اسماً مختصراً بدلاً من كتابة اسم الكلاس الكامل في كل مرة:
use App\Http\Middleware\MobileRedirect;
->withMiddleware(function (Middleware $middleware): void {
$middleware->alias([
'mobile.redirect' => MobileRedirect::class,
]);
})
ثم نستخدم الاختصار في ملف المسارات:
Route::get('/contact', [ContactController::class, 'index'])
->middleware('mobile.redirect');
وتسجيل الاختصار اختياري تماماً؛ يمكنك دائماً تمرير اسم الكلاس مباشرة: ->middleware(MobileRedirect::class).
تسجيل الـ middleware في لارافيل 10 وما قبله
إذا كنت تعمل على مشروع قديم، فالتسجيل يتم في ملف app/Http/Kernel.php عبر خصائص الكلاس. للتسجيل العام:
// app/Http/Kernel.php
protected $middleware = [
\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,
\App\Http\Middleware\MobileRedirect::class, // الـ middleware الخاص بنا
];
وللتسجيل باسم مختصر يُستخدم على مسارات محددة:
// لارافيل 10: $middlewareAliases
// لارافيل 9 وما قبله: $routeMiddleware
protected $middlewareAliases = [
'auth' => \App\Http\Middleware\Authenticate::class,
'guest' => \App\Http\Middleware\RedirectIfAuthenticated::class,
'verified' => \Illuminate\Auth\Middleware\EnsureEmailIsVerified::class,
'mobile.redirect' => \App\Http\Middleware\MobileRedirect::class,
];
جدول مقارنة سريع
| الغرض | لارافيل 10 وما قبله | لارافيل 11 / 12 / 13 |
|---|---|---|
| ملف الإعداد | app/Http/Kernel.php |
bootstrap/app.php |
| middleware عام | $middleware |
$middleware->append() / prepend() |
| اسم مختصر | $routeMiddleware ثم $middlewareAliases |
$middleware->alias([...]) |
| المجموعات | $middlewareGroups |
$middleware->appendToGroup() / web() / api() |
| الأولوية | $middlewarePriority |
$middleware->priority([...]) |
| داخل الكونترولر | $this->middleware() في الـ constructor |
HasMiddleware أو #[Middleware] |
تعيين الـ middleware على المسارات Routes
على مسار واحد
use App\Http\Middleware\MobileRedirect;
Route::get('/contact', [ContactController::class, 'index'])
->middleware(MobileRedirect::class);
أكثر من middleware على المسار نفسه
Route::get('/dashboard', [DashboardController::class, 'index'])
->middleware(['auth', 'verified', 'mobile.redirect']);
على مجموعة مسارات
ماذا لو كان لديك خمسة مسارات تريد تطبيق الـ middleware نفسه عليها؟ تكرار ->middleware('mobile.redirect') على كل مسار يعمل بلا مشاكل، لكنه تكرار يمكن اختصاره باستخدام مجموعة:
// بدلاً من التكرار
Route::get('/contact', [ContactController::class, 'index'])->middleware('mobile.redirect');
Route::get('/about', [AboutController::class, 'index'])->middleware('mobile.redirect');
Route::get('/cats', [CatController::class, 'index'])->middleware('mobile.redirect');
// استخدم مجموعة
Route::middleware('mobile.redirect')->group(function () {
Route::get('/contact', [ContactController::class, 'index']);
Route::get('/about', [AboutController::class, 'index']);
Route::get('/cats', [CatController::class, 'index']);
});
ويمكن دمج الـ middleware مع بادئة للمسار واسم للمجموعة:
Route::middleware(['auth', 'role:admin'])
->prefix('admin')
->name('admin.')
->group(function () {
Route::get('/users', [UserController::class, 'index'])->name('users.index');
});
استثناء مسار من الـ middleware
أحياناً تريد استثناء مسار واحد من middleware مطبّق على المجموعة كلها، وهنا تأتي الدالة withoutMiddleware:
Route::middleware([EnsureTokenIsValid::class])->group(function () {
Route::get('/', HomeController::class);
Route::get('/profile', [ProfileController::class, 'show'])
->withoutMiddleware([EnsureTokenIsValid::class]);
});
انتبه: withoutMiddleware يعمل على middleware المسارات فقط، ولا يستطيع إلغاء الـ global middleware. لهذا السبب إن كان الـ middleware يحتاج استثناءات، فلا تسجّله عالمياً منذ البداية.
مجموعات الـ middleware والمجموعتان web و api
يمكنك تجميع عدة middleware تحت مفتاح واحد ليسهل تطبيقها معاً:
->withMiddleware(function (Middleware $middleware): void {
$middleware->appendToGroup('admin-area', [
\App\Http\Middleware\EnsureUserIsAdmin::class,
\App\Http\Middleware\LogAdminActivity::class,
]);
})Route::middleware('admin-area')->group(function () {
// ...
});
ويحتوي لارافيل على مجموعتين جاهزتين هما web وapi، وتُطبَّقان تلقائياً على ملفَّي routes/web.php وroutes/api.php على التوالي. مجموعة web تضم إدارة الجلسات والكوكيز والحماية من تزوير الطلبات
وربط النماذج، بينما تضم مجموعة api ربط النماذج فقط. ولإضافة middleware إليهما:
->withMiddleware(function (Middleware $middleware): void {
$middleware->web(append: [
\App\Http\Middleware\SetLocale::class,
]);
$middleware->api(prepend: [
\App\Http\Middleware\EnsureTokenIsValid::class,
]);
// استبدال أحد عناصر المجموعة بآخر
$middleware->web(replace: [
\Illuminate\Session\Middleware\StartSession::class => \App\Http\Middleware\StartCustomSession::class,
]);
// أو حذفه نهائياً
$middleware->web(remove: [
\Illuminate\Session\Middleware\StartSession::class,
]);
})
تسجيل الـ middleware داخل الكونترولر
هناك ثلاث حالات لتطبيق الـ middleware على دوال الكونترولر:
- على جميع الدوال دون استثناء.
- only: يُطبّق فقط على الدوال المحددة.
- except: يُطبّق على جميع الدوال باستثناء الدوال المحددة.
الطريقة الحديثة: واجهة HasMiddleware
الطريقة القديمة كانت استدعاء $this->middleware() داخل الـ constructor، لكنها حُذفت ابتداءً من لارافيل 11. البديل هو تطبيق واجهة HasMiddleware التي تفرض وجود دالة ثابتة باسم middleware:
<?php
namespace App\Http\Controllers;
use Illuminate\Routing\Controllers\HasMiddleware;
use Illuminate\Routing\Controllers\Middleware;
class PostController extends Controller implements HasMiddleware
{
/**
* Get the middleware that should be assigned to the controller.
*/
public static function middleware(): array
{
return [
'auth', // على جميع الدوال
new Middleware('log', only: ['index']), // على index فقط
new Middleware('subscribed', except: ['store']), // على الجميع عدا store
];
}
public function index() { /* ... */ }
public function store() { /* ... */ }
}
ويمكن أيضاً تمرير middleware على شكل closure مباشرة دون إنشاء كلاس مستقل:
use Closure;
use Illuminate\Http\Request;
public static function middleware(): array
{
return [
function (Request $request, Closure $next) {
// منطق بسيط لا يستحق كلاساً كاملاً
return $next($request);
},
];
}
الأحدث: PHP Attributes في لارافيل 13
أضاف لارافيل 13 طريقة أوضح باستخدام الـ attributes، بحيث تكتب قواعد الحماية فوق الكلاس أو فوق الدالة مباشرة، فتصبح مرئية في مكانها الطبيعي:
<?php
namespace App\Http\Controllers;
use Illuminate\Routing\Attributes\Controllers\Middleware;
#[Middleware('auth')]
class PostController extends Controller
{
#[Middleware('permission:create articles')]
public function create() { /* ... */ }
public function index() { /* ... */ }
}
الطريقة القديمة (لارافيل 10 وما قبله)
public function __construct()
{
$this->middleware('auth'); // على جميع الدوال
$this->middleware('auth')->only(['show']); // على show فقط
$this->middleware('auth')->except(['show']); // على الجميع عدا show
}
تمرير وسائط إلى الـ middleware
كثيراً ما تحتاج middleware واحداً يخدم عدة حالات، مثل التحقق من دور المستخدم. تُمرَّر الوسائط الإضافية بعد المعامل $next مباشرة:
<?php
namespace App\Http\Middleware;
use Closure;
use Illuminate\Http\Request;
use Symfony\Component\HttpFoundation\Response;
class EnsureUserHasRole
{
public function handle(Request $request, Closure $next, string $role): Response
{
if (! $request->user()->hasRole($role)) {
abort(403);
}
return $next($request);
}
}
وتُمرَّر القيمة في المسار بعد نقطتين رأسيتين :، وتُفصل القيم المتعددة بفاصلة:
Route::put('/post/{id}', [PostController::class, 'update'])
->middleware(EnsureUserHasRole::class.':editor');
Route::put('/post/{id}', [PostController::class, 'update'])
->middleware(EnsureUserHasRole::class.':editor,publisher');
// أو باستخدام الاختصار
Route::put('/post/{id}', [PostController::class, 'update'])
->middleware('role:editor,publisher');
ولاستقبال عدد غير محدد من الأدوار استخدم المعاملات المتغيرة:
public function handle(Request $request, Closure $next, string ...$roles): Response
{
if (! $request->user()->hasAnyRole($roles)) {
abort(403);
}
return $next($request);
}
الـ Terminable Middleware
أحياناً تحتاج تنفيذ عمل بعد إرسال الاستجابة إلى المتصفح فعلياً، مثل تسجيل إحصائيات أو كتابة سجلات، دون أن ينتظر المستخدم انتهاء هذا العمل. عرّف دالة terminate في الـ middleware، وسيستدعيها لارافيل تلقائياً بعد إرسال
الاستجابة (بشرط أن يستخدم الخادم FastCGI مثل PHP-FPM):
<?php
namespace App\Http\Middleware;
use Closure;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Log;
use Symfony\Component\HttpFoundation\Response;
class LogRequestDuration
{
public function handle(Request $request, Closure $next): Response
{
return $next($request);
}
/**
* Handle tasks after the response has been sent to the browser.
*/
public function terminate(Request $request, Response $response): void
{
Log::info('request.finished', [
'uri' => $request->getRequestUri(),
'status' => $response->getStatusCode(),
]);
}
}
ملاحظة دقيقة: عند استدعاء terminate ينشئ لارافيل نسخة جديدة من الـ middleware من الـ container. فإن أردت أن تكون النسخة نفسها المستخدمة في handle (لتمرير حالة بينهما)، سجّله كـ singleton في AppServiceProvider:
public function register(): void
{
$this->app->singleton(\App\Http\Middleware\LogRequestDuration::class);
}
ترتيب التنفيذ والأولوية priority
يُنفَّذ الـ middleware واحداً تلو الآخر حسب ترتيب تسجيله: العام أولاً، ثم المجموعة، ثم middleware المسار. والترتيب ليس تفصيلاً شكلياً — تخيّل middleware للصلاحيات يعمل قبل middleware المصادقة، عندها سيحاول قراءة $request->user() وهي فارغة.
في الحالات النادرة التي تحتاج فيها فرض ترتيب معيّن بغض النظر عن ترتيب كتابته في المسار، استخدم priority:
->withMiddleware(function (Middleware $middleware): void {
$middleware->priority([
\Illuminate\Cookie\Middleware\EncryptCookies::class,
\Illuminate\Session\Middleware\StartSession::class,
\Illuminate\Routing\Middleware\SubstituteBindings::class,
\Illuminate\Contracts\Auth\Middleware\AuthenticatesRequests::class,
\App\Http\Middleware\EnsureUserHasRole::class,
\Illuminate\Auth\Middleware\Authorize::class,
]);
})
ولإدراج middleware في القائمة الحالية دون استبدالها كلها:
->withMiddleware(function (Middleware $middleware): void {
$middleware->prependToPriorityList(
before: \Illuminate\Routing\Middleware\SubstituteBindings::class,
prepend: \App\Http\Middleware\EnsureTokenIsValid::class,
);
$middleware->appendToPriorityList(
after: \Illuminate\Routing\Middleware\SubstituteBindings::class,
append: \App\Http\Middleware\EnsureUserIsSubscribed::class,
);
})
الاختصارات (Aliases) الجاهزة في لارافيل
يوفّر لارافيل مجموعة اختصارات جاهزة يمكنك استخدامها مباشرة دون تسجيل:
| الاختصار | الكلاس | الوظيفة |
|---|---|---|
auth |
Illuminate\Auth\Middleware\Authenticate |
يشترط تسجيل الدخول |
guest |
Illuminate\Auth\Middleware\RedirectIfAuthenticated |
للزوّار غير المسجلين فقط |
verified |
Illuminate\Auth\Middleware\EnsureEmailIsVerified |
يشترط تفعيل البريد الإلكتروني |
can |
Illuminate\Auth\Middleware\Authorize |
يتحقق من صلاحية عبر Gate أو Policy |
throttle |
Illuminate\Routing\Middleware\ThrottleRequests |
تحديد معدل الطلبات |
signed |
Illuminate\Routing\Middleware\ValidateSignature |
التحقق من توقيع الرابط |
password.confirm |
Illuminate\Auth\Middleware\RequirePassword |
يطلب تأكيد كلمة المرور |
cache.headers |
Illuminate\Http\Middleware\SetCacheHeaders |
ضبط ترويسات الكاش |
مثال على الاستخدام المباشر:
Route::post('/comments', [CommentController::class, 'store'])
->middleware(['auth', 'verified', 'throttle:10,1']);
اختبار الـ middleware
الـ middleware جزء من طبقة الحماية، ويستحق اختباراً. أسهل طريقة هي اختبار المسار المحمي نفسه:
<?php
use App\Models\User;
it('redirects mobile visitors to the mobile subdomain', function () {
$this->withServerVariables(['HTTP_USER_AGENT' => 'Mozilla/5.0 (iPhone; CPU iPhone OS 17_0)'])
->get('/contact')
->assertRedirect('https://m.example.test/contact');
});
it('allows desktop visitors through', function () {
$this->withServerVariables(['HTTP_USER_AGENT' => 'Mozilla/5.0 (Windows NT 10.0; Win64; x64)'])
->get('/contact')
->assertOk();
});
it('blocks guests from the dashboard', function () {
$this->get('/dashboard')->assertRedirect('/login');
$this->actingAs(User::factory()->create())
->get('/dashboard')
->assertOk();
});
وإذا أردت تعطيل الـ middleware مؤقتاً أثناء اختبار شيء آخر، استخدم $this->withoutMiddleware() — لكن لا تجعلها عادة، فأنت بذلك تختبر تطبيقاً مختلفاً عن الحقيقي.
أخطاء شائعة وحلولها
- الـ middleware لا يعمل إطلاقاً: غالباً لم يُسجَّل. تحقق من
bootstrap/app.php(أوKernel.phpفي الإصدارات القديمة)، ومن تطابق اسم الاختصار حرفياً بين التسجيل والمسار. - ما زال السلوك القديم يظهر: المسارات مخزّنة في الكاش. نفّذ
php artisan route:clearوphp artisan config:clear. - نسيان
returnقبل$next($request): النتيجة صفحة بيضاء فارغة، لأن الـ middleware لم يُرجع أي استجابة. - حلقة إعادة توجيه لا نهائية: يحدث عندما يعيد الـ middleware التوجيه إلى مسار محمي بالـ middleware نفسه. استثنِ مسار الوجهة دائماً.
$request->user()فارغة: الـ middleware يعمل قبلStartSessionأو قبل المصادقة. راجع الترتيب أو استخدمpriority.- خطأ في نوع الإرجاع: استخدم
Symfony\Component\HttpFoundation\ResponseوليسIlluminate\Http\Responseفي التوقيع. - الخلط بين
redirect()وabort(403): استخدم إعادة التوجيه عندما يستطيع المستخدم فعل شيء لتصحيح الوضع (تسجيل الدخول مثلاً)، واستخدمabortعندما يكون مصادقاً لكنه ببساطة غير مخوّل. - middleware عام يعمل على ملفات الـ assets والـ API: راجع هل تحتاجه عالمياً فعلاً، أم يكفي على مجموعة
web.
محاذير وأفضل الممارسات
- الأداء أولاً: الـ middleware العام يعمل على كل طلب، فإن استهلك موارد كثيرة (استعلام قاعدة بيانات، نداء API خارجي) فسيبطئ التطبيق كله. استخدم الكاش، أو انقل العملية إلى Queue أو إلى
terminate(). - الترتيب مهم: ضع الـ middleware الخفيف والسريع في الأعلى، والذي يرفض أكبر عدد من الطلبات مبكراً أولاً، حتى لا تُنفَّذ عمليات ثقيلة لطلب سيُرفض في النهاية.
- مسؤولية واحدة لكل middleware: middleware يتحقق من الدور، وآخر يسجّل النشاط. لا تحشر كل شيء في كلاس واحد.
- لا تعدّل الطلب بصمت: تغيير قيم
$requestداخل middleware يجعل تتبع الأخطاء لاحقاً صعباً جداً. - احذر التعارض مع مكونات الإطار: لارافيل مبني فوق مكوّنات
Symfony، وعند كتابة middleware أنت تكتب تحت الإطار الأساسي؛ فتجنّب لمس الجلسة أو الكوكيز أو الترويسات بطريقة تتعارض مع الـ middleware المدمج. - اختر المستوى المناسب: عام للسلوك الذي لا استثناء له، ومجموعة للسلوك المشترك بين قسم كامل من الموقع، ومسار للحالات الخاصة.
- لا تضع صلاحيات دقيقة في الـ middleware: استخدم
Policiesعندما يعتمد القرار على السجل نفسه. - اكتب اختباراً لكل middleware أمني: أي middleware يمنع الوصول يجب أن يكون له اختبار يثبت أنه يمنع فعلاً.
الخلاصة
ملخّص العمل مع الـ middleware في أربع خطوات:
1. الإنشاء
php artisan make:middleware MobileRedirect
2. تحديد النوع: before أم after
public function handle(Request $request, Closure $next): Response
{
// before: منطق يُنفّذ قبل الكونترولر
if ($request->query('mobile') === '1') {
return redirect()->away('https://m.example.test');
}
$response = $next($request);
// after: منطق يُنفّذ بعد توليد الاستجابة
return $response;
}
3. التسجيل
// لارافيل 11 وما بعده — bootstrap/app.php
->withMiddleware(function (Middleware $middleware): void {
$middleware->append(\App\Http\Middleware\MobileRedirect::class); // عام
$middleware->alias(['mobile.redirect' => \App\Http\Middleware\MobileRedirect::class]); // اختصار
})
// لارافيل 10 وما قبله — app/Http/Kernel.php
protected $middleware = [
\App\Http\Middleware\MobileRedirect::class,
];
protected $middlewareAliases = [
'mobile.redirect' => \App\Http\Middleware\MobileRedirect::class,
];
4. التطبيق
// على مسار
Route::get('/contact', [ContactController::class, 'index'])->middleware('mobile.redirect');
// على مجموعة
Route::middleware('mobile.redirect')->group(function () {
Route::get('/contact', [ContactController::class, 'index']);
Route::get('/about', [AboutController::class, 'index']);
});
// داخل الكونترولر (لارافيل 11+)
class PostController extends Controller implements HasMiddleware
{
public static function middleware(): array
{
return [
'auth',
new Middleware('mobile.redirect', only: ['show']),
new Middleware('subscribed', except: ['index']),
];
}
}
أسئلة شائعة
ما الفرق بين الـ middleware والـ Form Request؟
الـ middleware يقرر هل يُسمح للطلب بالوصول أصلاً، أما الـ Form Request فيتحقق من صحة البيانات بعد وصول الطلب إلى الكونترولر. الأول بوابة، والثاني مدقق مدخلات.
ما الفرق بين الـ middleware والـ Gates و Policies؟
الـ middleware مناسب للقرارات العامة على مستوى المسار (هل المستخدم مسجّل؟ هل هو مشرف؟)، بينما الـ Policies مناسبة للقرارات المرتبطة بسجل بعينه (هل يملك هذا المستخدم هذا المقال؟).
هل يمكن تنفيذ middleware على الـ API فقط؟
نعم، عبر $middleware->api(append: [...]) في bootstrap/app.php، أو بتطبيقه على مجموعة مسارات داخل routes/api.php.
أين ذهب ملف Kernel.php؟
حُذف في لارافيل 11 ضمن تبسيط بنية التطبيق، وانتقلت إعداداته إلى bootstrap/app.php. المشاريع المُرقّاة من لارافيل 10 قد تحتفظ بالملف وهو ما زال يعمل، لكن المشاريع الجديدة لا تحتوي عليه.
كم عدد الـ middleware الذي يمكن تطبيقه على مسار واحد؟
لا يوجد حد تقني، لكن كل middleware يضيف وقتاً إلى الطلب. إن وجدت نفسك تطبّق ستة أو سبعة على المسار نفسه، فغالباً بعضها يستحق أن يُدمج في مجموعة واحدة.
جميل جدا
شكراً لك 🌱 سعيد أن المقال نال إعجابك.
جزاك الله خير على هذا المحتوى الجميل والقيّم
وإياك، وجزاك الله خيراً على كلماتك الطيبة. أسأل الله أن ينفع به كل من يقرأه.
حقيقي شرحك جامد ربنا يباركلك
ربنا يبارك فيك ويكرمك 🙏 كلامك ده بيخليني أكمل. لو في موضوع تحب أشرحه بنفس الطريقة، قولّي.
ابداااع
شكراً جزيلاً! تعليقك هذا وقود لمقالات قادمة إن شاء الله 🚀
:
🙏
مدونة حضرتك بقت من اهم مراجعي .. شكرا
والله هاد من اكثر التعليقات الي بتفرح اي أي حد بيكتب محتوى تقني — إن اللي بيكتبه بيرجعله ناس وقت الشغل الحقيقي. متشكر جداً لثقتك، ولو لقيت أي نقطة ناقصة أو محتاجة توضيح أكتر في أي مقال، بلّغني وهعدّلها. الف حمد وشكر لك يا رب
شرح ممتاز ما شاء الله
تبارك الله، شكراً لمرورك وتشجيعك. وإن كان لديك اقتراح لموضوع تودّ شرحه بنفس التفصيل فأنا أرحب به.