Form Request در Laravel؛ راهنمای کامل اعتبارسنجی
Form Request در Laravel یکی از بهترین ابزارها برای جداسازی منطق اعتبارسنجی و مجوزدهی از کنترلرهاست. با استفاده از Form Request میتوان قوانین Validation، پیامهای خطا، نام فیلدها، آمادهسازی داده قبل از اعتبارسنجی، دسترسی کاربر به عملیات و دریافت دادههای معتبر را در یک کلاس مستقل، تمیز و قابل تست مدیریت کرد. این مقاله یک راهنمای کامل، فنی و آماده انتشار درباره Form Request Laravel است و برای تیمهای نرمافزاری، برنامهنویسان Backend و شرکتهایی که پروژههای حرفهای Laravel توسعه میدهند نوشته شده است.
برای شنیدن متن، روی «پخش صوت مقاله» بزنید.
مقدمه
در هر نرمافزار تحت وب، دادهای که از سمت کاربر وارد سیستم میشود قابل اعتماد نیست. کاربر ممکن است یک فیلد ضروری را خالی بگذارد، ایمیل نامعتبر وارد کند، مقدار عددی را بهصورت متن ارسال کند، فایل غیرمجاز آپلود کند یا حتی عمداً دادهای ارسال کند که امنیت و منطق سیستم را به خطر بیندازد. به همین دلیل، اعتبارسنجی ورودیها یکی از مهمترین بخشهای توسعه Backend است.
در Laravel، روشهای مختلفی برای اعتبارسنجی وجود دارد. سادهترین روش استفاده از متد validate() داخل Controller است. این روش برای فرمهای کوچک مناسب است، اما وقتی پروژه بزرگتر میشود و قوانین اعتبارسنجی پیچیدهتر میشوند، نوشتن همه Ruleها داخل Controller باعث شلوغی، تکرار کد و سخت شدن نگهداری پروژه میشود. اینجاست که Form Request وارد میشود.
Form Request در Laravel یک کلاس اختصاصی برای مدیریت اعتبارسنجی و مجوزدهی Request است. بهجای اینکه قوانین اعتبارسنجی را مستقیماً داخل Controller بنویسیم، آنها را در یک کلاس جداگانه قرار میدهیم. در نتیجه، Controller سبکتر، خواناتر و حرفهایتر میشود.
طبق مستندات رسمی Laravel، هر Form Request تولیدشده توسط لاراول معمولاً دو متد اصلی دارد: authorize و rules. متد authorize مشخص میکند آیا کاربر فعلی اجازه انجام این درخواست را دارد یا نه، و متد rules قوانین اعتبارسنجی دادههای ورودی را برمیگرداند. مطالعه مستندات رسمی Laravel درباره Form Request Validation
برای شرکتهای تولید نرمافزار، استفاده درست از Form Request فقط یک تکنیک ساده نیست؛ بلکه بخشی از معماری تمیز، امنیت، تستپذیری و استانداردسازی کد است. در این مقاله بهصورت کامل و فنی بررسی میکنیم Form Request چیست، چرا اهمیت دارد، چگونه ساخته میشود، چطور در Controller استفاده میشود، چه متدهایی دارد، در API چگونه رفتار میکند و چه Best Practiceهایی برای پروژههای حرفهای Laravel باید رعایت شود. 🚀
Form Request در Laravel چیست؟
Form Request یک کلاس PHP است که از کلاس پایه Illuminate\Foundation\Http\FormRequest ارثبری میکند و برای اعتبارسنجی Requestهای ورودی استفاده میشود. این کلاس معمولاً داخل مسیر زیر قرار میگیرد:
app/Http/Requests
برای مثال، اگر بخواهیم فرم ایجاد محصول را اعتبارسنجی کنیم، میتوانیم یک Form Request به نام StoreProductRequest بسازیم:
php artisan make:request StoreProductRequest
خروجی این دستور، فایلی شبیه نمونه زیر است:
<?php
namespace App\Http\Requests;
use Illuminate\Foundation\Http\FormRequest;
class StoreProductRequest extends FormRequest
{
public function authorize(): bool
{
return false;
}
public function rules(): array
{
return [
//
];
}
}
در این کلاس دو بخش اصلی داریم:
| متد | کاربرد |
|---|---|
| authorize() | بررسی مجوز کاربر برای انجام درخواست |
| rules() | تعریف قوانین اعتبارسنجی دادههای ورودی |
نکته مهم این است که مقدار پیشفرض authorize() معمولاً false است. یعنی اگر آن را تغییر ندهید، درخواست شما با خطای 403 مواجه میشود. بنابراین در سادهترین حالت باید آن را true کنید:
public function authorize(): bool
{
return true;
}
چرا باید از Form Request استفاده کنیم؟
استفاده از Form Request در پروژههای حرفهای Laravel مزایای زیادی دارد. مهمترین دلیل، جدا کردن مسئولیت اعتبارسنجی از Controller است.
فرض کنید یک Controller برای ایجاد محصول داریم:
public function store(Request $request)
{
$data = $request->validate([
'title' => ['required', 'string', 'max:255'],
'price' => ['required', 'integer', 'min:0'],
'description' => ['nullable', 'string'],
'status' => ['required', 'in:active,inactive'],
]);
Product::create($data);
return redirect()->route('products.index');
}
این کد برای شروع قابل قبول است. اما اگر قوانین بیشتر شوند، فایل آپلود شود، پیام خطا سفارشی شود، شرطهای پیچیده داشته باشیم یا همین قوانین در چند جای پروژه استفاده شوند، Controller شلوغ میشود.
نسخه بهتر با Form Request:
public function store(StoreProductRequest $request)
{
Product::create($request->validated());
return redirect()->route('products.index');
}
در این حالت، Controller فقط کار اصلی خود را انجام میدهد: دریافت داده معتبر، فراخوانی Model یا Service و بازگرداندن Response.
مزایای Form Request:
| مزیت | توضیح |
|---|---|
| سبک شدن Controller | قوانین اعتبارسنجی از Controller خارج میشوند |
| افزایش خوانایی | هر Request کلاس مخصوص خود را دارد |
| تستپذیری بهتر | قوانین اعتبارسنجی مستقلتر قابل بررسی هستند |
| قابلیت استفاده مجدد | Ruleها و پیامها در کلاس جداگانه قرار دارند |
| امنیت بیشتر | فقط دادههای معتبر وارد منطق برنامه میشوند |
| مناسب معماری شرکتی | مسئولیتها بهتر تفکیک میشوند |
| مدیریت بهتر API | پاسخ خطای Validation استانداردتر میشود |
| امکان Authorization | بررسی مجوز در همان کلاس Request انجام میشود |
ساخت Form Request در Laravel
برای ساخت Form Request از دستور Artisan استفاده میکنیم:
php artisan make:request StoreProductRequest
برای ویرایش محصول:
php artisan make:request UpdateProductRequest
ساختار پیشنهادی برای پروژههای بزرگتر:
app/
Http/
Requests/
Product/
StoreProductRequest.php
UpdateProductRequest.php
User/
StoreUserRequest.php
UpdateUserRequest.php
Order/
StoreOrderRequest.php
این ساختار باعث میشود Requestهای هر بخش جدا و قابل مدیریت باشند.
نمونه کامل:
<?php
namespace App\Http\Requests\Product;
use Illuminate\Foundation\Http\FormRequest;
class StoreProductRequest extends FormRequest
{
public function authorize(): bool
{
return true;
}
public function rules(): array
{
return [
'title' => ['required', 'string', 'max:255'],
'slug' => ['nullable', 'string', 'max:255', 'unique:products,slug'],
'price' => ['required', 'integer', 'min:0'],
'description' => ['nullable', 'string'],
'status' => ['required', 'in:active,inactive'],
];
}
}
استفاده در Controller:
use App\Http\Requests\Product\StoreProductRequest;
public function store(StoreProductRequest $request)
{
$product = Product::create($request->validated());
return redirect()->route('products.show', $product);
}
در Laravel، وقتی Form Request را در متد Controller Type-Hint میکنید، اعتبارسنجی قبل از اجرای منطق متد Controller انجام میشود. اگر داده معتبر باشد، اجرای Controller ادامه پیدا میکند؛ اما اگر Validation شکست بخورد، Laravel بهصورت خودکار پاسخ مناسب تولید میکند. در Requestهای سنتی Web معمولاً Redirect به صفحه قبل همراه با خطاها انجام میشود و در API معمولاً پاسخ JSON خطای اعتبارسنجی برمیگردد.
تفاوت Form Request با validate داخل Controller
هر دو روش برای اعتبارسنجی استفاده میشوند، اما کاربرد آنها متفاوت است.
اعتبارسنجی داخل Controller
$data = $request->validate([
'title' => ['required', 'string', 'max:255'],
]);
این روش برای فرمهای کوچک و ساده مناسب است.
اعتبارسنجی با Form Request
public function store(StoreProductRequest $request)
{
Product::create($request->validated());
}
این روش برای پروژههای حرفهای و فرمهای پیچیده مناسبتر است.
| معیار | validate داخل Controller | Form Request |
|---|---|---|
| مناسب برای | فرمهای ساده | فرمهای متوسط و پیچیده |
| خوانایی Controller | متوسط | بسیار بهتر |
| قابلیت استفاده مجدد | کمتر | بیشتر |
| تستپذیری | محدودتر | بهتر |
| پیامهای سفارشی | ممکن است، اما شلوغتر | تمیزتر |
| Authorization | معمولاً جداگانه | داخل authorize() |
| ساختار شرکتی | ضعیفتر | حرفهایتر |
در پروژههای کوچک میتوان از validate() استفاده کرد؛ اما در پروژههای شرکتی، Form Request انتخاب تمیزتر و قابل نگهداریتری است.
متد authorize در Form Request
متد authorize() مشخص میکند کاربر فعلی اجازه انجام این Request را دارد یا نه.
نمونه ساده:
public function authorize(): bool
{
return true;
}
اگر مقدار false برگردد، Laravel درخواست را متوقف کرده و پاسخ 403 برمیگرداند.
مثال کاربردی برای ویرایش مقاله:
public function authorize(): bool
{
return $this->user()->can('update', $this->post);
}
در این مثال، از Policy برای بررسی مجوز ویرایش مقاله استفاده شده است.
مستندات رسمی Laravel توضیح میدهد که علاوه بر Authentication، Laravel روشهایی سازمانیافته برای Authorization فراهم میکند؛ از جمله Gateها و Policyها، که برای بررسی اجازه کاربر نسبت به منابع مختلف استفاده میشوند. مطالعه مستندات رسمی Laravel درباره Authorization
استفاده از authorize() در Form Request برای سناریوهایی مناسب است که مجوز انجام همان درخواست باید دقیق بررسی شود. برای مثال:
- آیا کاربر اجازه ویرایش این محصول را دارد؟
- آیا کاربر مالک این سفارش است؟
- آیا مدیر اجازه حذف این کاربر را دارد؟
- آیا مشتری اجازه مشاهده این فاکتور را دارد؟
نمونه:
public function authorize(): bool
{
$invoice = $this->route('invoice');
return $invoice && $invoice->user_id === $this->user()->id;
}
متد rules در Form Request
متد rules() قلب Form Request است. این متد آرایهای از قوانین اعتبارسنجی را برمیگرداند.
نمونه:
public function rules(): array
{
return [
'name' => ['required', 'string', 'max:255'],
'email' => ['required', 'email', 'unique:users,email'],
'password' => ['required', 'string', 'min:8', 'confirmed'],
];
}
قوانین رایج Laravel:
| Rule | کاربرد |
|---|---|
| required | فیلد الزامی است |
| nullable | فیلد میتواند خالی باشد |
| string | مقدار باید رشته باشد |
| integer | مقدار باید عدد صحیح باشد |
| numeric | مقدار باید عددی باشد |
| مقدار باید ایمیل معتبر باشد | |
| min | حداقل مقدار یا طول |
| max | حداکثر مقدار یا طول |
| unique | مقدار باید در جدول یکتا باشد |
| exists | مقدار باید در جدول وجود داشته باشد |
| confirmed | تأیید مقدار، مثل password confirmation |
| array | مقدار باید آرایه باشد |
| file | مقدار باید فایل باشد |
| image | فایل باید تصویر باشد |
| mimes | فرمت فایل مجاز |
| date | مقدار باید تاریخ معتبر باشد |
نمونه برای محصول:
public function rules(): array
{
return [
'title' => ['required', 'string', 'max:255'],
'sku' => ['required', 'string', 'max:100', 'unique:products,sku'],
'price' => ['required', 'integer', 'min:0'],
'stock' => ['required', 'integer', 'min:0'],
'image' => ['nullable', 'image', 'mimes:jpg,jpeg,png,webp', 'max:2048'],
'categories' => ['nullable', 'array'],
'categories.*' => ['integer', 'exists:categories,id'],
];
}
در مستندات رسمی Laravel، برای دادههای تو در تو میتوان از Dot Syntax استفاده کرد؛ مثلاً author.name یا items.*.product_id. این قابلیت برای فرمهای پیچیده و آرایهای بسیار کاربردی است.
دریافت داده معتبر با validated
بعد از موفقیت Validation، میتوان دادههای معتبر را با متد validated() دریافت کرد:
$data = $request->validated();
این متد فقط دادههایی را برمیگرداند که در قوانین اعتبارسنجی تعریف شدهاند و اعتبارسنجی را گذراندهاند.
نمونه:
public function store(StoreProductRequest $request)
{
$product = Product::create($request->validated());
return redirect()->route('products.show', $product);
}
استفاده از validated() نسبت به $request->all() بسیار امنتر است؛ چون فقط فیلدهای مجاز و معتبر وارد Model یا Service میشوند.
نمونه بد:
Product::create($request->all());
نمونه بهتر:
Product::create($request->validated());
در پروژههای شرکتی، این تفاوت بسیار مهم است؛ چون استفاده نادرست از $request->all() ممکن است باعث Mass Assignment یا ورود دادههای ناخواسته به منطق برنامه شود.
متد safe در Form Request
Laravel علاوه بر validated() متد safe() را نیز ارائه میدهد. این متد امکان کار کنترلشدهتر با دادههای معتبر را فراهم میکند.
نمونه:
$validated = $request->safe()->only(['title', 'price']);
یا:
$validated = $request->safe()->except(['image']);
کاربرد safe() زمانی بیشتر میشود که بخواهید بخشی از دادههای معتبر را جدا کنید یا داده را قبل از ارسال به Service تفکیک کنید.
مثال:
public function store(StoreProductRequest $request)
{
$productData = $request->safe()->except(['categories']);
$categoryIds = $request->safe()->only(['categories']);
$product = $this->productService->create($productData, $categoryIds);
return redirect()->route('products.show', $product);
}
پیامهای سفارشی در Form Request
یکی از مزایای Form Request این است که میتوانید پیامهای خطای Validation را در همان کلاس مدیریت کنید.
public function messages(): array
{
return [
'title.required' => 'عنوان محصول الزامی است.',
'title.max' => 'عنوان محصول نباید بیشتر از ۲۵۵ کاراکتر باشد.',
'price.required' => 'قیمت محصول الزامی است.',
'price.integer' => 'قیمت باید عدد صحیح باشد.',
'image.image' => 'فایل انتخابشده باید تصویر باشد.',
];
}
این روش مخصوصاً برای پروژههای فارسیزبان بسیار کاربردی است. البته اگر پروژه بزرگ باشد، بهتر است بسیاری از پیامهای عمومی در فایلهای زبان Laravel قرار بگیرند و فقط پیامهای خاص در Form Request نوشته شوند.
نامگذاری سفارشی فیلدها با attributes
گاهی نمیخواهید در پیام خطا نام فیلد انگلیسی نمایش داده شود. برای این کار میتوانید از متد attributes() استفاده کنید:
public function attributes(): array
{
return [
'title' => 'عنوان محصول',
'price' => 'قیمت',
'stock' => 'موجودی',
'image' => 'تصویر محصول',
];
}
در نتیجه، اگر Rule مربوط به title خطا بدهد، Laravel میتواند بهجای title عبارت «عنوان محصول» را نمایش دهد.
این قابلیت برای بهبود تجربه کاربری در فرمهای فارسی بسیار مهم است.
آمادهسازی داده قبل از اعتبارسنجی با prepareForValidation
گاهی دادهای که از سمت کاربر میآید قبل از اعتبارسنجی نیاز به تغییر یا نرمالسازی دارد. برای این کار از متد prepareForValidation() استفاده میشود.
مثال: ساخت slug از title
use Illuminate\Support\Str;
protected function prepareForValidation(): void
{
$this->merge([
'slug' => Str::slug($this->input('title')),
]);
}
مثال: تبدیل اعداد فارسی به انگلیسی یا پاکسازی فاصلههای اضافی:
protected function prepareForValidation(): void
{
$this->merge([
'email' => strtolower(trim($this->input('email'))),
'title' => trim($this->input('title')),
]);
}
این متد قبل از اجرای Ruleها فراخوانی میشود. بنابراین اگر داده نیاز به اصلاح اولیه داشته باشد، اینجا محل مناسبی است.
کاربردهای رایج prepareForValidation():
| کاربرد | مثال |
|---|---|
| Trim کردن رشتهها | حذف فاصله اول و آخر عنوان |
| Lowercase کردن ایمیل | تبدیل ایمیل به حروف کوچک |
| ساخت Slug | تولید slug از title |
| تبدیل مقدار Checkbox | تبدیل on به true |
| نرمالسازی شماره موبایل | حذف فاصله و کاراکترهای اضافی |
| افزودن داده Route | اضافه کردن product_id از Route به داده |
نمونه اضافه کردن مقدار Route:
protected function prepareForValidation(): void
{
$this->merge([
'product_id' => $this->route('product')?->id,
]);
}
پردازش بعد از اعتبارسنجی با passedValidation
اگر بعد از موفقیت Validation نیاز دارید داده را تغییر دهید، میتوانید از passedValidation() استفاده کنید.
protected function passedValidation(): void
{
$this->replace([
...$this->validated(),
'title' => trim($this->title),
]);
}
تفاوت مهم:
| متد | زمان اجرا |
|---|---|
| prepareForValidation() | قبل از اعتبارسنجی |
| passedValidation() | بعد از موفقیت اعتبارسنجی |
در بسیاری از پروژهها، prepareForValidation() کاربرد بیشتری دارد، اما passedValidation() نیز برای سناریوهای خاص مفید است.
اعتبارسنجی شرطی در Form Request
گاهی Ruleها به شرایط خاص وابسته هستند. مثلاً اگر نوع محصول دیجیتال باشد، فایل دانلودی الزامی است؛ اما اگر فیزیکی باشد، وزن کالا الزامی است.
نمونه:
public function rules(): array
{
return [
'type' => ['required', 'in:physical,digital'],
'download_file' => ['required_if:type,digital', 'file'],
'weight' => ['required_if:type,physical', 'numeric', 'min:0'],
];
}
یا برای ویرایش محصول:
public function rules(): array
{
return [
'title' => ['required', 'string', 'max:255'],
'image' => [$this->isMethod('post') ? 'required' : 'nullable', 'image'],
];
}
اعتبارسنجی شرطی برای فرمهای واقعی بسیار مهم است، چون معمولاً همه فیلدها در همه شرایط الزامی نیستند.
Rule یکتا در Store و Update
یکی از چالشهای رایج در Form Request، اعتبارسنجی فیلدهای یکتا مثل ایمیل، SKU یا slug هنگام ویرایش است.
برای ایجاد محصول:
'slug' => ['required', 'string', 'max:255', 'unique:products,slug']
اما برای ویرایش، باید رکورد فعلی نادیده گرفته شود:
use Illuminate\Validation\Rule;
public function rules(): array
{
$product = $this->route('product');
return [
'slug' => [
'required',
'string',
'max:255',
Rule::unique('products', 'slug')->ignore($product?->id),
],
];
}
این روش از خطای اشتباه هنگام ویرایش جلوگیری میکند. اگر محصول فعلی همان slug قبلی را داشته باشد، Validation نباید خطا بدهد.
Form Request در APIهای Laravel
در APIها نیز Form Request بسیار کاربردی است. تفاوت اصلی در نوع پاسخ خطا است. در درخواستهای Web، Validation ناموفق معمولاً باعث Redirect میشود؛ اما در API، Laravel معمولاً پاسخ JSON با وضعیت 422 برمیگرداند.
نمونه API Controller:
public function store(StoreProductRequest $request)
{
$product = Product::create($request->validated());
return response()->json([
'data' => $product,
'message' => 'Product created successfully.',
], 201);
}
اگر داده نامعتبر باشد، پاسخ میتواند شبیه این باشد:
{
"message": "The title field is required.",
"errors": {
"title": [
"The title field is required."
]
}
}
در APIهای حرفهای، بهتر است ساختار خطاها استاندارد باشد تا Frontend، اپلیکیشن موبایل یا مصرفکننده API بتواند بهراحتی پیامها را نمایش دهد.
شخصیسازی پاسخ خطای Validation در API
گاهی میخواهید ساختار خطاهای API را کنترل کنید. برای این کار میتوان متد failedValidation() را در Form Request Override کرد.
use Illuminate\Contracts\Validation\Validator;
use Illuminate\Http\Exceptions\HttpResponseException;
protected function failedValidation(Validator $validator): void
{
throw new HttpResponseException(response()->json([
'success' => false,
'message' => 'دادههای ارسالی معتبر نیستند.',
'errors' => $validator->errors(),
], 422));
}
این روش در پروژههایی که استاندارد Response مشخص دارند مفید است. با این حال، بهتر است این منطق برای همه API Requestها تکرار نشود. میتوانید یک Base Form Request بسازید و Requestهای API از آن ارثبری کنند.
ساخت BaseFormRequest برای پروژههای بزرگ
در پروژههای حرفهای، ممکن است بخواهید رفتار مشترک بین همه Form Requestها داشته باشید. برای مثال:
- پاسخ خطای استاندارد API
- تبدیل اعداد فارسی به انگلیسی
- Trim کردن خودکار رشتهها
- مدیریت پیام خطا
- بررسی نوع درخواست
نمونه:
<?php
namespace App\Http\Requests;
use Illuminate\Foundation\Http\FormRequest;
abstract class BaseFormRequest extends FormRequest
{
protected function prepareForValidation(): void
{
$this->merge(
$this->normalize($this->all())
);
}
protected function normalize(array $data): array
{
return collect($data)->map(function ($value) {
return is_string($value) ? trim($value) : $value;
})->toArray();
}
}
سپس:
class StoreProductRequest extends BaseFormRequest
{
public function authorize(): bool
{
return true;
}
public function rules(): array
{
return [
'title' => ['required', 'string', 'max:255'],
];
}
}
این الگو مخصوصاً در پروژههای بزرگ و شرکتی مفید است؛ اما باید مراقب باشید Base Class بیش از حد پیچیده نشود.
Form Request و Service Layer
در معماری حرفهای Laravel، Form Request معمولاً کنار Service Layer استفاده میشود.
جریان پیشنهادی:
Route → Controller → Form Request → Service → Model/Repository → Response
نمونه:
public function store(StoreOrderRequest $request)
{
$order = $this->orderService->create(
$request->validated()
);
return redirect()->route('orders.show', $order);
}
در این ساختار:
- Form Request مسئول اعتبارسنجی و مجوزدهی است.
- Controller مسئول دریافت Request و بازگرداندن Response است.
- Service مسئول اجرای منطق تجاری است.
- Model مسئول تعامل با دیتابیس است.
این تفکیک مسئولیتها باعث میشود پروژه تمیز، تستپذیر و قابل توسعه بماند.
Form Request و Mass Assignment
یکی از مزایای مهم Form Request، کاهش ریسک Mass Assignment است. Mass Assignment زمانی خطرناک میشود که دادههای خام Request مستقیماً وارد Model شوند:
User::create($request->all());
اگر کاربر فیلدی مثل is_admin یا role_id را در Request ارسال کند و Model بهدرستی محافظت نشده باشد، ممکن است امنیت سیستم به خطر بیفتد.
روش بهتر:
User::create($request->validated());
البته استفاده از validated() جایگزین تنظیم صحیح $fillable یا $guarded در Model نیست. هر دو باید با هم استفاده شوند:
protected $fillable = [
'name',
'email',
'password',
];
در پروژههای سازمانی، ترکیب Form Request و تنظیم درست Model یکی از اصول مهم امنیت ورودی داده است.
Form Request و آپلود فایل
Form Request برای اعتبارسنجی فایلها نیز بسیار مناسب است.
نمونه:
public function rules(): array
{
return [
'avatar' => ['nullable', 'image', 'mimes:jpg,jpeg,png,webp', 'max:2048'],
'document' => ['required', 'file', 'mimes:pdf,doc,docx', 'max:5120'],
];
}
در Controller:
public function store(StoreProfileRequest $request)
{
$data = $request->validated();
if ($request->hasFile('avatar')) {
$data['avatar_path'] = $request->file('avatar')->store('avatars', 'public');
}
Profile::create($data);
return redirect()->route('profile.show');
}
نکته مهم این است که خود ذخیره فایل معمولاً داخل Controller، Service یا Action انجام میشود؛ اما اعتبارسنجی نوع، حجم و الزامی بودن فایل داخل Form Request قرار میگیرد.
اعتبارسنجی آرایهها و دادههای تو در تو
در فرمهای واقعی، مخصوصاً سفارشها، فاکتورها و تنظیمات، دادهها اغلب آرایهای هستند.
نمونه ثبت سفارش:
public function rules(): array
{
return [
'customer_id' => ['required', 'integer', 'exists:customers,id'],
'items' => ['required', 'array', 'min:1'],
'items.*.product_id' => ['required', 'integer', 'exists:products,id'],
'items.*.quantity' => ['required', 'integer', 'min:1'],
'items.*.discount' => ['nullable', 'numeric', 'min:0'],
];
}
این قوانین مشخص میکنند:
- items باید آرایه باشد.
- حداقل یک آیتم باید وجود داشته باشد.
- هر آیتم باید product_id معتبر داشته باشد.
- هر آیتم باید quantity معتبر داشته باشد.
این قابلیت برای سیستمهای فروشگاهی، فاکتور، CRM و ERP بسیار کاربردی است.
استفاده از Ruleهای سفارشی در Form Request
گاهی Ruleهای آماده Laravel کافی نیستند. در این شرایط میتوان Custom Rule ساخت.
ساخت Rule:
php artisan make:rule ValidNationalCode
نمونه استفاده:
use App\Rules\ValidNationalCode;
public function rules(): array
{
return [
'national_code' => ['required', new ValidNationalCode],
];
}
Custom Rule برای اعتبارسنجیهایی مثل کد ملی، شماره شبا، شناسه مالیاتی، قوانین داخلی شرکت یا کنترلهای خاص دامنه کاربرد دارد.
در پروژههای شرکتی، بهتر است قوانین پیچیده و تکراری به Rule Class منتقل شوند تا Form Request تمیز بماند.
Form Request و Policy
گاهی اعتبارسنجی داده کافی نیست و باید بررسی شود کاربر اجازه انجام عملیات را دارد یا نه. این کار را میتوان داخل authorize() و با Policy انجام داد.
نمونه:
public function authorize(): bool
{
$product = $this->route('product');
return $this->user()->can('update', $product);
}
Policy:
public function update(User $user, Product $product): bool
{
return $user->company_id === $product->company_id;
}
این روش برای پروژههای چندشرکتی، پنلهای مدیریتی، SaaS و سیستمهای دارای نقشهای مختلف بسیار مناسب است.
Form Request و Route Model Binding
Form Request میتواند به پارامترهای Route دسترسی داشته باشد. مثلاً اگر Route شما این باشد:
Route::put('/products/{product}', [ProductController::class, 'update']);
در Form Request میتوانید به محصول دسترسی داشته باشید:
$product = $this->route('product');
نمونه کامل:
use Illuminate\Validation\Rule;
public function rules(): array
{
$product = $this->route('product');
return [
'title' => ['required', 'string', 'max:255'],
'slug' => [
'required',
'string',
'max:255',
Rule::unique('products', 'slug')->ignore($product->id),
],
];
}
این قابلیت هنگام Update بسیار مهم است.
Form Request در پروژههای چندزبانه
برای پروژههای فارسی یا چندزبانه، بهتر است پیامهای خطا، نام فیلدها و ترجمهها بهدرستی مدیریت شوند.
روشها:
- پیامهای عمومی در فایلهای زبان Laravel
- پیامهای خاص در متد messages()
- نام فیلدها در متد attributes()
- استفاده از ترجمهها با __('validation.custom_key')
نمونه:
public function messages(): array
{
return [
'title.required' => __('validation.product_title_required'),
];
}
این روش باعث میشود پروژه برای چند زبان آمادهتر باشد.
جدول مقایسه متدهای مهم Form Request
| متد | کاربرد | زمان اجرا | مثال |
|---|---|---|---|
| authorize() | بررسی مجوز درخواست | قبل از Validation | آیا کاربر اجازه ویرایش دارد؟ |
| rules() | تعریف قوانین اعتبارسنجی | هنگام Validation | title required باشد |
| messages() | پیامهای خطای سفارشی | هنگام خطا | عنوان محصول الزامی است |
| attributes() | نام نمایشی فیلدها | هنگام تولید پیام | title = عنوان محصول |
| prepareForValidation() | آمادهسازی داده قبل از Validation | قبل از Ruleها | trim کردن title |
| passedValidation() | پردازش بعد از موفقیت Validation | بعد از Ruleها | جایگزینی داده نهایی |
| validated() | دریافت داده معتبر | داخل Controller/Service | ذخیره داده |
| safe() | دریافت کنترلشده داده معتبر | بعد از Validation | only یا except |
نمونه کامل Form Request برای محصول
<?php
namespace App\Http\Requests\Product;
use Illuminate\Foundation\Http\FormRequest;
use Illuminate\Support\Str;
use Illuminate\Validation\Rule;
class StoreProductRequest extends FormRequest
{
public function authorize(): bool
{
return $this->user()?->can('create', Product::class) ?? false;
}
protected function prepareForValidation(): void
{
$this->merge([
'title' => trim((string) $this->input('title')),
'slug' => $this->input('slug')
? Str::slug($this->input('slug'))
: Str::slug($this->input('title')),
]);
}
public function rules(): array
{
return [
'title' => ['required', 'string', 'max:255'],
'slug' => ['required', 'string', 'max:255', 'unique:products,slug'],
'price' => ['required', 'integer', 'min:0'],
'stock' => ['required', 'integer', 'min:0'],
'status' => ['required', Rule::in(['active', 'inactive'])],
'description' => ['nullable', 'string'],
'image' => ['nullable', 'image', 'mimes:jpg,jpeg,png,webp', 'max:2048'],
'categories' => ['nullable', 'array'],
'categories.*' => ['integer', 'exists:categories,id'],
];
}
public function messages(): array
{
return [
'title.required' => 'عنوان محصول الزامی است.',
'slug.unique' => 'این آدرس محصول قبلاً ثبت شده است.',
'price.required' => 'قیمت محصول الزامی است.',
'price.integer' => 'قیمت باید عدد صحیح باشد.',
'image.image' => 'فایل انتخابشده باید تصویر باشد.',
];
}
public function attributes(): array
{
return [
'title' => 'عنوان محصول',
'slug' => 'آدرس محصول',
'price' => 'قیمت',
'stock' => 'موجودی',
'status' => 'وضعیت',
'image' => 'تصویر محصول',
];
}
}
نکته: در این نمونه از Product::class در authorize() استفاده شده است؛ بنابراین باید Model مربوطه را در بالای فایل use کنید.
نمونه UpdateProductRequest
<?php
namespace App\Http\Requests\Product;
use Illuminate\Foundation\Http\FormRequest;
use Illuminate\Support\Str;
use Illuminate\Validation\Rule;
class UpdateProductRequest extends FormRequest
{
public function authorize(): bool
{
return $this->user()?->can('update', $this->route('product')) ?? false;
}
protected function prepareForValidation(): void
{
if ($this->has('slug')) {
$this->merge([
'slug' => Str::slug($this->input('slug')),
]);
}
}
public function rules(): array
{
$product = $this->route('product');
return [
'title' => ['required', 'string', 'max:255'],
'slug' => [
'required',
'string',
'max:255',
Rule::unique('products', 'slug')->ignore($product->id),
],
'price' => ['required', 'integer', 'min:0'],
'stock' => ['required', 'integer', 'min:0'],
'status' => ['required', Rule::in(['active', 'inactive'])],
'description' => ['nullable', 'string'],
'image' => ['nullable', 'image', 'mimes:jpg,jpeg,png,webp', 'max:2048'],
];
}
}
استفاده حرفهای در Controller
<?php
namespace App\Http\Controllers\Admin;
use App\Http\Controllers\Controller;
use App\Http\Requests\Product\StoreProductRequest;
use App\Http\Requests\Product\UpdateProductRequest;
use App\Models\Product;
use App\Services\ProductService;
class ProductController extends Controller
{
public function __construct(
protected ProductService $productService
) {}
public function store(StoreProductRequest $request)
{
$product = $this->productService->create(
$request->validated()
);
return redirect()
->route('admin.products.show', $product)
->with('success', 'محصول با موفقیت ایجاد شد.');
}
public function update(UpdateProductRequest $request, Product $product)
{
$this->productService->update(
$product,
$request->validated()
);
return redirect()
->route('admin.products.show', $product)
->with('success', 'محصول با موفقیت ویرایش شد.');
}
}
در این ساختار، کنترلر بسیار تمیز است و تمام قوانین اعتبارسنجی در کلاسهای مخصوص قرار گرفتهاند.
اشتباهات رایج در استفاده از Form Request
1. فراموش کردن تغییر authorize به true
اگر authorize() مقدار false برگرداند، Request همیشه رد میشود.
public function authorize(): bool
{
return true;
}
یا بهتر:
return $this->user()->can('create', Product::class);
2. استفاده از $request->all()
حتی بعد از Form Request، نباید داده خام را ذخیره کنید:
Product::create($request->all());
روش درست:
Product::create($request->validated());
3. نوشتن منطق تجاری داخل Form Request
Form Request جای اجرای منطق تجاری نیست. برای مثال ثبت سفارش، کاهش موجودی، ارسال پیامک یا ذخیره فایل پیچیده بهتر است در Service انجام شود.
4. سنگین کردن prepareForValidation
این متد برای آمادهسازی سبک داده است، نه پردازش سنگین.
5. استفاده نکردن از Rule::unique در Update
برای Update باید رکورد فعلی نادیده گرفته شود، وگرنه کاربر هنگام ذخیره مقدار قبلی با خطای اشتباه مواجه میشود.
6. تکرار پیامهای عمومی در همه Requestها
پیامهای عمومی بهتر است در فایل زبان قرار بگیرند.
7. ترکیب Validation و Authorization بدون ساختار
بهتر است Ruleهای اعتبارسنجی در rules() و بررسی مجوز در authorize() بماند.
بهترین روشهای Form Request در Laravel
1. برای فرمهای جدی همیشه Form Request بسازید
اگر فرم بیش از چند Rule ساده دارد، Form Request ارزشمند است.
2. برای Store و Update کلاس جدا داشته باشید
قوانین ایجاد و ویرایش معمولاً متفاوت هستند. بهتر است StoreProductRequest و UpdateProductRequest جدا باشند.
3. از validated() یا safe() استفاده کنید
هیچوقت داده خام Request را مستقیماً وارد Model نکنید.
4. Authorization را جدی بگیرید
اگر عملیات حساس است، authorize() را فقط true نگذارید. از Policy استفاده کنید.
5. پیامها را فارسی و کاربردی بنویسید
پیام خطا باید برای کاربر نهایی قابل فهم باشد.
6. Form Request را بیش از حد سنگین نکنید
منطق تجاری، Queryهای پیچیده و عملیات زمانبر را به Service منتقل کنید.
7. برای دادههای تو در تو از Dot Syntax استفاده کنید
این روش برای فاکتور، سفارش و فرمهای آرایهای ضروری است.
8. داده را قبل از Validation نرمال کنید
Trim، lowercase و slug generation را میتوان در prepareForValidation() انجام داد.
9. در APIها ساختار خطا را استاندارد کنید
برای پروژههای API محور، پاسخ خطا باید برای Frontend قابل پیشبینی باشد.
10. تست اعتبارسنجی بنویسید
برای فرمهای مهم مثل ثبتنام، پرداخت، سفارش و پروفایل، تست بنویسید تا تغییرات آینده باعث شکستن قوانین نشود.
Form Request و سئو در پروژههای Laravel
شاید Form Request مستقیماً ابزار SEO نباشد، اما در سایتهای شرکتی، فروشگاهی و محتوایی نقش مهمی در کیفیت دادههای سئو دارد. برای مثال، اگر فرم ایجاد مقاله دارید، Form Request میتواند این موارد را کنترل کند:
- عنوان مقاله الزامی باشد.
- Meta Title طول مناسب داشته باشد.
- Meta Description بیش از حد طولانی نباشد.
- Slug یکتا و استاندارد باشد.
- Canonical URL معتبر باشد.
- مقاله بدون وضعیت انتشار مشخص ذخیره نشود.
- تصویر شاخص فرمت و حجم مناسب داشته باشد.
نمونه:
public function rules(): array
{
return [
'title' => ['required', 'string', 'max:255'],
'slug' => ['required', 'string', 'max:255', 'unique:posts,slug'],
'meta_title' => ['nullable', 'string', 'max:60'],
'meta_description' => ['nullable', 'string', 'max:160'],
'body' => ['required', 'string'],
'published_at' => ['nullable', 'date'],
];
}
این نوع اعتبارسنجی باعث میشود محتوای سایت با ساختار بهتر، دادههای تمیزتر و خطاهای کمتر منتشر شود.
FAQ؛ سوالات متداول درباره Form Request Laravel
1. Form Request در Laravel چیست؟
Form Request یک کلاس اختصاصی برای اعتبارسنجی و مجوزدهی Requestهای ورودی است. این کلاس کمک میکند قوانین Validation از Controller جدا شوند و کد تمیزتر، قابل تستتر و قابل نگهداریتر باشد.
2. چطور در Laravel یک Form Request بسازیم؟
با دستور Artisan زیر:
php artisan make:request StoreProductRequest
فایل ساختهشده معمولاً در مسیر app/Http/Requests قرار میگیرد.
3. متد authorize در Form Request چه کاری انجام میدهد؟
متد authorize() مشخص میکند آیا کاربر فعلی اجازه انجام این درخواست را دارد یا نه. اگر false برگرداند، Laravel پاسخ 403 تولید میکند.
4. متد rules در Form Request چیست؟
متد rules() قوانین اعتبارسنجی دادههای ورودی را برمیگرداند؛ مانند required، email، unique، exists، max و سایر Ruleهای Laravel.
5. تفاوت validated و all چیست؟
all() همه دادههای Request را برمیگرداند، حتی فیلدهایی که نباید ذخیره شوند. اما validated() فقط دادههایی را برمیگرداند که Rule داشتهاند و اعتبارسنجی را گذراندهاند.
6. آیا Form Request برای API هم کاربرد دارد؟
بله. Form Request در API بسیار مفید است و در صورت شکست Validation معمولاً پاسخ JSON با وضعیت 422 برمیگرداند.
7. prepareForValidation چه کاربردی دارد؟
این متد قبل از اجرای Validation اجرا میشود و برای آمادهسازی دادهها کاربرد دارد؛ مثل trim کردن رشتهها، ساخت slug یا نرمالسازی شماره موبایل.
8. آیا باید برای Store و Update دو Request جدا بسازیم؟
در بسیاری از پروژههای حرفهای بله؛ چون قوانین ایجاد و ویرایش معمولاً تفاوت دارند، مخصوصاً در Ruleهایی مثل unique.
9. آیا میتوان پیامهای خطا را فارسی کرد؟
بله. میتوان از متد messages() برای پیامهای سفارشی و از attributes() برای نام فارسی فیلدها استفاده کرد.
10. آیا Form Request جایگزین Service است؟
خیر. Form Request مسئول اعتبارسنجی و مجوزدهی Request است. منطق تجاری باید در Service، Action یا کلاسهای مناسب دیگر قرار گیرد.
11. آیا Form Request امنیت پروژه را بهتر میکند؟
بله، اگر درست استفاده شود. Form Request کمک میکند فقط دادههای معتبر وارد منطق برنامه شوند و همراه با validated() و تنظیم درست $fillable ریسک ورود داده ناخواسته کاهش پیدا میکند.
12. آیا در پروژههای کوچک هم باید Form Request استفاده کرد؟
برای فرمهای بسیار ساده الزامی نیست، اما اگر پروژه قرار است رشد کند یا تیمی توسعه داده شود، استفاده از Form Request از ابتدا میتواند ساختار کد را حرفهایتر کند.
جمعبندی
Form Request در Laravel یکی از ابزارهای کلیدی برای ساخت برنامههای تمیز، امن و قابل نگهداری است. با استفاده از Form Request میتوان قوانین اعتبارسنجی، بررسی مجوز، پیامهای خطا، نام فیلدها، آمادهسازی داده و دریافت داده معتبر را در یک کلاس مستقل مدیریت کرد. این کار باعث میشود Controllerها سبکتر شوند و هر بخش از برنامه مسئولیت مشخصتری داشته باشد.
در پروژههای حرفهای Laravel، Form Request معمولاً در کنار Controllerهای سبک، Service Layer، Policy، Modelهای امن و API Resourceها استفاده میشود. این ترکیب باعث میشود معماری پروژه خواناتر، تستپذیرتر و مناسبتر برای توسعه بلندمدت باشد.
نکته مهم این است که Form Request نباید به محل اجرای منطق تجاری تبدیل شود. وظیفه آن اعتبارسنجی و مجوزدهی Request است. عملیاتهایی مثل ثبت سفارش، کاهش موجودی، ارسال پیامک، ذخیره پیچیده فایل یا پردازش پرداخت بهتر است در Service یا Action انجام شود.
اگر در پروژه Laravel خود هنوز قوانین Validation را داخل Controllerها مینویسید، استفاده از Form Request یکی از سادهترین و مؤثرترین قدمها برای حرفهایتر کردن معماری کد است. ✅
CTA
اگر پروژه Laravel شما در حال رشد است و Controllerها پر از Ruleهای اعتبارسنجی، شرطهای دسترسی و کدهای تکراری شدهاند، زمان بازطراحی ساختار Requestها رسیده است. تیم ما میتواند در طراحی Form Requestهای استاندارد، پیادهسازی Validation حرفهای، ساختاردهی API Responseها، افزایش امنیت ورودی داده، بازطراحی Controllerها و بهبود معماری Laravel به شما کمک کند. برای بررسی فنی پروژه خود با ما تماس بگیرید و کیفیت کد Backend را به سطح حرفهایتری برسانید. 🚀
منابع رسمی
- مستندات رسمی Laravel درباره Validation و Form Request؛ برای مطالعه ساخت Form Request، متدهای authorize و rules، دریافت داده معتبر و رفتار خطاهای اعتبارسنجی.
مطالعه مستندات رسمی Laravel درباره Form Request Validation - مستندات رسمی Laravel درباره Authorization؛ برای آشنایی با Gateها، Policyها و بررسی مجوز کاربر در عملیات مختلف.
مطالعه مستندات رسمی Laravel درباره Authorization