التحقق من النماذج في Laravel
التحقق هو "نقطة التفتيش الأمنية" في Laravel — جميع البيانات الداخلة يجب أن تجتاز التحقق، ولا يمكن لأي بيانات غير صالحة أن تنفذ.
1. ما ستتعلمه
- قواعد التحقق: required/email/unique/exists/file/image/custom
- صنف التحقق من Form Request: make:request
- قواعد التحقق المخصصة: قواعد الإغلاق وكائنات القواعد
- معالجة رسائل الخطأ: مشاركة $errors في العرض واستجابات API JSON
- التحقق الشرطي وقواعد "Bail/Sometimes"
2. قصة حقيقية لمدقق أمني
(1) المشكلة: إدخال المستخدم يسبب تناقضات في البيانات
عندما سجلت Alice في ShopMetrics، أدخلت "abc" كبريد إلكتروني و"123" كرقم هاتف وتركت حقل اسم المتجر فارغًا — النظام قبل كل شيء، مما أدى إلى قاعدة بيانات مليئة بالبيانات غير الصالحة. قدم مستخدم خبيث منتجًا بسعر سلبي عبر API لمتجر Bob، مما أدى إلى مبلغ طلب بقيمة -999 دولار وتعطل التقارير المالية. أثناء تدقيق أمني، اكتشف Charlie أن 17 نقطة نهاية ليس لديها أي تحقق من الإدخال.
(2) حلول التحقق في Laravel
يعترض تحقق Laravel البيانات قبل دخولها النظام — مصفوفة واحدة من القواعد تغطي كل منطق التحقق، وصنف FormRequest يبقي المتحكمات نظيفة.
// قواعد التحقق — 30 ثانية للكتابة، تحمي للأبد
$validated = $request->validate([
'name' => 'required|string|max:255',
'email' => 'required|email|unique:users',
'price' => 'required|numeric|min:0',
]);
(3) العائد
بعد أن فعّلت Alice التحقق، انخفضت التسجيلات غير الصالحة إلى صفر؛ أسعار منتجات Bob لن تكون سالبة أبدًا بعد الآن؛ واجتاز Charlie جميع نقاط النهاية الـ 17 في التدقيق الأمني.
3. مرجع سريع لقواعد التحقق
(1) القواعد الشائعة
| القاعدة | الوصف | مثال |
|---|---|---|
required |
مطلوب | 'name' => 'required' |
email |
تنسيق بريد إلكتروني | 'email' => 'required|email' |
unique:table,column |
فريد | 'slug' => 'unique:shops,slug' |
exists:table,column |
موجود | 'shop_id' => 'exists:shops,id' |
min:n |
حد أدنى للقيمة/الطول | 'price' => 'numeric|min:0' |
max:n |
حد أقصى للقيمة/الطول | 'name' => 'string|max:255' |
numeric |
رقم | 'quantity' => 'required|numeric' |
integer |
عدد صحيح | 'page' => 'integer|min:1' |
string |
سلسلة نصية | 'name' => 'required|string' |
boolean |
قيمة منطقية | 'is_active' => 'boolean' |
date |
تاريخ | 'published_at' => 'date' |
file |
ملف | 'logo' => 'file|max:2048' |
image |
ملف صورة | 'photo' => 'image|mimes:jpeg,png' |
confirmed |
تأكيد ثانوي | 'password' => 'confirmed' |
regex:pattern |
تعابير نمطية | 'phone' => 'regex:/^[0-9]{10}$/' |
in:a,b,c |
قيمة تعداد | 'status' => 'in:active,suspended' |
(2) عملية خط أنابيب التحقق
flowchart TD
A[إدخال الطلب] --> B[قواعد التحقق]
B -->|نجاح| C[بيانات مُنظفة]
C --> D[منطق المتحكم]
B -->|فشل| E[إعادة توجيه مع الأخطاء]
E --> F["عرض \$errors في العرض"]
(1) ▶ مثال: التحقق من إنشاء منتج ShopMetrics
// تحقق مباشر في المتحكم
public function store(Request $request): RedirectResponse
{
$validated = $request->validate([
'name' => 'required|string|max:255',
'sku' => 'required|string|unique:products,sku',
'price' => 'required|numeric|min:0.01|max:999999.99',
'stock' => 'required|integer|min:0',
'category_id' => 'required|exists:categories,id',
'description' => 'nullable|string|max:5000',
'is_active' => 'boolean',
]);
$product = Product::create($validated);
return redirect()->route('products.show', $product)
->with('success', 'تم إنشاء المنتج.');
}
الناتج:
// تم التنفيذ بنجاح
4. صنف التحقق من Form Request
(1) إنشاء Form Request
php artisan make:request StoreShopRequest
php artisan make:request UpdateShopRequest
(2) تعريف القواعد والتفويض
// app/Http/Requests/StoreShopRequest.php
class StoreShopRequest extends FormRequest
{
public function authorize(): bool
{
return auth()->check() && auth()->user()->can('create', Shop::class);
}
public function rules(): array
{
return [
'name' => 'required|string|max:255',
'slug' => 'required|string|unique:shops,slug',
'description' => 'nullable|string|max:5000',
'domain' => 'nullable|url',
'status' => 'in:active,suspended',
];
}
public function messages(): array
{
return [
'name.required' => 'اسم المتجر مطلوب.',
'slug.unique' => 'رابط URL هذا مستخدم بالفعل.',
'domain.url' => 'يرجى إدخال عنوان URL صالح.',
];
}
}
(3) استخدامه في المتحكم
public function store(StoreShopRequest $request): RedirectResponse
{
// $request->validated() يحتوي فقط على البيانات المُتحقق منها
$shop = Shop::create($request->validated());
return redirect()->route('shops.show', $shop);
}
| البُعد | تحقق مباشر | Form Request |
|---|---|---|
| الموقع | داخل دالة المتحكم | ملف صنف منفصل |
| قابلية إعادة الاستخدام | منخفضة | ✅ قابلية إعادة الاستخدام عبر متحكمات متعددة |
| فحص التفويض | يدوي | ✅ authorize() |
| كود المتحكم | أطول | مبسّط |
| مناسب لـ | تحقق بسيط | تحقق معقد/قابل لإعادة الاستخدام |
(1) ▶ مثال: StoreShopRequest لـ ShopMetrics
// app/Http/Requests/StoreShopRequest.php
class StoreShopRequest extends FormRequest
{
public function authorize(): bool
{
return auth()->user()->role === 'tenant_owner';
}
public function rules(): array
{
return [
'name' => 'required|string|max:255',
'slug' => 'required|alpha_dash|unique:shops,slug,NULL,id,tenant_id,' . tenant()->id,
'description' => 'nullable|string|max:5000',
'status' => 'sometimes|in:active,suspended',
];
}
public function messages(): array
{
return [
'slug.unique' => 'لديك بالفعل متجر بهذا الرابط.',
'slug.alpha_dash' => 'الرابط يمكن أن يحتوي فقط على أحرف وأرقام وشرطات.',
];
}
protected function prepareForValidation(): void
{
$this->merge([
'tenant_id' => tenant()->id,
'slug' => Str::slug($this->slug ?? $this->name),
]);
}
}
الناتج:
// تم التنفيذ بنجاح
5. قواعد التحقق المخصصة
(1) قواعد الإغلاق
// قاعدة مخصصة مباشرة
$validated = $request->validate([
'discount' => [
'required',
'numeric',
'min:0',
function (string $attribute, mixed $value, Closure $fail) {
if ($value > request('subtotal')) {
$fail('لا يمكن أن يتجاوز الخصم المبلغ الجزئي.');
}
},
],
]);
(2) كائن القاعدة
php artisan make:rule ValidCouponCode
// app/Rules/ValidCouponCode.php
class ValidCouponCode implements ValidationRule
{
public function validate(string $attribute, mixed $value, Closure $fail): void
{
$coupon = Coupon::where('code', $value)
->where('expires_at', '>', now())
->where('usage_limit', '>', DB::raw('usage_count'))
->first();
if (!$coupon) {
$fail('رمز الكوبون هذا غير صالح أو منتهي الصلاحية.');
}
}
}
// الاستخدام
public function rules(): array
{
return [
'coupon_code' => ['nullable', 'string', new ValidCouponCode()],
];
}
| الطريقة | السيناريوهات المناسبة | قابلية إعادة الاستخدام |
|---|---|---|
| إغلاق | منطق بسيط لمرة واحدة | منخفضة |
| كائن قاعدة | منطق معقد/قابل لإعادة الاستخدام | ✅ |
طريقة Rule::class |
تحقق مرتبط بقاعدة البيانات | ✅ |
(3) طرق صنف Rule
use Illuminate\Validation\Rule;
// فريد مع تجاهل النموذج الحالي
'slug' => Rule::unique('shops', 'slug')->ignore($shop->id),
// in مع قيم ديناميكية
'status' => Rule::in(['active', 'suspended', 'closed']),
// exists مع استعلام إضافي
'shop_id' => Rule::exists('shops', 'id')->where(function ($query) {
$query->where('tenant_id', tenant()->id);
}),
(1) ▶ مثال: قواعد التحقق من طلبات ShopMetrics
// app/Http/Requests/StoreOrderRequest.php
class StoreOrderRequest extends FormRequest
{
public function rules(): array
{
return [
'items' => 'required|array|min:1',
'items.*.product_id' => [
'required',
'integer',
Rule::exists('products', 'id')->where('is_active', true),
],
'items.*.quantity' => 'required|integer|min:1|max:100',
'coupon_code' => ['nullable', 'string', new ValidCouponCode()],
'discount' => [
'sometimes',
'numeric',
'min:0',
function ($attribute, $value, $fail) {
if ($value > $this->input('subtotal', 0)) {
$fail('لا يمكن أن يتجاوز الخصم المبلغ الجزئي.');
}
},
],
];
}
}
الناتج:
// تم التنفيذ بنجاح
6. معالجة رسائل الخطأ
(1) عرض الأخطاء في صفحات الويب
<!-- عرض جميع الأخطاء -->
@if ($errors->any())
<div class="alert alert-error">
<ul>
@foreach ($errors->all() as $error)
<li>{{ $error }}</li>
@endforeach
</ul>
</div>
@endif
<!-- عرض خطأ لحقل محدد -->
<input type="text" name="name" value="{{ old('name') }}"
class="{{ $errors->has('name') ? 'border-red-500' : '' }}">
@error('name')
<p class="text-red-500 text-sm">{{ $message }}</p>
@enderror
(2) استجابة خطأ API JSON
{
"message": "البيانات المقدمة غير صالحة.",
"errors": {
"name": ["حقل الاسم مطلوب."],
"email": ["يجب أن يكون البريد الإلكتروني عنوان بريد إلكتروني صالح."]
}
}
| نوع الطلب | سلوك فشل التحقق | تنسيق غير صالح |
|---|---|---|
| نماذج الويب | إعادة توجيه للنموذج + عرض الأخطاء | متغير عرض $errors |
| API JSON | يعيد 422 + JSON | {"errors": {...}} |
| AJAX | يعيد 422 + JSON | مثل API |
(1) ▶ مثال: معالجة أخطاء التحقق في API لـ ShopMetrics
// app/Http/Controllers/Api/ShopController.php
public function store(StoreShopRequest $request): JsonResponse
{
$shop = Shop::create($request->validated());
return response()->json([
'message' => 'تم إنشاء المتجر بنجاح.',
'data' => new ShopResource($shop),
], 201);
}
// العميل يستلم 422 عند فشل التحقق:
// {
// "message": "الرابط مستخدم بالفعل.",
// "errors": {
// "slug": ["الرابط مستخدم بالفعل."]
// }
// }
الناتج:
// تم التنفيذ بنجاح
7. التحقق الشرطي
(1) قاعدة sometimes
// التحقق فقط إذا كان الحقل موجودًا
$validated = $request->validate([
'name' => 'required|string',
'notes' => 'sometimes|nullable|string|max:5000',
]);
// قواعد شرطية بناءً على حقل آخر
Validator::make($data, [
'payment_method' => 'required|in:credit_card,bank_transfer',
'card_number' => 'required_if:payment_method,credit_card|numeric',
'bank_account' => 'required_if:payment_method,bank_transfer|numeric',
]);
(2) قاعدة bail
// إيقاف التحقق بعد أول فشل في حقل
$validated = $request->validate([
'email' => 'bail|required|email|unique:users',
// إذا فشل required، لن يعمل email وunique
]);
| القاعدة | الوظيفة | حالة الاستخدام |
|---|---|---|
sometimes |
تحقق فقط إذا كان الحقل موجودًا | حقل اختياري |
bail |
إيقاف بعد أول فشل | تحقق مكلف |
required_if |
حقل مطلوب | طريقة الدفع ← رقم البطاقة |
required_unless |
حقل اختياري | — |
required_with |
مطلوب | تأكيد كلمة المرور |
prohibited_if |
محظور شرطيًا | — |
exclude_if |
استبعاد شرطي | لا يُكتب في validated |
nullable |
يسمح بـ null | حقل اختياري |
(1) ▶ مثال: سيناريو التحقق الشرطي في ShopMetrics
// app/Http/Requests/UpdateSubscriptionRequest.php
class UpdateSubscriptionRequest extends FormRequest
{
public function rules(): array
{
return [
'plan_id' => 'required|exists:plans,id',
'payment_method' => 'required|in:credit_card,paypal,bank_transfer',
'card_number' => 'required_if:payment_method,credit_card|string|size:16',
'card_cvv' => 'required_if:payment_method,credit_card|string|size:3',
'paypal_email' => 'required_if:payment_method,paypal|email',
'bank_account' => 'required_if:payment_method,bank_transfer|string',
'coupon_code' => 'sometimes|nullable|string|max:50',
];
}
}
الناتج:
// تم التنفيذ بنجاح
8. مثال شامل: عملية التحقق الكاملة لـ ShopMetrics
// ============================================
// شامل: التحقق من طلبات ShopMetrics
// يغطي: Form Request، قواعد مخصصة، شرطي، أخطاء
// ============================================
// app/Rules/SufficientStock.php
class SufficientStock implements ValidationRule
{
public function validate(string $attribute, mixed $value, Closure $fail): void
{
$productId = request()->input(str_replace('.quantity', '.product_id', $attribute));
$product = Product::find($productId);
if ($product && $value > $product->stock) {
$fail("متاح فقط {$product->stock} وحدة من {$product->name}.");
}
}
}
// app/Http/Requests/StoreOrderRequest.php
class StoreOrderRequest extends FormRequest
{
public function authorize(): bool
{
return auth()->check();
}
public function rules(): array
{
return [
'items' => 'required|array|min:1|max:50',
'items.*.product_id' => [
'required',
'integer',
Rule::exists('products', 'id')->where('is_active', true),
],
'items.*.quantity' => [
'required',
'integer',
'min:1',
'max:100',
new SufficientStock(),
],
'coupon_code' => 'sometimes|nullable|string|max:50',
'notes' => 'sometimes|nullable|string|max:1000',
'shipping_address.line1' => 'required|string|max:255',
'shipping_address.city' => 'required|string|max:100',
'shipping_address.zip' => 'required|string|max:20',
'shipping_address.country' => 'required|string|size:2',
];
}
public function messages(): array
{
return [
'items.required' => 'سلة التسوق فارغة.',
'items.min' => 'أضف عنصرًا واحدًا على الأقل لتقديم الطلب.',
'items.*.product_id.exists' => 'أحد المنتجات المحددة غير متاح.',
'shipping_address.line1.required' => 'عنوان الشارع مطلوب.',
];
}
}
// المتحكم يبقى نظيفًا
public function store(StoreOrderRequest $request): RedirectResponse
{
$order = $this->orderService->createFromRequest($request);
return redirect()->route('orders.show', $order)
->with('success', 'تم تقديم الطلب بنجاح!');
}
❓ أسئلة شائعة
validate() وForm Request؟validate() مباشرة؛ للتحقق المعقد (10+ قواعد، قابلية إعادة الاستخدام، أو فحص التفويض)، استخدم Form Request. Form Request هو أفضل ممارسة.Rule::unique('shops', 'slug')->ignore($shop->id) أو تنسيق unique:shops,slug,{shop}. يجب أن يستبعد التحقق من التحديث السجل الحالي؛ وإلا سيفشل التحقق دائمًا.{"message":"...","errors":{"field":["رسالة خطأ"]}}. يجب على الواجهة الأمامية تحليل كائن errors بناءً على رمز الحالة 422 لعرض الأخطاء على مستوى الحقل.prepareForValidation؟validated().$this->postJson() لإرسال بيانات غير صالحة؛ تحقق من خطأ 422 ورسالة الخطأ $response->assertJsonValidationErrors('name'). يمكنك أيضًا استخدام Validator::make() لاختبار القواعد مباشرة.unique وexists تستعلم قاعدة البيانات، مما يكلف أداءً. لواجهات API عالية التردد، فكر في استخدام bail لإنهاء العملية مبكرًا أو تخزين نتائج التحقق مؤقتًا. في معظم السيناريوهات، الأداء ليس مشكلة.📖 ملخص
- تحقق Laravel يعترض البيانات قبل دخولها النظام، ويوفر مجموعة واسعة من القواعد القابلة للدمج
- صنف Form Request يغلف منطق التحقق، مبقيًا المتحكم نظيفًا
- قواعد الإغلاق مناسبة للمنطق لمرة واحدة، بينما كائنات القواعد مناسبة للمنطق القابل لإعادة الاستخدام
- يعيد التوجيه ويعرض رسالة خطأ عند فشل التحقق على الويب؛ API يعيد استجابة JSON برمز 422
- "sometimes"/nullable: يعالج الحقول الاختيارية؛ "bail": إنهاء مبكر
- prepareForValidation: يعدّل بيانات الإدخال قبل التحقق
📝 تمارين
-
تمرين أساسي (⭐): أنشئ
StoreProductRequestلمنتج ShopMetrics، بما في ذلك قواعد التحقق منnameوskuوpriceوstockوcategory_id، للتأكد من أن SKU فريد والسعر رقم موجب. -
تمرين متقدم (⭐⭐): أنشئ كائن قاعدة
ValidCouponCodeللتحقق من أن الكوبون لم تنتهِ صلاحيته وما زال لديه استخدامات متبقية. طبّق هذه القاعدة علىStoreOrderRequestواختبر استجابات الكوبونات الصالحة وغير الصالحة. -
تحدٍ (⭐⭐⭐): نفذ قاعدة SufficientStock (للتحقق من أن كمية الطلب لا تتجاوز المخزون) على حقول
items.*.quantityفيStoreOrderRequestللتعامل مع مشاكل تنافس المخزون عند طلب عناصر متعددة في نفس الوقت.



