التحقق من صحة النماذج في Laravel Validation
تُعد عملية التحقق من صحة البيانات (Form Validation) واحدة من أهم الخطوات في أي تطبيق ويب جاد، لأنك ببساطة لا تريد أن تدخل بيانات ناقصة أو غير صحيحة إلى قاعدة البيانات ثم تدفع ثمن ذلك لاحقًا في شكل أخطاء، وتجربة مستخدم سيئة، ومشاكل في التقارير، وأحيانًا ثغرات أمنية لا تُرى بسهولة في البداية. وفي Laravel، هذه العملية ليست فقط سهلة، بل أيضًا ممتعة إلى حد ما، لأن الإطار نفسه يعطيك أدوات واضحة ونظيفة ومرنة لتكتب قواعد التحقق وتعرض رسائل الخطأ وتتعامل مع البيانات بطريقة احترافية. الجميل في Laravel أنه لا يكتفي بأن يقول لك “هذا المدخل غير صحيح”، بل يمنحك نظامًا كاملًا يساعدك على بناء تجربة إدخال بيانات منظمة، سواء كنت تعمل على نموذج تسجيل مستخدم جديد، أو صفحة إضافة منتج، أو نموذج حجز موعد، أو حتى API يستقبل بيانات من تطبيق موبايل.
في هذا المقال سنأخذ موضوع Form Validation in Laravel من البداية إلى مستوى عملي حقيقي، وسنستخدم أمثلة واقعية بدل الشرح النظري الجاف. سنبني نماذج مختلفة، ونطبق عليها قواعد التحقق، ونفهم كيف نعرض الأخطاء للمستخدم بطريقة لطيفة وواضحة، وكيف نخصص رسائل الخطأ، وكيف نتعامل مع المدخلات داخل Controllers وRequests، وكيف نتحقق من الحقول الشرطية، وكيف نستخدم قواعد مثل unique وexists وnullable وconfirmed وemail وregex وغيرها. والأهم من ذلك، سنحاول أن نفهم “لماذا” نفعل هذا، وليس فقط “كيف”.
لماذا التحقق من صحة البيانات مهم جدًا؟
تخيل معي أن لديك نموذج تسجيل يحتوي على الاسم والبريد الإلكتروني وكلمة المرور. إذا سمحت للمستخدم بإرسال أي شيء دون تحقق، فقد تصل إلى قاعدة البيانات بيانات مثل بريد إلكتروني غير صالح، أو كلمة مرور قصيرة جدًا، أو اسم فارغ، أو رقم هاتف مكتوب بحروف، أو حتى نصوص ملوثة قد تسبب مشاكل لاحقًا. هنا لا نتحدث فقط عن “تنظيم”، بل عن حماية للتطبيق كله. التحقق من صحة البيانات هو خط الدفاع الأول قبل أن تدخل المعلومات إلى النظام الداخلي.
هناك نقطة مهمة جدًا يغفل عنها الكثير من المبتدئين: التحقق من صحة البيانات ليس مجرد خطوة “واجهة” لتحسين المظهر، بل هو جزء أساسي من منطق التطبيق. صحيح أن الواجهة قد تمنع المستخدم من الضغط على زر الإرسال إذا كان الحقل فارغًا، لكن هذا لا يكفي أبدًا، لأن أي شخص يستطيع إرسال طلبات مباشرة إلى السيرفر متجاوزًا الواجهة. لذلك يجب أن يكون التحقق موجودًا دائمًا في جهة الخادم Server-side Validation، حتى لو كان لديك تحقق في JavaScript أو في الواجهة الأمامية.
ومن واقع التجربة، أكثر المشاكل التي تظهر في المشاريع ليست من الأكواد الكبيرة المعقدة، بل من التفاصيل الصغيرة التي تُترك بلا تحقق. اسم مستخدم مكرر، رقم هاتف غير مكتمل، بريد إلكتروني كتب مرة بشكل صحيح ومرة بشكل خاطئ، أو خانة نصية من المفترض أن تقبل 100 حرف فقط لكنها استقبلت 10,000 حرف. لهذا فإن التحقق الجيد ليس رفاهية، بل ضرورة.
كيف يفكر Laravel في Form Validation؟
Laravel يجعل عملية التحقق من صحة البيانات بسيطة ومنظمة. لديك أكثر من طريقة لكتابة القواعد، لكن الفكرة الأساسية واحدة: تحدد ما الذي تتوقعه من البيانات، ثم يرفض Laravel أي طلب لا يحقق هذه الشروط. عندها يعيد الأخطاء تلقائيًا إلى المستخدم، ويمكنك عرضها بسهولة داخل القالب Blade أو داخل JSON response إذا كنت تبني API.
أبسط شكل للتحقق يكون داخل الـ Controller:
public function store(Request $request)
{
$validated = $request->validate([
'name' => 'required|string|max:255',
'email' => 'required|email|unique:users,email',
'password' => 'required|min:8|confirmed',
]);
// إذا وصل التنفيذ هنا، فهذا يعني أن البيانات صالحة
}
هذا المثال الصغير يلخص فلسفة Laravel في التحقق: قواعد واضحة، وإرجاع تلقائي للأخطاء، وتقليل الكود المكرر قدر الإمكان. فإذا فشل التحقق، لن يستمر التنفيذ، وسيتم إعادة المستخدم إلى الصفحة السابقة مع رسائل الخطأ.
أول مثال واقعي: نموذج تسجيل مستخدم جديد
لنبدأ بمثال شائع جدًا، لأن أغلب التطبيقات تحتاجه. نفترض أن لدينا صفحة تسجيل تحتوي على الحقول التالية: الاسم، البريد الإلكتروني، كلمة المرور، وتأكيد كلمة المرور.
نموذج Blade
<form action="{{ route('register.store') }}" method="POST">
@csrf
<div>
<label for="name">الاسم</label>
<input type="text" name="name" id="name" value="{{ old('name') }}">
@error('name')
<div class="text-danger">{{ $message }}</div>
@enderror
</div>
<div>
<label for="email">البريد الإلكتروني</label>
<input type="email" name="email" id="email" value="{{ old('email') }}">
@error('email')
<div class="text-danger">{{ $message }}</div>
@enderror
</div>
<div>
<label for="password">كلمة المرور</label>
<input type="password" name="password" id="password">
@error('password')
<div class="text-danger">{{ $message }}</div>
@enderror
</div>
<div>
<label for="password_confirmation">تأكيد كلمة المرور</label>
<input type="password" name="password_confirmation" id="password_confirmation">
</div>
<button type="submit">إنشاء الحساب</button>
</form>
هنا نلاحظ عدة أمور مهمة. أولًا استخدمنا @csrf لحماية النموذج من هجمات CSRF. ثانيًا استخدمنا old('name') حتى لا نفقد قيمة الحقل عند حدوث خطأ. ثالثًا استخدمنا @error('field') لعرض الرسالة الخاصة بكل حقل. هذه التفاصيل الصغيرة تصنع فرقًا كبيرًا في تجربة المستخدم، لأن المستخدم لا يحب أن يعيد كتابة كل شيء من الصفر فقط لأنه أخطأ في خانة واحدة.
Controller
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Hash;
public function store(Request $request)
{
$validated = $request->validate([
'name' => 'required|string|min:3|max:255',
'email' => 'required|email|unique:users,email',
'password' => 'required|string|min:8|confirmed',
]);
User::create([
'name' => $validated['name'],
'email' => $validated['email'],
'password' => Hash::make($validated['password']),
]);
return redirect()->route('login')->with('success', 'تم إنشاء الحساب بنجاح');
}
هذا مثال ممتاز على التحقق العملي. لاحظ أننا لم نستخدم كلمة المرور كما هي، بل قمنا بتشفيرها باستخدام Hash::make. وهذا يذكرنا بأن التحقق لا يكفي وحده، بل يجب أن نكمل معه المعالجة الصحيحة للبيانات.
شرح أهم قواعد التحقق في Laravel
Laravel يحتوي على مجموعة كبيرة من قواعد التحقق، ولكل قاعدة وظيفة واضحة. من المهم أن تفهم استخدام كل واحدة في السياق المناسب بدل الحفظ الأعمى.
1) required
تعني أن الحقل يجب ألا يكون فارغًا.
'name' => 'required'
هذه القاعدة هي الأساس. إذا كان الحقل مهمًا لعملية الحفظ أو المعالجة، فعادةً تبدأ بـ required.
2) string
تعني أن القيمة يجب أن تكون نصًا.
'name' => 'required|string'
مفيدة عندما تتوقع نصوصًا مثل الاسم، العنوان، الوصف، أو الملاحظات.
3) email
للتحقق من أن المدخل بريد إلكتروني صالح.
'email' => 'required|email'
هذه القاعدة تتأكد من الشكل العام للبريد الإلكتروني، وتمنع الأخطاء الواضحة مثل ahmed@ أو test.com.
4) min و max
للتحكم في الحد الأدنى والحد الأقصى للطول أو الحجم.
'password' => 'required|min:8'
'name' => 'required|max:255'
في النصوص، min و max تعنيان عدد الأحرف. وفي الملفات، قد تعنيان الحجم. لذلك يجب قراءة السياق جيدًا.
5) confirmed
تُستخدم عندما تريد أن يتطابق حقل مع حقل تأكيده.
'password' => 'required|confirmed'
وهذا يتطلب وجود حقل باسم password_confirmation.
6) unique
تُستخدم للتأكد من أن القيمة غير موجودة مسبقًا في قاعدة البيانات.
'email' => 'required|email|unique:users,email'
مثالية للبريد الإلكتروني، اسم المستخدم، رقم الهاتف في بعض الحالات، أو أي قيمة يجب ألا تتكرر.
7) exists
تتحقق من أن القيمة موجودة بالفعل في جدول معين.
'category_id' => 'required|exists:categories,id'
مفيدة جدًا في العلاقات بين الجداول، مثل اختيار فئة منتج أو قسم أو مدينة.
8) nullable
تعني أن الحقل يمكن أن يكون فارغًا، وإذا كان فارغًا فلن تُطبَّق عليه القواعد الأخرى إلا إذا كانت هناك قيمة فعلية.
'phone' => 'nullable|string|min:10'
9) numeric و integer
للتحقق من أن القيمة رقمية أو عدد صحيح.
'price' => 'required|numeric|min:0'
'quantity' => 'required|integer|min:1'
10) regex
للتحقق عبر نمط معين.
'phone' => ['required', 'regex:/^[0-9]{10,15}$/']
هذه القاعدة قوية جدًا، لكن يجب استخدامها بحذر، لأن أنماط regex قد تصبح معقدة بسرعة.
مثال واقعي: إضافة منتج في متجر إلكتروني
الآن لنأخذ مثالًا أكثر واقعية من التسجيل: نموذج إضافة منتج. هذا النموذج غالبًا يحتوي على الاسم، الوصف، السعر، الكمية، صورة المنتج، والفئة.
نموذج Blade
<form action="{{ route('products.store') }}" method="POST" enctype="multipart/form-data">
@csrf
<div>
<label>اسم المنتج</label>
<input type="text" name="name" value="{{ old('name') }}">
@error('name')
<div>{{ $message }}</div>
@enderror
</div>
<div>
<label>الوصف</label>
<textarea name="description">{{ old('description') }}</textarea>
@error('description')
<div>{{ $message }}</div>
@enderror
</div>
<div>
<label>السعر</label>
<input type="text" name="price" value="{{ old('price') }}">
@error('price')
<div>{{ $message }}</div>
@enderror
</div>
<div>
<label>الكمية</label>
<input type="number" name="quantity" value="{{ old('quantity') }}">
@error('quantity')
<div>{{ $message }}</div>
@enderror
</div>
<div>
<label>الفئة</label>
<select name="category_id">
<option value="">اختر الفئة</option>
@foreach($categories as $category)
<option value="{{ $category->id }}" {{ old('category_id') == $category->id ? 'selected' : '' }}>
{{ $category->name }}
</option>
@endforeach
</select>
@error('category_id')
<div>{{ $message }}</div>
@enderror
</div>
<div>
<label>صورة المنتج</label>
<input type="file" name="image">
@error('image')
<div>{{ $message }}</div>
@enderror
</div>
<button type="submit">حفظ المنتج</button>
</form>
Controller
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Storage;
public function store(Request $request)
{
$validated = $request->validate([
'name' => 'required|string|min:3|max:255',
'description' => 'required|string|min:20',
'price' => 'required|numeric|min:0',
'quantity' => 'required|integer|min:1',
'category_id' => 'required|exists:categories,id',
'image' => 'nullable|image|mimes:jpg,jpeg,png,webp|max:2048',
]);
if ($request->hasFile('image')) {
$validated['image'] = $request->file('image')->store('products', 'public');
}
Product::create($validated);
return redirect()->route('products.index')->with('success', 'تمت إضافة المنتج بنجاح');
}
هذا المثال يكشف لنا كيف يمكن لقواعد التحقق أن تحمي النظام من أخطاء كثيرة. على سبيل المثال، لو لم نستخدم exists:categories,id فقد يتم إرسال رقم فئة غير موجود في قاعدة البيانات، وبالتالي يصبح المنتج غير مرتبط بشيء صحيح. ولو لم نستخدم image|mimes|max فقد يرفع المستخدم ملفًا غير مناسب أو كبير جدًا يرهق السيرفر.
استخدام FormRequest بدل كتابة التحقق داخل Controller
عندما يكبر المشروع، كتابة التحقق داخل الـ Controller في كل مرة تصبح غير مريحة. وهنا يأتي دور Form Request، وهي طريقة أنظف وأرتب لفصل منطق التحقق عن منطق التحكم في الطلبات.
إنشاء Request جديد
php artisan make:request StoreProductRequest
داخل StoreProductRequest
namespace App\Http\Requests;
use Illuminate\Foundation\Http\FormRequest;
class StoreProductRequest extends FormRequest
{
public function authorize(): bool
{
return true;
}
public function rules(): array
{
return [
'name' => 'required|string|min:3|max:255',
'description' => 'required|string|min:20',
'price' => 'required|numeric|min:0',
'quantity' => 'required|integer|min:1',
'category_id' => 'required|exists:categories,id',
'image' => 'nullable|image|mimes:jpg,jpeg,png,webp|max:2048',
];
}
}
استخدامه في Controller
use App\Http\Requests\StoreProductRequest;
public function store(StoreProductRequest $request)
{
$validated = $request->validated();
if ($request->hasFile('image')) {
$validated['image'] = $request->file('image')->store('products', 'public');
}
Product::create($validated);
return redirect()->route('products.index')->with('success', 'تمت إضافة المنتج بنجاح');
}
هذه الطريقة ممتازة لأنها تجعل الكود أوضح. كل ما يتعلق بالتحقق يذهب إلى Request مستقل، وكل ما يتعلق بالمنطق التجاري يبقى في Controller. ومع الوقت ستشكر نفسك على هذا القرار، خاصة عندما يصبح التطبيق كبيرًا ويحتوي على عشرات النماذج.
رسائل الخطأ المخصصة
الرسائل الافتراضية في Laravel جيدة، لكنها أحيانًا تكون عامة جدًا. في المشاريع الحقيقية، غالبًا نحتاج إلى رسائل أوضح وألطف، وربما بلغة المستخدم المحلية. Laravel يسمح لك بتخصيص الرسائل بكل سهولة.
مثال
public function store(Request $request)
{
$messages = [
'name.required' => 'من فضلك أدخل اسم المنتج.',
'name.min' => 'اسم المنتج يجب ألا يقل عن 3 أحرف.',
'description.required' => 'الوصف مطلوب.',
'price.required' => 'يرجى تحديد السعر.',
'price.numeric' => 'السعر يجب أن يكون رقمًا.',
'category_id.exists' => 'الفئة المختارة غير موجودة.',
'image.image' => 'الملف المرفوع يجب أن يكون صورة.',
];
$validated = $request->validate([
'name' => 'required|string|min:3|max:255',
'description' => 'required|string|min:20',
'price' => 'required|numeric|min:0',
'quantity' => 'required|integer|min:1',
'category_id' => 'required|exists:categories,id',
'image' => 'nullable|image|mimes:jpg,jpeg,png,webp|max:2048',
], $messages);
}
الرسائل الجيدة ليست مجرد “تجميل”؛ بل جزء مهم من تجربة الاستخدام. المستخدم لا يريد أن يقرأ رسالة تقنية باردة مثل “The name field is required.” في كل مرة، خصوصًا إذا كان التطبيق عربيًا. كلما كانت الرسالة قريبة من لغة المستخدم، زادت فرصه في فهم الخطأ وإصلاحه بسرعة.
تسمية الحقول بطريقة مفهومة
أحيانًا تكون المشكلة ليست في القاعدة نفسها، بل في اسم الحقل الذي يظهر داخل الرسالة. Laravel يسمح لك بتخصيص أسماء الحقول بشكل ألطف عبر attributes.
public function attributes(): array
{
return [
'name' => 'اسم المنتج',
'description' => 'الوصف',
'price' => 'السعر',
'quantity' => 'الكمية',
'category_id' => 'الفئة',
'image' => 'صورة المنتج',
];
}
عندها يمكن أن تظهر الرسائل بشكل أفضل وأكثر إنسانية. وهذه لمسة صغيرة لكنها مهمة جدًا، خاصة في التطبيقات العربية.
التحقق الشرطي: أحيانًا نحتاج الحقل وأحيانًا لا
ليس كل الحقول تكون مطلوبة دائمًا. أحيانًا تحتاج أن تجعل الحقل إلزاميًا فقط إذا تحقق شرط معين. وهنا تأتي قواعد مثل required_if وrequired_unless وsometimes.
required_if
'discount' => 'required_if:has_discount,1|numeric|min:0'
هذا يعني أن حقل الخصم مطلوب فقط إذا كانت قيمة has_discount تساوي 1.
sometimes
$request->validate([
'coupon_code' => 'sometimes|string|max:50',
]);
sometimes تعني أن القاعدة تُطبَّق فقط إذا أرسل المستخدم الحقل. وهذه مفيدة جدًا في النماذج التي تحتوي على تحديثات جزئية.
nullable
'phone' => 'nullable|string|min:10|max:20'
هنا إذا كان الهاتف غير موجود فلا مشكلة، لكن إذا وُجد فيجب أن يكون نصًا ضمن الطول المسموح.
التحقق من البيانات في التحديث Update
التحقق في التحديث يختلف قليلًا عن التحقق في الإنشاء، خاصة عند استخدام unique. لأنك لا تريد أن يعتبر Laravel القيمة الحالية الخاصة بالسجل نفسه “مكررة”.
مثال
public function update(Request $request, User $user)
{
$validated = $request->validate([
'name' => 'required|string|min:3|max:255',
'email' => 'required|email|unique:users,email,' . $user->id,
'password' => 'nullable|string|min:8|confirmed',
]);
if (!empty($validated['password'])) {
$validated['password'] = Hash::make($validated['password']);
} else {
unset($validated['password']);
}
$user->update($validated);
return back()->with('success', 'تم تحديث البيانات بنجاح');
}
هذا المثال مهم جدًا، لأن كثيرًا من المبتدئين ينسون استثناء السجل الحالي من قاعدة unique، فيظن Laravel أن البريد الإلكتروني المستخدم موجود بالفعل، رغم أنه يعود لنفس المستخدم. لهذا السبب يجب التعامل مع التحديث بعناية.
التحقق باستخدام Rule Class
في بعض الحالات، كتابة القواعد كسلاسل نصية تصبح غير كافية أو غير واضحة، خصوصًا مع التحقق المتقدم. هنا يمكن استخدام Rule من Laravel.
use Illuminate\Validation\Rule;
$request->validate([
'email' => [
'required',
'email',
Rule::unique('users')->ignore($user->id),
],
]);
هذه الطريقة أوضح وأكثر أمانًا ومرونة، خاصة عندما تتعامل مع قيم ديناميكية. كما أنها أسهل للقراءة من سلسلة طويلة جدًا فيها commas وعناصر متداخلة.
التحقق من المصفوفات والحقول المتعددة
في كثير من النماذج، لا تتعامل مع حقل واحد بل مع مجموعة حقول متكررة، مثل قائمة منتجات داخل طلب، أو أرقام هواتف متعددة، أو صور متعددة.
مثال على مصفوفة
$request->validate([
'tags' => 'required|array',
'tags.*' => 'required|string|min:2|max:50',
]);
هنا tags يجب أن تكون مصفوفة، وكل عنصر داخلها يجب أن يكون نصًا صالحًا. استخدام * يعني “كل عنصر داخل المصفوفة”.
مثال عملي: صور متعددة
$request->validate([
'photos' => 'required|array',
'photos.*' => 'image|mimes:jpg,jpeg,png,webp|max:2048',
]);
هذا رائع جدًا في تطبيقات المعارض أو المنتجات أو ملفات المستخدم. بدلاً من التحقق من صورة واحدة، تتحقق من كل الصور دفعة واحدة.
عرض الأخطاء في Blade بطريقة منظمة
عرض الأخطاء ليس فقط بوضع @error تحت كل حقل، بل يمكن أيضًا عرض جميع الأخطاء في أعلى الصفحة.
@if ($errors->any())
<div class="alert alert-danger">
<ul>
@foreach ($errors->all() as $error)
<li>{{ $error }}</li>
@endforeach
</ul>
</div>
@endif
هذا مفيد عندما تريد أن يرى المستخدم كل المشاكل دفعة واحدة. لكن في النماذج الطويلة، من الأفضل الجمع بين الطريقتين: رسالة عامة في الأعلى، ورسالة محددة قرب كل حقل. بهذه الطريقة يحصل المستخدم على نظرة شاملة، ثم يجد التوجيه السريع في المكان الصحيح.
التحقق في APIs
لو كنت تبني API، فشكل الاستجابة مختلف قليلًا. Laravel يعيد عادة JSON يحتوي على الأخطاء عند فشل التحقق، وهذا ممتاز لتطبيقات الموبايل وواجهات SPA.
مثال
public function store(Request $request)
{
$validated = $request->validate([
'title' => 'required|string|max:255',
'content' => 'required|string|min:50',
]);
Post::create($validated);
return response()->json([
'message' => 'تم إنشاء المقال بنجاح',
'data' => $validated,
], 201);
}
إذا كانت البيانات غير صحيحة، فلن يصل التنفيذ إلى Post::create. وسيتم إرجاع أخطاء التحقق بشكل افتراضي. هذا السلوك مفيد جدًا لأنك لا تحتاج إلى كتابة الكثير من منطق الخطأ يدويًا في كل مرة.
استخدام after للتحقق المتقدم
أحيانًا القواعد التقليدية لا تكفي، وتحتاج إلى منطق إضافي بعد تطبيق القواعد الأساسية. هنا يمكن استخدام after.
$validator = Validator::make($request->all(), [
'start_date' => 'required|date',
'end_date' => 'required|date',
]);
$validator->after(function ($validator) use ($request) {
if ($request->start_date > $request->end_date) {
$validator->errors()->add('end_date', 'تاريخ النهاية يجب أن يكون بعد تاريخ البداية.');
}
});
if ($validator->fails()) {
return back()->withErrors($validator)->withInput();
}
هذا النوع من التحقق مفيد عندما تعتمد البيانات على بعضها البعض. فليس كل شيء يمكن أن يُعبَّر عنه بقاعدة جاهزة. أحيانًا تحتاج إلى حكم منطقي خاص بالتطبيق نفسه.
مثال واقعي: نموذج حجز موعد
لنفرض أن لديك نظام حجز عيادة أو خدمة. النموذج يحتوي على الاسم، الهاتف، التاريخ، الوقت، ونوع الخدمة.
Controller
public function book(Request $request)
{
$validated = $request->validate([
'full_name' => 'required|string|min:3|max:255',
'phone' => ['required', 'regex:/^[0-9]{10,15}$/'],
'appointment_date' => 'required|date|after_or_equal:today',
'appointment_time' => 'required',
'service_id' => 'required|exists:services,id',
], [
'full_name.required' => 'الاسم الكامل مطلوب.',
'phone.regex' => 'رقم الهاتف يجب أن يحتوي على أرقام فقط وبطول مناسب.',
'appointment_date.after_or_equal' => 'لا يمكن اختيار تاريخ سابق لليوم.',
'service_id.exists' => 'الخدمة المختارة غير موجودة.',
]);
Appointment::create($validated);
return back()->with('success', 'تم حجز الموعد بنجاح');
}
هذا المثال يوضح أن التحقق الجيد لا يكتفي بالتأكد من وجود البيانات، بل يضمن أيضًا أن البيانات منطقية. تاريخ الموعد لا يجب أن يكون في الماضي. هذه قاعدة بديهية، لكنها مهمة جدًا في الواقع.
التعامل مع البيانات القديمة old input
من أجمل الأشياء في Laravel أنه عند فشل التحقق، يعيد المستخدم إلى الصفحة السابقة ويحتفظ بالقيم المدخلة، بحيث لا يضطر إلى إعادة كتابة كل شيء.
<input type="text" name="name" value="{{ old('name') }}">
هذا السطر الصغير يجعل تجربة المستخدم أفضل بكثير. تخيل أن المستخدم كتب نموذجًا طويلًا، ثم أخطأ فقط في خانة واحدة، وفقد كل شيء. هذا كفيل بإحباطه. لذلك old() ليست مجرد ميزة جانبية، بل عنصر أساسي في تجربة الاستخدام.
لماذا لا يجب أن نثق بالواجهة الأمامية وحدها؟
قد تقول: “أنا أتحقق من البيانات في JavaScript، فلماذا أحتاج Laravel validation أيضًا؟” والجواب بسيط: لأن أي تحقق في الواجهة الأمامية يمكن تجاوزه. المستخدم قد يعطل JavaScript، أو يستخدم Postman، أو يرسل الطلب يدويًا، أو يعبث بالـ network request. لهذا السبب التحقق الحقيقي يجب أن يكون في السيرفر، بينما التحقق في الواجهة الأمامية يكون فقط لتحسين السرعة وتجربة الاستخدام.
فكر في التحقق الأمامي كأنه موظف استقبال لطيف، وفكر في التحقق الخلفي كأنه الحارس الحقيقي على الباب. كلاهما مهم، لكن الحارس هو الذي يقرر فعليًا من يدخل.
نصائح عملية لبناء Validation احترافي
عندما تبدأ بتصميم التحقق في أي مشروع، حاول أن تنظر إليه كجزء من تجربة كاملة، لا كقائمة قواعد فقط. اجعل الرسائل واضحة وبسيطة، ولا تستخدم مصطلحات تقنية معقدة للمستخدم العادي. لا تضع قواعد صارمة بشكل مبالغ فيه إذا لم يكن هناك سبب حقيقي، لأن الإفراط في التشدد قد يزعج المستخدم. في الوقت نفسه، لا تكن متساهلًا لدرجة تسمح ببيانات غير مفيدة أو غير آمنة.
إذا كان لديك نموذج طويل جدًا، فكر في تقسيمه إلى خطوات، لأن التحقق على مراحل أحيانًا يكون أسهل من نموذج ضخم في صفحة واحدة. وإذا كان لديك حقل مثل “الوصف”، فتذكر أن عدد الأحرف وحده لا يكفي؛ أحيانًا تحتاج أيضًا إلى التحقق من المحتوى المنطقي أو الكلمات الممنوعة أو التنسيق الخاص. وإذا كان لديك ملفات مرفوعة، فلا تنسَ التحقق من النوع والحجم والعدد.
ومن الأمور المفيدة جدًا أيضًا أن تعيد استخدام قواعد التحقق نفسها في أماكن متعددة بدل تكرارها داخل كل Controller. هنا تأتي أهمية FormRequest أو حتى إنشاء قواعد مخصصة عندما يكبر المشروع. التكرار الزائد في التحقق قد يؤدي إلى أخطاء صعبة التتبع لاحقًا.
إنشاء Custom Validation Rule
في بعض الأحيان تحتاج إلى قاعدة خاصة بك. مثلًا، تريد التأكد من أن اسم المستخدم لا يحتوي على كلمة معينة، أو أن الرقم يبدأ بصيغة محددة، أو أن الرمز المرسل يطابق منطقًا داخليًا خاصًا.
إنشاء Rule
php artisan make:rule StrongPassword
داخل rule
namespace App\Rules;
use Closure;
use Illuminate\Contracts\Validation\ValidationRule;
class StrongPassword implements ValidationRule
{
public function validate(string $attribute, mixed $value, Closure $fail): void
{
if (strlen($value) < 10) {
$fail('كلمة المرور يجب ألا تقل عن 10 أحرف.');
}
if (!preg_match('/[A-Z]/', $value)) {
$fail('كلمة المرور يجب أن تحتوي على حرف كبير واحد على الأقل.');
}
if (!preg_match('/[0-9]/', $value)) {
$fail('كلمة المرور يجب أن تحتوي على رقم واحد على الأقل.');
}
}
}
استخدامه
use App\Rules\StrongPassword;
$request->validate([
'password' => ['required', 'confirmed', new StrongPassword],
]);
هذه الطريقة ممتازة عندما يصبح منطق التحقق أكثر تعقيدًا. بدل أن تضع كل شيء داخل Controller، تنقل القاعدة إلى كائن مستقل وقابل لإعادة الاستخدام.
التحقق من الملف المرفوع بشكل صحيح
رفع الملفات واحد من أكثر الأماكن التي يقع فيها الأخطاء، لأن المستخدم قد يرفع صورة كبيرة جدًا، أو ملفًا غير صالح، أو امتدادًا لا تريده، أو حتى ملفًا يبدو صورة من الخارج لكنه في الحقيقة ليس كذلك. Laravel يساعدك هنا بقواعد قوية.
'avatar' => 'nullable|image|mimes:jpg,jpeg,png,webp|max:1024'
هذا يعني أن الصورة اختيارية، لكن إذا تم رفعها فيجب أن تكون صورة فعلًا وبامتدادات معينة، وألا يتجاوز حجمها 1MB تقريبًا. يمكنك تعديل الرقم بحسب حاجتك.
ومن الجيد أن ترفق التحقق من الملفات مع عملية تخزين منظمة:
if ($request->hasFile('avatar')) {
$path = $request->file('avatar')->store('avatars', 'public');
}
التعامل مع الحقول المعقدة في الواقع
أحيانًا النموذج لا يكون بسيطًا. ربما لديك حقول تعتمد على نوع المستخدم. إذا كان المستخدم “بائعًا” فهناك حقول إضافية مثل اسم المتجر والسجل التجاري، وإذا كان “عميلًا” فهذه الحقول غير مطلوبة. في هذه الحالة لا يجب أن تفرض نفس القواعد على الجميع.
$request->validate([
'user_type' => 'required|in:customer,seller',
'store_name' => 'required_if:user_type,seller|string|max:255',
'commercial_register' => 'required_if:user_type,seller|string|max:100',
]);
هذا مثال واضح على كيف يجعل Laravel المنطق الشرطي بسيطًا ومفهومًا.
التحقق ليس فقط رفضًا، بل توجيه
الكثير يظن أن validation وظيفته فقط رفض المدخلات الخاطئة، لكن الحقيقة أنه أيضًا يوجه المستخدم. عندما تقول له “السعر يجب أن يكون رقمًا”، فأنت لا ترفض فقط، بل ترشده إلى ما هو صحيح. عندما تقول له “اختر فئة موجودة”، فأنت توجهه إلى ارتباط صحيح بالبيانات. عندما تقول له “تاريخ الموعد لا يمكن أن يكون في الماضي”، فأنت تعلمه قاعدة النظام بطريقة لطيفة.
لهذا السبب من المهم أن تكون الرسائل إنسانية وليست آلية. المثال الجيد يقول: “من فضلك أدخل البريد الإلكتروني بشكل صحيح”، وليس فقط: “The email field must be a valid email address.” الفرق هنا ليس لغويًا فقط، بل شعوري أيضًا.
مثال متكامل: مقال مدونة
لنختم بمثال واقعي آخر، وهو نموذج إضافة مقال إلى مدونة.
Request
public function rules(): array
{
return [
'title' => 'required|string|min:10|max:255',
'slug' => 'required|string|max:255|unique:posts,slug',
'excerpt' => 'nullable|string|max:500',
'content' => 'required|string|min:200',
'status' => 'required|in:draft,published',
'published_at' => 'nullable|date',
'category_id' => 'required|exists:categories,id',
'tags' => 'nullable|array',
'tags.*' => 'string|max:50',
'featured_image' => 'nullable|image|mimes:jpg,jpeg,png,webp|max:2048',
];
}
Controller
public function store(StorePostRequest $request)
{
$validated = $request->validated();
if ($request->hasFile('featured_image')) {
$validated['featured_image'] = $request->file('featured_image')->store('posts', 'public');
}
$post = Post::create([
'title' => $validated['title'],
'slug' => $validated['slug'],
'excerpt' => $validated['excerpt'] ?? null,
'content' => $validated['content'],
'status' => $validated['status'],
'published_at' => $validated['published_at'] ?? null,
'category_id' => $validated['category_id'],
'featured_image' => $validated['featured_image'] ?? null,
]);
if (!empty($validated['tags'])) {
$post->tags()->sync($validated['tags']);
}
return redirect()->route('posts.index')->with('success', 'تم نشر المقال بنجاح');
}
هذا المثال يوضح كيف يصبح التحقق جزءًا طبيعيًا من بناء الميزات، وليس خطوة منفصلة ومزعجة. وعندما تنظر إلى الكود كله، ستلاحظ أن كل شيء منطقي: تحقق من العنوان، التحقق من الرابط slug، التحقق من المحتوى، التحقق من الحالة، ثم الحفظ. هكذا تُبنى التطبيقات الجيدة: خطوة وراء خطوة، وكل خطوة لها معنى.
خلاصة عملية
Form Validation في Laravel ليس موضوعًا نظريًا صغيرًا، بل هو من أساسيات بناء التطبيقات النظيفة والآمنة والسهلة الاستخدام. كلما أتقنت التحقق، كلما أصبحت مشاريعك أكثر ثباتًا واحترافية. تعلم القواعد الأساسية أولًا، ثم انتقل إلى FormRequest، ثم إلى Rule المخصصة، ثم إلى التحقق الشرطي والحقول المصفوفية، وأخيرًا إلى بناء تجربة مستخدم راقية حول رسائل الأخطاء والحفاظ على البيانات المدخلة.
أجمل ما في Laravel أن هذه الأمور لا تأتي على شكل تعقيد مزعج، بل على شكل أدوات منظمة تساعدك على كتابة كود واضح وسهل القراءة. ومع الوقت، ستلاحظ أن التحقق الجيد لا يحمي التطبيق فقط، بل يرفع جودة كل جزء فيه. المستخدم يشعر بذلك، وأنت كمطور تشعر به أيضًا عندما يصبح الكود أسهل في الصيانة والتوسعة.