کنترلرها در لاراول؛ راهنمای کامل Controller
کنترلرها در لاراول یکی از اصلیترین بخشهای معماری برنامه هستند و نقش واسط بین Route، Request، Model، Service و Response را ایفا میکنند. بهجای نوشتن منطق پردازش درخواستها داخل فایلهای Route، میتوان این منطق را در کلاسهایی منظم، قابل تست و قابل نگهداری به نام Controller قرار داد. در این مقاله، بهصورت کامل و فنی با مفهوم Controller در Laravel، انواع کنترلرها، Resource Controller، Single Action Controller، Dependency Injection، Middleware، اعتبارسنجی، ساختار حرفهای کنترلرها، اشتباهات رایج و بهترین روشهای طراحی کنترلر در پروژههای شرکتی آشنا میشویم.
برای شنیدن متن، روی «پخش صوت مقاله» بزنید.
مقدمه
در توسعه نرمافزارهای تحت وب، مدیریت درخواستهای کاربران یکی از مهمترین بخشهای معماری برنامه است. هر بار که کاربر وارد یک صفحه میشود، فرمی ارسال میکند، اطلاعاتی را ویرایش میکند یا از یک API استفاده میکند، یک Request به برنامه ارسال میشود. این Request باید دریافت، بررسی، پردازش و در نهایت به یک Response مناسب تبدیل شود. در فریمورک Laravel، یکی از اصلیترین اجزایی که این فرآیند را مدیریت میکند، Controller است.
کنترلرها در لاراول کمک میکنند منطق پردازش درخواستها از فایلهای Route جدا شود. اگر تمام کدهای مربوط به دریافت ورودی، اعتبارسنجی، ارتباط با مدل، اجرای منطق برنامه و بازگرداندن پاسخ را داخل routes/web.php یا routes/api.php بنویسیم، پروژه خیلی زود شلوغ، سختنگهداری و غیرقابل توسعه میشود. Controllerها این مشکل را حل میکنند و به ما اجازه میدهند رفتارهای مرتبط را در کلاسهای مشخص و قابل سازماندهی قرار دهیم.
در مستندات رسمی Laravel توضیح داده شده است که بهجای تعریف تمام منطق پردازش Requestها بهصورت Closure در فایلهای Route، میتوان این رفتار را با کلاسهای Controller سازماندهی کرد؛ همچنین کنترلرها میتوانند منطقهای مرتبط با یک موضوع، مثل نمایش، ایجاد، ویرایش و حذف کاربران را در یک کلاس گروهبندی کنند.
برای یک شرکت تولید نرمافزار، طراحی درست Controllerها اهمیت زیادی دارد. کنترلرهای شلوغ، وابسته و بدون ساختار میتوانند باعث افزایش هزینه توسعه، سخت شدن تستنویسی، کاهش خوانایی کد و پیچیدگی در نگهداری پروژه شوند. در مقابل، کنترلرهای تمیز و استاندارد باعث میشوند برنامه قابل توسعه، قابل تست، قابل فهم و مناسب کار تیمی باشد.
در این مقاله، بهصورت کامل و کاربردی بررسی میکنیم که کنترلرها در Laravel چه هستند، چگونه ساخته میشوند، چه انواعی دارند، چطور باید در پروژههای واقعی استفاده شوند و چه اشتباهاتی در طراحی آنها باید avoided شود. 🚀
کنترلر در لاراول چیست؟
Controller در Laravel یک کلاس PHP است که وظیفه مدیریت درخواستهای ورودی و تولید پاسخ مناسب را بر عهده دارد. کنترلر معمولاً در مسیر زیر قرار میگیرد:
app/Http/Controllers
یک کنترلر ساده میتواند به این شکل باشد:
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
class ProductController extends Controller
{
public function index()
{
return 'Product List';
}
}
سپس میتوان این کنترلر را در Route استفاده کرد:
use App\Http\Controllers\ProductController;
Route::get('/products', [ProductController::class, 'index']);
در این مثال، وقتی کاربر آدرس /products را باز میکند، متد index از کلاس ProductController اجرا میشود.
کنترلر را میتوان بهعنوان لایه هماهنگکننده در نظر گرفت. یعنی Controller معمولاً نباید همه منطق برنامه را خودش انجام دهد، بلکه باید Request را دریافت کند، دادهها را اعتبارسنجی کند یا به کلاسهای مربوطه بسپارد، Service یا Model مناسب را فراخوانی کند و در نهایت Response برگرداند.
چرا باید از Controller استفاده کنیم؟
استفاده از کنترلرها فقط یک انتخاب ظاهری برای مرتبتر کردن کد نیست؛ بلکه یک اصل مهم در طراحی نرمافزارهای قابل نگهداری است.
فرض کنید یک Route ساده برای ایجاد محصول داشته باشیم:
Route::post('/products', function (Request $request) {
$data = $request->validate([
'title' => ['required', 'string', 'max:255'],
'price' => ['required', 'integer'],
]);
Product::create($data);
return redirect('/products');
});
این کد برای یک مثال کوچک قابل قبول است، اما در یک پروژه واقعی خیلی زود بزرگتر میشود. ممکن است نیاز به بررسی مجوز کاربر، ثبت لاگ، ارسال نوتیفیکیشن، آپلود تصویر، مدیریت تراکنش دیتابیس و بازگرداندن پاسخ API داشته باشید. اگر همه این موارد داخل Route نوشته شوند، فایل Route به یک فایل شلوغ و غیرحرفهای تبدیل میشود.
کنترلرها مزایای مهمی دارند:
| مزیت | توضیح |
|---|---|
| جداسازی مسئولیتها | Route فقط مسیر را تعریف میکند و Controller منطق پردازش را مدیریت میکند |
| خوانایی بهتر | کدها در کلاسها و متدهای مشخص قرار میگیرند |
| تستپذیری بیشتر | متدهای کنترلر و منطق وابسته به آن راحتتر تست میشوند |
| توسعهپذیری | اضافه کردن قابلیتهای جدید سادهتر میشود |
| مناسب کار تیمی | هر توسعهدهنده بهتر میتواند بخش مربوط به خود را پیدا و مدیریت کند |
| هماهنگی با MVC | کنترلر نقش واسط بین Model و View/Response را اجرا میکند |
در معماری MVC، کنترلر بخش مهمی از جریان برنامه است. کاربر درخواست را ارسال میکند، Route آن را به Controller هدایت میکند، Controller در صورت نیاز با Model، Service یا سایر کلاسها تعامل میکند و سپس پاسخ مناسب را به کاربر برمیگرداند.
ساخت کنترلر در لاراول
برای ساخت کنترلر در Laravel از Artisan استفاده میشود:
php artisan make:controller ProductController
این دستور یک فایل به نام ProductController.php در مسیر app/Http/Controllers ایجاد میکند.
نمونه کنترلر ساختهشده:
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
class ProductController extends Controller
{
//
}
حالا میتوانید متدهای مورد نیاز را داخل آن تعریف کنید:
class ProductController extends Controller
{
public function index()
{
return view('products.index');
}
public function show($id)
{
return view('products.show', [
'id' => $id,
]);
}
}
و در Route:
use App\Http\Controllers\ProductController;
Route::get('/products', [ProductController::class, 'index']);
Route::get('/products/{id}', [ProductController::class, 'show']);
ساختار استاندارد کنترلر در پروژههای Laravel
در پروژههای حرفهای، بهتر است کنترلرها بر اساس دامنه کاری یا موجودیتهای سیستم نامگذاری شوند. برای مثال:
app/Http/Controllers/
ProductController.php
OrderController.php
CustomerController.php
InvoiceController.php
Auth/
LoginController.php
RegisterController.php
Admin/
DashboardController.php
UserController.php
Api/
ProductController.php
OrderController.php
این ساختار باعث میشود کنترلرهای پنل مدیریت، API، احراز هویت و بخش عمومی سایت از هم جدا شوند. در یک شرکت نرمافزاری که چند توسعهدهنده روی پروژه کار میکنند، این تفکیک بسیار مهم است.
برای مثال، کنترلرهای API میتوانند در Namespace جداگانه قرار بگیرند:
namespace App\Http\Controllers\Api;
use App\Http\Controllers\Controller;
class ProductController extends Controller
{
public function index()
{
//
}
}
و Route مربوط به آن در routes/api.php تعریف شود:
use App\Http\Controllers\Api\ProductController;
Route::get('/products', [ProductController::class, 'index']);
متدهای رایج در کنترلرهای لاراول
در بسیاری از پروژهها، کنترلرها مخصوصاً برای عملیات CRUD از یک الگوی مشخص پیروی میکنند.
| متد | کاربرد |
|---|---|
| index | نمایش لیست رکوردها |
| create | نمایش فرم ایجاد رکورد جدید |
| store | ذخیره رکورد جدید |
| show | نمایش جزئیات یک رکورد |
| edit | نمایش فرم ویرایش |
| update | بهروزرسانی رکورد |
| destroy | حذف رکورد |
نمونه:
class ProductController extends Controller
{
public function index()
{
$products = Product::latest()->paginate(20);
return view('products.index', compact('products'));
}
public function create()
{
return view('products.create');
}
public function store(Request $request)
{
$data = $request->validate([
'title' => ['required', 'string', 'max:255'],
'price' => ['required', 'integer'],
]);
Product::create($data);
return redirect()->route('products.index');
}
public function show(Product $product)
{
return view('products.show', compact('product'));
}
public function edit(Product $product)
{
return view('products.edit', compact('product'));
}
public function update(Request $request, Product $product)
{
$data = $request->validate([
'title' => ['required', 'string', 'max:255'],
'price' => ['required', 'integer'],
]);
$product->update($data);
return redirect()->route('products.show', $product);
}
public function destroy(Product $product)
{
$product->delete();
return redirect()->route('products.index');
}
}
این ساختار برای بسیاری از بخشهای مدیریتی و CRUD در پروژههای لاراولی کاربرد دارد.
Resource Controller در لاراول
وقتی یک کنترلر برای عملیات CRUD طراحی میشود، میتوان از Resource Controller استفاده کرد. لاراول برای این نوع کنترلرها ساختار استانداردی ارائه میدهد.
ساخت Resource Controller:
php artisan make:controller ProductController --resource
این دستور کنترلری با متدهای زیر ایجاد میکند:
index()
create()
store()
show()
edit()
update()
destroy()
برای تعریف Routeهای مربوط به این کنترلر کافی است بنویسید:
use App\Http\Controllers\ProductController;
Route::resource('products', ProductController::class);
طبق مستندات رسمی Laravel، Resource Routing میتواند Routeهای رایج مربوط به عملیات Create، Read، Update و Delete را فقط با یک خط کد به کنترلر متصل کند.
این قابلیت در پروژههای شرکتی بسیار مفید است، زیرا برای موجودیتهایی مثل محصول، مقاله، کاربر، سفارش، فاکتور و مشتری معمولاً همین الگوی CRUD استفاده میشود.
جدول Routeهای تولیدشده توسط Resource Controller
وقتی از دستور زیر استفاده میکنید:
Route::resource('products', ProductController::class);
لاراول بهصورت خودکار Routeهای زیر را ایجاد میکند:
| HTTP Method | URI | Controller Method | Route Name | کاربرد |
|---|---|---|---|---|
| GET | /products | index | products.index | نمایش لیست محصولات |
| GET | /products/create | create | products.create | نمایش فرم ایجاد محصول |
| POST | /products | store | products.store | ذخیره محصول جدید |
| GET | /products/{product} | show | products.show | نمایش جزئیات محصول |
| GET | /products/{product}/edit | edit | products.edit | نمایش فرم ویرایش |
| PUT/PATCH | /products/{product} | update | products.update | ویرایش محصول |
| DELETE | /products/{product} | destroy | products.destroy | حذف محصول |
این نامگذاری استاندارد باعث میشود تیم توسعه بهراحتی ساختار پروژه را درک کند.
API Resource Controller
اگر پروژه شما API دارد، معمولاً متدهای create و edit لازم نیستند؛ چون این متدها بیشتر برای نمایش فرم در وب استفاده میشوند. برای API میتوانید کنترلر Resource مخصوص API بسازید:
php artisan make:controller Api/ProductController --api
یا Route را به شکل API Resource تعریف کنید:
Route::apiResource('products', ProductController::class);
در این حالت Routeهای مربوط به create و edit ساخته نمیشوند.
نمونه API Controller:
namespace App\Http\Controllers\Api;
use App\Http\Controllers\Controller;
use App\Models\Product;
use Illuminate\Http\Request;
class ProductController extends Controller
{
public function index()
{
return Product::latest()->paginate(20);
}
public function store(Request $request)
{
$data = $request->validate([
'title' => ['required', 'string', 'max:255'],
'price' => ['required', 'integer'],
]);
return Product::create($data);
}
public function show(Product $product)
{
return $product;
}
public function update(Request $request, Product $product)
{
$product->update($request->validate([
'title' => ['required', 'string', 'max:255'],
'price' => ['required', 'integer'],
]));
return $product;
}
public function destroy(Product $product)
{
$product->delete();
return response()->noContent();
}
}
در پروژههای حرفهای بهتر است برای خروجی API از JsonResource استفاده شود تا ساختار Response کنترلشدهتر و پایدارتر باشد.
Single Action Controller یا Invokable Controller
گاهی یک کنترلر فقط قرار است یک کار مشخص انجام دهد. در چنین شرایطی، ایجاد چند متد داخل یک کنترلر ممکن است اضافه و غیرضروری باشد. لاراول برای این حالت از Single Action Controller یا Invokable Controller پشتیبانی میکند.
در مستندات رسمی Laravel گفته شده که اگر یک Controller Action پیچیده باشد، میتوان یک کلاس کنترلر کامل را به همان یک Action اختصاص داد و برای این کار متد __invoke را داخل کنترلر تعریف کرد.
ساخت کنترلر Invokable:
php artisan make:controller SendInvoiceController --invokable
نمونه:
namespace App\Http\Controllers;
use App\Models\Invoice;
class SendInvoiceController extends Controller
{
public function __invoke(Invoice $invoice)
{
// Send invoice to customer
return back()->with('success', 'Invoice sent successfully.');
}
}
تعریف Route:
use App\Http\Controllers\SendInvoiceController;
Route::post('/invoices/{invoice}/send', SendInvoiceController::class);
این نوع کنترلر برای عملیاتهایی مثل ارسال فاکتور، تایید سفارش، لغو اشتراک، خروجی گرفتن از گزارش، فعالسازی حساب کاربری یا پردازش یک Webhook بسیار مناسب است.
مزایای Single Action Controller:
- تمرکز روی یک مسئولیت مشخص
- تستپذیری بهتر
- خوانایی بالاتر
- کاهش شلوغی کنترلرهای بزرگ
- مناسب برای عملیات خاص و مستقل
Dependency Injection در کنترلرها
یکی از قابلیتهای مهم Laravel، پشتیبانی قدرتمند از Dependency Injection است. یعنی میتوانید وابستگیهای موردنیاز کنترلر را از طریق Constructor یا متد کنترلر دریافت کنید و Laravel Container آنها را مدیریت کند.
نمونه تزریق Service در Constructor:
namespace App\Http\Controllers;
use App\Services\ProductService;
class ProductController extends Controller
{
public function __construct(
protected ProductService $productService
) {}
public function index()
{
$products = $this->productService->getLatestProducts();
return view('products.index', compact('products'));
}
}
یا تزریق مستقیم در متد:
use Illuminate\Http\Request;
public function store(Request $request, ProductService $productService)
{
$productService->create($request->all());
return redirect()->route('products.index');
}
Dependency Injection باعث میشود کنترلر بهجای ساخت مستقیم کلاسها، وابستگیهای خود را از Container دریافت کند. این روش باعث کاهش Coupling، افزایش تستپذیری و بهبود معماری پروژه میشود.
در مستندات رسمی کنترلرهای Laravel نیز بخشی برای Dependency Injection در Controllerها وجود دارد و این موضوع یکی از قابلیتهای استاندارد کنترلرها محسوب میشود.
Middleware در کنترلرها
Middlewareها برای فیلتر کردن Request قبل یا بعد از رسیدن به کنترلر استفاده میشوند. برای مثال، بررسی احراز هویت، بررسی نقش کاربر، محدود کردن تعداد درخواستها، تنظیم Locale و موارد مشابه معمولاً با Middleware انجام میشود.
روش رایج اختصاص Middleware در Route:
Route::get('/profile', [ProfileController::class, 'show'])
->middleware('auth');
اما در پروژهها میتوان Middleware را در سطح گروه Route یا کنترلر نیز مدیریت کرد. طبق مستندات Laravel، Middleware میتواند به Routeهای کنترلر اختصاص داده شود و همچنین Laravel امکان استفاده از Middleware مرتبط با کنترلر را ارائه میکند.
مثال در Route Group:
Route::middleware(['auth'])->group(function () {
Route::resource('products', ProductController::class);
});
برای پنل مدیریت:
Route::prefix('admin')
->middleware(['auth', 'can:access-admin'])
->group(function () {
Route::resource('products', Admin\ProductController::class);
});
در پروژههای شرکتی، بهتر است Middlewareها بهصورت شفاف در Route Groupها تعریف شوند تا کنترل دسترسی و ساختار امنیتی پروژه خواناتر باشد.
Route Model Binding در کنترلرها
یکی از قابلیتهای بسیار کاربردی Laravel، Route Model Binding است. بهجای اینکه داخل کنترلر id را بگیرید و بعد مدل را با findOrFail پیدا کنید، میتوانید مستقیماً خود مدل را دریافت کنید.
روش معمولی:
public function show($id)
{
$product = Product::findOrFail($id);
return view('products.show', compact('product'));
}
روش بهتر با Route Model Binding:
public function show(Product $product)
{
return view('products.show', compact('product'));
}
Route:
Route::get('/products/{product}', [ProductController::class, 'show']);
لاراول بهصورت خودکار مقدار {product} را گرفته و مدل Product مربوطه را پیدا میکند. اگر رکورد وجود نداشته باشد، معمولاً پاسخ 404 برگردانده میشود.
این قابلیت باعث کوتاهتر شدن کنترلرها، کاهش کد تکراری و افزایش خوانایی میشود.
اعتبارسنجی Request در کنترلر
یکی از وظایف رایج کنترلرها، اعتبارسنجی دادههای ورودی است. سادهترین روش استفاده از متد validate است:
public function store(Request $request)
{
$data = $request->validate([
'title' => ['required', 'string', 'max:255'],
'price' => ['required', 'integer', 'min:0'],
]);
Product::create($data);
return redirect()->route('products.index');
}
این روش برای موارد ساده مناسب است. اما در پروژههای بزرگتر، بهتر است از Form Request استفاده شود.
ساخت Form Request:
php artisan make:request 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 [
'title' => ['required', 'string', 'max:255'],
'price' => ['required', 'integer', 'min:0'],
];
}
}
استفاده در کنترلر:
use App\Http\Requests\StoreProductRequest;
public function store(StoreProductRequest $request)
{
Product::create($request->validated());
return redirect()->route('products.index');
}
Form Request باعث میشود کنترلر تمیزتر شود و قوانین اعتبارسنجی در کلاس جداگانه قرار بگیرند. این روش برای پروژههای شرکتی و تیمی بسیار توصیه میشود.
کنترلر نباید چه کاری انجام دهد؟
یکی از اشتباهات رایج در پروژههای Laravel، تبدیل کنترلر به یک کلاس بسیار بزرگ و پیچیده است. کنترلر نباید همه مسئولیتهای برنامه را بر عهده بگیرد.
کنترلر بهتر است این کارها را انجام دهد:
- دریافت Request
- فراخوانی Form Request برای اعتبارسنجی
- بررسی سطحی مجوزها یا استفاده از Policy
- فراخوانی Service، Action یا Model
- بازگرداندن View، Redirect یا JSON Response
اما بهتر است این موارد مستقیماً داخل کنترلر انجام نشوند:
- منطق پیچیده تجاری
- Queryهای بسیار سنگین و طولانی
- پردازش فایلهای حجیم
- ارسال مستقیم ایمیلهای پیچیده
- محاسبات مالی حساس بدون Service جداگانه
- ارتباط مستقیم و تکراری با چندین سیستم خارجی
- کدهای تودرتو و شرطهای زیاد
نمونه کنترلر ضعیف:
public function store(Request $request)
{
// Validation
// Upload file
// Create product
// Calculate commission
// Send email
// Create log
// Clear cache
// Return response
}
در ظاهر همه چیز کار میکند، اما نگهداری چنین کدی در آینده سخت خواهد شد.
نسخه بهتر:
public function store(StoreProductRequest $request)
{
$product = $this->productService->create($request->validated());
return redirect()->route('products.show', $product);
}
در این حالت، منطق ساخت محصول داخل ProductService قرار میگیرد و کنترلر فقط نقش هماهنگکننده دارد.
استفاده از Service در کنار Controller
در پروژههای کوچک، ممکن است استفاده مستقیم از Model داخل Controller کافی باشد. اما در پروژههای متوسط و بزرگ، بهتر است منطق تجاری داخل Service Class قرار گیرد.
نمونه Service:
namespace App\Services;
use App\Models\Product;
class ProductService
{
public function create(array $data): Product
{
// Business logic can be here
return Product::create($data);
}
public function update(Product $product, array $data): Product
{
$product->update($data);
return $product;
}
}
کنترلر:
class ProductController extends Controller
{
public function __construct(
protected ProductService $productService
) {}
public function store(StoreProductRequest $request)
{
$product = $this->productService->create($request->validated());
return redirect()->route('products.show', $product);
}
}
این الگو مزایای زیادی دارد:
- کنترلر سبکتر میشود.
- منطق تجاری قابل استفاده مجدد میشود.
- تستنویسی آسانتر میشود.
- تغییرات آینده ریسک کمتری خواهند داشت.
- کد برای تیم توسعه خواناتر میشود.
کنترلر در APIهای لاراول
در APIها، کنترلر معمولاً بهجای View، پاسخ JSON برمیگرداند. برای مثال:
public function index()
{
return response()->json([
'data' => Product::latest()->paginate(20),
]);
}
اما در پروژههای حرفهای بهتر است از API Resource استفاده شود:
php artisan make:resource ProductResource
نمونه Resource:
namespace App\Http\Resources;
use Illuminate\Http\Request;
use Illuminate\Http\Resources\Json\JsonResource;
class ProductResource extends JsonResource
{
public function toArray(Request $request): array
{
return [
'id' => $this->id,
'title' => $this->title,
'price' => $this->price,
'created_at' => $this->created_at?->toDateTimeString(),
];
}
}
استفاده در کنترلر:
use App\Http\Resources\ProductResource;
public function show(Product $product)
{
return new ProductResource($product);
}
برای لیست:
public function index()
{
return ProductResource::collection(
Product::latest()->paginate(20)
);
}
این روش باعث میشود ساختار خروجی API مستقل از ساختار داخلی مدل باشد. در شرکتهای نرمافزاری که API برای اپلیکیشن موبایل، پنل مدیریت یا سیستمهای خارجی ارائه میدهند، این جداسازی بسیار مهم است.
کنترلر و Policy در لاراول
کنترل دسترسی یکی از بخشهای مهم کنترلرهاست. فرض کنید فقط مدیر سیستم اجازه حذف محصول را داشته باشد. میتوان این منطق را داخل کنترلر نوشت، اما روش بهتر استفاده از Policy است.
نمونه در کنترلر:
public function update(UpdateProductRequest $request, Product $product)
{
$this->authorize('update', $product);
$product->update($request->validated());
return redirect()->route('products.show', $product);
}
در اینجا منطق مجوز داخل Policy قرار دارد، نه داخل Controller. این کار باعث میشود کنترلر تمیزتر و سیاستهای دسترسی متمرکزتر باشند.
در پروژههای شرکتی، Policyها مخصوصاً برای نقشهای مختلف مثل مدیر، کارمند، مشتری، نماینده فروش یا پشتیبان بسیار مفید هستند.
کنترلر و Transaction دیتابیس
گاهی یک عملیات کنترلر شامل چند تغییر در دیتابیس است. مثلاً هنگام ثبت سفارش، باید سفارش ایجاد شود، آیتمهای سفارش ذخیره شوند، موجودی محصول کم شود و تراکنش مالی ثبت گردد. در این حالت، بهتر است از Transaction استفاده شود.
نمونه ساده:
use Illuminate\Support\Facades\DB;
public function store(StoreOrderRequest $request)
{
$order = DB::transaction(function () use ($request) {
$order = Order::create($request->validated());
// Create order items
// Update stock
// Create payment record
return $order;
});
return redirect()->route('orders.show', $order);
}
البته در پروژههای بزرگ، بهتر است این منطق داخل Service باشد:
public function store(StoreOrderRequest $request)
{
$order = $this->orderService->createOrder($request->validated());
return redirect()->route('orders.show', $order);
}
و Transaction داخل OrderService مدیریت شود. این کار باعث میشود کنترلر همچنان ساده بماند.
کنترلرهای وب در برابر کنترلرهای API
کنترلرهای Web و API از نظر هدف و نوع پاسخ متفاوت هستند.
| ویژگی | Web Controller | API Controller |
|---|---|---|
| نوع خروجی | View، Redirect، Session Flash | JSON، Resource، Status Code |
| محل Route | routes/web.php | routes/api.php |
| احراز هویت رایج | Session، Cookie | Token، Sanctum، Passport |
| اعتبارسنجی | Form Request یا validate | Form Request یا validate |
| پاسخ خطا | Redirect با Error Bag | JSON Error Response |
| کاربرد | سایت، پنل مدیریت، فرمها | اپلیکیشن موبایل، SPA، سرویس خارجی |
نمونه Web Controller:
public function store(StoreProductRequest $request)
{
Product::create($request->validated());
return redirect()
->route('products.index')
->with('success', 'Product created successfully.');
}
نمونه API Controller:
public function store(StoreProductRequest $request)
{
$product = Product::create($request->validated());
return response()->json([
'data' => new ProductResource($product),
], 201);
}
در طراحی پروژه باید از ابتدا مشخص شود کنترلر برای Web است یا API. ترکیب کردن بیش از حد این دو مدل در یک کنترلر میتواند باعث پیچیدگی شود.
کنترلرهای بزرگ؛ نشانه یک مشکل معماری
اگر کنترلر شما صدها خط کد دارد و هر متد آن چندین وظیفه انجام میدهد، احتمالاً با مشکل معماری مواجه هستید. به این نوع کنترلرها معمولاً Fat Controller گفته میشود.
نشانههای Fat Controller:
- متدهای بسیار طولانی
- Queryهای پیچیده داخل کنترلر
- شرطهای زیاد و تودرتو
- تکرار منطق در چند متد
- ارسال ایمیل، پردازش فایل و محاسبه مالی در همان متد
- سختی در تستنویسی
- وابستگی زیاد به Modelهای مختلف
راهحلها:
| مشکل | راهحل پیشنهادی |
|---|---|
| اعتبارسنجی زیاد داخل کنترلر | استفاده از Form Request |
| منطق تجاری پیچیده | انتقال به Service یا Action |
| Queryهای پرتکرار | استفاده از Scope یا Repository/Query Object |
| خروجی API نامنظم | استفاده از API Resource |
| کنترل دسترسی پراکنده | استفاده از Policy |
| عملیات زمانبر | استفاده از Job و Queue |
| ارسال اعلان | استفاده از Notification یا Event |
کنترلر خوب باید ساده، خوانا و قابل پیشبینی باشد.
بهترین روشهای طراحی کنترلر در Laravel
1. کنترلر را سبک نگه دارید
کنترلر نباید محل انباشته شدن همه منطق برنامه باشد. آن را بهعنوان هماهنگکننده در نظر بگیرید، نه مجری همه جزئیات.
2. از Form Request استفاده کنید
برای اعتبارسنجیهای جدی، Form Request انتخاب حرفهایتری نسبت به نوشتن مستقیم Ruleها داخل کنترلر است.
3. از Route Model Binding استفاده کنید
بهجای گرفتن id و اجرای findOrFail، مستقیماً مدل را در ورودی متد دریافت کنید.
4. برای CRUD از Resource Controller استفاده کنید
اگر کنترلر شما عملیات استاندارد CRUD دارد، Resource Controller باعث استانداردسازی و کاهش کد Route میشود.
5. برای عملیات مستقل از Invokable Controller استفاده کنید
اگر یک عملیات فقط یک Action دارد، Single Action Controller انتخاب تمیزتری است.
6. خروجی API را با Resource کنترل کنید
مدل را مستقیماً به API خروجی ندهید، مگر در پروژههای بسیار ساده. API Resource ساختار پاسخ را پایدارتر میکند.
7. منطق تجاری را به Service منتقل کنید
کنترلر نباید پر از قوانین تجاری، محاسبات، شرطهای دامنه و فرآیندهای پیچیده باشد.
8. نامگذاری را جدی بگیرید
نام کنترلر باید دقیق و قابل فهم باشد:
ProductController
OrderController
SendInvoiceController
Admin/UserController
Api/Auth/LoginController
9. کنترل دسترسی را با Policy مدیریت کنید
بهجای شرطهای دستی زیاد داخل کنترلر، از Policy، Gate و Middleware استفاده کنید.
10. از تستنویسی غافل نشوید
کنترلرها معمولاً با Feature Test بررسی میشوند. اگر کنترلر سبک باشد، تستنویسی آن سادهتر خواهد بود.
نمونه کنترلر حرفهای برای پروژه شرکتی
در این مثال، کنترلر محصول را برای یک پروژه شرکتی طراحی میکنیم:
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 index()
{
$products = Product::query()
->latest()
->paginate(20);
return view('admin.products.index', compact('products'));
}
public function create()
{
return view('admin.products.create');
}
public function store(StoreProductRequest $request)
{
$product = $this->productService->create(
$request->validated()
);
return redirect()
->route('admin.products.show', $product)
->with('success', 'محصول با موفقیت ایجاد شد.');
}
public function show(Product $product)
{
return view('admin.products.show', compact('product'));
}
public function edit(Product $product)
{
return view('admin.products.edit', compact('product'));
}
public function update(UpdateProductRequest $request, Product $product)
{
$this->productService->update(
$product,
$request->validated()
);
return redirect()
->route('admin.products.show', $product)
->with('success', 'محصول با موفقیت ویرایش شد.');
}
public function destroy(Product $product)
{
$this->productService->delete($product);
return redirect()
->route('admin.products.index')
->with('success', 'محصول با موفقیت حذف شد.');
}
}
در این نمونه چند اصل مهم رعایت شده است:
- کنترلر در Namespace مخصوص Admin قرار دارد.
- اعتبارسنجی با Form Request انجام میشود.
- منطق تجاری به ProductService منتقل شده است.
- Route Model Binding استفاده شده است.
- کنترلر خوانا و قابل نگهداری است.
- پیامهای مناسب برای کاربر برگردانده میشود.
اشتباهات رایج در کنترلرهای Laravel
1. نوشتن همه منطق داخل کنترلر
این رایجترین اشتباه است. کنترلر نباید شامل همه منطق پروژه باشد.
2. استفاده مستقیم از $request->all()
Product::create($request->all());
این کار میتواند از نظر امنیتی خطرناک باشد. بهتر است از validated() استفاده شود:
Product::create($request->validated());
3. خروجی دادن مستقیم Model در API
return Product::all();
برای پروژههای حرفهای بهتر است از Resource استفاده شود تا خروجی کنترلشده باشد.
4. Queryهای تکراری در چند کنترلر
اگر یک Query در چند جا تکرار میشود، بهتر است به Scope، Service یا Query Object منتقل شود.
5. ترکیب Web و API در یک کنترلر
در پروژههای بزرگ، بهتر است کنترلرهای Web و API جدا باشند.
6. کنترل دسترسی دستی و پراکنده
نوشتن شرطهای دسترسی در همه متدها باعث آشفتگی میشود. Policy و Middleware راهحل بهتری هستند.
7. نبود ساختار پوشهبندی
قرار دادن همه کنترلرها در یک پوشه بدون تفکیک Admin، Api، Auth و Front باعث سختی نگهداری پروژه میشود.
کنترلرها و سئو در پروژههای Laravel
اگر پروژه Laravel شما یک سایت شرکتی، فروشگاهی یا محتوایی است، کنترلرها میتوانند در کیفیت سئو نیز نقش غیرمستقیم اما مهمی داشته باشند. کنترلر مسئول تولید Response نهایی است و اگر این بخش درست طراحی نشود، ممکن است مشکلاتی مانند صفحات کند، محتوای تکراری، خطاهای 404 نامناسب، Redirectهای اشتباه یا نمایش محتوای ایندکسنشده ایجاد شود.
چند نکته مهم:
- کنترلر صفحات عمومی باید فقط محتوای منتشرشده را نمایش دهد.
- برای صفحات حذفشده یا غیرفعال، پاسخ 404 یا Redirect مناسب برگردانده شود.
- برای صفحات لیستی از Pagination استاندارد استفاده شود.
- Queryهای سنگین بهینه شوند تا سرعت صفحه کاهش پیدا نکند.
- اطلاعات Meta بهتر است از مدل یا Service مرتبط گرفته شود.
- در APIهای مرتبط با Frontend، خروجی باید پایدار و قابل پیشبینی باشد.
نمونه:
public function show(Post $post)
{
abort_unless($post->is_published, 404);
return view('blog.show', [
'post' => $post,
'metaTitle' => $post->meta_title ?: $post->title,
'metaDescription' => $post->meta_description,
]);
}
در این مثال، کنترلر از نمایش مقاله منتشرنشده جلوگیری میکند و اطلاعات متا را برای View آماده میکند.
FAQ؛ سوالات متداول درباره کنترلرها در لاراول
1. کنترلر در لاراول چیست؟
کنترلر یک کلاس PHP در Laravel است که درخواستهای ورودی را مدیریت میکند و پاسخ مناسب مانند View، Redirect یا JSON برمیگرداند. کنترلر معمولاً منطق مربوط به یک بخش از برنامه را سازماندهی میکند.
2. کنترلرها در لاراول کجا قرار دارند؟
بهصورت پیشفرض، کنترلرها در مسیر app/Http/Controllers قرار میگیرند. البته میتوان برای ساختار بهتر، زیرپوشههایی مثل Admin، Api یا Auth ایجاد کرد.
3. تفاوت Route و Controller چیست؟
Route مسیر درخواست را مشخص میکند، اما Controller منطق پردازش آن درخواست را مدیریت میکند. بهتر است Routeها ساده باشند و منطق اصلی در Controller یا کلاسهای وابسته قرار بگیرد.
4. Resource Controller چیست؟
Resource Controller نوعی کنترلر استاندارد برای عملیات CRUD است. این کنترلر معمولاً متدهایی مثل index، create، store، show، edit، update و destroy دارد.
5. Single Action Controller چیست؟
Single Action Controller یا Invokable Controller کنترلری است که فقط یک متد __invoke دارد و برای انجام یک عملیات مشخص استفاده میشود؛ مثل ارسال فاکتور، تایید سفارش یا پردازش Webhook.
6. آیا باید همه منطق برنامه را داخل کنترلر نوشت؟
خیر. کنترلر باید سبک و خوانا باشد. منطق پیچیده بهتر است داخل Service، Action، Job، Policy یا کلاسهای جداگانه قرار بگیرد.
7. بهترین روش اعتبارسنجی در کنترلر چیست؟
برای اعتبارسنجی ساده میتوان از $request->validate() استفاده کرد، اما در پروژههای حرفهای بهتر است از Form Request استفاده شود.
8. کنترلر API با کنترلر Web چه تفاوتی دارد؟
کنترلر Web معمولاً View یا Redirect برمیگرداند، اما کنترلر API معمولاً JSON Response و HTTP Status Code مناسب برمیگرداند.
9. آیا کنترلر باید مستقیماً با Model کار کند؟
در پروژههای کوچک، بله ممکن است کافی باشد. اما در پروژههای بزرگتر بهتر است منطق تجاری از طریق Service یا Action مدیریت شود.
10. چطور از بزرگ شدن کنترلر جلوگیری کنیم؟
با استفاده از Form Request، Service، Policy، Resource، Job، Event، Scope و تقسیم کنترلرها به ساختارهای کوچکتر و متمرکزتر میتوان از Fat Controller جلوگیری کرد.
جمعبندی
کنترلرها در لاراول یکی از مهمترین اجزای معماری برنامه هستند. آنها Requestهای ورودی را دریافت میکنند، با بخشهای مختلف برنامه مانند Model، Service، Policy و View تعامل دارند و در نهایت Response مناسب را برمیگردانند. استفاده درست از Controller باعث میشود پروژه تمیزتر، قابل توسعهتر، تستپذیرتر و مناسبتر برای کار تیمی باشد.
در پروژههای ساده، ممکن است یک کنترلر مستقیماً با Model کار کند و پاسخ برگرداند. اما در پروژههای شرکتی و حرفهای، کنترلر باید سبک بماند و مسئولیتهای پیچیده به کلاسهای تخصصی منتقل شوند. استفاده از Resource Controller برای CRUD، Invokable Controller برای عملیات مستقل، Form Request برای اعتبارسنجی، Policy برای کنترل دسترسی، API Resource برای خروجی API و Service برای منطق تجاری، ساختار پروژه را بسیار حرفهایتر میکند.
اگر کنترلرها بهدرستی طراحی شوند، توسعه قابلیتهای جدید سادهتر خواهد بود، خطاهای احتمالی کاهش پیدا میکند و تیم توسعه میتواند با سرعت و کیفیت بالاتری روی محصول کار کند. اما اگر کنترلرها شلوغ، وابسته و بدون ساختار باشند، پروژه بهمرور سختنگهداری و پرهزینه میشود.
بنابراین، تسلط بر کنترلرها در Laravel فقط یک مهارت ابتدایی نیست؛ بلکه یکی از پایههای اصلی توسعه نرمافزارهای حرفهای با لاراول است. 💼
CTA
اگر قصد دارید یک نرمافزار تحت وب حرفهای، API مقیاسپذیر یا پنل مدیریتی استاندارد با Laravel طراحی کنید، معماری صحیح کنترلرها نقش مهمی در موفقیت پروژه دارد. تیم ما میتواند در طراحی ساختار Controllerها، پیادهسازی Service Layer، بهینهسازی API، بازطراحی پروژههای قدیمی لاراولی و استانداردسازی معماری نرمافزار به شما کمک کند. برای بررسی فنی پروژه لاراول خود، با ما تماس بگیرید و توسعه نرمافزار خود را با ساختاری حرفهایتر ادامه دهید. 🚀
منابع رسمی
- مستندات رسمی کنترلرها در Laravel 12؛ معرفی Controller، ساخت کنترلر، Resource Controller، Middleware و Dependency Injection.
- مستندات رسمی Single Action Controller و Middleware در Laravel؛ توضیح کنترلرهای Invokable و نحوه اتصال Middleware به کنترلرها.
- مستندات رسمی Resource Controller در Laravel؛ توضیح Routeهای CRUD و Resource Routing در کنترلرها.