نظام التوجيه (Routing) في Laravel 11 — من الأساسيات إلى الاحتراف

إذا كان مشروعك عبارة عن مدينة، فنظام التوجيه (Routing) هو نظام الطرق والإشارات داخل هذه المدينة. كل مرة يكتب مستخدم رابطاً في المتصفح، يقوم Laravel بالبحث في ملف المسارات: أين أذهب؟ من يعالج هذا الطلب؟ بدون فهم Routing، لا يمكنك بناء أي تطبيق حقيقي. في هذا الدرس ستتعلم كل ما تحتاجه — من أبسط مسار إلى مجموعات متقدمة بـ Middleware.

1. أين تُعرَّف مسارات موقعك في Laravel؟

كل مسار (Route) في Laravel يُعرَّف في مجلد routes/. في Laravel 11، ستجد ملفين رئيسيين تستخدمهما يومياً:

مجلد routes/ في مشروع Laravel 11
routes/
├── web.php     ← مسارات الموقع العادي (متصفح، جلسات، CSRF)
└── api.php     ← مسارات الـ API (بدون جلسات، يعتمد Tokens)

هذا الدرس يتمحور حول routes/web.php — الملف الذي يُعرِّف المسارات التي يصل إليها المستخدم عبر متصفحه. كل رابط تراه في شريط العنوان يمر عبر هذا الملف أولاً.

كيف يعمل Laravel عند استلام طلب؟
المستخدم يكتب https://mysite.com/articles ← Laravel يفتح routes/web.php ← يبحث عن مسار يطابق /articles بطريقة GET ← يُنفّذ الكود المرتبط به ← يُرجع الاستجابة للمستخدم.

2. أنواع أساليب HTTP وكيف تستخدمها

لكل عملية في الويب أسلوب HTTP (HTTP Method) مخصص. Laravel يوفر دالة مسار لكل أسلوب. هذه هي الأساليب التي ستستخدمها أكثر:

routes/web.php — أنواع المسارات
use Illuminate\Support\Facades\Route;

// GET — لعرض صفحة أو جلب بيانات
Route::get('/articles', function () {
    return view('articles.index');
});

// POST — لاستلام بيانات نموذج (إرسال Form)
Route::post('/articles', function () {
    // حفظ المقال الجديد
});

// PUT / PATCH — لتعديل بيانات موجودة
Route::put('/articles/{id}', function (string $id) {
    // تحديث المقال
});

// DELETE — لحذف بيانات
Route::delete('/articles/{id}', function (string $id) {
    // حذف المقال
});
جدول مرجعي — متى أستخدم كل أسلوب؟
الأسلوب الاستخدام مثال
GETعرض صفحة أو جلب بيانات/articles أو /articles/5
POSTإنشاء سجل جديدإرسال نموذج تسجيل
PUTتحديث كامل لسجل موجودتعديل كل حقول مقال
PATCHتحديث جزئي لسجل موجودتغيير حالة المقال فقط
DELETEحذف سجلحذف مقال برقم معين

مسار يرد على كل الأساليب دفعة واحدة

في حالات نادرة، قد تحتاج مساراً يستجيب لأي أسلوب HTTP. Laravel يوفر هذا بـ Route::any():

routes/web.php
// يستجيب لأي أسلوب HTTP
Route::any('/webhook', function () {
    // معالجة webhook خارجي
});

// يستجيب فقط للأساليب المحددة
Route::match(['get', 'post'], '/contact', function () {
    // عرض النموذج (GET) أو معالجته (POST)
});

3. بارامترات المسارات (Route Parameters) — الروابط الديناميكية

في الواقع العملي، روابطك لن تكون ثابتة. ستحتاج لروابط مثل /articles/42 أو /users/ahmed/posts. هذا هو دور Route Parameters — أجزاء متغيرة داخل الرابط:

بارامتر إلزامي

يُكتب بين أقواس معقوفة {}. إذا لم يُوفَّر هذا الجزء من الرابط، سيُعطي Laravel خطأ 404.

routes/web.php — بارامتر إلزامي
// /articles/42 ← $id = 42
// /articles/99 ← $id = 99
Route::get('/articles/{id}', function (string $id) {
    return "عرض المقال رقم: " . $id;
});

// بارامترات متعددة
// /users/ahmed/posts/5
Route::get('/users/{username}/posts/{postId}', function (string $username, string $postId) {
    return "مقالات المستخدم {$username}، المقال رقم {$postId}";
});

بارامتر اختياري

إذا أردت أن يكون الجزء اختيارياً، أضف ? بعد الاسم وحدد قيمة افتراضية في الدالة:

