یکی از مشکلاتی که بسیاری از برنامهنویسان هنگام توسعه پروژههای Laravel با آن مواجه میشوند، بزرگشدن کنترلرها و پیچیدهشدن منطق برنامه است. در ابتدای پروژه ممکن است تمام عملیات مربوط به ثبت اطلاعات، محاسبات، بررسی شرایط و ارتباط با دیتابیس را مستقیماً داخل کنترلر بنویسیم؛ اما با گسترش پروژه، نگهداری و توسعه چنین کدی دشوار میشود.
برای حل این مشکل، میتوانیم از الگوی معماری Service Layer استفاده کنیم. در این الگو، منطق تجاری برنامه از کنترلرها جدا میشود و در کلاسهایی مستقل قرار میگیرد که مسئول انجام عملیات مشخصی هستند.
در این مقاله یاد میگیریم Service Layer چیست، چه تفاوتی با Controller و Model دارد، چه زمانی باید از آن استفاده کنیم و چگونه آن را در Laravel پیادهسازی کنیم.
در پایان، یک سرویس برای ایجاد محصول خواهیم ساخت و یاد میگیریم چگونه آن را با استفاده از Dependency Injection در کنترلر فراخوانی کنیم.
Service Layer چیست؟
Service Layer یک الگوی طراحی نرمافزار است که مسئولیتهای مربوط به اجرای عملیات تجاری برنامه را در یک لایه مستقل قرار میدهد.
برای مثال، فرض کنید در حال توسعه یک فروشگاه اینترنتی هستید. هنگام ثبت سفارش ممکن است لازم باشد:
- اطلاعات سفارش اعتبارسنجی شود.
- موجودی محصولات بررسی شود.
- مبلغ نهایی محاسبه شود.
- سفارش در دیتابیس ثبت شود.
- موجودی کالا کاهش پیدا کند.
- اطلاعات پرداخت پردازش شود.
- پیام تأیید برای مشتری ارسال شود.
اگر تمام این عملیات را داخل یک متد کنترلر قرار دهید، با افزایش قابلیتهای فروشگاه، کنترلر بهتدریج بزرگ و پیچیده میشود.
در معماری Service Layer میتوانید منطق مربوط به ثبت سفارش را در یک کلاس مستقل قرار دهید؛ برای مثال:
app/Services/OrderService.php
سپس کنترلر فقط درخواست را دریافت کند، اعتبارسنجیهای مربوط به ورودی را انجام دهد و سرویس مناسب را برای اجرای عملیات تجاری فراخوانی کند.
نکته مهم این است که Service Layer یک قابلیت اجباری یا کلاس از پیش تعریفشده در Laravel نیست؛ بلکه الگویی است که میتوانید متناسب با نیاز پروژه خود پیادهسازی کنید.
چرا از Service Layer در Laravel استفاده کنیم؟
Laravel امکانات مختلفی برای ساخت برنامههای منظم در اختیار ما قرار میدهد؛ اما نحوه تقسیم مسئولیتها میان کنترلرها، سرویسها و مدلها به طراحی برنامهنویس بستگی دارد.
استفاده از Service Layer در پروژههای مناسب چند مزیت مهم دارد.
۱. جلوگیری از بزرگشدن کنترلرها
وقتی منطق تجاری در کلاسهای مستقل قرار میگیرد، کنترلرها سادهتر میشوند و وظیفه اصلی خود یعنی مدیریت درخواست و پاسخ را بهتر انجام میدهند.
۲. استفاده مجدد از منطق برنامه
فرض کنید عملیات ثبت سفارش باید هم از طریق وبسایت و هم از طریق API اجرا شود. اگر منطق ثبت سفارش داخل یک سرویس مستقل قرار گرفته باشد، هر دو کنترلر میتوانند از همان سرویس استفاده کنند.
در نتیجه، لازم نیست منطق مشابه را در چند قسمت برنامه تکرار کنید.
۳. سادهترشدن تستنویسی
وقتی منطق تجاری از کنترلر جدا باشد، میتوانید آن را مستقلتر آزمایش کنید. برای مثال، میتوانید بررسی کنید که سرویس هنگام دریافت اطلاعات معتبر چه نتیجهای تولید میکند یا در صورت بروز خطا چه رفتاری دارد.
۴. نگهداری آسانتر پروژه
وقتی هر کلاس مسئولیت مشخصی داشته باشد، پیدا کردن محل تغییرات و بررسی خطاها سادهتر خواهد شد.
۵. افزایش انعطافپذیری
اگر در آینده نحوه محاسبه قیمت، ثبت سفارش یا ارسال اعلان تغییر کند، میتوانید بخش مربوطه را بدون تغییرات گسترده در کنترلرها اصلاح کنید.
البته استفاده از Service Layer بهتنهایی تضمینکننده کد تمیز نیست. اگر همه منطق برنامه را در یک کلاس بزرگ به نام AppService قرار دهید، فقط پیچیدگی را از کنترلر به سرویس منتقل کردهاید.
تفاوت Controller، Service و Model در Laravel چیست؟
برای استفاده صحیح از Service Layer، باید نقش آن را در کنار سایر اجزای Laravel بشناسیم.
| بخش | مسئولیت اصلی | مثال |
|---|---|---|
| Controller | دریافت درخواست و مدیریت پاسخ | دریافت درخواست ایجاد محصول |
| Service | اجرای منطق تجاری برنامه | بررسی شرایط و ایجاد محصول |
| Model | نمایش و تعامل با دادههای دیتابیس | مدل Product |
| Form Request | اعتبارسنجی و مجوزهای مرتبط با درخواست | بررسی نام و قیمت محصول |
| Middleware | بررسی درخواست پیش از رسیدن به کنترلر | احراز هویت کاربر |
این تقسیمبندی یک راهنمای معماری است، نه قانونی که در تمام پروژهها باید بدون استثنا اجرا شود.
برای مثال، عملیات سادهای مانند نمایش یک رکورد ممکن است به سرویس جداگانه نیاز نداشته باشد. اما وقتی یک عملیات شامل چند مرحله، قواعد تجاری یا استفاده مجدد در چند بخش برنامه است، جداسازی آن در سرویس میتواند مفید باشد.
مرحله اول: ساختار پوشه Services در Laravel
بهصورت پیشفرض، Laravel الزام نمیکند که پوشهای به نام Services در پروژه داشته باشید. با این حال، میتوانید آن را در پوشه app ایجاد کنید.
ساختار پیشنهادی ما به این صورت است:
app/
├── Http/
│ ├── Controllers/
│ │ └── ProductController.php
│ └── Requests/
│ └── StoreProductRequest.php
├── Models/
│ └── Product.php
└── Services/
└── ProductService.php
در این ساختار:
ProductController.phpدرخواست HTTP را مدیریت میکند.StoreProductRequest.phpمسئول اعتبارسنجی ورودی است.Product.phpمدل مرتبط با جدول محصولات است.ProductService.phpعملیات تجاری ایجاد محصول را انجام میدهد.
برای ایجاد پوشه Services میتوانید از فایلمنیجر یا ترمینال استفاده کنید.
در سیستمعاملهایی که دستور mkdir در دسترس است، اجرا کنید:
mkdir app/Services
سپس فایلی با نام ProductService.php در این پوشه بسازید.
مرحله دوم: ایجاد مدل محصول و جدول دیتابیس
برای مثال عملی، فرض میکنیم فروشگاه ما جدولی برای ذخیره محصولات دارد.
اگر هنوز مدل محصول را ایجاد نکردهاید، دستور زیر را اجرا کنید:
php artisan make:model Product -m
این دستور مدل Product و یک Migration برای آن ایجاد میکند.
فایل Migration جدید را در مسیر database/migrations باز کنید و متد up() را به شکل زیر تنظیم کنید:
public function up(): void
{
Schema::create('products', function (Blueprint $table) {
$table->id();
$table->string('name');
$table->decimal('price', 12, 2);
$table->timestamps();
});
}
در این مثال، جدول محصولات شامل نام، قیمت و زمان ایجاد و بهروزرسانی رکورد است.
سپس Migration را اجرا کنید:
php artisan migrate
اکنون مدل app/Models/Product.php را باز کنید و ویژگی fillable را اضافه کنید:
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class Product extends Model
{
protected $fillable = [
'name',
'price',
];
}
ویژگی fillable مشخص میکند کدام فیلدها برای عملیات Mass Assignment مجاز هستند.
در پروژه واقعی، باید فیلدهای مدل را متناسب با ساختار جدول خود تعریف کنید و از قرار دادن فیلدهای حساس یا مدیریتی در این فهرست خودداری کنید.
مرحله سوم: ساخت اولین Service در Laravel
اکنون نوبت به ایجاد سرویس میرسد.
فایل زیر را بسازید:
app/Services/ProductService.php
کد زیر را داخل آن قرار دهید:
<?php
namespace App\Services;
use App\Models\Product;
class ProductService
{
public function createProduct(array $data): Product
{
return Product::create($data);
}
}
در این مثال، متد createProduct() اطلاعات محصول را دریافت میکند و با استفاده از مدل Product یک رکورد جدید ایجاد میکند.
شاید این سرویس در نگاه اول بسیار ساده به نظر برسد. در پروژهای کوچک، حتی میتوان همین عملیات را مستقیماً در کنترلر اجرا کرد.
اما هدف این مثال، آشنایی با نحوه جداسازی عملیات است. هنگامی که ایجاد محصول شامل قواعد تجاری بیشتری شود، میتوانیم آنها را در همین سرویس یا کلاسهای مسئولیتمحور دیگر پیادهسازی کنیم.
برای مثال، در یک فروشگاه واقعی ممکن است لازم باشد شرایط قیمتگذاری، محدودیتهای کسبوکار، ثبت رویداد یا هماهنگی با یک سرویس خارجی نیز بررسی شود.
نکته مهم این است که اعتبارسنجی ورودی و منطق تجاری را بدون دلیل در چند لایه تکرار نکنیم.
مرحله چهارم: ساخت Form Request برای اعتبارسنجی
بهتر است کنترلر یا سرویس مجبور نباشد تمام جزئیات اعتبارسنجی دادههای HTTP را مدیریت کند.
برای این مثال، یک Form Request میسازیم:
php artisan make:request StoreProductRequest
فایل زیر ایجاد میشود:
app/Http/Requests/StoreProductRequest.php
محتوای آن را به شکل زیر تنظیم کنید:
<?php
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', 'max:255'],
'price' => ['required', 'numeric', 'min:0'],
];
}
}
در این مثال، نام محصول اجباری است و قیمت باید عددی و بزرگتر یا مساوی صفر باشد.
متد authorize() در این نمونه مقدار true برمیگرداند تا تمرکز آموزش روی اعتبارسنجی باشد. در پروژه واقعی، اگر ایجاد محصول فقط برای مدیران مجاز است، باید دسترسی مناسب را با Policy، Gate یا سازوکار مجوزدهی پروژه بررسی کنید.
مرحله پنجم: استفاده از Service در کنترلر
اکنون یک کنترلر برای محصولات ایجاد میکنیم:
php artisan make:controller ProductController
فایل app/Http/Controllers/ProductController.php را به شکل زیر تنظیم کنید:
<?php
namespace App\Http\Controllers;
use App\Http\Requests\StoreProductRequest;
use App\Services\ProductService;
use Illuminate\Http\RedirectResponse;
class ProductController extends Controller
{
public function store(
StoreProductRequest $request,
ProductService $productService
): RedirectResponse {
$productService->createProduct(
$request->validated()
);
return redirect()
->back()
->with('success', 'محصول با موفقیت ایجاد شد.');
}
}
در این کد، سه اتفاق مهم رخ میدهد.
اول، Laravel درخواست را دریافت میکند و قوانین تعریفشده در StoreProductRequest را اجرا میکند.
دوم، اطلاعات اعتبارسنجیشده با متد validated() به سرویس ارسال میشوند.
سوم، سرویس محصول را ایجاد میکند و کنترلر پاسخ مناسب را برمیگرداند.
دقت کنید که در این مثال، سرویس بهصورت مستقیم از طریق پارامتر متد کنترلر تزریق شده است. Laravel میتواند کلاسهای قابلساخت را از طریق Service Container شناسایی و ایجاد کند؛ بنابراین برای یک کلاس معمولی مانند ProductService معمولاً نیازی به ثبت دستی آن در Service Provider نیست.
این قابلیت یکی از امکانات مهم Laravel برای پیادهسازی Dependency Injection است.
مرحله ششم: تعریف Route برای ایجاد محصول
برای دسترسی به متد کنترلر، فایل routes/web.php را باز کنید و Route زیر را اضافه کنید:
use App\Http\Controllers\ProductController;
use Illuminate\Support\Facades\Route;
Route::post('/products', [ProductController::class, 'store'])
->name('products.store');
اکنون مسیر products.store برای ثبت محصول در دسترس است.
اگر این مسیر در بخش مدیریتی سایت قرار دارد، باید آن را با Middleware مناسب محافظت کنید. برای مثال، اگر Middleware مربوط به مدیریت در پروژه شما تعریف شده است، میتوانید Route را در همان گروه قرار دهید.
مرحله هفتم: ارسال درخواست ایجاد محصول
برای آزمایش مسیر، میتوانید یک فرم ساده Blade ایجاد کنید.
برای نمونه، در یک فایل Blade بنویسید:
@if (session('success'))
<p>{{ session('success') }}</p>
@endif
<form method="POST" action="{{ route('products.store') }}">
@csrf
<div>
<label for="name">نام محصول</label>
<input
id="name"
type="text"
name="name"
value="{{ old('name') }}"
>
@error('name')
<p>{{ $message }}</p>
@enderror
</div>
<div>
<label for="price">قیمت محصول</label>
<input
id="price"
type="number"
name="price"
min="0"
step="0.01"
value="{{ old('price') }}"
>
@error('price')
<p>{{ $message }}</p>
@enderror
</div>
<button type="submit">ثبت محصول</button>
</form>
این فرم باید در یک صفحه قابل دسترسی قرار بگیرد تا کاربر بتواند اطلاعات را ارسال کند.
در این مثال، توکن CSRF برای محافظت از فرم استفاده شده است. همچنین اگر اطلاعات نامعتبر باشند، Laravel خطاهای اعتبارسنجی را به فرم برمیگرداند.
برای پروژه واقعی، بهتر است صفحه ثبت محصول فقط در اختیار کاربرانی قرار گیرد که مجوز لازم را دارند.
مرحله هشتم: توسعه Service Layer برای منطق تجاری پیچیدهتر
تا اینجا یک سرویس ساده ساختیم. اما ارزش واقعی Service Layer زمانی بیشتر مشخص میشود که عملیات برنامه چند مرحله داشته باشد.
فرض کنید میخواهیم ایجاد محصول را فقط برای قیمتهای معتبر مجاز بدانیم و همزمان یک رویداد برای اطلاعرسانی به بخش دیگری از برنامه ثبت کنیم.
میتوانیم سرویس را به شکل زیر توسعه دهیم:
<?php
namespace App\Services;
use App\Models\Product;
use Illuminate\Support\Facades\DB;
use InvalidArgumentException;
class ProductService
{
public function createProduct(array $data): Product
{
if ((float) $data['price'] < 0) {
throw new InvalidArgumentException(
'قیمت محصول نمیتواند منفی باشد.'
);
}
return DB::transaction(function () use ($data) {
return Product::create($data);
});
}
}
در این مثال، پیش از ایجاد محصول یک شرط تجاری بررسی میشود و عملیات ایجاد رکورد داخل تراکنش دیتابیس قرار میگیرد.
البته در همین مثال ساده، قانون min:0 در Form Request نیز قیمت منفی را رد میکند. بررسی دوباره در سرویس زمانی ارزشمند است که بخواهیم یک قانون تجاری مهم را مستقل از نوع ورودی حفظ کنیم؛ بهخصوص اگر همان سرویس از چند مسیر مختلف فراخوانی شود.
همچنین در این نمونه فقط یک عملیات دیتابیس داریم؛ بنابراین تراکنش الزاماً ضروری نیست. تراکنش زمانی اهمیت بیشتری پیدا میکند که چند عملیات وابسته باید همگی با موفقیت انجام شوند یا در صورت بروز خطا، تغییرات دیتابیس بازگردانده شوند.
این موضوع نشان میدهد که نباید صرفاً برای پیچیدهترکردن کد از ابزارهای معماری استفاده کنیم. هر لایه باید دلیل مشخصی برای وجود خود داشته باشد.
مرحله نهم: آیا تمام منطق برنامه باید داخل Service قرار بگیرد؟
خیر. یکی از اشتباهات رایج، انتقال تمام کدهای کنترلر به یک کلاس سرویس بزرگ است.
برای طراحی مناسب، بهتر است مسئولیت هر بخش را در نظر بگیرید:
- Controller: مدیریت درخواست و پاسخ HTTP
- Form Request: اعتبارسنجی دادههای ورودی و در صورت نیاز بررسی مجوز مرتبط با درخواست
- Service: اجرای یک عملیات یا مجموعهای از قواعد تجاری مرتبط
- Model: نمایش داده و ارتباط با دیتابیس
- Policy و Gate: کنترل مجوزهای دسترسی
- Job: اجرای کارهایی که باید در صف پردازش شوند
- Event و Listener: اطلاعرسانی به بخشهای دیگر برنامه و واکنش به رخدادها
این تقسیمبندی به این معنا نیست که همه پروژهها باید تمام این اجزا را داشته باشند. معماری مناسب، معماریای است که پیچیدگی واقعی پروژه را مدیریت کند، نه اینکه تعداد فایلها را بیدلیل افزایش دهد.
برای مثال، اگر فقط میخواهید یک رکورد ساده را نمایش دهید، معمولاً ایجاد یک سرویس جداگانه ضرورت ندارد. اما اگر یک عملیات شامل چند مرحله تجاری است یا باید از چند مسیر مختلف اجرا شود، Service Layer میتواند انتخاب خوبی باشد.
تفاوت Service Layer با Repository Pattern چیست؟
این دو مفهوم گاهی با یکدیگر اشتباه گرفته میشوند، درحالیکه مسئولیت متفاوتی دارند.
Service Layer بر اجرای عملیات تجاری تمرکز دارد؛ مانند ثبت سفارش، محاسبه مبلغ نهایی یا ایجاد محصول با رعایت قواعد کسبوکار.
Repository Pattern معمولاً برای جداکردن منطق دسترسی به داده از بخشهای دیگر برنامه استفاده میشود. برای مثال، ممکن است بخواهید نحوه دریافت محصولات از دیتابیس را پشت یک رابط مشخص قرار دهید.
در یک پروژه ساده Laravel، استفاده مستقیم از Eloquent در سرویسها کاملاً قابل قبول است و همیشه نیازی به Repository جداگانه وجود ندارد.
اگر پروژه شما چند منبع داده دارد، به روشهای مختلف ذخیرهسازی وابسته است یا به یک لایه انتزاعی مشخص برای دسترسی به داده نیاز دارد، Repository Pattern میتواند مفید باشد.
بهطور خلاصه، این دو الگو الزاماً جایگزین یکدیگر نیستند و میتوانند در صورت نیاز در کنار هم استفاده شوند.
آیا Service Layer باعث افزایش سرعت Laravel میشود؟
هدف اصلی Service Layer افزایش سرعت اجرای برنامه نیست؛ بلکه سازماندهی بهتر کد، کاهش تکرار و سادهترکردن توسعه و نگهداری است.
افزودن چند کلاس بهخودیخود باعث سریعترشدن کوئریهای دیتابیس یا کاهش زمان پاسخ نمیشود. حتی اگر معماری بیش از حد پیچیده شود، ممکن است نگهداری و اشکالزدایی دشوارتر شود.
برای بهبود عملکرد Laravel باید عواملی مانند کوئریهای دیتابیس، ایندکسها، کش، پردازشهای سنگین و نحوه استفاده از منابع را بررسی کنید.
با این حال، جداسازی مناسب مسئولیتها میتواند شناسایی مشکلات و تست بخشهای مختلف برنامه را آسانتر کند.
جمعبندی
Service Layer یکی از الگوهای کاربردی برای ساخت پروژههای قابلنگهداری در Laravel است. با استفاده از این الگو میتوان منطق تجاری را از کنترلر جدا کرد و امکان استفاده مجدد از عملیات برنامه را افزایش داد.
در این آموزش، یک سرویس به نام ProductService ساختیم، عملیات ایجاد محصول را داخل آن قرار دادیم، ورودیها را با Form Request اعتبارسنجی کردیم و سرویس را با Dependency Injection در کنترلر به کار بردیم.
به یاد داشته باشید که Service Layer یک الزام در Laravel نیست. برای عملیات ساده، استفاده مستقیم از مدل در کنترلر ممکن است کافی باشد؛ اما وقتی منطق تجاری پیچیدهتر میشود، جداسازی آن در سرویسهای مستقل میتواند به خوانایی و توسعهپذیری پروژه کمک کند.
بهترین رویکرد این است که معماری را متناسب با نیاز واقعی پروژه انتخاب کنید و از ایجاد لایههای غیرضروری خودداری کنید.
سوالات متداول
۱. Service Layer در Laravel چیست؟
Service Layer الگویی برای قرار دادن منطق تجاری برنامه در کلاسهای مستقل است. این کار باعث میشود کنترلرها سادهتر شوند و عملیات برنامه راحتتر در بخشهای مختلف استفاده شوند.
۲. آیا Laravel بهصورت پیشفرض Service Layer دارد؟
خیر. Laravel پوشه یا کلاس اجباری برای Service Layer ایجاد نمیکند. برنامهنویس میتواند پوشهای مانند app/Services بسازد و کلاسهای سرویس را متناسب با نیاز پروژه در آن قرار دهد.
۳. تفاوت Service با Controller در Laravel چیست؟
Controller درخواست HTTP را مدیریت میکند و پاسخ مناسب را برمیگرداند؛ درحالیکه Service مسئول اجرای عملیات تجاری برنامه است. این جداسازی کمک میکند منطق برنامه در چند کنترلر تکرار نشود.
۴. آیا استفاده از Service Layer در تمام پروژههای Laravel ضروری است؟
خیر. برای پروژههای کوچک و عملیات ساده ممکن است نیازی به سرویس جداگانه نباشد. این الگو بیشتر زمانی مفید است که عملیات تجاری پیچیده باشد یا از چند بخش برنامه استفاده شود.
۵. آیا برای استفاده از Service Layer باید Service Provider بسازیم؟
در حالت معمول خیر. Laravel میتواند کلاسهای معمولی را از طریق Service Container ایجاد و وابستگیهای قابلشناسایی آنها را تزریق کند. ثبت دستی در Service Provider بیشتر زمانی اهمیت دارد که بخواهید یک Interface را به پیادهسازی مشخصی متصل کنید یا رفتار خاصی برای ساخت وابستگیها تعریف کنید.
۶. آیا میتوان از Service Layer در APIهای Laravel استفاده کرد؟
بله. یکی از مزیتهای این الگو امکان استفاده مجدد از منطق تجاری است. برای مثال، کنترلر وب و کنترلر API میتوانند هر دو از یک سرویس برای ایجاد سفارش استفاده کنند؛ البته اعتبارسنجی درخواست و پاسخ هر بخش ممکن است متفاوت باشد.
۷. تفاوت Service Layer و Repository Pattern چیست؟
Service Layer عملیات تجاری را مدیریت میکند، درحالیکه Repository Pattern معمولاً برای جداکردن نحوه دسترسی به داده استفاده میشود. در بسیاری از پروژهها استفاده از Eloquent در سرویس کافی است و نیازی به Repository جداگانه نیست.





