تخطَّ إلى المحتوى

النماذج والتحقق من البيانات في Laravel

الدرس 6 من 11 · Laravel

في هذا الدرس سنكمل المدونة التي نبنيها: صفحة يكتب فيها المستخدم مقالاً جديداً، فيتحقق Laravel من البيانات، ويعرض الأخطاء بوضوح، ثم يحفظ المقال ويعرض رسالة نجاح. تذكّر كم سطراً كتبت لنموذج التواصل في كورس PHP، وستلاحظ كم يختصر Laravel من العمل.

نحتاج مسارين: واحد يعرض النموذج، وآخر يستقبل بياناته:

routes/web.php
Route::get('/posts/create', [PostController::class, 'create'])->name('posts.create');
Route::post('/posts', [PostController::class, 'store'])->name('posts.store');

لاحظ أن المسار /posts/create مكتوب قبل المسار /posts/{id} في الملف، حتى لا تُفهم كلمة create على أنها رقم مقال.

app/Http/Controllers/PostController.php
public function create()
{
return view('posts.create');
}
resources/views/posts/create.blade.php
@extends('layouts.app')
@section('title', 'مقال جديد')
@section('content')
<h1>مقال جديد</h1>
<form action="{{ route('posts.store') }}" method="POST">
@csrf
<label for="title">العنوان</label>
<input type="text" id="title" name="title" value="{{ old('title') }}">
@error('title')
<p class="error">{{ $message }}</p>
@enderror
<label for="body">المحتوى</label>
<textarea id="body" name="body">{{ old('body') }}</textarea>
@error('body')
<p class="error">{{ $message }}</p>
@enderror
<button type="submit">نشر</button>
</form>
@endsection

أهم سطر هنا هو @csrf. يتحول في الصفحة إلى حقل مخفي فيه رمز سري فريد:

الناتج
<input type="hidden" name="_token" value="..." autocomplete="off">

لماذا؟ هجوم اسمه CSRF (Cross-Site Request Forgery): موقع خبيث يخدع متصفحك ليرسل نموذجاً لموقعك وأنت مسجل الدخول، فينشر أو يحذف باسمك دون علمك. الرمز السري يثبت أن النموذج جاء من صفحتك أنت. وأي طلب POST بدون هذا الرمز يرفضه Laravel بخطأ 419 Page Expired.

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

التحقق من البيانات: validate

Section titled “التحقق من البيانات: validate”
app/Http/Controllers/PostController.php
public function store(Request $request)
{
$validated = $request->validate([
'title' => 'required|min:5|max:100',
'body' => 'required|min:20',
]);
Post::create($validated);
return redirect()->route('posts.index')
->with('success', 'تم نشر المقال بنجاح');
}

الدالة validate تستقبل قواعد لكل حقل، مفصولة بالعلامة |:

القاعدة معناها
required الحقل إجباري
min:5 و max:100 أقل وأكثر عدد حروف للنص (أو قيمة للرقم)
email بريد إلكتروني صحيح
numeric و integer رقم، ورقم صحيح
unique:users,email القيمة غير مكررة في عمود بقاعدة البيانات
confirmed يطابق حقلاً آخر اسمه ..._confirmation، مثل تأكيد كلمة المرور

والسحر هنا: إذا فشل التحقق، لا يُنفَّذ أي سطر بعد validate. يعيد Laravel المستخدم تلقائياً إلى صفحة النموذج، ومعه الأخطاء والقيم التي كتبها. وإذا نجح، تُرجع validate مصفوفة بالحقول المتحقق منها فقط، فنمررها مباشرة إلى Post::create.

عرض الأخطاء والاحتفاظ بالقيم

Section titled “عرض الأخطاء والاحتفاظ بالقيم”

عند إرسال عنوان «قصير» ومحتوى فارغ، يعود المستخدم للنموذج ويرى:

الناتج
<input type="text" id="title" name="title" value="قصير">
<p class="error">The title field must be at least 5 characters.</p>
<textarea id="body" name="body"></textarea>
<p class="error">The body field is required.</p>
  • @error('title') … @enderror: يظهر محتواه فقط إذا كان للحقل خطأ، والمتغير $message فيه نص الخطأ.
  • old('title'): يعيد ما كتبه المستخدم، فلا يضطر لكتابته من جديد.

الرسائل الافتراضية بالإنجليزية. أسرع حل: مرّر رسائلك كمعامل ثانٍ لـ validate، بصيغة اسم_الحقل.القاعدة:

$request->validate([
'title' => 'required|min:5|max:100',
'body' => 'required|min:20',
], [
'title.required' => 'اكتب عنواناً للمقال',
'body.min' => 'المحتوى يجب ألا يقل عن :min حرفاً',
]);

الرمز :min يُستبدل تلقائياً بالقيمة من القاعدة. عند إرسال عنوان فارغ ومحتوى «قصير»:

الناتج
<p class="error">اكتب عنواناً للمقال</p>
<p class="error">المحتوى يجب ألا يقل عن 20 حرفاً</p>

بعد الحفظ، لا نعرض صفحة مباشرة، بل نحوّل المستخدم لصفحة المقالات. لماذا؟ لو عرضنا الصفحة مباشرة ثم ضغط المستخدم «تحديث»، سيرسل المتصفح النموذج مرة ثانية وينشر المقال مكرراً! هذا النمط اسمه Post/Redirect/Get.

والدالة with('success', '...') تحفظ رسالة مؤقتة (Flash) تظهر في الطلب التالي فقط ثم تختفي. نعرضها في صفحة المقالات:

resources/views/posts/index.blade.php
@if (session('success'))
<p class="alert">{{ session('success') }}</p>
@endif

بعد نشر مقال صحيح، يرى المستخدم:

الناتج
<p class="alert">تم نشر المقال بنجاح</p>
<h1>كل المقالات</h1>
<article>
<h2><a href="http://localhost:8000/posts/1">تعلّمت Laravel اليوم</a></h2>
</article>
  1. أضف المسارين posts.create و posts.store، والدالتين create و store في المتحكم.
  2. أنشئ صفحة النموذج بـ @csrf و @error و old.
  3. جرّب الإرسال فارغاً، ثم بعنوان قصير، ثم ببيانات صحيحة، وتأكد من ظهور رسالة النجاح.
  4. احذف @csrf مؤقتاً وجرّب الإرسال، ولاحظ خطأ 419، ثم أعده.
  5. تحدٍّ: أضف رسائل عربية لكل القواعد، وأضف صفحة تعديل بدالتي edit و update.
  • خطأ 419 Page Expired: نسيت @csrf داخل النموذج.
  • ضياع ما كتبه المستخدم بعد الخطأ: نسيت value="{{ old('title') }}".
  • التحقق في المتصفح فقط: required في HTML يمكن تجاوزها. validate على الخادم إجبارية دائماً.

سؤال

ماذا يحدث إذا فشل $request->validate() في المتحكم؟

سؤال

ما وظيفة @csrf داخل النموذج؟

  • مسار GET يعرض النموذج، ومسار POST يستقبله، والنموذج يحتاج @csrf دائماً.
  • $request->validate([...]) تتحقق بقواعد مثل required|min:5، وعند الفشل تعيد المستخدم تلقائياً.
  • @error و $message لعرض الأخطاء، و old() للاحتفاظ بالقيم.
  • الرسائل العربية بمعامل ثانٍ لـ validate أو بملفات اللغة في lang/ar.
  • بعد الحفظ حوّل بـ redirect()->route(...)->with(...)، واعرض الرسالة بـ session().