routes/web.php — بارامتر اختياري
// /users        ← $name = 'زائر'
// /users/ahmed  ← $name = 'ahmed'
Route::get('/users/{name?}', function (string $name = 'زائر') {
    return "مرحباً يا {$name}!";
});

تقييد البارامتر بـ Regex

يمكنك إضافة قيد لضمان أن البارامتر يتبع نمطاً معيناً (أرقام فقط، حروف فقط...). هذا يمنع الطلبات غير الصالحة من ان تصل لكودك.

routes/web.php — تقييد بارامتر
// يقبل فقط الأرقام: /articles/42 ✓ | /articles/abc ✗
Route::get('/articles/{id}', function (string $id) {
    return "مقال رقم: " . $id;
})->whereNumber('id');

// يقبل فقط الحروف الإنجليزية
Route::get('/users/{username}', function (string $username) {
    return "ملف المستخدم: " . $username;
})->whereAlpha('username');

// قيد مخصص بـ Regex
Route::get('/category/{slug}', function (string $slug) {
    return "تصنيف: " . $slug;
})->where('slug', '[a-z0-9\-]+');
فائدة التقييد من ناحية الأمان:
إذا كنت تستخدم $id في استعلام قاعدة البيانات، التقييد بـ whereNumber يمنع أي محاولة لحقن سلاسل نصية غير متوقعة في مسارك. طبقة دفاع إضافية بجانب Eloquent.

4. تسمية المسارات (Named Routes) — ليس مجرد رفاهية

تخيل أنك كتبت الرابط /user/profile في 30 مكان في قوالب Blade، ثم قررت تغيير الرابط إلى /account/profile. ستحتاج لتعديل 30 مكان!

الحل: تسمية المسارات. تعطي كل مسار اسماً مستقلاً عن الرابط الفعلي. عند تغيير الرابط، يكفي تعديل سطر واحد في ملف المسارات وسيتحدث كل شيء تلقائياً.

routes/web.php — تعريف أسماء المسارات
Route::get('/user/profile', function () {
    return view('profile');
})->name('profile');

Route::get('/articles/{id}', function (string $id) {
    return view('articles.show', ['id' => $id]);
})->name('articles.show');

Route::get('/dashboard', function () {
    return view('dashboard');
})->name('dashboard');

الآن، في أي مكان (Blade أو Controller)، أنشئ الروابط هكذا:

استخدام الأسماء في Blade وPHP
{{-- في ملف Blade --}}
<a href="{{ route('profile') }}">ملف المستخدم</a>

{{-- مسار مع بارامتر --}}
<a href="{{ route('articles.show', ['id' => 42]) }}">اقرأ المقال</a>

// في PHP (Controller مثلاً)
return redirect()->route('dashboard');  // إعادة توجيه لـ dashboard
اتفاقية تسمية المسارات في Laravel:
العرف السائد هو استخدام النقطة للفصل: resource.action. مثال: articles.index، articles.show، articles.store، articles.destroy. هذا النمط متوافق مع ما ينشئه Laravel تلقائياً عند استخدام Route::resource().

5. مجموعات المسارات (Route Groups) — تنظيم وقوة

عندما يكبر مشروعك، ستجد أن كثيراً من المسارات تشترك في خصائص متشابهة: كلها تتطلب تسجيل دخول، أو كلها تبدأ بنفس البادئة /admin. بدلاً من تكرار هذا في كل مسار، تُجمّعها في Group واحد.

تجميع برابط مشترك (Prefix)

routes/web.php — مجموعة بـ prefix
// بدلاً من تكرار /admin في كل مسار:
Route::prefix('admin')->group(function () {
    Route::get('/dashboard', function () {   // ← /admin/dashboard
        return view('admin.dashboard');
    });

    Route::get('/users', function () {       // ← /admin/users
        return view('admin.users');
    });

    Route::get('/settings', function () {   // ← /admin/settings
        return view('admin.settings');
    });
});

تجميع بـ Middleware (الحماية)

هذا هو الاستخدام الأكثر شيوعاً للمجموعات — حماية مجموعة من المسارات بـ Middleware واحد. مثلاً: جميع صفحات لوحة التحكم تتطلب تسجيل الدخول:

routes/web.php — مجموعة محمية بـ auth
// كل هذه المسارات تتطلب تسجيل الدخول
Route::middleware('auth')->group(function () {
    Route::get('/dashboard', function () {
        return view('dashboard');
    })->name('dashboard');

    Route::get('/profile', function () {
        return view('profile');
    })->name('profile');

    Route::get('/settings', function () {
        return view('settings');
    })->name('settings');
});

// هذا المسار لا يتطلب تسجيل دخول (خارج المجموعة)
Route::get('/about', function () {
    return view('about');
});

الجمع بين Prefix وMiddleware واسم مشترك

في المشاريع الحقيقية، تجمع خصائص متعددة معاً لتكوين مجموعات قوية ومنظمة:

routes/web.php — مجموعة متكاملة
Route::prefix('admin')
    ->middleware(['auth', 'admin'])   // تسجيل دخول + صلاحية أدمن
    ->name('admin.')                  // أسماء المسارات ستبدأ بـ admin.
    ->group(function () {

        Route::get('/dashboard', function () {
            return view('admin.dashboard');
        })->name('dashboard');  // الاسم الكامل: admin.dashboard

        Route::get('/users', function () {
            return view('admin.users');
        })->name('users');   // الاسم الكامل: admin.users
    });

6. توجيه المسارات للـ Controllers — الطريقة الاحترافية

في الأمثلة السابقة كتبنا الكود مباشرة داخل routes/web.php باستخدام Closures. هذا مقبول للاختبار والمثل البسيطة، لكن في المشاريع الحقيقية يجب أن يكون ملف المسارات نظيفاً — فقط تعريف الروابط لا الكود. الكود ينتقل إلى Controllers.

routes/web.php — توجيه لـ Controller
use App\Http\Controllers\ArticleController;
use App\Http\Controllers\UserController;

// [ClassName::class, 'methodName']
Route::get('/articles', [ArticleController::class, 'index'])->name('articles.index');
Route::get('/articles/{id}', [ArticleController::class, 'show'])->name('articles.show');
Route::get('/articles/create', [ArticleController::class, 'create'])->name('articles.create');
Route::post('/articles', [ArticleController::class, 'store'])->name('articles.store');
Route::delete('/articles/{id}', [ArticleController::class, 'destroy'])->name('articles.destroy');

Route::resource() — الطريقة المثلى للـ CRUD

لاحظت تكرار نفس النمط للعمليات الأساسية (CRUD)؟ Laravel عنده حل أنيق: سطر واحد يُنشئ 7 مسارات تلقائياً!

Route::resource('articles', ArticleController::class);

هذا السطر ينشئ هذه المسارات تلقائياً:

الأسلوب الرابط الدالة في Controller اسم المسار الوظيفة
GET/articlesindexarticles.indexقائمة المقالات
GET/articles/createcreatearticles.createنموذج إنشاء
POST/articlesstorearticles.storeحفظ جديد
GET/articles/{article}showarticles.showعرض مقال
GET/articles/{article}/editeditarticles.editنموذج تعديل
PUT/PATCH/articles/{article}updatearticles.updateتحديث
DELETE/articles/{article}destroyarticles.destroyحذف
لإنشاء Controller جاهز لـ CRUD بأمر واحد:
php artisan make:controller ArticleController --resource
سيُنشئ Controller بالدوال السبع (index, create, store, show, edit, update, destroy) جاهزة للتعبئة.

7. Route Model Binding — Laravel يجلب البيانات تلقائياً

هذه خاصية رائعة في Laravel. بدلاً من أن تكتب بنفسك: $article = Article::findOrFail($id); في كل دالة Controller، Laravel يستطيع فعل ذلك تلقائياً إذا سمّيت البارامتر بنفس اسم المتغير.

بدون Route Model Binding

الطريقة التقليدية (المرهقة)
// في routes/web.php
Route::get('/articles/{id}', [ArticleController::class, 'show']);

// في ArticleController.php
public function show(string $id)
{
    $article = Article::findOrFail($id);  // نجلبه يدوياً
    return view('articles.show', compact('article'));
}

مع Route Model Binding (الطريقة الأنيقة)

Route Model Binding — Laravel يجلبه تلقائياً
// في routes/web.php — اسم البارامتر {article} يطابق اسم Model
Route::get('/articles/{article}', [ArticleController::class, 'show']);

// في ArticleController.php
public function show(Article $article)   // ← Laravel يجلبه تلقائياً!
{
    // $article جاهز للاستخدام مباشرةً
    // إذا لم يُوجد ID المطلوب → 404 تلقائياً
    return view('articles.show', compact('article'));
}
كيف يعمل Route Model Binding؟
Laravel يرى أن نوع المتغير في دالة Controller هو Article (Eloquent Model)، فيبحث تلقائياً بـ Article::findOrFail($article) ويُمرّر النتيجة للدالة. إذا لم يجد سجلاً بالـ ID المطلوب، يُعيد تلقائياً خطأ 404 Not Found. لا حاجة لكتابة حتى سطر واحد للتعامل مع حالة عدم الوجود.

8. أوامر Artisan الضرورية لإدارة المسارات

عند العمل على مشروع كبير مع مسارات كثيرة، هذه الأوامر ستوفر عليك وقتاً كبيراً:

لعرض قائمة بكل المسارات المُعرَّفة في مشروعك:

php artisan route:list

سيعرض جدولاً بالأسلوب، الرابط، الاسم، الـ Middleware، والـ Controller لكل مسار.

لعرض مسار محدد بالاسم:

php artisan route:list --name=articles

لتخزين المسارات في الكاش (للإنتاج — يسرّع التطبيق):

php artisan route:cache

لمسح كاش المسارات (ضروري بعد كل تعديل على المسارات في بيئة الإنتاج):

php artisan route:clear
⚠️ تنبيه مهم: إذا استخدمت route:cache في بيئة الإنتاج، أي تعديل على ملفات المسارات لن يُطبَّق حتى تُعيد تشغيل php artisan route:cache. لهذا السبب، لا تستخدم Closures داخل ملفات المسارات إذا كنت ستستخدم الكاش — الكاش يعمل فقط مع المسارات الموجهة لـ Controllers.

أسئلة شائعة عن Routing في Laravel

أسئلة حقيقية يطرحها الطلاب عند التعامل مع نظام التوجيه في Laravel لأول مرة:

استخدم Closure (الدالة المباشرة) فقط في حالتين: الاختبار السريع لفكرة، أو المسارات البسيطة جداً (مثل صفحة "من نحن" الثابتة).

في كل حالة أخرى، وجّه للـ Controller. هذا يجعل ملف المسارات نظيفاً، الكود قابلاً للاختبار، والمشروع سهل الصيانة عند النمو. كقاعدة: إذا كان الكود داخل الـ Closure أكثر من 3 أسطر، انقله لـ Controller.

هذا سؤال دقيق ومهم! Laravel يتحقق من المسارات حسب ترتيب تعريفها. إذا عرّفت /articles/{id} أولاً، فطلب /articles/create سيُطابقه وسيتعامل مع الكلمة "create" كـ ID!

الحل: ضع المسارات الثابتة دائماً قبل المسارات الديناميكية:

Route::get('/articles/create', ...);    // ← ثابت أولاً
Route::get('/articles/{id}', ...);       // ← ديناميكي بعده

Laravel يوفر عدة طرق لإعادة التوجيه:

// إعادة توجيه لرابط مباشر
return redirect('/dashboard');

// إعادة توجيه باسم المسار (الأفضل)
return redirect()->route('dashboard');

// إعادة توجيه مع بارامتر
return redirect()->route('articles.show', ['article' => 42]);

// إعادة التوجيه للصفحة السابقة
return redirect()->back();

استخدم دائماً redirect()->route() مع الاسم للحصول على مرونة التعديل مستقبلاً.

نعم، تماماً. يمكنك إضافة Middleware لمسار واحد بدون مجموعة:

// Middleware على مسار واحد
Route::get('/admin', function () {
    return view('admin.dashboard');
})->middleware('auth');

// Middleware متعدد على مسار واحد
Route::get('/admin', function () {
    // ...
})->middleware(['auth', 'admin']);

لكن إذا كان عندك 5+ مسارات تشترك في نفس الـ Middleware، استخدم المجموعة لإبقاء الكود DRY.

9. ملخص — دليل مرجعي سريع للـ Routing

جدول يجمع كل ما تعلمته في هذا الدرس للرجوع إليه في أي وقت:

تريد فعل ماذا؟ الصيغة
تعريف مسار GET بسيط Route::get('/path', fn() => view('name'));
تمرير بارامتر إلزامي Route::get('/users/{id}', fn($id) => ...);
تمرير بارامتر اختياري Route::get('/search/{q?}', fn($q = null) => ...);
تقييد البارامتر بأرقام فقط Route::get('/{id}', ...)->whereNumber('id');
تسمية المسار Route::get('/path', ...)->name('route.name');
توجيه لـ Controller Route::get('/path', [Ctrl::class, 'method']);
إنشاء 7 مسارات CRUD دفعة واحدة Route::resource('articles', ArticleController::class);
مجموعة بـ prefix مشترك Route::prefix('admin')->group(...);
حماية مجموعة بـ Middleware Route::middleware('auth')->group(...);
عرض قائمة كل المسارات php artisan route:list
الدرس القادم

المتحكمات (Controllers) — أين يعيش الكود الحقيقي 🧠

الآن تعرف كيف تُعرِّف المسارات وتوجّهها. الخطوة التالية هي تعلم كيف تكتب منطق تطبيقك داخل Controllers بطريقة منظمة وقابلة للصيانة.

تعلم المتحكمات
المحرر الذكي

اكتب الكود وشاهد النتيجة فوراً

جرب الآن مجاناً
قناة ديف عربي

تابع أحدث الدروس والتحديثات مباشرة على واتساب

انضم الآن